on master help sign in
.ghci+1−1
since splitsnapshots
unifiedsplit
@@ -1,4 +1,4 @@
1−:set -fwarn-unused-binds -fwarn-unused-imports -fwarn-orphans
1+:set -Wunused-binds -Wunused-imports -Worphans
2:set -isrc
2:set -isrc
3:load Main
3:load Main
4
4
.github/workflows/ci.ymlnew file+33
@@ -0,0 +1,33 @@
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
.travis.ymldeleted−13
@@ -1,13 +0,0 @@
1−sudo: required
2−env:
3−- GHCVER=7.6.3
4−- GHCVER=7.8.4
5−- GHCVER=7.10.3
6−- GHCVER=8.0.2
7−- GHCVER=8.2.1
8−- GHCVER=8.4.1
9−- GHCVER=head
10−
11−script:
12−- curl -sL https://raw.github.com/ndmitchell/neil/master/travis.sh | sh
13−- sh afl.sh
CHANGES.txt+2
@@ -1,5 +1,7 @@
1Changelog for Hexml
1Changelog for Hexml
2
2
3+0.3.4, released 2018-09-10
4+ #15, licensed under BSD-3-Clause OR Apache-2.0
30.3.3, released 2017-10-27
50.3.3, released 2017-10-27
4 #13, allow the . character in tags and attributes
6 #13, allow the . character in tags and attributes
50.3.2, released 2017-05-22
70.3.2, released 2017-05-22
LICENSE+190−3
@@ -1,9 +1,20 @@
1−Copyright Neil Mitchell 2018.
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
2All rights reserved.
13All rights reserved.
3
14
4Redistribution and use in source and binary forms, with or without
15Redistribution and use in source and binary forms, with or without
5−modification, are permitted provided that the following conditions are
16+modification, are permitted provided that the following conditions
6−met:
17+are met:
7
18
8 * Redistributions of source code must retain the above copyright
19 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
20 notice, this list of conditions and the following disclaimer.
@@ -28,3 +39,179 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41OF 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.mdnew file+5
@@ -0,0 +1,5 @@
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+4−2
@@ -1,4 +1,4 @@
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/lts?label=Stackage)](https://www.stackage.org/package/hexml) [![Linux Build Status](https://img.shields.io/travis/ndmitchell/hexml.svg?label=Linux%20build)](https://travis-ci.org/ndmitchell/hexml) [![Windows Build Status](https://img.shields.io/appveyor/ci/ndmitchell/hexml.svg?label=Windows%20build)](https://ci.appveyor.com/project/ndmitchell/hexml)
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
2
3An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast. In particular:
3An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast. In particular:
4
4
@@ -8,8 +8,10 @@ An XML DOM-style parser, that only parses a subset of XML, but is designed to be
8
8
9The 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.
9The 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
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)).
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
12
13Hexml is tested with [AFL](http://lcamtuf.coredump.cx/afl/).
13Hexml is tested with [AFL](http://lcamtuf.coredump.cx/afl/).
14
14
15If you want lenses for Hexml, see [hexml-lens](http://hackage.haskell.org/package/hexml-lens).
15If 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.
appveyor.ymldeleted−5
@@ -1,5 +0,0 @@
1−build: off
2−cache: "c:\\sr -> appveyor.yml"
3−
4−test_script:
5−- ps: Invoke-Expression (Invoke-WebRequest 'https://raw.githubusercontent.com/ndmitchell/neil/master/appveyor.ps1')
cbits/hexml.c+2−2
@@ -40,7 +40,7 @@ typedef struct
40 // have a cursor at the end, which is stack scoped, so children write out, then I do
40 // have a cursor at the end, which is stack scoped, so children write out, then I do
41 // when you commit, you copy over from end to front
41 // when you commit, you copy over from end to front
42
42
43− // nodes
43+ // nodes
44 int size;
44 int size;
45 int used_front; // front entries, stored for good
45 int used_front; // front entries, stored for good
46 int used_back; // back entries, stack based, copied into front
46 int used_back; // back entries, stack based, copied into front
@@ -539,7 +539,7 @@ document* hexml_document_parse(const char* s, int slen)
539 d->nodes.nodes[0].outer = start_length(0, slen);
539 d->nodes.nodes[0].outer = start_length(0, slen);
540 d->nodes.nodes[0].inner = start_length(0, slen);
540 d->nodes.nodes[0].inner = start_length(0, slen);
541 d->nodes.nodes[0].attrs = start_length(0, 0);
541 d->nodes.nodes[0].attrs = start_length(0, 0);
542−
542+
543 // Introduce an intermediate result, otherwise behaviour is undefined
543 // Introduce an intermediate result, otherwise behaviour is undefined
544 // because there is no guaranteed ordering between LHS and RHS evaluation
544 // because there is no guaranteed ordering between LHS and RHS evaluation
545 str content = parse_content(d);
545 str content = parse_content(d);
gen/Lib.hs+3−3
@@ -10,7 +10,7 @@ module Lib(
10import Control.Monad.Extra
10import Control.Monad.Extra
11import Control.Monad.Trans.Writer
11import Control.Monad.Trans.Writer
12import Data.Maybe
12import Data.Maybe
13−import Data.List
13+import Data.List.Extra
14import Data.Bits
14import Data.Bits
15import Data.Monoid
15import Data.Monoid
16import Numeric
16import Numeric
@@ -67,7 +67,7 @@ choices :: [Parser o ()] -> Parser o ()
67choices = foldr1 choice
67choices = foldr1 choice
68
68
69optional :: Parser o () -> Parser o ()
69optional :: Parser o () -> Parser o ()
70−optional x = choice x $ return ()
70+optional x = choice x $ pure ()
71
71
72
72
73---------------------------------------------------------------------
73---------------------------------------------------------------------
@@ -106,7 +106,7 @@ charSetC sets =
106 ,\x -> fromMaybe ("table[(int) *p] & " ++ showBit (bit $ fromJust $ elemIndex x hard)) $ simple x)
106 ,\x -> fromMaybe ("table[(int) *p] & " ++ showBit (bit $ fromJust $ elemIndex x hard)) $ simple x)
107 where
107 where
108 showBit x = "0x" ++ showHex (x :: Int) ""
108 showBit x = "0x" ++ showHex (x :: Int) ""
109− disp c = showBit $ foldr (.|.) 0 [bit i | (i, h) <- zip [0..] hard, fromCharSet h c]
109+ disp c = showBit $ foldr (.|.) 0 [bit i | (i, h) <- zipFrom 0 hard, fromCharSet h c]
110
110
111 hard = nub $ filter (isNothing . simple) sets
111 hard = nub $ filter (isNothing . simple) sets
112
112
gen/Main.hs+3−3
@@ -23,18 +23,18 @@ main = do
23 putStrLn $ rerender src res
23 putStrLn $ rerender src res
24 writeFileChanged "test.h" $ unlines $ compile html
24 writeFileChanged "test.h" $ unlines $ compile html
25 buildRule ["test.h","test.c"] ["gen" <.> (if isWindows then "exe" else "")] $
25 buildRule ["test.h","test.c"] ["gen" <.> (if isWindows then "exe" else "")] $
26− system_ "gcc test.c -o gen -Werror"
26+ system_ "gcc test.c -o gen -Werror -Wall -Wextra -DTEST"
27 system_ "gen"
27 system_ "gen"
28
28
29writeFileChanged :: FilePath -> String -> IO ()
29writeFileChanged :: FilePath -> String -> IO ()
30writeFileChanged file new = do
30writeFileChanged file new = do
31− old <- ifM (doesFileExist file) (readFile' file) (return "")
31+ old <- ifM (doesFileExist file) (readFile' file) (pure "")
32 when (old /= new) $
32 when (old /= new) $
33 writeFile file new
33 writeFile file new
34
34
35buildRule :: [FilePath] -> [FilePath] -> IO () -> IO ()
35buildRule :: [FilePath] -> [FilePath] -> IO () -> IO ()
36buildRule from to act = do
36buildRule from to act = do
37− let modTime x = ifM (doesFileExist x) (Just <$> getModificationTime x) (return Nothing)
37+ let modTime x = ifM (doesFileExist x) (Just <$> getModificationTime x) (pure Nothing)
38 from <- mapM modTime from
38 from <- mapM modTime from
39 to <- mapM modTime to
39 to <- mapM modTime to
40 when (any isNothing (from ++ to) || maximum from >= minimum to)
40 when (any isNothing (from ++ to) || maximum from >= minimum to)
gen/Parser.hs+4−2
@@ -13,7 +13,7 @@ data Out
13
13
14is lower upper x = x >= lower && x <= upper
14is lower upper x = x >= lower && x <= upper
15isName1 x = x == ':' || x == '_' || is 'a' 'z' x || is 'A' 'Z' x
15isName1 x = x == ':' || x == '_' || is 'a' 'z' x || is 'A' 'Z' x
16−isName x = isName1 x || x == '-' || is '0' '9' x
16+isName x = isName1 x || x == '-' || x == '.' || is '0' '9' x
17isSpace x = x == ' ' || x == '\t' || x == '\r' || x == '\n'
17isSpace x = x == ' ' || x == '\t' || x == '\r' || x == '\n'
18
18
19whitespace = many $ match isSpace
19whitespace = many $ match isSpace
@@ -52,7 +52,9 @@ tag = do
52 let name_ = name `choice` abort "Missing tag name"
52 let name_ = name `choice` abort "Missing tag name"
53 choices
53 choices
54 [do lit "!--"
54 [do lit "!--"
55+ many $ match (/= '-')
55− many $ lit "-->" -- FIXME: Not correct!!!
56+ -- FIXME: Not correct!!!
57+ lit "-->"
56 out TagComment
58 out TagComment
57 ,do lit "?"
59 ,do lit "?"
58 name_
60 name_
gen/test.c+16−9
@@ -234,7 +234,6 @@ static inline node* node_alloc(node_buffer* b)
234// The ranges completely overlap
234// The ranges completely overlap
235static inline void reverse(node* buffer, int from, int n)
235static inline void reverse(node* buffer, int from, int n)
236{
236{
237− int to = from + n - 1;
238 for (int to = from + n - 1; to > from; from++, to--)
237 for (int to = from + n - 1; to > from; from++, to--)
239 {
238 {
240 node temp = buffer[to];
239 node temp = buffer[to];
@@ -278,7 +277,7 @@ static inline str node_commit(node_buffer* b)
278 }
277 }
279
278
280 int n = open - (b->size - b->used_back);
279 int n = open - (b->size - b->used_back);
281− memmove_reverse(b->nodes, b->size - b->used_back - n + 1, b->used_front, n);
280+ memmove_reverse(b->nodes, b->size - b->used_back, b->used_front, n);
282 b->used_back -= n;
281 b->used_back -= n;
283 b->used_front += n;
282 b->used_front += n;
284 return start_length(b->used_front - n, n);
283 return start_length(b->used_front - n, n);
@@ -317,9 +316,7 @@ static inline str gap(const char* ref, const char* start, const char* end)
317}
316}
318
317
319#define P_Abort(x) return x
318#define P_Abort(x) return x
320−#define P_Tag \
319+#define P_Tag tag_start = p;
321− printf("START: Tag %s\n", p); \
322− tag_start = p;
323#define P_NameStart name_start = p
320#define P_NameStart name_start = p
324#define P_NameEnd name_end = p
321#define P_NameEnd name_end = p
325#define P_AttribsStart \
322#define P_AttribsStart \
@@ -333,15 +330,21 @@ static inline str gap(const char* ref, const char* start, const char* end)
333 attr* attr = attr_alloc(&d->attrs); \
330 attr* attr = attr_alloc(&d->attrs); \
334 attr->name = gap(d->body, name_start, name_end); \
331 attr->name = gap(d->body, name_start, name_end); \
335 attr->value = gap(d->body, quote_start, p);
332 attr->value = gap(d->body, quote_start, p);
336−#define P_TagComment printf("MISSING: TagComment %s\n", p)
333+#define P_TagComment \
334+ // No action required - comments just flow as normal text
337#define P_TagOpen \
335#define P_TagOpen \
338 me->nodes.length = -1; \
336 me->nodes.length = -1; \
339 me->inner = gap(d->body, p, p); \
337 me->inner = gap(d->body, p, p); \
340 me->outer = gap(d->body, tag_start, tag_start);
338 me->outer = gap(d->body, tag_start, tag_start);
341#define P_TagClose \
339#define P_TagClose \
342− str str = node_commit(&d->nodes); \
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]; \
343 me = &d->nodes.nodes[d->nodes.size - d->nodes.used_back]; \
344− me->nodes = str; \
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"; \
345 me->inner = gap(d->body, &d->body[me->inner.start], tag_start); \
348 me->inner = gap(d->body, &d->body[me->inner.start], tag_start); \
346 me->outer = gap(d->body, &d->body[me->outer.start], p);
349 me->outer = gap(d->body, &d->body[me->outer.start], p);
347#define P_TagOpenClose \
350#define P_TagOpenClose \
@@ -399,7 +402,7 @@ document* hexml_document_parse(const char* s, int slen)
399 // because there is no guaranteed ordering between LHS and RHS evaluation
402 // because there is no guaranteed ordering between LHS and RHS evaluation
400 const char* res = parser(s, d);
403 const char* res = parser(s, d);
401 if (res != NULL)
404 if (res != NULL)
402− d->error_message = strdup(res);
405+ set_error(d, res);
403 else
406 else
404 {
407 {
405 d->nodes.nodes[0].nodes = node_commit(&d->nodes);
408 d->nodes.nodes[0].nodes = node_commit(&d->nodes);
@@ -412,6 +415,8 @@ document* hexml_document_parse(const char* s, int slen)
412/////////////////////////////////////////////////////////////////////
415/////////////////////////////////////////////////////////////////////
413// TEST HARNESS (temporary)
416// TEST HARNESS (temporary)
414
417
418+#ifdef TEST
419+
415static char* resolve(const document* d, str loc)
420static char* resolve(const document* d, str loc)
416{
421{
417 char* buf = malloc(loc.length + 3);
422 char* buf = malloc(loc.length + 3);
@@ -459,6 +464,8 @@ int main()
459 return 0;
464 return 0;
460}
465}
461
466
467+#endif
468+
462/*
469/*
463void main()
470void main()
464{
471{
ghci.bat+1
@@ -1 +1,2 @@
1gcc -O2 -std=c99 -c cbits\hexml.c -o cbits\hexml.o -Wall -Wextra -Werror && ghci.exe cbits\hexml.o %*
1gcc -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 %*
hexml.cabal+4−3
@@ -1,19 +1,20 @@
1cabal-version: >= 1.18
1cabal-version: >= 1.18
2build-type: Simple
2build-type: Simple
3name: hexml
3name: hexml
4−version: 0.3.3
4+version: 0.3.4
5license: BSD3
5license: BSD3
6+x-license: BSD-3-Clause OR Apache-2.0
6license-file: LICENSE
7license-file: LICENSE
7category: XML
8category: XML
8author: Neil Mitchell <ndmitchell@gmail.com>
9author: Neil Mitchell <ndmitchell@gmail.com>
9maintainer: Neil Mitchell <ndmitchell@gmail.com>
10maintainer: Neil Mitchell <ndmitchell@gmail.com>
10−copyright: Neil Mitchell 2018
11+copyright: Neil Mitchell 2016-2023
11synopsis: XML subset DOM parser
12synopsis: XML subset DOM parser
12description:
13description:
13 An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast.
14 An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast.
14homepage: https://github.com/ndmitchell/hexml#readme
15homepage: https://github.com/ndmitchell/hexml#readme
15bug-reports: https://github.com/ndmitchell/hexml/issues
16bug-reports: https://github.com/ndmitchell/hexml/issues
16−tested-with: GHC==8.2.1, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3
17+tested-with: GHC==9.6, GHC==9.4, GHC==9.2, GHC==9.0, GHC==8.10, GHC==8.8
17extra-doc-files:
18extra-doc-files:
18 README.md
19 README.md
19 CHANGES.txt
20 CHANGES.txt
src/Main.hs+4−4
@@ -44,12 +44,12 @@ main = do
44 attributes (head $ children doc) === [Attribute "id" "1", Attribute "extra" "2"]
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]
45 map (`attributeBy` "id") (childrenBy doc "test") === map (fmap (Attribute "id")) [Just "1", Just "2", Just "4", Nothing]
46
46
47− Right _ <- return $ parse $ "<test " <> BS.unwords [BS.pack $ "x" ++ show i ++ "='value'" | i <- [1..10000]] <> " />"
47+ Right _ <- pure $ parse $ "<test " <> BS.unwords [BS.pack $ "x" ++ show i ++ "='value'" | i <- [1..10000]] <> " />"
48− Right _ <- return $ parse $ BS.unlines $ replicate 10000 "<test x='value' />"
48+ Right _ <- pure $ parse $ BS.unlines $ replicate 10000 "<test x='value' />"
49
49
50 let attrs = ["usd:jpy","test","extra","more","stuff","jpy:usd","xxx","xxxx"]
50 let attrs = ["usd:jpy","test","extra","more","stuff","jpy:usd","xxx","xxxx"]
51− Right doc <- return $ parse $ "<test " <> BS.unwords [x <> "='" <> x <> "'" | x <- attrs] <> ">middle</test>"
51+ Right doc <- pure $ parse $ "<test " <> BS.unwords [x <> "='" <> x <> "'" | x <- attrs] <> ">middle</test>"
52− [c] <- return $ childrenBy doc "test"
52+ [c] <- pure $ childrenBy doc "test"
53 forM_ attrs $ \a -> attributeBy c a === Just (Attribute a a)
53 forM_ attrs $ \a -> attributeBy c a === Just (Attribute a a)
54 forM_ ["missing","gone","nothing"] $ \a -> attributeBy c a === Nothing
54 forM_ ["missing","gone","nothing"] $ \a -> attributeBy c a === Nothing
55 putStrLn "\nSuccess"
55 putStrLn "\nSuccess"
src/Text/XML/Hexml.hs+9−9
@@ -90,11 +90,11 @@ parse src = do
90 if err /= nullPtr then do
90 if err /= nullPtr then do
91 bs <- BS.packCString =<< hexml_document_error doc
91 bs <- BS.packCString =<< hexml_document_error doc
92 hexml_document_free doc
92 hexml_document_free doc
93− return $ Left bs
93+ pure $ Left bs
94 else do
94 else do
95 node <- hexml_document_node doc
95 node <- hexml_document_node doc
96 doc <- newForeignPtr hexml_document_free_funptr doc
96 doc <- newForeignPtr hexml_document_free_funptr doc
97− return $ Right $ Node src0 doc node
97+ pure $ Right $ Node src0 doc node
98
98
99-- | Given a node, rerender it to something with an equivalent parse tree.
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.
100-- Mostly useful for debugging - if you want the real source document use 'outer' instead.
@@ -103,7 +103,7 @@ render (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d -> do
103 i <- hexml_node_render d n nullPtr 0
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
104 res <- BS.create (fromIntegral i) $ \ptr -> void $ hexml_node_render d n (castPtr ptr) i
105 touchBS src
105 touchBS src
106− return res
106+ pure res
107
107
108applyStr :: BS.ByteString -> Str -> BS.ByteString
108applyStr :: BS.ByteString -> Str -> BS.ByteString
109applyStr bs Str{..} = BS.take (fromIntegral strLength) $ BS.drop (fromIntegral strStart) bs
109applyStr bs Str{..} = BS.take (fromIntegral strLength) $ BS.drop (fromIntegral strStart) bs
@@ -118,7 +118,7 @@ attrPeek :: BS.ByteString -> ForeignPtr CDocument -> Ptr CAttr -> Attribute
118attrPeek src doc a = unsafePerformIO $ withForeignPtr doc $ \_ -> do
118attrPeek src doc a = unsafePerformIO $ withForeignPtr doc $ \_ -> do
119 name <- applyStr src <$> peekElemOff (castPtr a) 0
119 name <- applyStr src <$> peekElemOff (castPtr a) 0
120 val <- applyStr src <$> peekElemOff (castPtr a) 1
120 val <- applyStr src <$> peekElemOff (castPtr a) 1
121− return $ Attribute name val
121+ pure $ Attribute name val
122
122
123-- | Get the name of a node, e.g. @\<test /\>@ produces @\"test\"@.
123-- | Get the name of a node, e.g. @\<test /\>@ produces @\"test\"@.
124name :: Node -> BS.ByteString
124name :: Node -> BS.ByteString
@@ -142,7 +142,7 @@ outer = nodeBS 2
142contents :: Node -> [Either BS.ByteString Node]
142contents :: Node -> [Either BS.ByteString Node]
143contents n@(Node src _ _) = f (strStart inner) outers
143contents n@(Node src _ _) = f (strStart inner) outers
144 where
144 where
145− f i [] = string i (strEnd inner) ++ []
145+ f i [] = string i (strEnd inner)
146 f i ((x, n):xs) = string i (strStart x) ++ Right n : f (strEnd x) xs
146 f i ((x, n):xs) = string i (strStart x) ++ Right n : f (strEnd x) xs
147
147
148 string start end | start == end = []
148 string start end | start == end = []
@@ -156,7 +156,7 @@ children (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d ->
156 alloca $ \count -> do
156 alloca $ \count -> do
157 res <- hexml_node_children d n count
157 res <- hexml_node_children d n count
158 count <- fromIntegral <$> peek count
158 count <- fromIntegral <$> peek count
159− return [Node src doc $ plusPtr res $ i*szNode | i <- [0..count-1]]
159+ pure [Node src doc $ plusPtr res $ i*szNode | i <- [0..count-1]]
160
160
161-- | Get the attributes of this node.
161-- | Get the attributes of this node.
162attributes :: Node -> [Attribute]
162attributes :: Node -> [Attribute]
@@ -164,7 +164,7 @@ attributes (Node src doc n) = unsafePerformIO $ withForeignPtr doc $ \d ->
164 alloca $ \count -> do
164 alloca $ \count -> do
165 res <- hexml_node_attributes d n count
165 res <- hexml_node_attributes d n count
166 count <- fromIntegral <$> peek count
166 count <- fromIntegral <$> peek count
167− return [attrPeek src doc $ plusPtr res $ i*szAttr | i <- [0..count-1]]
167+ pure [attrPeek src doc $ plusPtr res $ i*szAttr | i <- [0..count-1]]
168
168
169-- | Get the direct children of this node which have a specific name.
169-- | Get the direct children of this node which have a specific name.
170-- A more efficient version of:
170-- A more efficient version of:
@@ -177,7 +177,7 @@ childrenBy (Node src doc n) str = go nullPtr
177 BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
177 BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
178 r <- hexml_node_child d n old bs $ fromIntegral len
178 r <- hexml_node_child d n old bs $ fromIntegral len
179 touchBS src
179 touchBS src
180− return $ if r == nullPtr then [] else Node src doc r : go r
180+ pure $ if r == nullPtr then [] else Node src doc r : go r
181
181
182-- | Get the first attribute of this node which has a specific name, if there is one.
182-- | Get the first attribute of this node which has a specific name, if there is one.
183-- A more efficient version of:
183-- A more efficient version of:
@@ -188,7 +188,7 @@ attributeBy (Node src doc n) str = unsafePerformIO $ withForeignPtr doc $ \d ->
188 BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
188 BS.unsafeUseAsCStringLen str $ \(bs, len) -> do
189 r <- hexml_node_attribute d n bs $ fromIntegral len
189 r <- hexml_node_attribute d n bs $ fromIntegral len
190 touchBS src
190 touchBS src
191− return $ if r == nullPtr then Nothing else Just $ attrPeek src doc r
191+ pure $ if r == nullPtr then Nothing else Just $ attrPeek src doc r
192
192
193-- | Find the starting location of a node, the @<@ character.
193-- | Find the starting location of a node, the @<@ character.
194-- The first character will be reported as @(line 1,column 1)@, because thats
194-- The first character will be reported as @(line 1,column 1)@, because thats