hexml 0.3.4 → 0.3.5
raw patch · 7 files changed
+60/−37 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGES.txt +2/−0
- LICENSE +1/−1
- README.md +4/−2
- cbits/hexml.c +34/−16
- hexml.cabal +4/−4
- src/Main.hs +6/−5
- src/Text/XML/Hexml.hs +9/−9
CHANGES.txt view
@@ -1,5 +1,7 @@ Changelog for Hexml +0.3.5, released 2024-08-25+ #1, add support for <![CDATA[ ... ]]> 0.3.4, released 2018-09-10 #15, licensed under BSD-3-Clause OR Apache-2.0 0.3.3, released 2017-10-27
LICENSE view
@@ -1,4 +1,4 @@-Copyright Neil Mitchell 2016-2018.+Copyright Neil Mitchell 2016-2024. Licensed under either of:
README.md view
@@ -1,4 +1,4 @@-# Hexml [](https://hackage.haskell.org/package/hexml) [](https://www.stackage.org/package/hexml) [](https://travis-ci.org/ndmitchell/hexml) [](https://ci.appveyor.com/project/ndmitchell/hexml)+# Hexml [](https://hackage.haskell.org/package/hexml) [](https://www.stackage.org/package/hexml) [](https://github.com/ndmitchell/hexml/actions) An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast. In particular: @@ -8,8 +8,10 @@ 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. -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)).+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)). Hexml is tested with [AFL](http://lcamtuf.coredump.cx/afl/). If you want lenses for Hexml, see [hexml-lens](http://hackage.haskell.org/package/hexml-lens).++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.
cbits/hexml.c view
@@ -40,7 +40,7 @@ // have a cursor at the end, which is stack scoped, so children write out, then I do // when you commit, you copy over from end to front - // nodes + // nodes int size; int used_front; // front entries, stored for good int used_back; // back entries, stack based, copied into front@@ -466,26 +466,44 @@ // have found a </ break; }- else if (peek_at(d, 1) == '!' && peek_at(d, 2) == '-' && peek_at(d, 3) == '-')+ else if (peek_at(d, 1) == '!') {- skip(d, 3);- // you can't reuse the two '-' characters for the closing as well- if (peek_at(d, 0) == '\0' || peek_at(d, 1) == '\0')- {- set_error(d, "Didn't get a closing comment");- return start_end(0, 0);- }- skip(d, 2);- for (;;)+ if (peek_at(d, 2) == '-' && peek_at(d, 3) == '-') {- if (!find(d, '>'))+ skip(d, 3);+ // you can't reuse the two '-' characters for the closing as well+ if (peek_at(d, 0) == '\0' || peek_at(d, 1) == '\0') { set_error(d, "Didn't get a closing comment"); return start_end(0, 0); }- skip(d, 1);- if (peek_at(d, -3) == '-' && peek_at(d, -2) == '-')- break;+ skip(d, 2);+ for (;;)+ {+ if (!find(d, '>'))+ {+ set_error(d, "Didn't get a closing comment");+ return start_end(0, 0);+ }+ skip(d, 1);+ if (peek_at(d, -3) == '-' && peek_at(d, -2) == '-')+ break;+ }+ } else if (d->end - d->cursor >= 9 && memcmp(d->cursor + 2, "[CDATA[", 7) == 0) {+ skip(d, 9);+ for (;;)+ {+ if (!find(d, '>'))+ {+ set_error(d, "Didn't close CDATA");+ return start_end(0, 0);+ }+ skip(d, 1);+ if (peek_at(d, -3) == ']' && peek_at(d, -2) == ']')+ break;+ }+ } else {+ parse_tag(d); } } else@@ -539,7 +557,7 @@ d->nodes.nodes[0].outer = start_length(0, slen); d->nodes.nodes[0].inner = start_length(0, slen); d->nodes.nodes[0].attrs = start_length(0, 0);- + // Introduce an intermediate result, otherwise behaviour is undefined // because there is no guaranteed ordering between LHS and RHS evaluation str content = parse_content(d);
hexml.cabal view
@@ -1,20 +1,20 @@-cabal-version: >= 1.18+cabal-version: 1.18 build-type: Simple name: hexml-version: 0.3.4+version: 0.3.5 license: BSD3 x-license: BSD-3-Clause OR Apache-2.0 license-file: LICENSE category: XML author: Neil Mitchell <ndmitchell@gmail.com> maintainer: Neil Mitchell <ndmitchell@gmail.com>-copyright: Neil Mitchell 2016-2018+copyright: Neil Mitchell 2016-2024 synopsis: XML subset DOM parser description: An XML DOM-style parser, that only parses a subset of XML, but is designed to be fast. homepage: https://github.com/ndmitchell/hexml#readme bug-reports: https://github.com/ndmitchell/hexml/issues-tested-with: GHC==8.4.3, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3+tested-with: GHC==9.6, GHC==9.4, GHC==9.2, GHC==9.0, GHC==8.10, GHC==8.8 extra-doc-files: README.md CHANGES.txt
src/Main.hs view
@@ -20,6 +20,7 @@ ,(False, "<test") ,(True, "<?xml version=\"1.1\"?>\n<greeting>Hello, world!</greeting>") ,(True, "<foo bar.baz=\"qux\"></foo>")+ ,(True, "<test><![CDATA[foo<x>]]</y>]><z>]baz]]></test>") ] main :: IO ()@@ -44,12 +45,12 @@ attributes (head $ children doc) === [Attribute "id" "1", Attribute "extra" "2"] map (`attributeBy` "id") (childrenBy doc "test") === map (fmap (Attribute "id")) [Just "1", Just "2", Just "4", Nothing] - Right _ <- return $ parse $ "<test " <> BS.unwords [BS.pack $ "x" ++ show i ++ "='value'" | i <- [1..10000]] <> " />"- Right _ <- return $ parse $ BS.unlines $ replicate 10000 "<test x='value' />"+ Right _ <- pure $ parse $ "<test " <> BS.unwords [BS.pack $ "x" ++ show i ++ "='value'" | i <- [1..10000]] <> " />"+ Right _ <- pure $ parse $ BS.unlines $ replicate 10000 "<test x='value' />" let attrs = ["usd:jpy","test","extra","more","stuff","jpy:usd","xxx","xxxx"]- Right doc <- return $ parse $ "<test " <> BS.unwords [x <> "='" <> x <> "'" | x <- attrs] <> ">middle</test>"- [c] <- return $ childrenBy doc "test"+ Right doc <- pure $ parse $ "<test " <> BS.unwords [x <> "='" <> x <> "'" | x <- attrs] <> ">middle</test>"+ [c] <- pure $ childrenBy doc "test" forM_ attrs $ \a -> attributeBy c a === Just (Attribute a a) forM_ ["missing","gone","nothing"] $ \a -> attributeBy c a === Nothing putStrLn "\nSuccess"@@ -80,5 +81,5 @@ | otherwise = error "Invalid name" validAttr x | BS.notElem '\"' x = x | otherwise = error "Invalid attribute"- validStr x | BS.notElem '<' x || BS.isInfixOf "<!--" x = x+ validStr x | BS.notElem '<' x || BS.isInfixOf "<!--" x || BS.isInfixOf "<![CDATA[" x = x | otherwise = error $ show ("Invalid string", x)
src/Text/XML/Hexml.hs view
@@ -90,11 +90,11 @@ if err /= nullPtr then do bs <- BS.packCString =<< hexml_document_error doc hexml_document_free doc- return $ Left bs+ pure $ Left bs else do node <- hexml_document_node doc doc <- newForeignPtr hexml_document_free_funptr doc- return $ Right $ Node src0 doc node+ pure $ Right $ Node src0 doc node -- | Given a node, rerender it to something with an equivalent parse tree. -- Mostly useful for debugging - if you want the real source document use 'outer' instead.@@ -103,7 +103,7 @@ i <- hexml_node_render d n nullPtr 0 res <- BS.create (fromIntegral i) $ \ptr -> void $ hexml_node_render d n (castPtr ptr) i touchBS src- return res+ pure res applyStr :: BS.ByteString -> Str -> BS.ByteString applyStr bs Str{..} = BS.take (fromIntegral strLength) $ BS.drop (fromIntegral strStart) bs@@ -118,7 +118,7 @@ attrPeek src doc a = unsafePerformIO $ withForeignPtr doc $ \_ -> do name <- applyStr src <$> peekElemOff (castPtr a) 0 val <- applyStr src <$> peekElemOff (castPtr a) 1- return $ Attribute name val+ pure $ Attribute name val -- | Get the name of a node, e.g. @\<test /\>@ produces @\"test\"@. name :: Node -> BS.ByteString@@ -142,7 +142,7 @@ contents :: Node -> [Either BS.ByteString Node] contents n@(Node src _ _) = f (strStart inner) outers where- f i [] = string i (strEnd inner) ++ []+ f i [] = string i (strEnd inner) f i ((x, n):xs) = string i (strStart x) ++ Right n : f (strEnd x) xs string start end | start == end = []@@ -156,7 +156,7 @@ alloca $ \count -> do res <- hexml_node_children d n count count <- fromIntegral <$> peek count- return [Node src doc $ plusPtr res $ i*szNode | i <- [0..count-1]]+ pure [Node src doc $ plusPtr res $ i*szNode | i <- [0..count-1]] -- | Get the attributes of this node. attributes :: Node -> [Attribute]@@ -164,7 +164,7 @@ alloca $ \count -> do res <- hexml_node_attributes d n count count <- fromIntegral <$> peek count- return [attrPeek src doc $ plusPtr res $ i*szAttr | i <- [0..count-1]]+ pure [attrPeek src doc $ plusPtr res $ i*szAttr | i <- [0..count-1]] -- | Get the direct children of this node which have a specific name. -- A more efficient version of:@@ -177,7 +177,7 @@ BS.unsafeUseAsCStringLen str $ \(bs, len) -> do r <- hexml_node_child d n old bs $ fromIntegral len touchBS src- return $ if r == nullPtr then [] else Node src doc r : go r+ pure $ if r == nullPtr then [] else Node src doc r : go r -- | Get the first attribute of this node which has a specific name, if there is one. -- A more efficient version of:@@ -188,7 +188,7 @@ BS.unsafeUseAsCStringLen str $ \(bs, len) -> do r <- hexml_node_attribute d n bs $ fromIntegral len touchBS src- return $ if r == nullPtr then Nothing else Just $ attrPeek src doc r+ pure $ if r == nullPtr then Nothing else Just $ attrPeek src doc r -- | Find the starting location of a node, the @<@ character. -- The first character will be reported as @(line 1,column 1)@, because thats