on fdbc6d8 help sign in
30 files changed+245−2111
since splitsnapshots
unifiedsplit
.ghci+1−13 .github/workflows/ci.yml−33 .gitignore−11 CHANGES.txt−19 LICENSE−217 PULL_REQUEST_TEMPLATE.md−5 README.md+2−17 Setup.hs−2 afl.sh−40 cbits/fuzz.c−26 cbits/grammar.txt−129 cbits/hexml.c+121−181 cbits/hexml.h+16−19 cbits/main.c−46 gen/.ghci−3 gen/.ghcid−1 gen/Lib.hs−201 gen/Main.hs−70 gen/Parser.hs−76 gen/test.c−488 ghci.bat+1−2 go.bat+2 hexml.cabal−55 main.c+22 src/Main.hs+6−78 src/Text/XML/Hexml.hs+74−173 vs/hexml.sln−28 vs/hexml.vcxproj−153 xml/example.xml−23 xml/simple.xml−2
.ghci+1−13
@@ -1,17 +1,5 @@
1−:set -Wunused-binds -Wunused-imports -Worphans
1+:set -fwarn-unused-binds -fwarn-unused-imports
22:set -isrc
33:load Main
44
55:def test \xs -> return $ ":main " ++ xs
6−
7−:{
8−:def docs_ const $ return $ unlines
9− [":!cabal configure"
10− ,":!cabal haddock"]
11−:}
12−
13−:{
14−:def docs const $ return $ unlines
15− [":docs_"
16− ,":!start dist\\doc\\html\\hexml\\Text-XML-Hexml.html"]
17−:}
.github/workflows/ci.ymldeleted−33
@@ -1,33 +0,0 @@
1−name: ci
2−on:
3− push:
4− pull_request:
5− schedule:
6− - cron: '0 3 * * 6' # 3am Saturday
7−jobs:
8− test:
9− runs-on: ${{ matrix.os }}
10−
11− strategy:
12− fail-fast: false
13− matrix:
14− os: [ubuntu-latest]
15− ghc: ['9.6', '9.4', '9.2', '9.0', '8.10', '8.8']
16− include:
17− - os: windows-latest
18− - os: macOS-latest
19−
20− steps:
21− - run: git config --global core.autocrlf false
22− - uses: actions/checkout@v2
23− - uses: haskell/actions/setup@v2
24− id: setup-haskell
25− with:
26− ghc-version: ${{ matrix.ghc }}
27− - run: cabal v2-freeze --enable-tests
28− - uses: actions/cache@v2
29− with:
30− path: ${{ steps.setup-haskell.outputs.cabal-store }}
31− key: ${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
32− restore-keys: ${{ runner.os }}-${{ matrix.ghc }}-
33− - uses: ndmitchell/neil@master
.gitignoredeleted−11
@@ -1,11 +0,0 @@
1−/dist/
2−*.o
3−*.prof
4−*.suo
5−*.opendb
6−*.db
7−*.user
8−/.stack-work/
9−/issues/
10−/vs/x64/
11−/afl-results/
CHANGES.txtdeleted−19
@@ -1,19 +0,0 @@
1−Changelog for Hexml
2−
3−0.3.4, released 2018-09-10
4− #15, licensed under BSD-3-Clause OR Apache-2.0
5−0.3.3, released 2017-10-27
6− #13, allow the . character in tags and attributes
7−0.3.2, released 2017-05-22
8− #10, mark the small fast FFI calls as "unsafe" (up to 10x faster)
9−0.3.1, released 2016-12-27
10− #9, don't walk off the end of the character table
11−0.3, released 2016-12-22
12− #8, Mac compatibility
13− Fix a bug when parsing closing comments
14− #7, prefix all the C functions with hexml_
15−0.2, released 2016-12-14
16− #6, remove buffer overruns
17− #5, add lower bounds
18−0.1, released 2016-12-04
19− Initial version
LICENSEdeleted−217
@@ -1,217 +0,0 @@
1−Copyright Neil Mitchell 2016-2023.
2−
3−Licensed under either of:
4−
5−* BSD-3-Clause license (https://opensource.org/licenses/BSD-3-Clause)
6−* Apache License, version 2.0 (https://opensource.org/licenses/Apache-2.0)
7−
8−As a user, you may use this code under either license, at your option.
9−
10−
11−---------------------------------------------------------------------
12−BSD 3-Clause License
13−All rights reserved.
14−
15−Redistribution and use in source and binary forms, with or without
16−modification, are permitted provided that the following conditions
17−are met:
18−
19− * Redistributions of source code must retain the above copyright
20− notice, this list of conditions and the following disclaimer.
21−
22− * Redistributions in binary form must reproduce the above
23− copyright notice, this list of conditions and the following
24− disclaimer in the documentation and/or other materials provided
25− with the distribution.
26−
27− * Neither the name of Neil Mitchell nor the names of other
28− contributors may be used to endorse or promote products derived
29− from this software without specific prior written permission.
30−
31−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42−
43−
44−---------------------------------------------------------------------
45−Apache License
46−Version 2.0, January 2004
47−<http://www.apache.org/licenses/>
48−
49−Terms and Conditions for use, reproduction, and distribution
50−1. Definitions
51−"License" shall mean the terms and conditions for use, reproduction,
52−and distribution as defined by Sections 1 through 9 of this document.
53−
54−"Licensor" shall mean the copyright owner or entity authorized by the
55−copyright owner that is granting the License.
56−
57−"Legal Entity" shall mean the union of the acting entity and all
58−other entities that control, are controlled by, or are under common
59−control with that entity. For the purposes of this definition,
60−"control" means (i) the power, direct or indirect, to cause the
61−direction or management of such entity, whether by contract or
62−otherwise, or (ii) ownership of fifty percent (50%) or more of the
63−outstanding shares, or (iii) beneficial ownership of such entity.
64−
65−"You" (or "Your") shall mean an individual or Legal Entity exercising
66−permissions granted by this License.
67−
68−"Source" form shall mean the preferred form for making modifications,
69−including but not limited to software source code, documentation
70−source, and configuration files.
71−
72−"Object" form shall mean any form resulting from mechanical
73−transformation or translation of a Source form, including but not
74−limited to compiled object code, generated documentation, and
75−conversions to other media types.
76−
77−"Work" shall mean the work of authorship, whether in Source or Object
78−form, made available under the License, as indicated by a copyright
79−notice that is included in or attached to the work).
80−
81−"Derivative Works" shall mean any work, whether in Source or Object
82−form, that is based on (or derived from) the Work and for which the
83−editorial revisions, annotations, elaborations, or other
84−modifications represent, as a whole, an original work of authorship.
85−For the purposes of this License, Derivative Works shall not include
86−works that remain separable from, or merely link (or bind by name) to
87−the interfaces of, the Work and Derivative Works thereof.
88−
89−"Contribution" shall mean any work of authorship, including the
90−original version of the Work and any modifications or additions to
91−that Work or Derivative Works thereof, that is intentionally
92−submitted to Licensor for inclusion in the Work by the copyright
93−owner or by an individual or Legal Entity authorized to submit on
94−behalf of the copyright owner. For the purposes of this definition,
95−"submitted" means any form of electronic, verbal, or written
96−communication sent to the Licensor or its representatives, including
97−but not limited to communication on electronic mailing lists, source
98−code control systems, and issue tracking systems that are managed by,
99−or on behalf of, the Licensor for the purpose of discussing and
100−improving the Work, but excluding communication that is conspicuously
101−marked or otherwise designated in writing by the copyright owner as
102−"Not a Contribution."
103−
104−"Contributor" shall mean Licensor and any individual or Legal Entity
105−on behalf of whom a Contribution has been received by Licensor and
106−subsequently incorporated within the Work.
107−
108−2. Grant of Copyright License
109−Subject to the terms and conditions of this License, each Contributor
110−hereby grants to You a perpetual, worldwide, non-exclusive,
111−no-charge, royalty-free, irrevocable copyright license to reproduce,
112−prepare Derivative Works of, publicly display, publicly perform,
113−sublicense, and distribute the Work and such Derivative Works in
114−Source or Object form.
115−
116−3. Grant of Patent License
117−Subject to the terms and conditions of this License, each Contributor
118−hereby grants to You a perpetual, worldwide, non-exclusive,
119−no-charge, royalty-free, irrevocable (except as stated in this
120−section) patent license to make, have made, use, offer to sell, sell,
121−import, and otherwise transfer the Work, where such license applies
122−only to those patent claims licensable by such Contributor that are
123−necessarily infringed by their Contribution(s) alone or by
124−combination of their Contribution(s) with the Work to which such
125−Contribution(s) was submitted. If You institute patent litigation
126−against any entity (including a cross-claim or counterclaim in a
127−lawsuit) alleging that the Work or a Contribution incorporated within
128−the Work constitutes direct or contributory patent infringement, then
129−any patent licenses granted to You under this License for that Work
130−shall terminate as of the date such litigation is filed.
131−
132−4. Redistribution
133−You may reproduce and distribute copies of the Work or Derivative
134−Works thereof in any medium, with or without modifications, and in
135−Source or Object form, provided that You meet the following
136−conditions:
137−
138−(a) You must give any other recipients of the Work or Derivative
139−Works a copy of this License; and
140−(b) You must cause any modified files to carry prominent notices
141−stating that You changed the files; and
142−(c) You must retain, in the Source form of any Derivative Works that
143−You distribute, all copyright, patent, trademark, and attribution
144−notices from the Source form of the Work, excluding those notices
145−that do not pertain to any part of the Derivative Works; and
146−(d) If the Work includes a "NOTICE" text file as part of its
147−distribution, then any Derivative Works that You distribute must
148−include a readable copy of the attribution notices contained within
149−such NOTICE file, excluding those notices that do not pertain to any
150−part of the Derivative Works, in at least one of the following
151−places: within a NOTICE text file distributed as part of the
152−Derivative Works; within the Source form or documentation, if
153−provided along with the Derivative Works; or, within a display
154−generated by the Derivative Works, if and wherever such third-party
155−notices normally appear. The contents of the NOTICE file are for
156−informational purposes only and do not modify the License. You may
157−add Your own attribution notices within Derivative Works that You
158−distribute, alongside or as an addendum to the NOTICE text from the
159−Work, provided that such additional attribution notices cannot be
160−construed as modifying the License.
161−You may add Your own copyright statement to Your modifications and
162−may provide additional or different license terms and conditions for
163−use, reproduction, or distribution of Your modifications, or for any
164−such Derivative Works as a whole, provided Your use, reproduction,
165−and distribution of the Work otherwise complies with the conditions
166−stated in this License.
167−
168−5. Submission of Contributions
169−Unless You explicitly state otherwise, any Contribution intentionally
170−submitted for inclusion in the Work by You to the Licensor shall be
171−under the terms and conditions of this License, without any
172−additional terms or conditions. Notwithstanding the above, nothing
173−herein shall supersede or modify the terms of any separate license
174−agreement you may have executed with Licensor regarding such
175−Contributions.
176−
177−6. Trademarks
178−This License does not grant permission to use the trade names,
179−trademarks, service marks, or product names of the Licensor, except
180−as required for reasonable and customary use in describing the origin
181−of the Work and reproducing the content of the NOTICE file.
182−
183−7. Disclaimer of Warranty
184−Unless required by applicable law or agreed to in writing, Licensor
185−provides the Work (and each Contributor provides its Contributions)
186−on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
187−either express or implied, including, without limitation, any
188−warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
189−or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for
190−determining the appropriateness of using or redistributing the Work
191−and assume any risks associated with Your exercise of permissions
192−under this License.
193−
194−8. Limitation of Liability
195−In no event and under no legal theory, whether in tort (including
196−negligence), contract, or otherwise, unless required by applicable
197−law (such as deliberate and grossly negligent acts) or agreed to in
198−writing, shall any Contributor be liable to You for damages,
199−including any direct, indirect, special, incidental, or consequential
200−damages of any character arising as a result of this License or out
201−of the use or inability to use the Work (including but not limited to
202−damages for loss of goodwill, work stoppage, computer failure or
203−malfunction, or any and all other commercial damages or losses), even
204−if such Contributor has been advised of the possibility of such
205−damages.
206−
207−9. Accepting Warranty or Additional Liability
208−While redistributing the Work or Derivative Works thereof, You may
209−choose to offer, and charge a fee for, acceptance of support,
210−warranty, indemnity, or other liability obligations and/or rights
211−consistent with this License. However, in accepting such obligations,
212−You may act only on Your own behalf and on Your sole responsibility,
213−not on behalf of any other Contributor, and only if You agree to
214−indemnify, defend, and hold each Contributor harmless for any
215−liability incurred by, or claims asserted against, such Contributor
216−by reason of your accepting any such warranty or additional
217−liability.
PULL_REQUEST_TEMPLATE.mddeleted−5
@@ -1,5 +0,0 @@
1−Thanks for the pull request!
2−
3−By raising this pull request you confirm you are licensing your contribution under all licenses that apply to this project (see LICENSE) and that you have no patents covering your contribution.
4−
5−If you care, my PR preferences are at https://github.com/ndmitchell/neil#contributions, but they're all guidelines, and I'm not too fussy - you don't have to read them.
README.md+2−17
@@ -1,17 +1,2 @@
1−# Hexml [![Hackage version](https://img.shields.io/hackage/v/hexml.svg?label=Hackage)](https://hackage.haskell.org/package/hexml) [![Stackage version](https://www.stackage.org/package/hexml/badge/nightly?label=Stackage)](https://www.stackage.org/package/hexml) [![Build status](https://img.shields.io/github/actions/workflow/status/ndmitchell/hexml/ci.yml?branch=master)](https://github.com/ndmitchell/hexml/actions)
2−
3−An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast. In particular:
4−
5−* Entities, e.g. `&amp;`, are not expanded.
6−* Not all the validity conditions are checked.
7−* No support for `<!DOCTYPE` related features.
8−
9−The name "hexml" is a combination of "Hex" (a curse) and "XML". The "X" should not be capitalised because the parser is more curse and less XML.
10−
11−Hexml may be suitable if you want to quickly parse XML, from known sources, and a full XML parser has been shown to be a bottleneck. As an alternative to hexml, which supports things like entities but is still pretty fast, see [Pugixml](http://pugixml.org/) (with a [Haskell binding](https://hackage.haskell.org/package/pugixml) - but be aware the Haskell binding [can segfault](https://github.com/philopon/pugixml-hs/issues/5)).
12−
13−Hexml is tested with [AFL](http://lcamtuf.coredump.cx/afl/).
14−
15−If you want lenses for Hexml, see [hexml-lens](http://hackage.haskell.org/package/hexml-lens).
16−
17−The optimisation work around Hexml spawned [Xeno](http://hackage.haskell.org/package/xeno), a Haskell-only alternative to Hexml. There is a [talk](https://ndmitchell.com/#hexml_12_oct_2017) covering the performance tricks of Hexml and Xeno.
1+# hexml
2+A bad XML parser
Setup.hsdeleted−2
@@ -1,2 +0,0 @@
1−import Distribution.Simple
2−main = defaultMain
afl.shdeleted−40
@@ -1,40 +0,0 @@
1−# Script based on code from https://github.com/ndmitchell/hexml/issues/6
2−# More examples of things to do are at that ticket
3−set -e
4−
5−echo DISABLED DUE TO SEGFAULT IN CLANG WHEN COMPILING AFL
6−echo See https://github.com/ndmitchell/hexml/issues/14
7−exit 0
8−
9−# First install AFL
10−if ! command -v afl-clang-fast > /dev/null ; then
11− wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
12− tar xf afl-latest.tgz
13− cd afl-*
14− make
15− cd llvm_mode && make && cd ..
16− sudo make install
17− cd libdislocator && make && sudo make install && cd ..
18− cd ..
19−fi
20−
21−# Compile it
22−AFL_USE_ASAN=1 afl-clang-fast -O2 -Icbits cbits/fuzz.c -o $PWD/hexml-fuzz
23−
24−# Fuzz it
25−if ! grep -q core /proc/sys/kernel/core_pattern ; then
26− echo core | sudo tee /proc/sys/kernel/core_pattern
27−fi
28−if [ -z "$CI" ] ; then
29− afl-fuzz -T hexml -m none -x /usr/local/share/afl/dictionaries/xml.dict -i $PWD/xml -o $PWD/afl-results -- $PWD/hexml-fuzz @@
30−else
31− AFL_EXIT_WHEN_DONE=1 timeout 5m afl-fuzz -T hexml -m none -x /usr/local/share/afl/dictionaries/xml.dict -i $PWD/xml -o $PWD/afl-results -- $PWD/hexml-fuzz @@ > /dev/null || true
32− cat afl-results/fuzzer_stats
33− grep "unique_crashes *: 0" afl-results/fuzzer_stats # fail if there are failures
34−fi
35−
36−# Minimize failures
37−# $ AFL_PRELOAD=/usr/local/lib/afl/libdislocator.so afl-cmin -i results/crashes/ -o results.shrunk -- $PWD/a.out @@
38−# $ mkdir results.min
39−# $ for x in `ls results.shrunk`; do AFL_PRELOAD=/usr/local/lib/afl/libdislocator.so afl-tmin -i results.shrunk/$x -o results.min/$x -- $PWD/a.out @@; done
40−# $ AFL_PRELOAD=/usr/local/lib/afl/libdislocator.so afl-analyze -i results.shrunk/id:000013,sig:11,src:000002,op:havoc,rep:4 -- ./a.out @@
cbits/fuzz.cdeleted−26
@@ -1,26 +0,0 @@
1−#include <stdio.h>
2−#include <stdlib.h>
3−#include <string.h>
4−#include "hexml.c"
5−
6−int main(int ac, char** av)
7−{
8−#ifdef __AFL_HAVE_MANUAL_CONTROL
9− __AFL_INIT();
10−#endif
11−
12− FILE *f = fopen(av[1], "rb");
13− fseek(f, 0, SEEK_END);
14− size_t fsize = ftell(f);
15− rewind(f);
16−
17− char* string = malloc(fsize+1);
18− memset(string, 0, fsize+1);
19− (void) fread(string, fsize, 1, f);
20− fclose(f);
21−
22− document *doc = hexml_document_parse(string, fsize);
23− hexml_document_free(doc);
24− free(string);
25− return 0;
26−}
cbits/grammar.txtdeleted−129
@@ -1,129 +0,0 @@
1−-- Taken from https://www.w3.org/TR/xml11/ and simplified
2−
3−[1] document ::= content
4−[39] element ::= XMLDecl | EmptyElemTag | STag content ETag
5−[23] XMLDecl ::= '<?xml' (S Attribute)* S? '?>'
6−[10] AttValue ::= '"' [^<"] '"' | "'" [^<'] "'"
7−[44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
8−[25] Eq ::= S? '=' S?
9−[40] STag ::= '<' Name (S Attribute)* S? '>' [WFC: Unique Att Spec]
10−[41] Attribute ::= Name Eq AttValue
11−[43] content ::= (element | CDSect | Comment | [^<]+)*
12−[42] ETag ::= '</' Name S? '>'
13−[18] CDSect ::= CDStart CData CDEnd
14−[19] CDStart ::= '<![CDATA['
15−[20] CData ::= (Char* - (Char* ']]>' Char*))
16−[21] CDEnd ::= ']]>'
17−[15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
18−
19−-- The original, mechanically extracted
20−
21−[1] document ::= ( prolog element Misc* ) - ( Char* RestrictedChar Char* )
22−[2] Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
23−[2a] RestrictedChar ::= [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]
24−[3] S ::= (#x20 | #x9 | #xD | #xA)+
25−[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
26−[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
27−[5] Name ::= NameStartChar (NameChar)*
28−[6] Names ::= Name (#x20 Name)*
29−[7] Nmtoken ::= (NameChar)+
30−[8] Nmtokens ::= Nmtoken (#x20 Nmtoken)*
31−[9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"'
32−| "'" ([^%&'] | PEReference | Reference)* "'"
33−[10] AttValue ::= '"' ([^<&"] | Reference)* '"'
34−| "'" ([^<&'] | Reference)* "'"
35−[11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")
36−[12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'"
37−[13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]
38−[14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
39−[15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
40−[16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'
41−[17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))
42−[18] CDSect ::= CDStart CData CDEnd
43−[19] CDStart ::= '<![CDATA['
44−[20] CData ::= (Char* - (Char* ']]>' Char*))
45−[21] CDEnd ::= ']]>'
46−[22] prolog ::= XMLDecl Misc* (doctypedecl Misc*)?
47−[23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
48−[24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')
49−[25] Eq ::= S? '=' S?
50−[26] VersionNum ::= '1.1'
51−[27] Misc ::= Comment | PI | S
52−[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' [VC: Root Element Type] [WFC: External Subset]
53−[28a] DeclSep ::= PEReference | S [WFC: PE Between Declarations]
54−[28b] intSubset ::= (markupdecl | DeclSep)*
55−[29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment [VC: Proper Declaration/PE Nesting] [WFC: PEs in Internal Subset]
56−[30] extSubset ::= TextDecl? extSubsetDecl
57−[31] extSubsetDecl ::= ( markupdecl | conditionalSect | DeclSep)*
58−[32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"')) [VC: Standalone Document Declaration]
59−[39] element ::= EmptyElemTag
60−| STag content ETag [WFC: Element Type Match]
61−[VC: Element Valid]
62−[40] STag ::= '<' Name (S Attribute)* S? '>' [WFC: Unique Att Spec]
63−[41] Attribute ::= Name Eq AttValue [VC: Attribute Value Type]
64−[WFC: No External Entity References]
65−[WFC: No < in Attribute Values]
66−[42] ETag ::= '</' Name S? '>'
67−[43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
68−[44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>' [WFC: Unique Att Spec]
69−[45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' [VC: Unique Element Type Declaration]
70−[46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children
71−[47] children ::= (choice | seq) ('?' | '*' | '+')?
72−[48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?
73−[49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')' [VC: Proper Group/PE Nesting]
74−[50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')' [VC: Proper Group/PE Nesting]
75−[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
76−| '(' S? '#PCDATA' S? ')' [VC: Proper Group/PE Nesting]
77−[VC: No Duplicate Types]
78−[52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
79−[53] AttDef ::= S Name S AttType S DefaultDecl
80−[54] AttType ::= StringType | TokenizedType | EnumeratedType
81−[55] StringType ::= 'CDATA'
82−[56] TokenizedType ::= 'ID' [VC: ID]
83−[VC: One ID per Element Type]
84−[VC: ID Attribute Default]
85−| 'IDREF' [VC: IDREF]
86−| 'IDREFS' [VC: IDREF]
87−| 'ENTITY' [VC: Entity Name]
88−[57] EnumeratedType ::= NotationType | Enumeration
89−[58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')' [VC: Notation Attributes]
90−[VC: One Notation Per Element Type]
91−[VC: No Notation on Empty Element]
92−[VC: No Duplicate Tokens]
93−[59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' [VC: Enumeration]
94−[VC: No Duplicate Tokens]
95−[60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED'
96−| (('#FIXED' S)? AttValue) [VC: Required Attribute]
97−[VC: Attribute Default Value Syntactically Correct]
98−[WFC: No < in Attribute Values]
99−[VC: Fixed Attribute Default]
100−[WFC: No External Entity References]
101−[61] conditionalSect ::= includeSect | ignoreSect
102−[62] includeSect ::= '<![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>' [VC: Proper Conditional Section/PE Nesting]
103−[63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>' [VC: Proper Conditional Section/PE Nesting]
104−[64] ignoreSectContents ::= Ignore ('<![' ignoreSectContents ']]>' Ignore)*
105−[65] Ignore ::= Char* - (Char* ('<![' | ']]>') Char*)
106−[66] CharRef ::= '&#' [0-9]+ ';'
107−| '&#x' [0-9a-fA-F]+ ';' [WFC: Legal Character]
108−[67] Reference ::= EntityRef | CharRef
109−[68] EntityRef ::= '&' Name ';' [WFC: Entity Declared]
110−[VC: Entity Declared]
111−[WFC: Parsed Entity]
112−[WFC: No Recursion]
113−[69] PEReference ::= '%' Name ';' [VC: Entity Declared]
114−[WFC: No Recursion]
115−[WFC: In DTD]
116−[70] EntityDecl ::= GEDecl | PEDecl
117−[71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>'
118−[72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>'
119−[73] EntityDef ::= EntityValue | (ExternalID NDataDecl?)
120−[74] PEDef ::= EntityValue | ExternalID
121−[75] ExternalID ::= 'SYSTEM' S SystemLiteral
122−| 'PUBLIC' S PubidLiteral S SystemLiteral
123−[76] NDataDecl ::= S 'NDATA' S Name [VC: Notation Declared]
124−[77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'
125−[78] extParsedEnt ::= ( TextDecl? content ) - ( Char* RestrictedChar Char* )
126−[80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
127−[81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* /* Encoding name contains only Latin characters */
128−[82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>' [VC: Unique Notation Name]
129−[83] PublicID ::= 'PUBLIC' S PubidLiteral
cbits/hexml.c+121−181
@@ -1,5 +1,5 @@
11#include "hexml.h"
2−#include <stdlib.h>
2+#include <malloc.h>
33#include <string.h>
44#include <assert.h>
55#include <ctype.h>
@@ -10,18 +10,18 @@ typedef int bool;
1010/////////////////////////////////////////////////////////////////////
1111// TYPES
1212
13−static inline int end(str s) { return s.start + s.length; }
13+int static inline end(str s) { return s.start + s.length; }
1414
15−static inline str start_length(int32_t start, int32_t length)
15+str static inline start_length(int32_t start, int32_t length)
1616{
17− assert(start >= 0 && length >= 0);
17+ if (start < 0 || length < 0) assert(0);
1818 str res;
1919 res.start = start;
2020 res.length = length;
2121 return res;
2222}
2323
24−static inline str start_end(int32_t start, int32_t end)
24+str static inline start_end(int32_t start, int32_t end)
2525{
2626 return start_length(start, end - start);
2727}
@@ -40,7 +40,7 @@ typedef struct
4040 // have a cursor at the end, which is stack scoped, so children write out, then I do
4141 // when you commit, you copy over from end to front
4242
43− // nodes
43+ // nodes
4444 int size;
4545 int used_front; // front entries, stored for good
4646 int used_back; // back entries, stack based, copied into front
@@ -51,11 +51,11 @@ typedef struct
5151
5252struct document
5353{
54− const char* body; // pointer to initial argument, not owned by us
54+ char* body; // pointer to initial argument, not owned by us
5555
5656 // things only used while parsing
57− const char* cursor; // pointer to where we are in body
58− const char* end; // pointer to one past the last char
57+ char* cursor; // pointer to where we are in body
58+ char* end; // pointer to one past the last char
5959 // if cursor is > end we have gone past the end
6060
6161 char* error_message;
@@ -63,8 +63,8 @@ struct document
6363 attr_buffer attrs;
6464};
6565
66−static inline int doc_length(const document* d) { return (int) (d->end - d->body); }
67−static inline int doc_position(const document* d) { return (int) (d->cursor - d->body); }
66+int static inline doc_length(document* d) { return d->end - d->body; }
67+int static inline doc_position(document* d) { return d->cursor - d->body; }
6868
6969
7070/////////////////////////////////////////////////////////////////////
@@ -72,45 +72,51 @@ static inline int doc_position(const document* d) { return (int) (d->cursor - d-
7272
7373typedef struct
7474{
75− const document* d;
75+ document* d;
7676 char* buffer;
7777 int length;
7878 int cursor;
7979} render;
8080
81−static inline void render_char(render* r, char c)
81+void static inline render_char(render* r, char c)
8282{
8383 if (r->cursor < r->length)
8484 r->buffer[r->cursor] = c;
8585 r->cursor++;
8686}
8787
88−static inline void bound(int idx, int mn, int mx)
88+void static inline bound(char* msg, int index, int mn, int mx)
8989{
90− assert(idx >= mn && idx <= mx);
90+ if (index < mn || index > mx)
91+ {
92+ printf("Bounds checking failed %s, got %i, which should be in %i:%i\n", msg, index, mn, mx);
93+ assert(0);
94+ }
9195}
9296
93−static inline void bound_str(str s, int mn, int mx)
97+void static inline bound_str(char* msg, str s, int mn, int mx)
9498{
95− assert(s.length >= 0);
96− bound(s.start, mn, mx);
97− bound(end(s), mn, mx);
99+ if (s.length < 0) assert(0);
100+ bound(msg, s.start, mn, mx);
101+ bound(msg, end(s), mn, mx);
98102}
99103
100−static void render_str(render* r, str s)
104+void render_str(render* r, str s)
101105{
102− bound_str(s, 0, doc_length(r->d));
106+ bound_str("render_str", s, 0, doc_length(r->d));
107+ render_char(r, '[');
103108 for (int i = 0; i < s.length; i++)
104109 render_char(r, r->d->body[s.start + i]);
110+ render_char(r, ']');
105111}
106112
107−static void render_tag(render* r, const node* n);
113+void render_tag(render* r, node* n);
108114
109−static void render_content(render* r, const node* n)
115+void render_content(render* r, node* n)
110116{
111− bound_str(n->inner, 0, doc_length(r->d));
112− bound_str(n->nodes, 0, r->d->nodes.used_front);
113− bound_str(n->attrs, 0, r->d->attrs.used);
117+ bound_str("render_conent inner", n->inner, 0, doc_length(r->d));
118+ bound_str("render_conent nodes", n->nodes, 0, r->d->nodes.used_front);
119+ bound_str("render_conent attrs", n->attrs, 0, r->d->attrs.used);
114120
115121 int done = n->inner.start;
116122 for (int i = 0; i < n->nodes.length; i++)
@@ -123,7 +129,7 @@ static void render_content(render* r, const node* n)
123129 render_str(r, start_end(done, end(n->inner)));
124130}
125131
126−static void render_tag(render* r, const node* n)
132+void render_tag(render* r, node* n)
127133{
128134 render_char(r, '<');
129135 render_str(r, n->name);
@@ -145,18 +151,14 @@ static void render_tag(render* r, const node* n)
145151 render_char(r, '>');
146152}
147153
148−int hexml_node_render(const document* d, const node* n, char* buffer, int length)
154+int document_render(document* d, char* buffer, int length)
149155{
150156 render r;
151157 r.d = d;
152158 r.buffer = buffer;
153159 r.length = length;
154160 r.cursor = 0;
155− // The root node (and only the root node) has an empty length, so just render its innards
156− if (n->name.length == 0)
157− render_content(&r, n);
158− else
159− render_tag(&r, n);
161+ render_content(&r, document_node(d));
160162 return r.cursor;
161163}
162164
@@ -164,10 +166,10 @@ int hexml_node_render(const document* d, const node* n, char* buffer, int length
164166/////////////////////////////////////////////////////////////////////
165167// NOT THE PARSER
166168
167−char* hexml_document_error(const document* d){return d->error_message;}
168−node* hexml_document_node(const document* d){return &d->nodes.nodes[0];}
169+char* document_error(document* d){return d->error_message;}
170+node* document_node(document* d){return &d->nodes.nodes[0];}
169171
170−void hexml_document_free(document* d)
172+void document_free(document* d)
171173{
172174 free(d->error_message);
173175 free(d->nodes.alloc);
@@ -175,94 +177,93 @@ void hexml_document_free(document* d)
175177 free(d);
176178}
177179
178−node* hexml_node_children(const document* d, const node* n, int* res)
180+node* node_children(document* d, node* n, int* res)
179181{
180182 *res = n->nodes.length;
181183 return &d->nodes.nodes[n->nodes.start];
182184}
183185
184−attr* hexml_node_attributes(const document* d, const node* n, int* res)
186+attr* node_attributes(document* d, node* n, int* res)
185187{
186188 *res = n->attrs.length;
187189 return &d->attrs.attrs[n->attrs.start];
188190}
189191
190192
191−attr* hexml_node_attribute(const document* d, const node* n, const char* s, int slen)
193+attr* node_attributeBy(document* d, node* n, char* s, int slen)
192194{
193− if (slen == -1) slen = (int) strlen(s);
194− const int limit = end(n->attrs);
195− for (int i = n->attrs.start; i < limit; i++)
196− {
197− attr* r = &d->attrs.attrs[i];
198− if (r->name.length == slen && memcmp(s, &d->body[r->name.start], slen) == 0)
199− return r;
200− }
195+ assert(0);
201196 return NULL;
202197}
203198
204199// Search for given strings within a node
205−node* hexml_node_child(const document* d, const node* parent, const node* prev, const char* s, int slen)
200+node* node_firstChildBy(document* d, node* n, char* s, int slen)
206201{
207− if (slen == -1) slen = (int) strlen(s);
208− int i = prev == NULL ? parent->nodes.start : (int) (prev + 1 - d->nodes.nodes);
209− const int limit = end(parent->nodes);
202+ if (slen == -1) slen = strlen(s);
203+ int i = n->nodes.start;
204+ int limit = i + n->nodes.length;
210205 for (; i < limit; i++)
211206 {
212− node* r = &d->nodes.nodes[i];
213− if (r->name.length == slen && memcmp(s, &d->body[r->name.start], slen) == 0)
214− return r;
207+ if (d->nodes.nodes[i].name.length == slen &&
208+ memcmp(s, &d->body[d->nodes.nodes[i].name.start], slen) == 0)
209+ return &d->nodes.nodes[i];
215210 }
216211 return NULL;
217212}
218213
214+node* node_nextChildBy(document* d, node* parent, node* prev, char* s, int slen)
215+{
216+ assert(0);
217+ return NULL;
218+}
219+
219220
220221/////////////////////////////////////////////////////////////////////
221222// PARSE TABLE
222223
223−static const char tag_name1 = 0x1;
224−static const char tag_name = 0x2;
225−static const char tag_space = 0x4;
224+const char tag_name1 = 0x1;
225+const char tag_name = 0x2;
226+const char tag_space = 0x4;
226227
227−static char parse_table[256];
228+char parse_table[256];
228229
229−static void init_parse_table()
230+void init_parse_table()
230231{
231232 static bool done = 0;
232233 if (done) return;
233234 for (int i = 0; i < 256; i++)
234235 {
235236 bool name1 = i == ':' || i == '_' || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z');
236− bool name = name1 || i == '-' || i == '.' || (i >= '0' && i <= '9');
237+ bool name = name1 || i == '-' || (i >= '0' && i <= '9');
237238 bool space = i == ' ' || i == '\t' || i == '\r' || i == '\n';
238239 parse_table[i] = (name1 ? tag_name1 : 0) | (name ? tag_name : 0) | (space ? tag_space : 0);
239240 }
240241 done = 1;
241242}
242243
243−static inline bool is(char c, char tag) { return parse_table[(unsigned char) c] & tag; }
244−static inline bool is_name1(char c) { return is(c, tag_name1); }
245−static inline bool is_name(char c) { return is(c, tag_name); }
246−static inline bool is_space(char c) { return is(c, tag_space); }
244+bool static inline is(char c, char tag) { return parse_table[c] & tag; }
245+bool static inline is_name1(char c) { return is(c, tag_name1); }
246+bool static inline is_name(char c) { return is(c, tag_name); }
247+bool static inline is_space(char c) { return is(c, tag_space); }
247248
248249
249250/////////////////////////////////////////////////////////////////////
250251// PARSER COMBINATORS
251252
252−static inline char peek_at(const document* d, int i) { return d->cursor[i]; }
253−static inline char peek(const document* d) { return peek_at(d, 0); }
254−static inline void skip(document* d, int i) { d->cursor += i; }
255−static inline char get(document* d) { char c = peek(d); skip(d, 1); return c; }
253+char static inline peekAt(document* d, int i) { return d->cursor[i]; }
254+void static inline skip(document* d, int i) { d->cursor += i; }
255+char static inline peek(document* d) { return peekAt(d, 0); }
256+char static inline get(document* d) { char c = peek(d); skip(d, 1); return c; }
256257
257258// Remove whitespace characters from the cursor while they are still whitespace
258−static inline void trim(document* d)
259+void static inline trim(document* d)
259260{
260261 while (is_space(peek(d)))
261262 skip(d, 1);
262263}
263264
264265// Find this character form the cursor onwards, if true adjust the cursor to that char, otherwise leave it at the end
265−static bool find(document* d, char c)
266+bool find(document* d, char c)
266267{
267268 char* x = memchr(d->cursor, c, d->end - d->cursor);
268269 if (x == NULL)
@@ -281,47 +282,30 @@ static bool find(document* d, char c)
281282/////////////////////////////////////////////////////////////////////
282283// PARSING CODE
283284
284−static inline void node_alloc(node_buffer* b, int ask)
285+void static inline node_alloc(node_buffer* b, int ask)
285286{
286287 int space = b->size - b->used_back - b->used_front;
287288 if (space >= ask) return;
288− int size2 = (b->size + 1000 + ask) * 2;
289− node* buf2 = malloc(size2 * sizeof(node));
290− assert(buf2);
291− memcpy(buf2, b->nodes, b->used_front * sizeof(node));
292− memcpy(&buf2[size2 - b->used_back], &b->nodes[b->size - b->used_back], b->used_back * sizeof(node));
293− free(b->alloc);
294− b->size = size2;
295− b->nodes = buf2;
296− b->alloc = buf2;
289+ assert(0);
297290}
298291
299−static inline void attr_alloc(attr_buffer* b, int ask)
292+void static inline attr_alloc(attr_buffer* b, int ask)
300293{
301294 int space = b->size - b->used;
302295 if (space >= ask) return;
303296 int size2 = (b->size + 1000 + ask) * 2;
304297 attr* buf2 = malloc(size2 * sizeof(attr));
305− assert(buf2);
306298 memcpy(buf2, b->attrs, b->used * sizeof(attr));
307299 free(b->alloc);
308− b->size = size2;
309300 b->attrs = buf2;
310301 b->alloc = buf2;
311302}
312303
313−static void set_error(document* d, const char* msg)
314−{
315− if (d->error_message != NULL) return; // keep the first error message
316− d->error_message = malloc(strlen(msg)+1);
317− assert(d->error_message);
318− strcpy(d->error_message, msg);
319−}
320−
321304// you now expect a name, perhaps preceeded by whitespace
322305// the name may be empty
323−static inline str parse_name(document* d)
306+str static inline parse_name(document* d)
324307{
308+ trim(d);
325309 int start = doc_position(d);
326310 if (!is_name1(peek(d)))
327311 return start_length(start, 0);
@@ -331,14 +315,10 @@ static inline str parse_name(document* d)
331315 return start_end(start, doc_position(d));
332316}
333317
334−static inline str parse_attrval(document* d)
318+str static inline parse_attrval(document* d)
335319{
336320 trim(d);
337− if (peek(d) != '=')
338− {
339− set_error(d, "Expected = in attribute, but missing");
340− return start_length(0, 0);
341− }
321+ if (peek(d) != '=') return start_length(0,0);
342322 skip(d, 1);
343323 trim(d);
344324 char c = peek(d);
@@ -348,29 +328,26 @@ static inline str parse_attrval(document* d)
348328 int start = doc_position(d);
349329 if (!find(d, c))
350330 {
351− set_error(d, "Couldn't find closing attribute bit");
331+ d->error_message = _strdup("Couldn't find closing attribute bit");
352332 return start_length(0, 0);
353333 }
354334 skip(d, 1);
355335 return start_end(start, doc_position(d) - 1);
356336 }
357337 else
358− {
359− set_error(d, "Invalid attribute");
360− return start_length(0, 0);
361− }
338+ return parse_name(d);
362339}
363340
364341
365342
366343// seen a tag name, now looking for attributes terminated by >
367344// puts the attributes it finds in the attribute buffer
368−static inline str parse_attributes(document* d)
345+str static inline parse_attributes(document* d)
369346{
370− int start = d->attrs.used;
371− while (d->error_message == NULL)
347+ str res;
348+ res.start = d->attrs.used;
349+ for (int i = 0; ; i++)
372350 {
373− trim(d);
374351 str name = parse_name(d);
375352 if (name.length == 0) break;
376353 attr_alloc(&d->attrs, 1);
@@ -378,80 +355,70 @@ static inline str parse_attributes(document* d)
378355 d->attrs.attrs[d->attrs.used].value = parse_attrval(d);
379356 d->attrs.used++;
380357 }
381− return start_end(start, d->attrs.used);
358+ res.length = d->attrs.used - res.start;
359+ return res;
382360}
383361
384−static str parse_content(document* d);
362+str parse_content(document* d);
385363
386364
387365// Add a new entry into tag, am at a '<'
388−static inline void parse_tag(document* d)
366+void static inline parse_tag(document* d)
389367{
390368 node_alloc(&d->nodes, 1);
391369 d->nodes.used_back++;
392− node* me = &d->nodes.nodes[d->nodes.size - d->nodes.used_back];
370+ int me = d->nodes.size - d->nodes.used_back;
393371
394− me->outer.start = doc_position(d);
372+ d->nodes.nodes[me].outer.start = doc_position(d);
395373 char c = get(d);
396374 assert(c == '<');
397375 if (peek(d) == '?') skip(d, 1);
398− me->name = parse_name(d);
399− if (me->name.length == 0)
400− {
401− set_error(d, "Missing tag name");
402− return;
403− }
404− me->attrs = parse_attributes(d);
405− if (d->error_message != NULL) return;
376+ d->nodes.nodes[me].name = parse_name(d);
377+ d->nodes.nodes[me].attrs = parse_attributes(d);
406378
407− c = peek(d);
408− if ((c == '/' || c == '?') && peek_at(d, 1) == '>')
379+ c = get(d);
380+ if ((c == '/' || c == '?') && peek(d) == '>')
409381 {
410− skip(d, 2);
411− me->nodes = start_length(0, 0);
412− me->outer.length = start_end(me->outer.start, doc_position(d)).length;
413− me->inner = start_length(doc_position(d), 0);
382+ skip(d, 1);
383+ d->nodes.nodes[me].nodes = start_length(0, 0);
384+ d->nodes.nodes[me].outer.length = start_end(d->nodes.nodes[me].outer.start, doc_position(d)).length;
385+ d->nodes.nodes[me].inner = start_length(doc_position(d), 0);
414386 return;
415387 }
416388 else if (c != '>')
417389 {
418− set_error(d, "Gunk at the end of the tag");
390+ d->error_message = _strdup("Gunk at the end of the tag");
419391 return;
420392 }
421− else
422− skip(d, 1);
423− me->inner.start = doc_position(d);
424− str content = parse_content(d);
425−
426− // parse_content may have allocated more nodes, so recompute me
427− me = &d->nodes.nodes[d->nodes.size - d->nodes.used_back];
428− me->nodes = content;
429− me->inner.length = start_end(me->inner.start, doc_position(d)).length;
393+ d->nodes.nodes[me].inner.start = doc_position(d);
394+ d->nodes.nodes[me].nodes = parse_content(d);
395+ d->nodes.nodes[me].inner.length = start_end(d->nodes.nodes[me].inner.start, doc_position(d)).length;
430396
431397 if (d->error_message != NULL) return;
432− if (peek(d) == '<' && peek_at(d, 1) == '/')
398+ if (peek(d) == '<' && peekAt(d, 1) == '/')
433399 {
434400 skip(d, 2);
435− if (d->end - d->cursor >= me->name.length &&
436− memcmp(d->cursor, &d->body[me->name.start], me->name.length) == 0)
401+ trim(d);
402+ if (d->end - d->cursor >= d->nodes.nodes[me].name.length &&
403+ memcmp(d->cursor, &d->body[d->nodes.nodes[me].name.start], d->nodes.nodes[me].name.length) == 0)
437404 {
438− skip(d, me->name.length);
405+ skip(d, d->nodes.nodes[me].name.length);
439406 trim(d);
440407 if (get(d) == '>')
441408 {
442− me->outer.length = start_end(me->outer.start, doc_position(d)).length;
409+ d->nodes.nodes[me].outer.length = start_end(d->nodes.nodes[me].outer.start, doc_position(d)).length;
443410 return;
444411 }
445412 }
446− set_error(d, "Mismatch in closing tags");
413+ d->error_message = _strdup("Mismatch in closing tags");
447414 return;
448415 }
449− set_error(d, "Weirdness when trying to close tags");
416+ d->error_message = _strdup("Weirdness when trying to close tags");
450417}
451418
452419// Parser until </, return the index of your node children
453420// Not inline as it is recursive
454−static str parse_content(document* d)
421+str parse_content(document* d)
455422{
456423 int before = d->nodes.used_back;
457424 while (d->error_message == NULL)
@@ -461,33 +428,11 @@ static str parse_content(document* d)
461428 {
462429 break;
463430 }
464− else if (peek_at(d, 1) == '/')
431+ if (peekAt(d, 1) == '/')
465432 {
466433 // have found a </
467434 break;
468435 }
469− else if (peek_at(d, 1) == '!' && peek_at(d, 2) == '-' && peek_at(d, 3) == '-')
470− {
471− skip(d, 3);
472− // you can't reuse the two '-' characters for the closing as well
473− if (peek_at(d, 0) == '\0' || peek_at(d, 1) == '\0')
474− {
475− set_error(d, "Didn't get a closing comment");
476− return start_end(0, 0);
477− }
478− skip(d, 2);
479− for (;;)
480− {
481− if (!find(d, '>'))
482− {
483− set_error(d, "Didn't get a closing comment");
484− return start_end(0, 0);
485− }
486− skip(d, 1);
487− if (peek_at(d, -3) == '-' && peek_at(d, -2) == '-')
488− break;
489− }
490− }
491436 else
492437 {
493438 parse_tag(d);
@@ -503,23 +448,20 @@ static str parse_content(document* d)
503448 return res;
504449}
505450
506−// Based on looking at ~50Kb XML documents, they seem to have ~700 attributes
507−// and ~300 nodes, so size appropriately to cope with that.
508451typedef struct
509452{
510453 document document;
511454 attr attrs[1000];
512− node nodes[500];
455+ node nodes[1000];
513456} buffer;
514457
515−document* hexml_document_parse(const char* s, int slen)
458+document* document_parse(char* s, int slen)
516459{
517− if (slen == -1) slen = (int) strlen(s);
460+ if (slen == -1) slen = strlen(s);
518461 assert(s[slen] == 0);
519462 init_parse_table();
520463
521464 buffer* buf = malloc(sizeof(buffer));
522− assert(buf);
523465 document* d = &buf->document;
524466 d->body = s;
525467 d->cursor = s;
@@ -529,7 +471,7 @@ document* hexml_document_parse(const char* s, int slen)
529471 d->attrs.used = 0;
530472 d->attrs.attrs = buf->attrs;
531473 d->attrs.alloc = NULL;
532− d->nodes.size = 500;
474+ d->nodes.size = 1000;
533475 d->nodes.used_back = 0;
534476 d->nodes.used_front = 1;
535477 d->nodes.nodes = buf->nodes;
@@ -539,13 +481,11 @@ document* hexml_document_parse(const char* s, int slen)
539481 d->nodes.nodes[0].outer = start_length(0, slen);
540482 d->nodes.nodes[0].inner = start_length(0, slen);
541483 d->nodes.nodes[0].attrs = start_length(0, 0);
484+ d->nodes.nodes[0].nodes = parse_content(d);
542485
543− // Introduce an intermediate result, otherwise behaviour is undefined
544− // because there is no guaranteed ordering between LHS and RHS evaluation
545− str content = parse_content(d);
546− d->nodes.nodes[0].nodes = content;
547−
548− if (d->cursor < d->end)
549− set_error(d, "Trailing junk at the end of the document");
486+ if (d->cursor < d->end && d->error_message == NULL)
487+ {
488+ d->error_message = _strdup("Trailing junk at the end of the document");
489+ }
550490 return d;
551491}
cbits/hexml.h+16−19
@@ -21,7 +21,6 @@ typedef struct
2121 str name; // tag name, e.g. <[foo]>
2222 str inner; // inner text, <foo>[bar]</foo>
2323 str outer; // outer text, [<foo>bar</foo>]
24− // Not usable, but required to get the correct sizing when returning arrays of nodes
2524 str attrs; // all the attributes, in the attribute buffer (not usable)
2625 str nodes; // all the nodes, in the node buffer (not usable)
2726} node;
@@ -29,29 +28,27 @@ typedef struct
2928// Convention: if slen can be -1 for nul-terminated, or given explicitly
3029
3130// Parse a document, returns a potentially invalid document - use document_error to check
32−// Must use document_free to release the memory (even if invalid).
33−// The string must be nul terminated, e.g. slen != -1 ==> slen[0]
34−document* hexml_document_parse(const char* s, int slen);
31+// Must use document_free to release the memory.
32+document* document_parse(char* s, int slen);
3533
3634// Free the memory returned from document_parse.
37−void hexml_document_free(document* d);
35+void document_free(document* d);
3836
39−// Generate a fresh string with the same semantics as the node.
40−// Requires an input buffer, returns the size of the rendered document.
41−// Requires the string passed to document_parse to be valid.
42−int hexml_node_render(const document* d, const node* n, char* buffer, int length);
37+// generate a fresh document with the same semantics
38+// requires an input buffer, returns the size of the rendered document
39+int document_render(document* d, char* buffer, int length);
4340
44−// Return either NULL (successful parse) or the error message.
45−char* hexml_document_error(const document* d);
41+// return either NULL (successful parse) or the error message
42+char* document_error(document* d);
4643
47−// Return the root node of the document - imagine the document is wrapped in <>$DOC</> tags.
48−node* hexml_document_node(const document* d);
44+// return the root node of the document - imagine the document is wrapped in <>$DOC</> tags
45+node* document_node(document* d);
4946
5047// List all items within a node
51−node* hexml_node_children(const document* d, const node* n, int* res);
52−attr* hexml_node_attributes(const document* d, const node* n, int* res);
48+node* node_children(document* d, node* n, int* res);
49+attr* node_attributes(document* d, node* n, int* res);
5350
54−// Search for given strings within a node, note that prev may be NULL
55−// Requires the string passed to document_parse to be valid.
56−node* hexml_node_child(const document* d, const node* parent, const node* prev, const char* s, int slen);
57−attr* hexml_node_attribute(const document* d, const node* n, const char* s, int slen);
51+// Search for given strings within a node
52+node* node_firstChildBy(document* d, node* n, char* s, int slen);
53+node* node_nextChildBy(document* d, node* parent, node* prev, char* s, int slen);
54+attr* node_attributeBy(document* d, node* n, char* s, int slen);
cbits/main.cdeleted−46
@@ -1,46 +0,0 @@
1−#include "hexml.h"
2−#include <stdio.h>
3−#include <malloc.h>
4−#include <time.h>
5−#include <string.h>
6−#include <assert.h>
7−
8−char* readFile(char* file)
9−{
10− FILE* f = fopen(file, "rb");
11− fseek(f, 0, SEEK_END);
12− int len = ftell(f);
13− fseek(f, 0, SEEK_SET);
14− char* res = malloc(len + 1);
15− assert(res);
16− res[len] = 0;
17− size_t n = fread(res, 1, len, f);
18− assert(n == len);
19− fclose(f);
20− return res;
21−}
22−
23−char* example = "<?xml version=\"1.0\" lang=\"ja\"?><foo><bar baz=\"qux\">quux</bar><bar baz=\"bar\">hoge</bar><piyo>&gt;piyopiyo&lt;</piyo></foo><foo2 />";
24−
25−int main(int argc, char **argv)
26−{
27− setbuf(stdout, NULL);
28− char* body = argv[1] == NULL ? example : readFile(argv[1]);
29− document* doc = hexml_document_parse(body, -1);
30− char* err = hexml_document_error(doc);
31− if (err == NULL)
32− {
33− int len = hexml_node_render(doc, hexml_document_node(doc), NULL, 0);
34− char* s = malloc(len + 1);
35− assert(s);
36− hexml_node_render(doc, hexml_document_node(doc), s, len);
37− s[len] = 0;
38− printf("Parse successful\n"); // , %s\n", s);
39− return 0;
40− }
41− else
42− {
43− printf("Parse failed, %s\n", err);
44− return 1;
45− }
46−}
gen/.ghcideleted−3
@@ -1,3 +0,0 @@
1−:set -fwarn-unused-binds -fwarn-unused-imports -fwarn-orphans
2−:load Main
3−:def test \xs -> return $ ":main" ++ xs
gen/.ghciddeleted−1
@@ -1 +0,0 @@
1−-c ghci --test=:test --reload=test.c
gen/Lib.hsdeleted−201
@@ -1,201 +0,0 @@
1−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2−
3−module Lib(
4− Parser,
5− out, many, optional, match, lit, choice, abort, choices,
6− run,
7− compile
8− ) where
9−
10−import Control.Monad.Extra
11−import Control.Monad.Trans.Writer
12−import Data.Maybe
13−import Data.List.Extra
14−import Data.Bits
15−import Data.Monoid
16−import Numeric
17−
18−
19−run :: Parser o () -> String -> ([(Int, o)], (Int, String), Maybe String)
20−run p = runC (toC $ toP p)
21−
22−compile :: Show o => Parser o () -> [String]
23−compile = prettyC . optimiseC . toC . toP
24−
25−---------------------------------------------------------------------
26−-- THE UNDERLYING PARSER TYPE
27−
28−data P o
29− = Empty
30− | Seq (P o) (P o)
31− | Choice (P o) (P o)
32− | Match CharSet
33− | Many (P o)
34− | Out o
35− | Panic String
36− deriving Show
37−
38−instance Monoid (P o) where
39− mempty = Empty
40− mappend = Seq
41−
42−newtype Parser o a = Parser (Writer (P o) a)
43− deriving (Functor, Applicative, Monad)
44−
45−toP :: Parser o a -> P o
46−toP (Parser p) = execWriter p
47−
48−out :: o -> Parser o ()
49−out o = Parser $ tell $ Out o
50−
51−abort :: String -> Parser o ()
52−abort x = Parser $ tell $ Panic x
53−
54−many :: Parser o () -> Parser o ()
55−many x = Parser $ tell $ Many $ toP x
56−
57−match :: (Char -> Bool) -> Parser o ()
58−match f = Parser $ tell $ Match $ CharSet f
59−
60−lit :: String -> Parser o ()
61−lit xs = forM_ xs $ \x -> match (== x)
62−
63−choice :: Parser o () -> Parser o () -> Parser o ()
64−choice a b = Parser $ tell $ Choice (toP a) (toP b)
65−
66−choices :: [Parser o ()] -> Parser o ()
67−choices = foldr1 choice
68−
69−optional :: Parser o () -> Parser o ()
70−optional x = choice x $ pure ()
71−
72−
73−---------------------------------------------------------------------
74−-- CHARSET
75−
76−chars = ['\1'..'\xff']
77−
78−newtype CharSet = CharSet {fromCharSet :: Char -> Bool}
79−
80−instance Eq CharSet where
81− CharSet f1 == CharSet f2 = all (\x -> f1 x == f2 x) chars
82−
83−instance Monoid CharSet where
84− mempty = CharSet $ const False
85− mappend (CharSet f1) (CharSet f2) = CharSet $ f1 ||^ f2
86−
87−instance Show CharSet where
88− show (CharSet p) = "[" ++ f chars ++ "]"
89− where
90− showChar x | x `elem` "[]-" = ['\\',x]
91− | otherwise = init $ tail $ show x
92− f xs = case dropWhile (not . p) xs of
93− [] -> []
94− x:xs -> case span p xs of
95− ([], rest) -> showChar x ++ f rest :: String
96− (ys, rest) -> showChar x ++ "-" ++ showChar (last ys) ++ f rest
97−
98−-- | Given we know the first argument are all true, can the second argument ever be False
99−implies :: [CharSet] -> CharSet -> Bool
100−implies known x = all (fromCharSet x) poss
101− where poss = filter (\c -> all (`fromCharSet` c) known) chars
102−
103−charSetC :: [CharSet] -> ([String], CharSet -> String)
104−charSetC sets =
105− (["static const char table[256] = {" ++ intercalate "," ("0":map disp chars) ++ "};"]
106− ,\x -> fromMaybe ("table[(int) *p] & " ++ showBit (bit $ fromJust $ elemIndex x hard)) $ simple x)
107− where
108− showBit x = "0x" ++ showHex (x :: Int) ""
109− disp c = showBit $ foldr (.|.) 0 [bit i | (i, h) <- zipFrom 0 hard, fromCharSet h c]
110−
111− hard = nub $ filter (isNothing . simple) sets
112−
113− simple x | x == CharSet (const True) = Just "*p"
114− | [x] <- filter (fromCharSet x) chars = Just $ "*p == " ++ show x
115− | otherwise = Nothing
116−
117−---------------------------------------------------------------------
118−-- THE UNDERLYING C TYPE
119−
120−data C o
121− = While CharSet [C o]
122− | Stmt o
123− | Next
124− | If CharSet [C o] [C o]
125− | Abort String
126− deriving Show
127−
128−-- Can you leave this statement successfully having consumed no characters?
129−empty :: P o -> Bool
130−empty Empty = True
131−empty (Choice a b) = empty b
132−empty Many{} = True
133−empty Out{} = True
134−empty Match{} = False
135−empty Panic{} = False
136−empty (Seq a b) = empty a && empty b
137−
138−-- What characters can come next and you commit
139−peek :: P o -> CharSet
140−peek Empty = mempty
141−peek Out{} = mempty
142−peek (Match f) = f
143−peek Panic{} = CharSet $ const True
144−peek (Choice a b) = peek a <> peek b
145−peek (Many x) = peek x
146−peek (Seq a b) = peek a <> (if empty a then peek b else mempty)
147−
148−toC :: P o -> [C o]
149−toC Empty = []
150−toC (Choice a b) = [If (peek a) (toC a) (toC b)]
151−toC (Match f) = [If f [Next] [Abort "Failed to match"]]
152−toC (Many x) = [While (peek x) (toC x)]
153−toC (Out x) = [Stmt x]
154−toC (Panic x) = [Abort x]
155−toC (Seq a b) = toC a ++ toC b
156−
157−runC :: [C o] -> String -> ([(Int, o)], (Int, String), Maybe String)
158−runC = f 0
159− where
160− f i [] s = ([], (i, s), Nothing)
161− f i (Abort x:_) s = ([], (i,s), Just x)
162− f i (Next:_) [] = ([], (i, []), Just "Next off the end")
163− f i (Next:r) (x:xs) = f (i+1) r xs
164− f i (Stmt x:r) xs = let (v1,v2,v3) = f i r xs in ((i,x):v1, v2, v3)
165− f i (If cond true false : r) xs
166− | x:_ <- xs, fromCharSet cond x = f i (true ++ r) xs
167− | otherwise = f i (false ++ r) xs
168− f i (While cond x:r) xs = f i (If cond (x ++ [While cond x]) [] : r) xs
169−
170−
171−prettyC :: Show o => [C o] -> [String]
172−prettyC xs =
173− prefix ++
174− concatMap f xs
175− where
176− (prefix, cond) = charSetC $ concatMap charSets xs
177− charSets (While c x) = c : concatMap charSets x
178− charSets (If c t f) = c : concatMap charSets t ++ concatMap charSets f
179− charSets _ = []
180−
181− fi = map (" "++) . concatMap f
182− f (Abort x) = ["P_Abort(" ++ show x ++ ");"]
183− f Next = ["p++;"]
184− f (Stmt x) = ["P_" ++ show x ++ ";"]
185− f (If c true []) = ["if (" ++ cond c ++ "){"] ++ fi true ++ ["}"]
186− f (If c true [false@If{}]) = ["if (" ++ cond c ++ "){"] ++ fi true ++ ["} else " ++ f1] ++ fs
187− where f1:fs = f false
188− f (If c true false) = ["if (" ++ cond c ++ "){"] ++ fi true ++ ["} else {"] ++ fi false ++ ["}"]
189− f (While c x) = ["while (" ++ cond c ++ "){"] ++ fi x ++ ["}"]
190−
191−optimiseC :: [C o] -> [C o]
192−optimiseC = f []
193− where
194− f known [] = []
195− f known (Abort x:_) = [Abort x]
196− f known (Next:xs) = Next : f [] xs
197− f known (Stmt x:xs) = Stmt x : f known xs
198− f known (If cond true false : xs)
199− | implies known cond = f known $ true ++ xs
200− | otherwise = If cond (f (cond:known) true) (f known false) : f [] xs -- might be a Next in body
201− f known (While cond x : xs) = While cond (f (cond:known) x) : f [] xs
gen/Main.hsdeleted−70
@@ -1,70 +0,0 @@
1−
2−module Main(main) where
3−
4−import Lib
5−import Parser
6−import Control.Monad.Extra
7−import System.Process.Extra
8−import System.Directory
9−import Data.Maybe
10−import System.IO.Extra
11−import System.FilePath
12−import System.Info.Extra
13−
14−
15−main :: IO ()
16−main = do
17− forM_ examples $ \(b, src) -> do
18− putStrLn ""
19− putStrLn src
20− let (res, trail, err) = run html src
21− print res
22− print (trail, err)
23− putStrLn $ rerender src res
24− writeFileChanged "test.h" $ unlines $ compile html
25− buildRule ["test.h","test.c"] ["gen" <.> (if isWindows then "exe" else "")] $
26− system_ "gcc test.c -o gen -Werror -Wall -Wextra -DTEST"
27− system_ "gen"
28−
29−writeFileChanged :: FilePath -> String -> IO ()
30−writeFileChanged file new = do
31− old <- ifM (doesFileExist file) (readFile' file) (pure "")
32− when (old /= new) $
33− writeFile file new
34−
35−buildRule :: [FilePath] -> [FilePath] -> IO () -> IO ()
36−buildRule from to act = do
37− let modTime x = ifM (doesFileExist x) (Just <$> getModificationTime x) (pure Nothing)
38− from <- mapM modTime from
39− to <- mapM modTime to
40− when (any isNothing (from ++ to) || maximum from >= minimum to)
41− act
42−
43−examples :: [(Bool, String)]
44−examples = take 1
45− [(True, "<test foo='123 xyz'><inner /> value</test>")
46− ,(True, "<test />")
47− ,(True, "<test id='bob'>here<extra/>there</test>")
48− ,(True, "<test /><close />")
49− ,(True, "<test /><!-- comment > --><close />")
50− ,(True, "<test id=\"bob value\" another-attr=\"test with <\">here </test> more text at the end<close />")
51− ,(False, "<test></more>")
52− ,(False, "<test")
53− ,(True, "<?xml version=\"1.1\"?>\n<greeting>Hello, world!</greeting>")
54− ]
55−
56−rerender :: String -> [(Int, Out)] -> String
57−rerender o = f
58− where
59− sub s e = take (e - s) $ drop s o
60−
61− f ((_,Tag):(_,TagComment):xs) = f xs
62− f ((_,Tag):xs) = "<" ++ f xs
63− f ((_,TagOpen):xs) = ">" ++ f xs
64− f ((_,TagOpenClose):xs) = "/>" ++ f xs
65− f ((s,NameStart):(e,NameEnd):(_,TagClose):xs) = "/" ++ sub s e ++ ">" ++ f xs
66− f ((s,NameStart):(e,NameEnd):xs) = sub s e ++ " " ++ f xs
67− f ((s,QuoteStart):(e,QuoteEnd):xs) = "=\"" ++ sub s e ++ "\" " ++ f xs
68− f ((_,AttribsStart):xs) = f xs
69− f ((_,AttribsEnd):xs) = f xs
70− f [] = []
gen/Parser.hsdeleted−76
@@ -1,76 +0,0 @@
1−
2−module Parser(Out(..), html) where
3−
4−import Lib
5−
6−data Out
7− = NameStart | NameEnd
8− | QuoteStart | QuoteEnd
9− | AttribsStart | AttribsEnd
10− | Tag
11− | TagComment | TagOpen | TagClose | TagOpenClose
12− deriving Show
13−
14−is lower upper x = x >= lower && x <= upper
15−isName1 x = x == ':' || x == '_' || is 'a' 'z' x || is 'A' 'Z' x
16−isName x = isName1 x || x == '-' || x == '.' || is '0' '9' x
17−isSpace x = x == ' ' || x == '\t' || x == '\r' || x == '\n'
18−
19−whitespace = many $ match isSpace
20−
21−name = do
22− out NameStart
23− match isName1
24− many $ match isName
25− out NameEnd
26− whitespace
27−
28−attrib = do
29− name
30− lit "=" `choice` abort "Expected = in attribute but missing"
31− whitespace
32− let quoted x = do
33− lit [x]
34− out QuoteStart
35− many $ match (/= x)
36− out QuoteEnd
37− lit [x] `choice` abort ("Couldn't find closing attribute quote (expected " ++ [x] ++ ")")
38− whitespace
39− choices
40− [quoted '\"'
41− ,quoted '\''
42− ,abort "Invalid attribute, expected quote (either ' or \")"]
43−
44−attribs = do
45− out AttribsStart
46− many attrib
47− out AttribsEnd
48−
49−tag = do
50− out Tag
51− lit "<"
52− let name_ = name `choice` abort "Missing tag name"
53− choices
54− [do lit "!--"
55− many $ match (/= '-')
56− -- FIXME: Not correct!!!
57− lit "-->"
58− out TagComment
59− ,do lit "?"
60− name_
61− attribs
62− lit "?>"
63− out TagOpenClose
64− ,do lit "/"
65− name_
66− lit ">"
67− out TagClose
68− ,do name_
69− attribs
70− choice
71− (lit "/>" >> out TagOpenClose)
72− (lit ">" >> out TagOpen)
73− ]
74−
75−
76−html = many $ choice tag $ match $ const True
gen/test.cdeleted−488
@@ -1,488 +0,0 @@
1−
2−#include "../cbits/hexml.h"
3−#include <stdlib.h>
4−#include <string.h>
5−#include <assert.h>
6−#include <stdio.h>
7−
8−/////////////////////////////////////////////////////////////////////
9−// TYPES
10−
11−static inline int end(str s) { return s.start + s.length; }
12−
13−static inline str start_length(int32_t start, int32_t length)
14−{
15− assert(start >= 0 && length >= 0);
16− str res;
17− res.start = start;
18− res.length = length;
19− return res;
20−}
21−
22−static inline str start_end(int32_t start, int32_t end)
23−{
24− return start_length(start, end - start);
25−}
26−
27−typedef struct
28−{
29− int size;
30− int used;
31− attr* attrs; // dynamically allocated buffer
32− attr* alloc; // what to call free on
33−} attr_buffer;
34−
35−typedef struct
36−{
37− // have a cursor at the front, which is all the stuff I have written out, final
38− // have a cursor at the end, which is stack scoped, so children write out, then I do
39− // when you commit, you copy over from end to front
40−
41− // nodes
42− int size;
43− int used_front; // front entries, stored for good
44− int used_back; // back entries, stack based, copied into front on </close>
45− // back entries which haven't yet been closed have nodes.length == -1
46− node* nodes; // dynamically allocated buffer
47− node* alloc; // what to call free on
48−} node_buffer;
49−
50−
51−struct document
52−{
53− const char* body; // pointer to initial argument, not owned by us
54− int body_len; // length of the body
55−
56− char* error_message;
57− node_buffer nodes;
58− attr_buffer attrs;
59−};
60−
61−
62−/////////////////////////////////////////////////////////////////////
63−// RENDER CODE
64−
65−typedef struct
66−{
67− const document* d; // Document, used to get at the body
68− char* buffer; // Buffer we are writing too
69− int length; // Length of buffer, do not write past the end.
70− // No character is reserved for a NULL terminator.
71− int cursor; // Cursor of where we are writing now
72−} render;
73−
74−static inline void render_char(render* r, char c)
75−{
76− if (r->cursor < r->length)
77− r->buffer[r->cursor] = c;
78− r->cursor++;
79−}
80−
81−static inline void bound(int idx, int mn, int mx)
82−{
83− assert(idx >= mn && idx <= mx);
84−}
85−
86−static inline void bound_str(str s, int mn, int mx)
87−{
88− assert(s.length >= 0);
89− bound(s.start, mn, mx);
90− bound(end(s), mn, mx);
91−}
92−
93−static void render_str(render* r, str s)
94−{
95− bound_str(s, 0, r->d->body_len);
96− for (int i = 0; i < s.length; i++)
97− render_char(r, r->d->body[s.start + i]);
98−}
99−
100−static void render_tag(render* r, const node* n);
101−
102−static void render_content(render* r, const node* n)
103−{
104− bound_str(n->inner, 0, r->d->body_len);
105− bound_str(n->nodes, 0, r->d->nodes.used_front);
106− bound_str(n->attrs, 0, r->d->attrs.used);
107−
108− int done = n->inner.start;
109− for (int i = 0; i < n->nodes.length; i++)
110− {
111− node* x = &r->d->nodes.nodes[n->nodes.start + i];
112− render_str(r, start_end(done, x->outer.start));
113− done = end(x->outer);
114− render_tag(r, x);
115− }
116− render_str(r, start_end(done, end(n->inner)));
117−}
118−
119−static void render_tag(render* r, const node* n)
120−{
121− render_char(r, '<');
122− render_str(r, n->name);
123− for (int i = 0; i < n->attrs.length; i++)
124− {
125− attr* x = &r->d->attrs.attrs[n->attrs.start + i];
126− render_char(r, ' ');
127− render_str(r, x->name);
128− render_char(r, '=');
129− render_char(r, '\"');
130− render_str(r, x->value);
131− render_char(r, '\"');
132− }
133− render_char(r, '>');
134− render_content(r, n);
135− render_char(r, '<');
136− render_char(r, '/');
137− render_str(r, n->name);
138− render_char(r, '>');
139−}
140−
141−int hexml_node_render(const document* d, const node* n, char* buffer, int length)
142−{
143− render r;
144− r.d = d;
145− r.buffer = buffer;
146− r.length = length;
147− r.cursor = 0;
148− // The root node (and only the root node) has an empty length, so just render its innards
149− if (n->name.length == 0)
150− render_content(&r, n);
151− else
152− render_tag(&r, n);
153− return r.cursor;
154−}
155−
156−
157−/////////////////////////////////////////////////////////////////////
158−// NOT THE PARSER
159−
160−char* hexml_document_error(const document* d){return d->error_message;}
161−node* hexml_document_node(const document* d){return &d->nodes.nodes[0];}
162−
163−void hexml_document_free(document* d)
164−{
165− free(d->error_message);
166− free(d->nodes.alloc);
167− free(d->attrs.alloc);
168− free(d);
169−}
170−
171−node* hexml_node_children(const document* d, const node* n, int* res)
172−{
173− *res = n->nodes.length;
174− return &d->nodes.nodes[n->nodes.start];
175−}
176−
177−attr* hexml_node_attributes(const document* d, const node* n, int* res)
178−{
179− *res = n->attrs.length;
180− return &d->attrs.attrs[n->attrs.start];
181−}
182−
183−attr* hexml_node_attribute(const document* d, const node* n, const char* s, int slen)
184−{
185− if (slen == -1) slen = (int) strlen(s);
186− const int limit = end(n->attrs);
187− for (int i = n->attrs.start; i < limit; i++)
188− {
189− attr* r = &d->attrs.attrs[i];
190− if (r->name.length == slen && memcmp(s, &d->body[r->name.start], slen) == 0)
191− return r;
192− }
193− return NULL;
194−}
195−
196−// Search for given strings within a node
197−node* hexml_node_child(const document* d, const node* parent, const node* prev, const char* s, int slen)
198−{
199− if (slen == -1) slen = (int) strlen(s);
200− int i = prev == NULL ? parent->nodes.start : (int) (prev + 1 - d->nodes.nodes);
201− const int limit = end(parent->nodes);
202− for (; i < limit; i++)
203− {
204− node* r = &d->nodes.nodes[i];
205− if (r->name.length == slen && memcmp(s, &d->body[r->name.start], slen) == 0)
206− return r;
207− }
208− return NULL;
209−}
210−
211−/////////////////////////////////////////////////////////////////////
212−// PARSING CODE
213−
214−static inline node* node_alloc(node_buffer* b)
215−{
216− int space = b->size - b->used_back - b->used_front;
217− if (space < 1)
218− {
219− int size2 = (b->size + 1000) * 2;
220− node* buf2 = malloc(size2 * sizeof(node));
221− assert(buf2);
222− memcpy(buf2, b->nodes, b->used_front * sizeof(node));
223− memcpy(&buf2[size2 - b->used_back], &b->nodes[b->size - b->used_back], b->used_back * sizeof(node));
224− free(b->alloc);
225− b->size = size2;
226− b->nodes = buf2;
227− b->alloc = buf2;
228− }
229− b->used_back++;
230− return &b->nodes[b->size - b->used_back];
231−}
232−
233−// buffer[from .. from+n] = reverse (buffer[from .. from+n])
234−// The ranges completely overlap
235−static inline void reverse(node* buffer, int from, int n)
236−{
237− for (int to = from + n - 1; to > from; from++, to--)
238− {
239− node temp = buffer[to];
240− buffer[to] = buffer[from];
241− buffer[from] = temp;
242− }
243−}
244−
245−// buffer[to .. to+n] = reverse (buffer[from .. from+n])
246−// The ranges may not overlap
247−static inline void memcpy_reverse(node* buffer, int from, int to, int n)
248−{
249− int end = from + n - 1;
250− for (int i = 0; i < n; i++)
251− buffer[to+i] = buffer[end-i];
252−}
253−
254−// buffer[to .. to+n] = reverse (buffer[from .. from+n])
255−// The ranges may overlap
256−static inline void memmove_reverse(node* buffer, int from, int to, int n)
257−{
258− assert(from >= to); // always the case in my usages
259− if (to + n < from)
260− memcpy_reverse(buffer, from, to, n);
261− else
262− {
263− int overlap = from - to;
264− memcpy_reverse(buffer, from, to + n - overlap, overlap);
265− reverse(buffer, to, n - overlap);
266− }
267−}
268−
269−static inline str node_commit(node_buffer* b)
270−{
271− // first search for an "open" node (nodes.length == -1)
272− int open;
273− for (open = b->size - b->used_back; open < b->size; open++)
274− {
275− if (b->nodes[open].nodes.length == -1)
276− break;
277− }
278−
279− int n = open - (b->size - b->used_back);
280− memmove_reverse(b->nodes, b->size - b->used_back, b->used_front, n);
281− b->used_back -= n;
282− b->used_front += n;
283− return start_length(b->used_front - n, n);
284−}
285−
286−static inline attr* attr_alloc(attr_buffer* b)
287−{
288− // Ensure there is at least one node left
289− int space = b->size - b->used;
290− if (space < 1)
291− {
292− int size2 = (b->size + 1000) * 2;
293− attr* buf2 = malloc(size2 * sizeof(attr));
294− assert(buf2);
295− memcpy(buf2, b->attrs, b->used * sizeof(attr));
296− free(b->alloc);
297− b->size = size2;
298− b->attrs = buf2;
299− b->alloc = buf2;
300− }
301− b->used++;
302− return &b->attrs[b->used - 1];
303−}
304−
305−static void set_error(document* d, const char* msg)
306−{
307− if (d->error_message != NULL) return; // keep the first error message
308− d->error_message = malloc(strlen(msg)+1);
309− assert(d->error_message);
310− strcpy(d->error_message, msg);
311−}
312−
313−static inline str gap(const char* ref, const char* start, const char* end)
314−{
315− return start_end(start - ref, end - ref);
316−}
317−
318−#define P_Abort(x) return x
319−#define P_Tag tag_start = p;
320−#define P_NameStart name_start = p
321−#define P_NameEnd name_end = p
322−#define P_AttribsStart \
323− me = node_alloc(&d->nodes); \
324− me->attrs.start = d->attrs.used; \
325− me->name = gap(d->body, name_start, name_end);
326−#define P_AttribsEnd \
327− me->attrs = start_end(me->attrs.start, d->attrs.used);
328−#define P_QuoteStart quote_start = p
329−#define P_QuoteEnd \
330− attr* attr = attr_alloc(&d->attrs); \
331− attr->name = gap(d->body, name_start, name_end); \
332− attr->value = gap(d->body, quote_start, p);
333−#define P_TagComment \
334− // No action required - comments just flow as normal text
335−#define P_TagOpen \
336− me->nodes.length = -1; \
337− me->inner = gap(d->body, p, p); \
338− me->outer = gap(d->body, tag_start, tag_start);
339−#define P_TagClose \
340− str nodes = node_commit(&d->nodes); \
341− if (d->nodes.used_back == 0) \
342− return "Closing tag with no openning"; \
343− me = &d->nodes.nodes[d->nodes.size - d->nodes.used_back]; \
344− me->nodes = nodes; \
345− if (me->name.length != gap(d->body, name_start, name_end).length || \
346− memcmp(&d->body[me->name.start], name_start, me->name.length) != 0) \
347− return "Mismatch in closing tag"; \
348− me->inner = gap(d->body, &d->body[me->inner.start], tag_start); \
349− me->outer = gap(d->body, &d->body[me->outer.start], p);
350−#define P_TagOpenClose \
351− me->nodes = start_end(0, 0); \
352− me->inner = gap(d->body, p, p); \
353− me->outer = gap(d->body, tag_start, p);
354−
355−// Given the parsed string, return either NULL (success) or an error message (failure)
356−static const char* parser(const char* p, document* d)
357−{
358− const char *tag_start; // Where the tag starts [<foo
359− const char *name_start, *name_end; // Where a name is <[foo] or <foo [bar]=
360− const char *quote_start; // Where an attribute quote is <foo bar='[123'>
361− node* me; // The current node I'm working on
362−# include "test.h"
363− return NULL;
364−}
365−
366−// Based on looking at ~50Kb XML documents, they seem to have ~700 attributes
367−// and ~300 nodes, so size appropriately to cope with that.
368−typedef struct
369−{
370− document document;
371− attr attrs[1000];
372− node nodes[500];
373−} buffer;
374−
375−document* hexml_document_parse(const char* s, int slen)
376−{
377− if (slen == -1) slen = (int) strlen(s);
378− assert(s[slen] == 0);
379−
380− buffer* buf = malloc(sizeof(buffer));
381− assert(buf);
382− document* d = &buf->document;
383− d->body = s;
384− d->body_len = slen;
385− d->error_message = NULL;
386− d->attrs.size = 1000;
387− d->attrs.used = 0;
388− d->attrs.attrs = buf->attrs;
389− d->attrs.alloc = NULL;
390− d->nodes.size = 500;
391− d->nodes.used_back = 0;
392− d->nodes.used_front = 1;
393− d->nodes.nodes = buf->nodes;
394− d->nodes.alloc = NULL;
395−
396− d->nodes.nodes[0].name = start_length(0, 0);
397− d->nodes.nodes[0].outer = start_length(0, slen);
398− d->nodes.nodes[0].inner = start_length(0, slen);
399− d->nodes.nodes[0].attrs = start_length(0, 0);
400−
401− // Introduce an intermediate result, otherwise behaviour is undefined
402− // because there is no guaranteed ordering between LHS and RHS evaluation
403− const char* res = parser(s, d);
404− if (res != NULL)
405− set_error(d, res);
406− else
407− {
408− d->nodes.nodes[0].nodes = node_commit(&d->nodes);
409− if (d->nodes.used_back != 0)
410− d->error_message = strdup("used_back failure");
411− }
412− return d;
413−}
414−
415−/////////////////////////////////////////////////////////////////////
416−// TEST HARNESS (temporary)
417−
418−#ifdef TEST
419−
420−static char* resolve(const document* d, str loc)
421−{
422− char* buf = malloc(loc.length + 3);
423− memcpy(&buf[1], &d->body[loc.start], loc.length);
424− buf[0] = '\"';
425− buf[loc.length+1] = '\"';
426− buf[loc.length+2] = 0;
427− return buf;
428−}
429−
430−static char* disp(str loc)
431−{
432− char* buf = malloc(100);
433− if (loc.length == 0)
434− strcpy(buf, "[]");
435− else
436− sprintf(buf, "[%i..%i]", loc.start, end(loc)-1);
437− return buf;
438−}
439−
440−int main()
441−{
442− setbuf(stdout, NULL);
443− const char* example = "<test foo='123 xyz' bar='t'><inner /> value</test>";
444− document* d = hexml_document_parse(example, -1);
445− if (d->error_message != NULL)
446− printf("Parse failure: %s\n", d->error_message);
447− else
448− {
449− char buf[1000];
450− int len = hexml_node_render(d, hexml_document_node(d), buf, sizeof(buf));
451− buf[len] = 0;
452− printf("Result = %s\n", buf);
453− printf("Used nodes = %i, attributes = %i\n", d->nodes.used_front, d->attrs.used);
454−
455− for (int i = 0; i < d->attrs.used; i++)
456− printf("Attr %i: %s=%s\n", i, resolve(d, d->attrs.attrs[i].name), resolve(d, d->attrs.attrs[i].value));
457− for (int i = 0; i < d->nodes.used_front; i++)
458− {
459− printf("Node %i: %s attrs=%s, nodes=%s\n", i, resolve(d, d->nodes.nodes[i].name), disp(d->nodes.nodes[i].attrs), disp(d->nodes.nodes[i].nodes));
460− printf(" inner %s\n outer %s\n", resolve(d, d->nodes.nodes[i].inner), resolve(d, d->nodes.nodes[i].outer));
461− }
462− }
463− hexml_document_free(d);
464− return 0;
465−}
466−
467−#endif
468−
469−/*
470−void main()
471−{
472− setbuf(stdout, NULL);
473− char* body = "<?xml version=\"1.0\" lang=\"ja\"?><foo><bar baz=\"qux\">quux</bar><bar baz=\"bar\">hoge</bar><piyo>&gt;piyopiyo&lt;</piyo></foo><foo2 />";
474− body = "<test>X</test> ";
475− document* doc = document_parse(body, -1);
476− char* err = document_error(doc);
477− if (err == NULL)
478− {
479− int len = document_render(doc, NULL, 0);
480− char* s = malloc(len + 1);
481− document_render(doc, s, len);
482− s[len] = 0;
483− printf("Parse successful, %s\n", s);
484− }
485− else
486− printf("Parse failed, %s\n", err);
487−}
488−*/
ghci.bat+1−2
@@ -1,2 +1 @@
1−gcc -O2 -std=c99 -c cbits\hexml.c -o cbits\hexml.o -Wall -Wextra -Werror && ghci.exe cbits\hexml.o %*
2−rem gcc -O2 -std=c99 -c gen\test.c -o gen\test.o -Wall -Wextra -Werror && ghci.exe gen\test.o %*
1+gcc -O3 -c cbits\hexml.c -o cbits\hexml.o && ghci.exe cbits\hexml.o %*
go.batnew file+2
@@ -0,0 +1,2 @@
1+rem sos -p .*.c -c "go 2>&1 | head -n12"
2+gcc -c hexml.c -O3 && gcc -c main.c -O3 && gcc -o hexml hexml.o main.o && hexml
hexml.cabaldeleted−55
@@ -1,55 +0,0 @@
1−cabal-version: >= 1.18
2−build-type: Simple
3−name: hexml
4−version: 0.3.4
5−license: BSD3
6−x-license: BSD-3-Clause OR Apache-2.0
7−license-file: LICENSE
8−category: XML
9−author: Neil Mitchell <ndmitchell@gmail.com>
10−maintainer: Neil Mitchell <ndmitchell@gmail.com>
11−copyright: Neil Mitchell 2016-2023
12−synopsis: XML subset DOM parser
13−description:
14− An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast.
15−homepage: https://github.com/ndmitchell/hexml#readme
16−bug-reports: https://github.com/ndmitchell/hexml/issues
17−tested-with: GHC==9.6, GHC==9.4, GHC==9.2, GHC==9.0, GHC==8.10, GHC==8.8
18−extra-doc-files:
19− README.md
20− CHANGES.txt
21−extra-source-files:
22− cbits/*.h
23− cbits/*.c
24−
25−source-repository head
26− type: git
27− location: https://github.com/ndmitchell/hexml.git
28−
29−library
30− hs-source-dirs: src
31− default-language: Haskell2010
32−
33− build-depends:
34− base >= 4.5 && < 5,
35− bytestring,
36− extra >= 0.3
37−
38− c-sources: cbits/hexml.c
39− include-dirs: cbits
40− includes: hexml.h
41− install-includes: hexml.h
42− cc-options: -std=c99
43−
44− exposed-modules:
45− Text.XML.Hexml
46−
47−test-suite hexml-test
48− type: exitcode-stdio-1.0
49− main-is: src/Main.hs
50− default-language: Haskell2010
51−
52− build-depends:
53− base,
54− bytestring,
55− hexml
main.cnew file+22
@@ -0,0 +1,22 @@
1+#include "hexml.h"
2+#include <stdio.h>
3+#include <malloc.h>
4+
5+void main()
6+{
7+ setbuf(stdout, NULL);
8+ char* body = "<?xml version=\"1.0\" lang=\"ja\"?><foo><bar baz=\"qux\">quux</bar><bar baz=\"bar\">hoge</bar><piyo>&gt;piyopiyo&lt;</piyo></foo><foo2 />";
9+ body = "<test>X</test> ";
10+ document* doc = document_parse(body, -1);
11+ char* err = document_error(doc);
12+ if (err == NULL)
13+ {
14+ int len = document_render(doc, NULL, 0);
15+ char* s = malloc(len + 1);
16+ document_render(doc, s, len);
17+ s[len] = 0;
18+ printf("Parse successful, %s\n", s);
19+ }
20+ else
21+ printf("Parse failed, %s\n", err);
22+}
src/Main.hs+6−78
@@ -1,84 +1,12 @@
1−{-# LANGUAGE OverloadedStrings #-}
21
32module Main(main) where
43
5−import Text.XML.Hexml as X
6−import qualified Data.ByteString.Char8 as BS
7−import Control.Monad
8−import Data.Monoid
9−import Data.Char
10−import Prelude
11−
12−
13−examples :: [(Bool, BS.ByteString)]
14−examples =
15− [(True, "<test id='bob'>here<extra/>there</test>")
16− ,(True, "<test /><close />")
17− ,(True, "<test /><!-- comment > --><close />")
18− ,(True, "<test id=\"bob value\" another-attr=\"test with <\">here </test> more text at the end<close />")
19− ,(False, "<test></more>")
20− ,(False, "<test")
21− ,(True, "<?xml version=\"1.1\"?>\n<greeting>Hello, world!</greeting>")
22− ,(True, "<foo bar.baz=\"qux\"></foo>")
23− ]
4+import Text.XML.Hexml
5+import qualified Data.ByteString as BS
246
257main :: IO ()
268main = do
27− forM_ examples $ \(parses, src) ->
28− case parse src of
29− Left err -> when parses $ fail $ "Unexpected parse failure, " ++ show err
30− Right doc -> do
31− unless parses $ fail "Unexpected parse success"
32− checkFind doc
33− let r = render doc
34− r === rerender doc
35− let Right d = parse r
36− r === render d
37−
38− let Right doc = parse "<test id=\"1\" extra=\"2\" />\n<test id=\"2\" /><b><test id=\"3\" /></b><test id=\"4\" /><test />"
39− map name (children doc) === ["test","test","b","test","test"]
40− location (children doc !! 2) === (2,16)
41− length (childrenBy doc "test") === 4
42− length (childrenBy doc "b") === 1
43− length (childrenBy doc "extra") === 0
44− attributes (head $ children doc) === [Attribute "id" "1", Attribute "extra" "2"]
45− map (`attributeBy` "id") (childrenBy doc "test") === map (fmap (Attribute "id")) [Just "1", Just "2", Just "4", Nothing]
46−
47− Right _ <- pure $ parse $ "<test " <> BS.unwords [BS.pack $ "x" ++ show i ++ "='value'" | i <- [1..10000]] <> " />"
48− Right _ <- pure $ parse $ BS.unlines $ replicate 10000 "<test x='value' />"
49−
50− let attrs = ["usd:jpy","test","extra","more","stuff","jpy:usd","xxx","xxxx"]
51− Right doc <- pure $ parse $ "<test " <> BS.unwords [x <> "='" <> x <> "'" | x <- attrs] <> ">middle</test>"
52− [c] <- pure $ childrenBy doc "test"
53− forM_ attrs $ \a -> attributeBy c a === Just (Attribute a a)
54− forM_ ["missing","gone","nothing"] $ \a -> attributeBy c a === Nothing
55− putStrLn "\nSuccess"
56−
57−
58−checkFind :: Node -> IO ()
59−checkFind n = do
60− forM_ (attributes n) $ \a -> attributeBy n (attributeName a) === Just a
61− attributeBy n "xxx" === Nothing
62− let cs = children n
63− forM_ ("xxx":map name cs) $ \c ->
64− map outer (filter ((==) c . name) cs) === map outer (childrenBy n c)
65− mapM_ checkFind $ children n
66−
67−
68−a === b = if a == b then putChar '.' else fail $ "mismatch, " ++ show a ++ " /= " ++ show b
69−
70−rerender :: Node -> BS.ByteString
71−rerender = inside
72− where
73− inside x = BS.concat $ map (either validStr node) $ contents x
74− node x = "<" <> BS.unwords (validName (name x) : map attr (attributes x)) <> ">" <>
75− inside x <>
76− "</" <> name x <> ">"
77− attr (Attribute a b) = validName a <> "=\"" <> validAttr b <> "\""
78−
79− validName x | BS.all (\x -> isAlphaNum x || x `elem` ("-.:_" :: String)) x = x
80− | otherwise = error "Invalid name"
81− validAttr x | BS.notElem '\"' x = x
82− | otherwise = error "Invalid attribute"
83− validStr x | BS.notElem '<' x || BS.isInfixOf "<!--" x = x
84− | otherwise = error $ show ("Invalid string", x)
9+ bs <- BS.readFile "C:/Neil/notes/core.xml"
10+ case parse bs of
11+ Left err -> error $ show err
12+ Right val -> print $ render val
src/Text/XML/Hexml.hs+74−173
@@ -1,205 +1,106 @@
1−{-# LANGUAGE RecordWildCards, BangPatterns #-}
1+{-# LANGUAGE RecordWildCards #-}
22
3−-- | A module for fast first-approximation parsing of XML.
4−-- Note that entities, e.g. @&amp;@, are not expanded.
53module Text.XML.Hexml(
6− Node, Attribute(..),
7− parse, render,
8− location, name, inner, outer,
9− attributes, children, contents,
10− attributeBy, childrenBy
4+ Document, Node, Attribute(..),
5+ parse, render, rootNode,
6+ nodeName, nodeInner, nodeOuter,
7+ nodeAttributes, nodeChildren, nodeContents,
8+ nodeAttributeBy, nodeChildrenBy
119 ) where
1210
13−import Control.Applicative
1411import Control.Monad
1512import Data.Int
1613import Foreign.C
1714import Foreign.Ptr
18−import Foreign.Marshal hiding (void)
1915import Foreign.ForeignPtr
2016import Foreign.Storable
2117import System.IO.Unsafe
2218import Data.Monoid
23−import Data.Tuple.Extra
24−import qualified Data.ByteString.Char8 as BS
19+import qualified Data.ByteString as BS
2520import qualified Data.ByteString.Unsafe as BS
2621import qualified Data.ByteString.Internal as BS
27−import Prelude
2822
2923data CDocument
3024data CNode
3125data CAttr
3226
33−szAttr = 2 * sizeOf (undefined :: Str)
34−szNode = 5 * sizeOf (undefined :: Str)
35−
36−
37−data Str = Str {strStart :: {-# UNPACK #-} !Int32, strLength :: {-# UNPACK #-} !Int32} deriving Show
38−
39−strEnd :: Str -> Int32
40−strEnd Str{..} = strStart + strLength
27+data Str = Str {start :: {-# UNPACK #-} !Int32, length :: {-# UNPACK #-} !Int32}
4128
4229instance Storable Str where
4330 sizeOf _ = 8
4431 alignment _ = alignment (0 :: Int64)
4532 peek p = Str <$> peekByteOff p 0 <*> peekByteOff p 4
46− poke p Str{..} = pokeByteOff p 0 strStart >> pokeByteOff p 4 strLength
33+ poke p Str{..} = pokeByteOff p 0 start >> pokeByteOff p 4 length
4734
48−foreign import ccall hexml_document_parse :: CString -> CInt -> IO (Ptr CDocument)
49−foreign import ccall hexml_document_free :: Ptr CDocument -> IO ()
50−foreign import ccall "&hexml_document_free" hexml_document_free_funptr :: FunPtr (Ptr CDocument -> IO ())
51−foreign import ccall hexml_node_render :: Ptr CDocument -> Ptr CNode -> CString -> CInt -> IO CInt
52−foreign import ccall unsafe hexml_document_error :: Ptr CDocument -> IO CString
53−foreign import ccall unsafe hexml_document_node :: Ptr CDocument -> IO (Ptr CNode)
35+foreign import ccall document_parse :: CString -> CInt -> IO (Ptr CDocument)
36+foreign import ccall document_free :: Ptr CDocument -> IO ()
37+foreign import ccall "&document_free" document_free_funptr :: FunPtr (Ptr CDocument -> IO ())
38+foreign import ccall document_render :: Ptr CDocument -> CString -> CInt -> IO CInt
39+foreign import ccall document_error :: Ptr CDocument -> IO CString
40+foreign import ccall document_node :: Ptr CDocument -> IO (Ptr CNode)
5441
55−foreign import ccall unsafe hexml_node_children :: Ptr CDocument -> Ptr CNode -> Ptr CInt -> IO (Ptr CNode)
56−foreign import ccall unsafe hexml_node_attributes :: Ptr CDocument -> Ptr CNode -> Ptr CInt -> IO (Ptr CAttr)
42+foreign import ccall node_children :: Ptr CDocument -> Ptr CNode -> Ptr CInt -> Ptr CNode
43+foreign import ccall node_attributes :: Ptr CDocument -> Ptr CNode -> Ptr CInt -> Ptr CAttr
5744
58−foreign import ccall unsafe hexml_node_child :: Ptr CDocument -> Ptr CNode -> Ptr CNode -> CString -> CInt -> IO (Ptr CNode)
59−foreign import ccall unsafe hexml_node_attribute :: Ptr CDocument -> Ptr CNode -> CString -> CInt -> IO (Ptr CAttr)
45+foreign import ccall node_firstChildBy :: Ptr CDocument -> Ptr CNode -> CString -> CInt -> IO (Ptr CNode)
46+foreign import ccall node_nextChildBy :: Ptr CDocument -> Ptr CNode -> Ptr CNode -> CString -> CInt -> IO (Ptr CNode)
47+foreign import ccall node_attributeBy :: Ptr CDocument -> Ptr CNode -> CString -> CInt -> IO (Ptr CAttr)
48+
49+data Document = Document BS.ByteString (ForeignPtr CDocument)
6050
61−-- | A node in an XML document, created by 'parse', then calling functions such
62−-- as 'children' on that initial 'Node'.
6351data Node = Node BS.ByteString (ForeignPtr CDocument) (Ptr CNode)
6452
65−-- | An XML attribute, comprising of a name and a value. As an example,
66−-- @hello=\"world\"@ would produce @Attribute \"hello\" \"world\"@.
67−data Attribute = Attribute
68− {attributeName :: BS.ByteString
69− ,attributeValue :: BS.ByteString
70− } deriving (Show, Eq, Ord)
71−
72−instance Show Node where
73− show d = "Node " ++ show (BS.unpack $ outer d)
74−
75−
76−touchBS :: BS.ByteString -> IO ()
77−touchBS = touchForeignPtr . fst3 . BS.toForeignPtr
78−
79−
80−-- | Parse a ByteString as an XML document, returning a 'Left' error message, or a 'Right' document.
81−-- Note that the returned node will have a 'name' of @\"\"@, no 'attributes', and 'contents' as per the document.
82−-- Often the first child will be the @\<?xml ... ?\>@ element. For documents which comprise an XML node and a single
83−-- root element, use @'children' n !! 1@.
84−parse :: BS.ByteString -> Either BS.ByteString Node
85−parse src = do
86− let src0 = src <> BS.singleton '\0'
87− unsafePerformIO $ BS.unsafeUseAsCStringLen src0 $ \(str, len) -> do
88− doc <- hexml_document_parse str (fromIntegral len - 1)
89− err <- hexml_document_error doc
90− if err /= nullPtr then do
91− bs <- BS.packCString =<< hexml_document_error doc
92− hexml_document_free doc
93− pure $ Left bs
94− else do
95− node <- hexml_document_node doc
96− doc <- newForeignPtr hexml_document_free_funptr doc
97− pure $ Right $ Node src0 doc node
98−
99−-- | Given a node, rerender it to something with an equivalent parse tree.
100−-- Mostly useful for debugging - if you want the real source document use 'outer' instead.
101−render :: Node -> BS.ByteString
102−render (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d -> do
103− i <- hexml_node_render d n nullPtr 0
104− res <- BS.create (fromIntegral i) $ \ptr -> void $ hexml_node_render d n (castPtr ptr) i
105− touchBS src
106− pure res
53+data Attribute = Attribute BS.ByteString BS.ByteString
54+
55+parse :: BS.ByteString -> Either BS.ByteString Document
56+parse src = unsafePerformIO $ BS.unsafeUseAsCStringLen (src <> BS.singleton 0) $ \(str, len) -> do
57+ doc <- document_parse str (fromIntegral len - 1)
58+ err <- document_error doc
59+ if err /= nullPtr then do
60+ bs <- BS.packCString =<< document_error doc
61+ document_free doc
62+ return $ Left bs
63+ else do
64+ doc <- newForeignPtr document_free_funptr doc
65+ return $ Right $ Document src doc
66+
67+render :: Document -> BS.ByteString
68+render (Document _ doc) = unsafePerformIO $ withForeignPtr doc $ \d -> do
69+ i <- document_render d nullPtr 0
70+ BS.create (fromIntegral i) $ \ptr -> void $ document_render d (castPtr ptr) i
71+
72+
73+rootNode :: Document -> Node
74+rootNode d@(Document src doc) = unsafePerformIO $ withForeignPtr doc $ \d -> do
75+ Node src doc <$> document_node d
10776
10877applyStr :: BS.ByteString -> Str -> BS.ByteString
109−applyStr bs Str{..} = BS.take (fromIntegral strLength) $ BS.drop (fromIntegral strStart) bs
110−
111−nodeStr :: Int -> Node -> Str
112−nodeStr i (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \_ -> peekElemOff (castPtr n) i
113−
114−nodeBS :: Int -> Node -> BS.ByteString
115−nodeBS i node@(Node src doc n) = applyStr src $ nodeStr i node
116−
117−attrPeek :: BS.ByteString -> ForeignPtr CDocument -> Ptr CAttr -> Attribute
118−attrPeek src doc a = unsafePerformIO $ withForeignPtr doc $ \_ -> do
119− name <- applyStr src <$> peekElemOff (castPtr a) 0
120− val <- applyStr src <$> peekElemOff (castPtr a) 1
121− pure $ Attribute name val
122−
123−-- | Get the name of a node, e.g. @\<test /\>@ produces @\"test\"@.
124−name :: Node -> BS.ByteString
125−name = nodeBS 0
126−
127−-- | Get the inner text, from inside the tag, e.g. @\<test /\>@ produces @\"\"@
128−-- and @\<test\>hello\</test\>@ produces @\"hello\"@.
129−-- The result will have identical layout/spacing to the source document.
130−inner :: Node -> BS.ByteString
131−inner = nodeBS 1
132−
133−-- | Get the outer text, including the tag itself, e.g. @\<test /\>@ produces @\"\<test /\>\"@
134−-- and @\<test\>hello\</test\>@ produces @\"\<test\>hello\</test\>\"@.
135−-- The result will have identical layout/spacing to the source document.
136−outer :: Node -> BS.ByteString
137−outer = nodeBS 2
138−
139−-- | Get the contents of a node, including both the content strings (as 'Left', never blank) and
140−-- the direct child nodes (as 'Right').
141−-- If you only want the child nodes, use 'children'.
142−contents :: Node -> [Either BS.ByteString Node]
143−contents n@(Node src _ _) = f (strStart inner) outers
144− where
145− f i [] = string i (strEnd inner)
146− f i ((x, n):xs) = string i (strStart x) ++ Right n : f (strEnd x) xs
147−
148− string start end | start == end = []
149− | otherwise = [Left $ applyStr src $ Str start (end - start)]
150− inner = nodeStr 1 n
151− outers = map (nodeStr 2 &&& id) $ children n
152−
153−-- | Get the direct child nodes of this node.
154−children :: Node -> [Node]
155−children (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d ->
156− alloca $ \count -> do
157− res <- hexml_node_children d n count
158− count <- fromIntegral <$> peek count
159− pure [Node src doc $ plusPtr res $ i*szNode | i <- [0..count-1]]
160−
161−-- | Get the attributes of this node.
162−attributes :: Node -> [Attribute]
163−attributes (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d ->
164− alloca $ \count -> do
165− res <- hexml_node_attributes d n count
166− count <- fromIntegral <$> peek count
167− pure [attrPeek src doc $ plusPtr res $ i*szAttr | i <- [0..count-1]]
168−
169−-- | Get the direct children of this node which have a specific name.
170−-- A more efficient version of:
171−--
172−-- > childrenBy p s = filter (\n -> name n == s) $ children p
173−childrenBy :: Node -> BS.ByteString -> [Node]
174−childrenBy (Node src doc n) str = go nullPtr
175− where
176− go old = unsafePerformIO $ withForeignPtr doc $ \d ->
177− BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
178− r <- hexml_node_child d n old bs $ fromIntegral len
179− touchBS src
180− pure $ if r == nullPtr then [] else Node src doc r : go r
181−
182−-- | Get the first attribute of this node which has a specific name, if there is one.
183−-- A more efficient version of:
184−--
185−-- > attributeBy n s = listToMaybe $ filter (\(Attribute a _) -> a == s $ attributes n
186−attributeBy :: Node -> BS.ByteString -> Maybe Attribute
187−attributeBy (Node src doc n) str = unsafePerformIO $ withForeignPtr doc $ \d ->
188− BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
189− r <- hexml_node_attribute d n bs $ fromIntegral len
190− touchBS src
191− pure $ if r == nullPtr then Nothing else Just $ attrPeek src doc r
192−
193−-- | Find the starting location of a node, the @<@ character.
194−-- The first character will be reported as @(line 1,column 1)@, because thats
195−-- how error messages typically do it.
196−location :: Node -> (Int, Int)
197−location n@(Node src _ _) = BS.foldl' f (pair 1 1) $ BS.take (fromIntegral i) src
198− where
199− pair !a !b = (a,b)
200−
201− i = strStart $ nodeStr 2 n
202− f (!line, !col) c
203− | c == '\n' = pair (line+1) 1
204− | c == '\t' = pair line (col+8)
205− | otherwise = pair line (col+1)
78+applyStr bs Str{..} = BS.take (fromIntegral length) $ BS.drop (fromIntegral start) bs
79+
80+nodePeek :: Int -> Node -> BS.ByteString
81+nodePeek i (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \_ -> do
82+ applyStr src <$> peekElemOff (castPtr n) i
83+
84+nodeName :: Node -> BS.ByteString
85+nodeName = nodePeek 0
86+
87+nodeInner :: Node -> BS.ByteString
88+nodeInner = nodePeek 1
89+
90+nodeOuter :: Node -> BS.ByteString
91+nodeOuter = nodePeek 2
92+
93+nodeContents :: Node -> [Either Node BS.ByteString]
94+nodeContents = undefined
95+
96+nodeChildren :: Node -> [Node]
97+nodeChildren = node_children `seq` undefined
98+
99+nodeAttributes :: Node -> [Attribute]
100+nodeAttributes = node_attributes `seq` undefined
101+
102+nodeChildrenBy :: Node -> BS.ByteString -> [Node]
103+nodeChildrenBy = node_firstChildBy `seq` node_nextChildBy `seq` undefined
104+
105+nodeAttributeBy :: Node -> BS.ByteString -> Maybe Attribute
106+nodeAttributeBy = node_attributeBy `seq` undefined
vs/hexml.slndeleted−28
@@ -1,28 +0,0 @@
1−
2−Microsoft Visual Studio Solution File, Format Version 12.00
3−# Visual Studio 14
4−VisualStudioVersion = 14.0.25420.1
5−MinimumVisualStudioVersion = 10.0.40219.1
6−Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hexml", "hexml.vcxproj", "{9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}"
7−EndProject
8−Global
9− GlobalSection(SolutionConfigurationPlatforms) = preSolution
10− Debug|x64 = Debug|x64
11− Debug|x86 = Debug|x86
12− Release|x64 = Release|x64
13− Release|x86 = Release|x86
14− EndGlobalSection
15− GlobalSection(ProjectConfigurationPlatforms) = postSolution
16− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Debug|x64.ActiveCfg = Debug|x64
17− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Debug|x64.Build.0 = Debug|x64
18− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Debug|x86.ActiveCfg = Debug|Win32
19− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Debug|x86.Build.0 = Debug|Win32
20− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Release|x64.ActiveCfg = Release|x64
21− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Release|x64.Build.0 = Release|x64
22− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Release|x86.ActiveCfg = Release|Win32
23− {9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}.Release|x86.Build.0 = Release|Win32
24− EndGlobalSection
25− GlobalSection(SolutionProperties) = preSolution
26− HideSolutionNode = FALSE
27− EndGlobalSection
28−EndGlobal
vs/hexml.vcxprojdeleted−153
@@ -1,153 +0,0 @@
1−<?xml version="1.0" encoding="utf-8"?>
2−<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3− <ItemGroup Label="ProjectConfigurations">
4− <ProjectConfiguration Include="Debug|Win32">
5− <Configuration>Debug</Configuration>
6− <Platform>Win32</Platform>
7− </ProjectConfiguration>
8− <ProjectConfiguration Include="Release|Win32">
9− <Configuration>Release</Configuration>
10− <Platform>Win32</Platform>
11− </ProjectConfiguration>
12− <ProjectConfiguration Include="Debug|x64">
13− <Configuration>Debug</Configuration>
14− <Platform>x64</Platform>
15− </ProjectConfiguration>
16− <ProjectConfiguration Include="Release|x64">
17− <Configuration>Release</Configuration>
18− <Platform>x64</Platform>
19− </ProjectConfiguration>
20− </ItemGroup>
21− <ItemGroup>
22− <ClCompile Include="..\cbits\hexml.c" />
23− <ClInclude Include="..\cbits\hexml.h" />
24− <ClCompile Include="..\cbits\main.c" />
25− </ItemGroup>
26− <PropertyGroup Label="Globals">
27− <ProjectGuid>{9DA5F6EE-A35E-4ED6-ABC2-A7707392E6EB}</ProjectGuid>
28− <Keyword>Win32Proj</Keyword>
29− <RootNamespace>hexml</RootNamespace>
30− <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
31− </PropertyGroup>
32− <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
33− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
34− <ConfigurationType>Application</ConfigurationType>
35− <UseDebugLibraries>true</UseDebugLibraries>
36− <PlatformToolset>v140</PlatformToolset>
37− <CharacterSet>Unicode</CharacterSet>
38− </PropertyGroup>
39− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
40− <ConfigurationType>Application</ConfigurationType>
41− <UseDebugLibraries>false</UseDebugLibraries>
42− <PlatformToolset>v140</PlatformToolset>
43− <WholeProgramOptimization>true</WholeProgramOptimization>
44− <CharacterSet>Unicode</CharacterSet>
45− </PropertyGroup>
46− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
47− <ConfigurationType>Application</ConfigurationType>
48− <UseDebugLibraries>true</UseDebugLibraries>
49− <PlatformToolset>v140</PlatformToolset>
50− <CharacterSet>Unicode</CharacterSet>
51− </PropertyGroup>
52− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
53− <ConfigurationType>Application</ConfigurationType>
54− <UseDebugLibraries>false</UseDebugLibraries>
55− <PlatformToolset>v140</PlatformToolset>
56− <WholeProgramOptimization>true</WholeProgramOptimization>
57− <CharacterSet>Unicode</CharacterSet>
58− </PropertyGroup>
59− <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
60− <ImportGroup Label="ExtensionSettings">
61− </ImportGroup>
62− <ImportGroup Label="Shared">
63− </ImportGroup>
64− <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
65− <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
66− </ImportGroup>
67− <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
68− <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
69− </ImportGroup>
70− <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
71− <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
72− </ImportGroup>
73− <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
74− <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
75− </ImportGroup>
76− <PropertyGroup Label="UserMacros" />
77− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
78− <LinkIncremental>true</LinkIncremental>
79− </PropertyGroup>
80− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
81− <LinkIncremental>true</LinkIncremental>
82− </PropertyGroup>
83− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
84− <LinkIncremental>false</LinkIncremental>
85− </PropertyGroup>
86− <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
87− <LinkIncremental>false</LinkIncremental>
88− </PropertyGroup>
89− <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
90− <ClCompile>
91− <PrecompiledHeader>
92− </PrecompiledHeader>
93− <WarningLevel>Level3</WarningLevel>
94− <Optimization>Disabled</Optimization>
95− <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_DEPRECATE</PreprocessorDefinitions>
96− </ClCompile>
97− <Link>
98− <SubSystem>Console</SubSystem>
99− <GenerateDebugInformation>true</GenerateDebugInformation>
100− </Link>
101− </ItemDefinitionGroup>
102− <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
103− <ClCompile>
104− <PrecompiledHeader>
105− </PrecompiledHeader>
106− <WarningLevel>Level3</WarningLevel>
107− <Optimization>Disabled</Optimization>
108− <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_DEPRECATE</PreprocessorDefinitions>
109− <AdditionalIncludeDirectories>..\cbits</AdditionalIncludeDirectories>
110− </ClCompile>
111− <Link>
112− <SubSystem>Console</SubSystem>
113− <GenerateDebugInformation>true</GenerateDebugInformation>
114− </Link>
115− </ItemDefinitionGroup>
116− <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
117− <ClCompile>
118− <WarningLevel>Level3</WarningLevel>
119− <PrecompiledHeader>
120− </PrecompiledHeader>
121− <Optimization>MaxSpeed</Optimization>
122− <FunctionLevelLinking>true</FunctionLevelLinking>
123− <IntrinsicFunctions>true</IntrinsicFunctions>
124− <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
125− </ClCompile>
126− <Link>
127− <SubSystem>Console</SubSystem>
128− <EnableCOMDATFolding>true</EnableCOMDATFolding>
129− <OptimizeReferences>true</OptimizeReferences>
130− <GenerateDebugInformation>true</GenerateDebugInformation>
131− </Link>
132− </ItemDefinitionGroup>
133− <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
134− <ClCompile>
135− <WarningLevel>Level3</WarningLevel>
136− <PrecompiledHeader>
137− </PrecompiledHeader>
138− <Optimization>MaxSpeed</Optimization>
139− <FunctionLevelLinking>true</FunctionLevelLinking>
140− <IntrinsicFunctions>true</IntrinsicFunctions>
141− <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
142− </ClCompile>
143− <Link>
144− <SubSystem>Console</SubSystem>
145− <EnableCOMDATFolding>true</EnableCOMDATFolding>
146− <OptimizeReferences>true</OptimizeReferences>
147− <GenerateDebugInformation>true</GenerateDebugInformation>
148− </Link>
149− </ItemDefinitionGroup>
150− <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
151− <ImportGroup Label="ExtensionTargets">
152− </ImportGroup>
153−</Project>
xml/example.xmldeleted−23
@@ -1,23 +0,0 @@
1−<?xml version="1.0"?>
2−<catalog>
3− <book id="bk101">
4− <author>Gambardella, Matthew</author>
5− <title>XML Developer's Guide</title>
6− <genre>Computer</genre>
7− <price>44.95</price>
8− <publish_date>2000-10-01</publish_date>
9− <description>An in-depth look at creating applications
10− with XML.</description>
11− </book>
12− <!-- more books -->
13− <book id="bk102">
14− <author>Ralls, Kim</author>
15− <title>Midnight Rain</title>
16− <genre>Fantasy</genre>
17− <price>5.95</price>
18− <publish_date>2000-12-16</publish_date>
19− <description>A former architect battles corporate zombies,
20− an evil sorceress, and her own childhood to become queen
21− of the world.</description>
22− </book>
23−</catalog>
xml/simple.xmldeleted−2
@@ -1,2 +0,0 @@
1−<a b="c">d</a>
2−