packages feed

X 0.3.0.0 → 0.3.1.0

raw patch · 15 files changed

+860/−410 lines, 15 filesdep ~basedep ~bytestringdep ~textsetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, bytestring, text

API changes (from Hackage documentation)

- Text.XML: instance Text.XML.Node (Text.XML.Types.Attr, Data.Text.Internal.Text)
- Text.XML: instance Text.XML.Node (Text.XML.Types.Attr, Data.Text.Short.Internal.ShortText)
- Text.XML: instance Text.XML.Node (Text.XML.Types.Attr, Text.XML.Types.CData)
- Text.XML: instance Text.XML.Node (Text.XML.Types.Attr, Text.XML.Types.Content)
- Text.XML: instance Text.XML.Node (Text.XML.Types.Attr, Text.XML.Types.Element)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], Data.Text.Internal.Text)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], Data.Text.Short.Internal.ShortText)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], Text.XML.Types.CData)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], Text.XML.Types.Content)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], Text.XML.Types.Element)
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], [Text.XML.Types.CData])
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], [Text.XML.Types.Content])
- Text.XML: instance Text.XML.Node ([Text.XML.Types.Attr], [Text.XML.Types.Element])
- Text.XML: instance Text.XML.Node Text.XML.Types.Attr
- Text.XML: instance Text.XML.Node Text.XML.Types.CData
- Text.XML: instance Text.XML.Node Text.XML.Types.Content
- Text.XML: instance Text.XML.Node Text.XML.Types.Element
- Text.XML: instance Text.XML.Node [Text.XML.Types.Attr]
- Text.XML: instance Text.XML.Node [Text.XML.Types.CData]
- Text.XML: instance Text.XML.Node [Text.XML.Types.Content]
- Text.XML: instance Text.XML.Node [Text.XML.Types.Element]
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.Attr
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.CData
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.CDataKind
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.Comment
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.Content
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.LName
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.PI
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.QName
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.URI
- Text.XML.Types: instance Control.DeepSeq.NFData Text.XML.Types.XmlDeclaration
- Text.XML.Types: instance Control.DeepSeq.NFData cnode => Control.DeepSeq.NFData (Text.XML.Types.Element' cnode)
- Text.XML.Types: instance Control.DeepSeq.NFData cnode => Control.DeepSeq.NFData (Text.XML.Types.Root' cnode)
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.Attr
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.CData
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.CDataKind
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.Comment
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.Content
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.LName
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.PI
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.QName
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.URI
- Text.XML.Types: instance Data.Data.Data Text.XML.Types.XmlDeclaration
- Text.XML.Types: instance Data.Data.Data cnode => Data.Data.Data (Text.XML.Types.Element' cnode)
- Text.XML.Types: instance Data.Data.Data cnode => Data.Data.Data (Text.XML.Types.Root' cnode)
- Text.XML.Types: instance Data.Foldable.Foldable Text.XML.Types.Element'
- Text.XML.Types: instance Data.Foldable.Foldable Text.XML.Types.Root'
- Text.XML.Types: instance Data.String.IsString Text.XML.Types.LName
- Text.XML.Types: instance Data.String.IsString Text.XML.Types.URI
- Text.XML.Types: instance Data.Traversable.Traversable Text.XML.Types.Element'
- Text.XML.Types: instance Data.Traversable.Traversable Text.XML.Types.Root'
- Text.XML.Types: instance GHC.Base.Functor Text.XML.Types.Element'
- Text.XML.Types: instance GHC.Base.Functor Text.XML.Types.Root'
- Text.XML.Types: instance GHC.Classes.Eq Text.XML.Types.Attr
- Text.XML.Types: instance GHC.Classes.Eq Text.XML.Types.CDataKind
- Text.XML.Types: instance GHC.Classes.Eq Text.XML.Types.LName
- Text.XML.Types: instance GHC.Classes.Eq Text.XML.Types.QName
- Text.XML.Types: instance GHC.Classes.Eq Text.XML.Types.URI
- Text.XML.Types: instance GHC.Classes.Ord Text.XML.Types.Attr
- Text.XML.Types: instance GHC.Classes.Ord Text.XML.Types.LName
- Text.XML.Types: instance GHC.Classes.Ord Text.XML.Types.QName
- Text.XML.Types: instance GHC.Classes.Ord Text.XML.Types.URI
- Text.XML.Types: instance GHC.Generics.Generic (Text.XML.Types.Element' cnode)
- Text.XML.Types: instance GHC.Generics.Generic (Text.XML.Types.Root' cnode)
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.Attr
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.CData
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.CDataKind
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.Comment
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.Content
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.LName
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.PI
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.QName
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.URI
- Text.XML.Types: instance GHC.Generics.Generic Text.XML.Types.XmlDeclaration
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.Attr
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.CData
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.CDataKind
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.Comment
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.Content
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.LName
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.PI
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.QName
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.URI
- Text.XML.Types: instance GHC.Show.Show Text.XML.Types.XmlDeclaration
- Text.XML.Types: instance GHC.Show.Show cnode => GHC.Show.Show (Text.XML.Types.Element' cnode)
- Text.XML.Types: instance GHC.Show.Show cnode => GHC.Show.Show (Text.XML.Types.Root' cnode)
+ Text.XML: instance Text.XML.Node (Text.XML.Types.Core.Attr, Data.Text.Internal.Text)
+ Text.XML: instance Text.XML.Node (Text.XML.Types.Core.Attr, Data.Text.Short.Internal.ShortText)
+ Text.XML: instance Text.XML.Node (Text.XML.Types.Core.Attr, Text.XML.Types.Core.CData)
+ Text.XML: instance Text.XML.Node (Text.XML.Types.Core.Attr, Text.XML.Types.Core.Content)
+ Text.XML: instance Text.XML.Node (Text.XML.Types.Core.Attr, Text.XML.Types.Core.Element)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], Data.Text.Internal.Text)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], Data.Text.Short.Internal.ShortText)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], Text.XML.Types.Core.CData)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], Text.XML.Types.Core.Content)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], Text.XML.Types.Core.Element)
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], [Text.XML.Types.Core.CData])
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], [Text.XML.Types.Core.Content])
+ Text.XML: instance Text.XML.Node ([Text.XML.Types.Core.Attr], [Text.XML.Types.Core.Element])
+ Text.XML: instance Text.XML.Node Text.XML.Types.Core.Attr
+ Text.XML: instance Text.XML.Node Text.XML.Types.Core.CData
+ Text.XML: instance Text.XML.Node Text.XML.Types.Core.Content
+ Text.XML: instance Text.XML.Node Text.XML.Types.Core.Element
+ Text.XML: instance Text.XML.Node [Text.XML.Types.Core.Attr]
+ Text.XML: instance Text.XML.Node [Text.XML.Types.Core.CData]
+ Text.XML: instance Text.XML.Node [Text.XML.Types.Core.Content]
+ Text.XML: instance Text.XML.Node [Text.XML.Types.Core.Element]
+ Text.XML.Types: qnameFromText :: (ShortText -> URI) -> Text -> Maybe QName
+ Text.XML.Types: qnameToText :: QName -> Text
+ Text.XML.Types: xmlns_elem_wellformed :: [(ShortText, URI)] -> Element -> Bool
+ Text.XML.Types: xmlns_elem_wellformed' :: (Either Attr [Content] -> [QName] -> [QName]) -> [(ShortText, URI)] -> Element -> Bool

Files

+ ChangeLog.md view
@@ -0,0 +1,19 @@+See also http://pvp.haskell.org/faq++### 0.3.1.0++- New `xmlns_elem_wellformed` predicate for verifying namespace-wellformedness+- New `qnameToText` and `qnameFromText` conversion functions+- Explicitly declare SafeHaskell levels; ensure all public modules are "Safe"+- Fix pure exception bypassing proper failure reporting+- Fix attribute uniqueness violations not being detected+- Fix too lax validation of `PITarget`s+- Improve too lax validation of namespace names+- Fix too lax validation of entity references++## 0.3.0.0++- New `SerializeXMLOptions(serializeSortAttributes)` option+- Add new `Serializexmlroot+- Major refactoring of `Text.XML.Cursor` API+- un-`Maybe` the `QName(qURI)` field
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
X.cabal view
@@ -1,6 +1,6 @@ cabal-version:   2.2 name:            X-version:         0.3.0.0+version:         0.3.1.0  license:         BSD-3-Clause AND GPL-3.0-or-later license-files:   LICENSE LICENSE.GPLv3@@ -24,6 +24,9 @@   * <http://www.w3.org/TR/xml-names/ Namespaces in XML 1.0 (3ed)>   . +extra-source-files:+  ChangeLog.md+ library   hs-source-dirs: src   exposed-modules:@@ -38,6 +41,8 @@       Common       Utils       Text.XML.Lexer+      Text.XML.NS+      Text.XML.Types.Core       Text.XML.Types.Internal    build-depends:@@ -47,6 +52,7 @@               || ^>= 4.10.0               || ^>= 4.11.0               || ^>= 4.12.0+              || ^>= 4.13.0     , bytestring ^>= 0.10.4     , deepseq    ^>= 1.3.0               || ^>= 1.4.0@@ -56,8 +62,20 @@   ghc-options:        -Wall   default-language:   Haskell2010   other-extensions:+      BangPatterns       DeriveDataTypeable+      DeriveFoldable+      DeriveFunctor+      DeriveGeneric+      DeriveTraversable       FlexibleInstances+      GeneralizedNewtypeDeriving+      LambdaCase+      OverloadedStrings+      RecordWildCards+      Safe+      ScopedTypeVariables+      Trustworthy       TypeSynonymInstances  source-repository head
src/Common.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE Safe #-}+ -- | -- Module    : Common -- Copyright : (c) Herbert Valerio Riedel 2019@@ -10,6 +12,7 @@  import           Control.Applicative as X import           Control.DeepSeq     as X (NFData (rnf), deepseq)+import           Control.Monad       as X (guard) import           Data.Data           as X (Data) import           Data.Foldable       as X (Foldable) import           Data.List           as X (sort)@@ -23,4 +26,3 @@ import           Data.Typeable       as X (Typeable) import           Data.Word           as X import           GHC.Generics        as X (Generic)-
src/Text/XML.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE Safe              #-}  {- 
src/Text/XML/Cursor.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE DeriveGeneric       #-} {-# LANGUAGE DeriveTraversable   #-} {-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE Safe                #-} {-# LANGUAGE ScopedTypeVariables #-}  {-
src/Text/XML/Input.hs view
@@ -1,9 +1,9 @@-{-# LANGUAGE DeriveDataTypeable         #-}-{-# LANGUAGE DeriveGeneric              #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase                 #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE LambdaCase         #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE RecordWildCards    #-}+{-# LANGUAGE Safe               #-}  -- | -- Module    : Text.XML.Input@@ -31,6 +31,7 @@ import           Utils  import           Text.XML.Lexer+import           Text.XML.NS import           Text.XML.Types  import qualified Data.Text       as T@@ -75,10 +76,10 @@     (rootPostElem,ts5) <- mnodes2 ts4      case ts5 of-      [] -> pure Root{..}+      []    -> pure Root{..}       (_:_) -> Left (-1,"unexpected (non-misc) content nodes after root element")   where-    ts0 = scanXML (dropBOM xs0)+    ts0 = scanXML' (dropBOM xs0)      -- mnodes :: [Token] -> Either _ ([MiscNodes],[Token]) @@ -101,9 +102,9 @@           | isWsCdata cdata          = go acc rest         go acc xs                    = pure (reverse acc, xs) --isWsCdata :: CData -> Bool-isWsCdata (CData _ t) = T.all isS t+    isWsCdata :: CData -> Bool+    isWsCdata (CData CDataText t) = T.all isS t+    isWsCdata (CData _ _)         = False  -- | parseXML to a list of 'Content' chunks --@@ -187,18 +188,22 @@  nodes cur_info ps (TokStart pos t as empty' : ts) = (node : siblings, open, toks)   where-    new_name  = annotName new_info t+    new_name  = if nsfail then t else annotName new_info t     prefixes  = filter (/= "xmlns") $ mapMaybe qPrefix (t : [ k | Attr k _ <- as ])     nsfail    = any (==Nothing) [ lookup pfx (fst new_info) | pfx <- prefixes ]-    rsvnsfail = not (all checkNS as)+    rsvnsfail = not (all xmlns_attr_wellformed as)     new_info  = foldr addNS cur_info as     node | rsvnsfail = Failure pos "invalid namespace declaration"          | nsfail    = Failure pos "undefined namespace prefix"+         | Just "xmlns" <- qPrefix t = Failure pos "invalid 'xmlns'-prefixed element name"          | otherwise =-                ElemF Element { elName    = new_name-                              , elAttribs = map (annotAttr new_info) as-                              , elContent = children-                              }+             let as' = map (annotAttr new_info) as+             in if not (noDupes (map attrKey as'))+                then Failure pos "attribute uniqueness violated"+                else ElemF Element { elName    = new_name+                                   , elAttribs = as'+                                   , elContent = children+                                   }      (children,(siblings,open,toks))       | empty'    = ([], nodes cur_info ps ts)@@ -240,27 +245,7 @@ addNS :: Attr -> NSInfo -> NSInfo addNS (Attr { attrKey = key, attrVal = val }) (ns,def) =   case (qPrefix key, qLName key) of-    (Nothing,"xmlns") -> (ns, if T.null val then nullNs else (URI (TS.fromText val)))+    (Nothing,"xmlns")     -> (ns, if T.null val then nullNs else (URI (TS.fromText val)))     (Just "xmlns", "xml") -> (ns,def)-    (Just "xmlns", k) -> ((unLName k, URI (TS.fromText val)) : ns, def)-    _                 -> (ns,def)---- | Check rules imposed on reserved namespaces by https://www.w3.org/TR/xml-names/-checkNS :: Attr -> Bool-checkNS = \case-    (Attr (QName { qPrefix = Just "xmlns", qLName = "xmlns"}) _  ) -> False-    (Attr (QName { qPrefix = Just "xmlns", qLName = "xml"})   uri) -> uri == xmlNamesNS'-    (Attr (QName { qPrefix = Just "xmlns", qLName = _})       uri) -> not (T.null uri) && isNotRsvd uri-    (Attr (QName { qPrefix = Nothing     , qLName = "xmlns"}) "")  -> True-    (Attr (QName { qPrefix = Nothing     , qLName = "xmlns"}) uri) -> isNotRsvd uri-    _                                                              -> True-  where-    xmlNamesNS' = TS.toText (unURI xmlNamesNS)-    xmlnsNS'    = TS.toText (unURI xmlnsNS)-    isNotRsvd uri = not (uri == xmlNamesNS' || uri == xmlnsNS')--xmlNamesNS :: URI-xmlNamesNS = URI ns_xml_uri--xmlnsNS :: URI-xmlnsNS = URI ns_xmlns_uri+    (Just "xmlns", k)     -> ((unLName k, URI (TS.fromText val)) : ns, def)+    _                     -> (ns,def)
src/Text/XML/Lexer.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveGeneric        #-} {-# LANGUAGE FlexibleInstances    #-} {-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE Safe                 #-} {-# LANGUAGE TypeSynonymInstances #-}  {-@@ -75,7 +76,6 @@ import qualified Data.Text       as T import qualified Data.Text.Lazy  as TL import qualified Data.Text.Short as TS-import           Numeric         (readHex)  nullNs :: URI nullNs = URI mempty@@ -112,6 +112,8 @@ type LChar              = (Pos,Char) type LString            = [LChar] +type LexCont            = LString -> [Token]+ -- | XML Lexer token. data Token              = TokStart !Pos QName [Attr] Bool                           -- ^ opening start-tag (the 'Bool' field denotes whether this is an empty tag)@@ -132,12 +134,18 @@  -- | Run XML lexer over 'XmlSource' scanXML :: XmlSource source => source -> [Token]-scanXML = tokens0 . eolNorm . go 0+scanXML = tokens2 . eolNorm . go 0   where     go !n src = case uncons src of       Just (c,src') -> (n,c) : go (n+1) src'       Nothing       -> [] +scanXML' :: XmlSource source => source -> [Token]+scanXML' = tokens1 . eolNorm . go 0+  where+    go !n src = case uncons src of+      Just (c,src') -> (n,c) : go (n+1) src'+      Nothing       -> []  {- @@ -155,32 +163,45 @@ eolNorm ((n,'\xD'):cs)             = (n,'\xA') : eolNorm cs eolNorm (c:cs)                     = c         : eolNorm cs ---tokens0 :: LString -> [Token]--- tokens0 ((_,'<'):(_,'?'):(_,'x'):(_,'m'):(_,'l'):(_,c):cs)---   | isS c = go1 (dropSpace cs)---   where---     go1 ((_,'v'):(_,'e'):(_,'r'):(_,'s'):(_,'i'):(_,'o'):(_,'n'):cs)-tokens0 cs = tokens' cs+-- prolog+tokens1 :: LexCont+tokens1 []                       = []+tokens1 ((_,'<') : (_,'!') : cs) = special  tokens1 cs+tokens1 ((n,'<') : (_,'?') : cs) = procins  tokens1 n cs+tokens1 ((_,'<') : cs)           = tag      tokens2 cs+tokens1 cs@(_:_)                 = sData    tokens1 cs +sData :: LexCont -> LString -> [Token]+sData lcont [] = lcont []+sData lcont cs@((n,_):_)+  = case breakn (not . isS) cs of+      ("",_:_) -> [TokError n "unexpected (non-misc) content nodes before root element"]+      (as,bs)  -> TokText CData { cdVerbatim = CDataText, cdData = T.pack as } : lcont bs -tokens' :: LString -> [Token]-tokens' ((_,'<') : (_,'!') : cs) = special cs-tokens' ((n,'<') : (_,'?') : cs) = procins n cs-tokens' ((_,'<') : cs) = tag cs-tokens' [] = []-tokens' cs@((n,_):_) = let (as,bs) = breakn ('<' ==) cs-                       in foldr cvt (tokens' bs) (decode_text as)+-- after prolog+tokens2 :: LexCont+tokens2 []                       = []+tokens2 ((_,'<') : (_,'!') : cs) = special  tokens2 cs+tokens2 ((n,'<') : (_,'?') : cs) = procins  tokens2 n cs+tokens2 ((_,'<') : cs)           = tag      tokens2 cs+tokens2 cs@(_:_)                 = charData tokens2 cs -  -- XXX: Note, some of the lines might be a bit inacuarate+charData :: LexCont -> LString -> [Token]+charData lcont [] = lcont []+charData lcont cs@((n,_):_)+  = let (as,bs) = breakn ('<' ==) cs+    in foldr cvt (lcont bs) (decode_text as)   where+    cvt :: Txt -> ([Token] -> [Token])+    -- XXX: Note, some of the lines might be a bit inacuarate     cvt (TxtBit x)  cont+      | T.isInfixOf "]]>" dat = [TokError n "invalid literal ']]>' sequence in text content"]       | T.all isChar dat = TokText CData { cdVerbatim = CDataText, cdData = dat } : cont       | otherwise        = [TokError n "invalid code-point in text content"]       where         dat = T.pack x-    cvt (CRefBit x) cont = case cref_to_char x of+    cvt (CRefBit _ False) _ = [TokError n "invalid character reference"]+    cvt (CRefBit x True) cont = case cref_to_char x of       Just c         | isChar c -> TokText CData { cdVerbatim = CDataText, cdData = T.singleton c } : cont         | otherwise -> [TokError n "invalid character reference"]@@ -189,17 +210,18 @@ -- -- PI        ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>' -- PITarget  ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))-procins :: Pos -> LString -> [Token]-procins n0 = go ""+procins :: LexCont -> Pos -> LString -> [Token]+procins lcont n0 = go ""   where-    go acc ((_,'?') : (_,'>') : ds) = mkPI (reverse acc) (tokens' ds)+    go acc ((_,'?') : (_,'>') : ds) = mkPI (reverse acc) (lcont ds)     go acc ((_,c) : ds)             = go (c:acc) ds     go _   []                       = eofErr      mkPI :: String -> [Token] -> [Token]     mkPI s0 ts       | tgt == "xml"  = mkXMLDecl s' ts-      | map toLower (TS.unpack tgt) == "xml" = [TokError (n0+2) "Invalid PI name"]+      | map toLower tgt0 == "xml" || not (isNCName tgt0) = [TokError (n0+2) "Invalid PI name"]+      | not (T.all isChar payload) = [TokError (n0+2) "invalid code-point in PI data"]       | otherwise     = TokPI n0 (PI tgt payload) : ts       where         (tgt0,s') = break isS s0@@ -238,9 +260,9 @@           | otherwise = [TokError n0 "Bad 'standalone' value in XML declaration"]         go3 enc rest = go4 enc Nothing rest -        go4 enc sd [] = TokXmlDecl (XmlDeclaration enc sd) : ts+        go4 enc sd []   = TokXmlDecl (XmlDeclaration enc sd) : ts         go4 enc sd [""] = TokXmlDecl (XmlDeclaration enc sd) : ts-        go4 _ _ _ = [TokError n0 "unexpected or malformed attribute in XML declaration"]+        go4 _ _ _       = [TokError n0 "unexpected or malformed attribute in XML declaration"]          isEnc [] = False         isEnc (c:cs) = (isAsciiLower c || isAsciiUpper c) &&@@ -255,32 +277,35 @@         unbrack _         = Nothing  -special :: LString -> [Token]+special :: LexCont -> LString -> [Token] -- <!-- -- -- Comment ::=  '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'-special ((_,'-') : (_,'-') : cs) = go "" cs+special lcont ((n0,'-') : (_,'-') : cs) = go "" cs   where     go acc ((n,'-') : (_,'-') : (_,x) : ds)-      | x == '>' = TokComment (Comment $ T.pack (reverse acc)) : tokens' ds+      | x == '>' = let dat = T.pack (reverse acc)+                   in if T.all isChar dat+                      then TokComment (Comment dat) : lcont ds+                      else [TokError (n0-2) "invalid code-point in comment"]       | otherwise = [TokError n "double hyphen within comment"]     go acc ((_,c) : ds) = go (c:acc) ds     go _ [] = eofErr  -- <![CDATA[-special ((n,'[') : (_,'C') : (_,'D') : (_,'A') : (_,'T') : (_,'A') : (_,'[') : cs) =+special lcont ((n,'[') : (_,'C') : (_,'D') : (_,'A') : (_,'T') : (_,'A') : (_,'[') : cs) =   let (xs,ts) = cdata cs       dat = T.pack xs-  in if T.all isChar dat then TokText CData { cdVerbatim = CDataVerbatim, cdData = dat } : tokens' ts+  in if T.all isChar dat then TokText CData { cdVerbatim = CDataVerbatim, cdData = dat } : lcont ts                          else [TokError (n-2) "invalid code-point in CDATA block"]   where     cdata ((_,']') : (_,']') : (_,'>') : ds) = ([],ds)-    cdata ((_,d) : ds)  = let (xs,ys) = cdata ds in (d:xs,ys)-    cdata [] = ([],[])+    cdata ((_,d) : ds)                       = let (xs,ys) = cdata ds in (d:xs,ys)+    cdata []                                 = ([],[])  -- <!DOCTYPE-special ((_,'D') : (_,'O') : (_,'C') : (_,'T') : (_,'Y') : (_,'P') : (_,'E') : cs) =-  let (xs,ts) = munch "" 0 cs in TokDTD (T.pack (reverse xs)) : tokens' ts+special lcont ((_,'D') : (_,'O') : (_,'C') : (_,'T') : (_,'Y') : (_,'P') : (_,'E') : cs) =+  let (xs,ts) = munch "" 0 cs in TokDTD (T.pack (reverse xs)) : lcont ts   where     munch acc nesting ((_,'>') : ds)      | nesting == (0::Int)            = (acc,ds)@@ -289,8 +314,8 @@     munch acc n ((_,x) : ds)          = munch (x:acc) n ds     munch acc _ []                    = (acc,[]) -- unterminated DTD markup -special ((n,_):_) = [TokError (n-1) "invalid element name"]-special [] = eofErr+special _ ((n,_):_) = [TokError (n-1) "invalid element name"]+special _ [] = eofErr  qualName :: LString -> (QName,LString) qualName xs = (QName { qURI    = nullNs@@ -314,46 +339,52 @@ Attribute     ::=  Name Eq AttValue  -}-tag :: LString -> [Token]-tag ((p,'/') : cs)+tag :: LexCont -> LString -> [Token]+tag lcont ((p,'/') : cs)   | isValidQName n   = TokEnd p n : case dropSpace ds of-                   (_,'>') : es -> tokens' es+                   (_,'>') : es -> lcont es                    -- tag was not properly closed...                    (p',_) : _   -> [TokError p' "expected '>'"]                    []           -> eofErr   | otherwise = [TokError p "invalid element name"]   where-    (n,ds) = qualName (dropSpace cs)-tag [] = eofErr-tag cs@((pos,_):_)-  | not (isValidQName n) = [TokError pos "invalid element name"]+    (n,ds) = qualName cs+tag _ [] = eofErr+tag lcont cs@((pos,_):_)+  | not (isValidQName n)                  = [TokError pos "invalid element name"]   | not (all (isValidQName . attrKey) as) = [TokError pos "invalid attribute name"]   | not (all (T.all isChar . attrVal) as) = [TokError pos "invalid attribute value"]-  | otherwise            = TokStart pos n as b : ts+  | otherwise                             = TokStart pos n as b : ts   where     (n,ds)    = qualName cs-    (as,b,ts) = attribs (dropSpace ds)+    (as,b,ts) = attribs lcont ds +attribs :: LexCont -> LString -> ([Attr], Bool, [Token])+attribs lcont = go+  where+    go cs = case (isS' cs, dropSpace cs) of+      (_,(_,'>') : ds) -> ([], False, lcont ds)+      (_,(_,'/') : ds) -> ([], True, case ds of+                              (_,'>') : es -> lcont es+                              (pos,_) : _  -> [TokError pos "expected '>'"]+                              []           -> eofErr)+      (_,(_,'?') : (_,'>') : ds) -> ([], True, lcont ds) -attribs :: LString -> ([Attr], Bool, [Token])-attribs cs = case cs of-    (_,'>') : ds -> ([], False, tokens' ds)-    (_,'/') : ds -> ([], True, case ds of-                            (_,'>') : es -> tokens' es-                            (pos,_) : _  -> [TokError pos "expected '>'"]-                            []           -> eofErr)-    (_,'?') : (_,'>') : ds -> ([], True, tokens' ds)+      -- doc ended within a tag..+      (_,[])       -> ([],False,eofErr) -    -- doc ended within a tag..-    []       -> ([],False,eofErr)+      (True,cs')   -> let (a,cs1) = attrib cs'+                          (as,b,ts) = go cs1+                      in (a:as,b,ts) -    _        -> let (a,cs1) = attrib cs-                    (as,b,ts) = attribs cs1-                in (a:as,b,ts)+      (False,(pos,_):_)  -> ([], False, [TokError pos "expected whitespace"]) +    isS' ((_,c):_) = isS c+    isS' []        = False+ attrib :: LString -> (Attr,LString)-attrib cs = ((Attr ks (fromString $ decode_attr vs)),dropSpace cs2)+attrib cs = ((Attr ks (fromString $ decode_attr vs)),cs2)   where     (vs,cs2) = attr_val (dropSpace cs1)     (ks,cs1) = qualName cs@@ -389,42 +420,46 @@ decode_attr cs = concatMap cvt (decode_text cs)   where     cvt (TxtBit x) = norm x-    cvt (CRefBit x)+    cvt (CRefBit _ False) = "\0"+    cvt (CRefBit x True)       | Just c <- cref_to_char x = [c]-      | otherwise                = "\0" -- triggers error lateron+      | otherwise                = "\0" -- triggers error lateron (we can't represent refs in att-values)      norm []         = []     norm ('\x9':xs) = '\x20' : norm xs     norm ('\xA':xs) = '\x20' : norm xs+    norm ('<':xs)   = '<' : '\0' : norm xs -- hack: trigger error; literal '<'s are not allowed here     norm (x:xs)     = x : norm xs -data Txt = TxtBit String | CRefBit String deriving Show+data Txt = TxtBit  String+         | CRefBit String Bool  decode_text :: [Char] -> [Txt]-decode_text xs@('&' : cs) = case break (';' ==) cs of-                              (as,_:bs) -> CRefBit as : decode_text bs-                              _         -> [TxtBit xs]+decode_text ('&' : cs) = case break (';' ==) cs of+                           (as,_:bs) -> CRefBit as True : decode_text bs+                           (as,"")   -> [CRefBit as False] decode_text []  = [] decode_text cs  = let (as,bs) = break ('&' ==) cs                   in TxtBit as : decode_text bs  cref_to_char :: [Char] -> Maybe Char cref_to_char cs = case cs of-  '#' : ds -> num_esc ds+  '#' : ds -> maybe (Just '\0') Just $ -- trigger error lateron+              num_esc ds   "lt"     -> Just '<'   "gt"     -> Just '>'   "amp"    -> Just '&'   "apos"   -> Just '\''   "quot"   -> Just '"'-  _        -> Nothing +  ""       -> Just '\0' -- trigger error+  (x:xs) | isNameStartChar x, all isNameChar xs -> Nothing+         | otherwise -> Just '\0' -- invalid name+ num_esc :: String -> Maybe Char num_esc cs = case cs of-               'x' : ds -> check (readHex ds)-               _        -> check (reads cs)--  where check [(n,"")] = cvt_char n-        check _        = Nothing+               'x' : ds -> decodeCharRefHex ds+               _        -> decodeCharRefDec cs  cvt_char :: Int -> Maybe Char cvt_char x@@ -436,8 +471,10 @@ simpleTokenize :: String -> [String] simpleTokenize [] = [] simpleTokenize (c:cs)-  | isSorEQ c = let (sep,rest) = span isSorEQ (c:cs)-                in  (if ('=' `elem` sep) then "=" else "") : simpleTokenize rest+  | isSorEQ c = case span isS (c:cs) of+                 (_,'=':rest) -> "=" : simpleTokenize (dropWhile isS rest)+                 (_:_,rest)   -> "" : simpleTokenize rest+                 ([],_)       -> error "impossible"    | c == '\'' = case break (== '\'') cs of                   (_,"")       -> [c:cs]@@ -454,4 +491,3 @@ isValidQName :: QName -> Bool isValidQName (QName { qPrefix = Just pfx, qLName = LName ln }) = isNCName (TS.unpack pfx) && isNCName (TS.unpack ln) isValidQName (QName { qPrefix = Nothing, qLName = LName ln })  = isNCName (TS.unpack ln)-
+ src/Text/XML/NS.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe              #-}++{-++Copyright (c) 2019  Herbert Valerio Riedel <hvr@gnu.org>++ This file is free software: you may copy, redistribute and/or modify it+ under the terms of the GNU General Public License as published by the+ Free Software Foundation, either version 3 of the License, or (at your+ option) any later version.++ This file is distributed in the hope that it will be useful, but+ WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+ General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program (see `LICENSE.GPLv3`).  If not, see+ <https://www.gnu.org/licenses/gpl-3.0.html>.++-}++-- |+-- Module    : Text.XML.NS+-- Copyright : (c) Herbert Valerio Riedel 2019+-- SPDX-License-Identifier: GPL-3.0-or-later+--+-- XML Namespace related helpers+--+module Text.XML.NS+    ( xmlns_attr+    , xmlns_def_attr+    , xmlns_from_attr++    , ns_xmlns_uri, xmlNamesNS+    , ns_xml_uri, xmlnsNS++    , xmlns_attr_wellformed++    , xmlns_elem_wellformed+    , xmlns_elem_wellformed'+    ) where++import           Common+import           Data.Char+import           Data.Either         (partitionEithers)+import qualified Data.Text           as T+import qualified Data.Text.Short     as TS+import           Text.XML.Types.Core+import           Utils++xmlNamesNS :: URI+xmlNamesNS = URI ns_xml_uri++xmlnsNS :: URI+xmlnsNS = URI ns_xmlns_uri++{-# NOINLINE ns_xml_uri #-}+ns_xml_uri :: ShortText+ns_xml_uri = "http://www.w3.org/XML/1998/namespace"++-- | Smart constructor for @xmlns:\<prefix\> = \<namespace-uri\>@+--+-- Invariant: @\<namespace-uri\>@ MUST be non-empty for non-empty prefixes+--+-- @since 0.3.0+xmlns_attr :: ShortText -- ^ namespace prefix (if empty, denotes the default namespace; see also 'xmlns_def_attr')+           -> URI -- ^ Namespace URI+           -> Attr+xmlns_attr pfx uri+  | TS.null pfx = xmlns_def_attr uri+  | not (isNCName (TS.unpack pfx)) = error "Text.XML.xmlns_attr: non-empty prefix is not a proper NCName"+  | isNullURI uri = error "Text.XML.xmlns_attr: empty namespace URI for non-empty prefix"+  | otherwise = Attr (QName { qPrefix = Just (TS.pack "xmlns"), qLName = LName pfx, qURI = xmlnsNS }) (TS.toText (unURI uri))++-- | Smart constructor for @xmlns = [\<namespace-uri\>|""]@ (i.e. for declaring the default namespace)+--+-- prop> xmlns_attr "" ns == xmlns_def_attr ns+--+-- @since 0.3.0+xmlns_def_attr :: URI -- ^ Default namespace URI (use /empty/ 'URI' to reset default namespace)+               -> Attr+xmlns_def_attr uri+  = Attr (QName { qPrefix = Nothing, qLName = LName (TS.pack "xmlns"), qURI = xmlnsNS })+         (if isNullURI uri then mempty else TS.toText (unURI uri))++-- | Convert @xmlns@ 'Attr' into a @(prefix,namespace-uri)@ pair; returns 'Nothing' if the argument isn't a @xmlns@ attribute.+--+-- An empty prefix denotes the default-namespace+--+-- prop> xmlns_from_attr (xmlns_attr pfx ns) == Just (pfx,ns)+--+-- @since 0.3.0+xmlns_from_attr :: Attr -> Maybe (ShortText,URI)+xmlns_from_attr (Attr (QName ln ns pfx) ns')+  | ns /= URI ns_xmlns_uri = Nothing+  | otherwise = Just $ case pfx of+                  Nothing -> (mempty,     URI (TS.fromText ns'))+                  Just _  -> (unLName ln, URI (TS.fromText ns'))++-- not public API yet+-- | Check rules imposed on reserved namespaces by+-- <https://www.w3.org/TR/xml-names/> as well as other basic+-- constraints.+xmlns_attr_wellformed :: Attr -> Bool+xmlns_attr_wellformed = \case+    (Attr (QName { qPrefix = Just "xmlns", qLName = "xmlns"}) _  ) -> False+    (Attr (QName { qPrefix = Just "xmlns", qLName = "xml"})   uri) -> uri == xmlNamesNS'+    (Attr (QName { qPrefix = Just "xmlns", qLName = _})       uri) -> not (T.null uri) && isNotRsvd uri && validURI uri+    (Attr (QName { qPrefix = Nothing     , qLName = "xmlns"}) "")  -> True+    (Attr (QName { qPrefix = Nothing     , qLName = "xmlns"}) uri) -> isNotRsvd uri && (T.null uri || validURI uri)+    _                                                              -> True -- not an attribute covered by xml-names+  where+    xmlNamesNS' = TS.toText (unURI xmlNamesNS)+    xmlnsNS'    = TS.toText (unURI xmlnsNS)+    isNotRsvd uri = not (uri == xmlNamesNS' || uri == xmlnsNS')++    -- simple heuristic; TODO: implement proper RFC3986 syntax test+    validURI = T.all validUriChar++    -- as per RFC3986+    validUriChar c = isAsciiLower c || isAsciiUpper c || isDigit c ||+                     c `elem` ("%-._~:/?#[]@!$&'()*+,;=" :: [Char])++-- | Verify whether sub-tree is wellformed with respect to namespaces+--+-- The first argument denotes an optional parent context of+-- @xmlns@-declarations that are in scope (where 'ShortText' and 'URI'+-- have the same semantics as for the arguments of 'xmlns_attr'). In+-- case of duplicate prefixes, earlier entries shadow later entries.+--+-- __NOTE__: This function doesn't take into account the namespace+-- prefixes of @xs:QName@-valued text-nodes or attributes; if you need+-- to handle such cases, see the 'xmlns_elem_wellformed'' function.+--+-- @since 0.3.1+xmlns_elem_wellformed :: [(ShortText,URI)] -> Element -> Bool+xmlns_elem_wellformed parentScope curElement = and+    [ qnameWF False (elName curElement)+    , all xmlns_attr_wellformed (elAttribs curElement)+    , noDupes xmlnsAttrs+    , all (qnameWF True . attrKey) nonXmlnsAttrs+    , noDupes nonXmlnsAttrs+    -- recurse into children+    , all (xmlns_elem_wellformed curScope0) children+    ]+  where+    (xmlnsAttrs, nonXmlnsAttrs) =+      partitionEithers .+      map (\attr -> maybe (Right attr) Left (xmlns_from_attr attr)) $+      elAttribs curElement++    curScope0 = xmlnsAttrs ++ parentScope+    curScope1 = ("xml",xmlNamesNS):("xmlns",xmlnsNS):curScope0++    curDefNS = fromMaybe "" (lookup "" curScope0)++    qnameWF False (QName _ uri Nothing)     = uri == curDefNS+    qnameWF True  (QName _ uri Nothing)     = isNullURI uri -- attributes are agnostic to xmlns=...+    qnameWF _     (QName _ uri (Just pfx))+       | Just uri' <- lookup pfx curScope1  = uri == uri'+       | otherwise                          = False++    children :: [Element]+    children = [ el | Elem el <- elContent curElement ]++-- | Variant of 'xmlns_elem_wellformed' which supports introspecting @xs:QName@ valued attributes and text-nodes.+--+-- The first argument is a function for extracting a (possibly empty) list of 'QName's from attribute values and text-nodes:+--+-- - Its first @'Either' 'Attr' ['Content']@ argument denotes either a (non-empty) attribute or an element's children which contain at least one non-empty text-node fragment.+--+-- - The second argument of type @['QName']@ denotes the path of element 'QName's in reverse order (i.e. the top-level element of the traversal is last item in this list) leading to the currently focused attribute or text-node.+--+-- This 'QName' extracing function may return a list to accomodate for test fields which may contain multiple @xs:QName@ such as e.g. for+--+-- > <xs:list itemType="xs:QName" />+--+-- The 'Text.XML.Types.qnameFromText' function can be useful for decoding @xs:QName@s text values.+--+-- prop> xmlns_elem_wellformed' (\_ _ -> []) topns el = xmlns_elem_wellformed topns el+--+-- @since 0.3.1+xmlns_elem_wellformed' :: (Either Attr [Content] -> [QName] -> [QName])+                       -> [(ShortText,URI)] -> Element -> Bool+xmlns_elem_wellformed' qnameMatcher = go []+  where+    go parentPath parentScope curElement = and+        [ qnameWF False (elName curElement)+        , all xmlns_attr_wellformed (elAttribs curElement)+        , noDupes xmlnsAttrs+        , all (qnameWF True . attrKey) nonXmlnsAttrs+        , noDupes nonXmlnsAttrs+        -- xs:QName in text-nodes and attrib values+        , all (qnameWF False) cdataQName+        , all (qnameWF False) attrQNames+        -- recurse into children+        , all (go curPath curScope0) children+        ]+      where+        curPath = elName curElement : parentPath++        cdataQName = if and [ T.all isS (cdData cd) | Text cd <- elContent curElement ]+                     then []+                     else qnameMatcher (Right (elContent curElement)) curPath++        attrQNames = concat [ qnameMatcher (Left attr) curPath+                            | attr@(Attr _ v) <- nonXmlnsAttrs, not (T.all isS v) ]++        (xmlnsAttrs, nonXmlnsAttrs) =+          partitionEithers .+          map (\attr -> maybe (Right attr) Left (xmlns_from_attr attr)) $+          elAttribs curElement++        curScope0 = xmlnsAttrs ++ parentScope+        curScope1 = ("xml",xmlNamesNS):("xmlns",xmlnsNS):curScope0++        curDefNS = fromMaybe "" (lookup "" curScope0)++        qnameWF False (QName _ uri Nothing)     = uri == curDefNS+        qnameWF True  (QName _ uri Nothing)     = isNullURI uri -- attributes are agnostic to xmlns=...+        qnameWF _     (QName _ uri (Just pfx))+           | Just uri' <- lookup pfx curScope1  = uri == uri'+           | otherwise                          = False++        children :: [Element]+        children = [ el | Elem el <- elContent curElement ]
src/Text/XML/Output.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE Safe              #-}  {- @@ -122,7 +123,7 @@     maybeToList xmldecl ++     map bMisc rootPreElem ++     (case rootDoctype of-       Nothing -> []+       Nothing             -> []        Just (dtd,moreMisc) -> ("<!DOCTYPE" <+> TLB.fromText dtd <+> ">") : map bMisc moreMisc     ) ++     [TLB.fromString (ppElementS sopts rootElement "")] ++
src/Text/XML/Proc.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE Safe #-}+ {-  Copyright (c) 2019  Herbert Valerio Riedel <hvr@gnu.org>
src/Text/XML/Types.hs view
@@ -1,9 +1,4 @@-{-# LANGUAGE DeriveDataTypeable         #-}-{-# LANGUAGE DeriveFoldable             #-}-{-# LANGUAGE DeriveFunctor              #-}-{-# LANGUAGE DeriveGeneric              #-}-{-# LANGUAGE DeriveTraversable          #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Safe #-}  {- @@ -23,295 +18,56 @@  along with this program (see `LICENSE.GPLv3`).  If not, see  <https://www.gnu.org/licenses/gpl-3.0.html>. -This file incorporates work covered by the following copyright and-permission notice:--    (c) 2007 Galois Inc.--    All rights reserved.--    Redistribution and use in source and binary forms, with or without-    modification, are permitted provided that the following conditions-    are met:--    1. Redistributions of source code must retain the above copyright-    notice, this list of conditions and the following disclaimer.--    2. Redistributions in binary form must reproduce the above copyright-    notice, this list of conditions and the following disclaimer in the-    documentation and/or other materials provided with the distribution.--    3. Neither the name of the author nor the names of his contributors-    may be used to endorse or promote products derived from this software-    without specific prior written permission.--    THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS-    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED-    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-    DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR-    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL-    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS-    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)-    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,-    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN-    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE-    POSSIBILITY OF SUCH DAMAGE.- -}  -- | -- Module    : Text.XML.Types--- Copyright : (c) Galois, Inc. 2007---             (c) Herbert Valerio Riedel 2019--- SPDX-License-Identifier: BSD-3-Clause AND GPL-3.0-or-later+-- Copyright : (c) Herbert Valerio Riedel 2019+-- SPDX-License-Identifier: GPL-3.0-or-later -- -- Basic XML types. ---module Text.XML.Types where--import           Common-import qualified Data.Text.Short as TS-import           Utils--type Root = Root' Content---- | Represents the implicit root node of an XML document------ @since 0.2.0-data Root' cnode = Root-  { rootXmlDeclaration :: Maybe XmlDeclaration -- ^ (optional) XML declaration-  , rootPreElem        :: MiscNodes            -- ^ Miscellaneous nodes before root element & DOCTYPE declaration-  , rootDoctype        :: Maybe (Text,MiscNodes) -- ^ optional DOCTYPE declaration and more miscellaneous nodes between DOCTYPE and root element-  , rootElement        :: Element' cnode       -- ^ The single root document element-  , rootPostElem       :: MiscNodes            -- ^ Miscellaneous nodes after root element-  } deriving (Show, Typeable, Data, Generic, Functor, Foldable, Traversable)--instance NFData cnode => NFData (Root' cnode)---- | Sequence of \"miscellaneous\" nodes------ @since 0.2.0-type MiscNodes = [Either Comment PI]---- | Denotes the @\<?xml version="1.0" encoding="..." standalone="..." ?\>@ declaration------ @since 0.2.0-data XmlDeclaration = XmlDeclaration (Maybe ShortText) (Maybe Bool)-  deriving (Show, Typeable, Data, Generic)--instance NFData XmlDeclaration---- | Processing instruction------ @since 0.2.0-data PI = PI-  { piTarget :: !ShortText -- ^ Invariant: MUST not be @[Xx][Mm][Ll]@-  , piData   :: !Text      -- ^ Invariant: MUST not contain @?>@-  } deriving (Show, Typeable, Data, Generic)--instance NFData PI---- | Represents a XML comment------ Invariant: SHOULD not contain @--@ (occurences of @--@ will be automatically substituted by @-~@ on serialization)------ @since 0.2.0-newtype Comment = Comment Text-  deriving (Show, Typeable, Data, Generic, NFData)---- | XML content------ @since 0.2.0-data Content-  = Elem Element-  | Text CData-  | CRef !ShortText-  | Proc PI-  | Comm Comment-  deriving (Show, Typeable, Data, Generic)--instance NFData Content--type Element = Element' Content---- | XML elements-data Element' cnode  = Element-  { elName    :: !QName-  , elAttribs :: [Attr]-  , elContent :: [cnode]-  } deriving (Show, Typeable, Data, Generic, Functor, Foldable, Traversable)--instance NFData cnode => NFData (Element' cnode)---- | XML attributes-data Attr     = Attr-  { attrKey :: !QName-  , attrVal :: !Text-  } deriving (Eq, Ord, Show, Typeable, Data, Generic)--instance NFData Attr---- | XML CData-data CData    = CData-  { cdVerbatim :: !CDataKind-  , cdData     :: !Text-  } deriving (Show, Typeable, Data, Generic)--instance NFData CData--data CDataKind-  = CDataText      -- ^ Ordinary character data; pretty printer escapes &, < etc.-  | CDataVerbatim  -- ^ Unescaped character data; pretty printer embeds it in <![CDATA[..-  | CDataRaw       -- ^ As-is character data; pretty printer passes it along without any escaping or CDATA wrap-up.-  deriving (Eq, Show, Typeable, Data, Generic)--instance NFData CDataKind---- | A <https://www.w3.org/TR/xml-names/#NT-NCName NCName>------ NB: Among other properties this means that an 'NCName' shall never be the empty string.------ @since 0.2.0-type NCName = ShortText---- | XML (expanded) qualified names----data QName    = QName-  { qLName  :: !LName -- ^ Local name part-  , qURI    :: !URI -- ^ Invariant: the `qURI' field MUST always be populated with the proper namespace. Specifically, entities belonging to the <http://www.w3.org/2000/xmlns/> or <http://www.w3.org/XML/1998/namespace> must have the 'qURI' field accordingly-  , qPrefix :: Maybe NCName -- ^ Invariant: MUST be a proper <https://www.w3.org/TR/xml-names/#NT-NCName NCName>-  } deriving (Show, Typeable, Data, Generic)--instance NFData QName---- | Compares namespace URI and local name for equality (i.e. the namespace prefix is ignored)------ @since 0.3.0-instance Eq QName where-  q1 == q2  = xn q1 == xn q2-    where-      xn (QName ln ns _) = (ns,ln)---- | Compares namespace URI and local name for equality (i.e. the namespace prefix is effectively ignored)------ The <http://www.w3.org/2000/xmlns/> namespace is considered less than any other namespace (including the null namespace)------ @since 0.3.0-instance Ord QName where-  compare = comparing sortKey-    where-      sortKey (QName ln ns pfx) = (not isXmlns,ns,key2)-        where-          isXmlns = URI ns_xmlns_uri == ns-          key2-            | isXmlns = if isNothing pfx then LName mempty else ln-            | otherwise = ln---- | XML local names------ Invariant: MUST be a proper <https://www.w3.org/TR/xml-names/#NT-NCName NCName>-newtype LName = LName { unLName :: NCName }-  deriving (Ord, Eq, Typeable, Data, IsString, NFData, Generic)---- due to the IsString instance we can just drop the constructor name-instance Show LName where-  showsPrec p (LName s) = showsPrec p s---- | URIs resembling @anyURI@------ Invariant: MUST be a valid @URI-reference@ as defined in <https://tools.ietf.org/html/rfc3986 RFC3986>----newtype URI = URI { unURI :: ShortText }-  deriving (Ord, Eq, Typeable, Data, IsString, NFData, Generic)---- | Test for /empty/ 'URI'------ >>> isNullURI (URI mempty)--- True------ >>> isNullURI (URI "")--- True------ >>> isNullURI (URI " ")--- False------ @since 0.3.0-isNullURI :: URI -> Bool-isNullURI (URI u) = TS.null u+module Text.XML.Types+    ( -- * Root node representation+      Root, Root'(..)+    , MiscNodes+    , XmlDeclaration(..) --- due to the IsString instance we can just drop the constructor name-instance Show URI where-  showsPrec p (URI s) = showsPrec p s+      -- * Element nodes+    , Element+    , Element'(..)+    , blank_element --- | Position expressed in number of code-points------ A negative value denotes EOF-type Pos = Int+    , xmlns_elem_wellformed+    , xmlns_elem_wellformed' --- blank elements --------------------------------------------------------------+      -- ** Element attributes+    , Attr(..) --- | Blank names-blank_name :: QName-blank_name = QName-  { qLName  = LName mempty-  , qURI    = URI mempty-  , qPrefix = Nothing-  }+    , xmlns_attr+    , xmlns_def_attr+    , xmlns_from_attr --- | Blank cdata-blank_cdata :: CData-blank_cdata = CData-  { cdVerbatim = CDataText-  , cdData     = mempty-  }+      -- * Non-element content nodes+    , Content(..)+    , PI(..)+    , CData(..), CDataKind(..), blank_cdata+    , Comment(..) --- | Blank elements-blank_element :: Element-blank_element = Element-  { elName    = blank_name-  , elAttribs = mempty-  , elContent = mempty-  }+      -- * Namespace-qualified names+    , QName(..), blank_name+    , qnameToText+    , qnameFromText --- | Smart constructor for @xmlns:\<prefix\> = \<namespace-uri\>@------ Invariant: @\<namespace-uri\>@ MUST be non-empty for non-empty prefixes------ @since 0.3.0-xmlns_attr :: ShortText -- ^ namespace prefix (if empty, denotes the default namespace; see also 'xmlns_def_attr')-           -> URI -- ^ Namespace URI-           -> Attr-xmlns_attr pfx uri-  | TS.null pfx = xmlns_def_attr uri-  | not (isNCName (TS.unpack pfx)) = error "Text.XML.xmlns_attr: non-empty prefix is not a proper NCName"-  | isNullURI uri = error "Text.XML.xmlns_attr: empty namespace URI for non-empty prefix"-  | otherwise = Attr (QName { qPrefix = Just (TS.pack "xmlns"), qLName = LName pfx, qURI = xmlnsNS }) (TS.toText (unURI uri))-  where-    xmlnsNS = URI ns_xmlns_uri+      -- ** Components of 'QName's+    , NCName+    , LName(..)+    , URI(..), isNullURI --- | Smart constructor for @xmlns = [\<namespace-uri\>|""]@ (i.e. for declaring the default namespace)------ prop> xmlns_attr "" ns == xmlns_def_attr ns------ @since 0.3.0-xmlns_def_attr :: URI -- ^ Default namespace URI (use /empty/ 'URI' to reset default namespace)-               -> Attr-xmlns_def_attr uri-  = Attr (QName { qPrefix = Nothing, qLName = LName (TS.pack "xmlns"), qURI = xmlnsNS })-         (if isNullURI uri then mempty else TS.toText (unURI uri))-  where-    xmlnsNS = URI ns_xmlns_uri+      -- * Miscellaneous+    , Pos+    ) where --- | Convert @xmlns@ 'Attr' into a @(prefix,namespace-uri)@ pair; returns 'Nothing' if the argument isn't a @xmlns@ attribute.------ An empty prefix denotes the default-namespace------ prop> xmlns_from_attr (xmlns_attr pfx ns) == Just (pfx,ns)------ @since 0.3.0-xmlns_from_attr :: Attr -> Maybe (ShortText,URI)-xmlns_from_attr (Attr (QName ln ns pfx) ns')-  | ns /= URI ns_xmlns_uri = Nothing-  | otherwise = Just $ case pfx of-                  Nothing -> (mempty,     URI (TS.fromText ns'))-                  Just _  -> (unLName ln, URI (TS.fromText ns'))+import           Text.XML.NS+import           Text.XML.Types.Core+import           Text.XML.Types.Internal
+ src/Text/XML/Types/Core.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE DeriveDataTypeable         #-}+{-# LANGUAGE DeriveFoldable             #-}+{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE DeriveGeneric              #-}+{-# LANGUAGE DeriveTraversable          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE Trustworthy                #-}++{-++Copyright (c) 2019  Herbert Valerio Riedel <hvr@gnu.org>++ This file is free software: you may copy, redistribute and/or modify it+ under the terms of the GNU General Public License as published by the+ Free Software Foundation, either version 3 of the License, or (at your+ option) any later version.++ This file is distributed in the hope that it will be useful, but+ WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU+ General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program (see `LICENSE.GPLv3`).  If not, see+ <https://www.gnu.org/licenses/gpl-3.0.html>.++This file incorporates work covered by the following copyright and+permission notice:++    (c) 2007 Galois Inc.++    All rights reserved.++    Redistribution and use in source and binary forms, with or without+    modification, are permitted provided that the following conditions+    are met:++    1. Redistributions of source code must retain the above copyright+    notice, this list of conditions and the following disclaimer.++    2. Redistributions in binary form must reproduce the above copyright+    notice, this list of conditions and the following disclaimer in the+    documentation and/or other materials provided with the distribution.++    3. Neither the name of the author nor the names of his contributors+    may be used to endorse or promote products derived from this software+    without specific prior written permission.++    THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS+    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+    DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+    POSSIBILITY OF SUCH DAMAGE.++-}++-- |+-- Module    : Text.XML.Types+-- Copyright : (c) Galois, Inc. 2007+--             (c) Herbert Valerio Riedel 2019+-- SPDX-License-Identifier: BSD-3-Clause AND GPL-3.0-or-later+--+-- Basic XML types.+--+module Text.XML.Types.Core where++import           Common+import qualified Data.Text.Short as TS++{-# NOINLINE ns_xmlns_uri #-}+ns_xmlns_uri :: ShortText+ns_xmlns_uri = "http://www.w3.org/2000/xmlns/"++type Root = Root' Content++-- | Represents the implicit root node of an XML document+--+-- @since 0.2.0+data Root' cnode = Root+  { rootXmlDeclaration :: Maybe XmlDeclaration -- ^ (optional) XML declaration+  , rootPreElem        :: MiscNodes            -- ^ Miscellaneous nodes before root element & DOCTYPE declaration+  , rootDoctype        :: Maybe (Text,MiscNodes) -- ^ optional DOCTYPE declaration and more miscellaneous nodes between DOCTYPE and root element+  , rootElement        :: Element' cnode       -- ^ The single root document element+  , rootPostElem       :: MiscNodes            -- ^ Miscellaneous nodes after root element+  } deriving (Show, Typeable, Data, Generic, Functor, Foldable, Traversable)++instance NFData cnode => NFData (Root' cnode)++-- | Sequence of \"miscellaneous\" nodes+--+-- @since 0.2.0+type MiscNodes = [Either Comment PI]++-- | Denotes the @\<?xml version="1.0" encoding="..." standalone="..." ?\>@ declaration+--+-- @since 0.2.0+data XmlDeclaration = XmlDeclaration (Maybe ShortText) (Maybe Bool)+  deriving (Show, Typeable, Data, Generic)++instance NFData XmlDeclaration++-- | Processing instruction+--+-- @since 0.2.0+data PI = PI+  { piTarget :: !ShortText -- ^ Invariant: MUST be a 'NCName' but not be @[Xx][Mm][Ll]@ (see also <https://www.w3.org/TR/xml/#NT-PITarget PITarget> and <https://www.w3.org/TR/xml-names/>)+  , piData   :: !Text      -- ^ Invariant: MUST not contain @?>@+  } deriving (Show, Typeable, Data, Generic)++instance NFData PI++-- | Represents a XML comment+--+-- Invariant: SHOULD not contain @--@ (occurences of @--@ will be automatically substituted by @-~@ on serialization)+--+-- @since 0.2.0+newtype Comment = Comment Text+  deriving (Show, Typeable, Data, Generic, NFData)++-- | XML content+--+-- @since 0.2.0+data Content+  = Elem Element+  | Text CData+  | CRef !ShortText+  | Proc PI+  | Comm Comment+  deriving (Show, Typeable, Data, Generic)++instance NFData Content++type Element = Element' Content++-- | XML elements+data Element' cnode  = Element+  { elName    :: !QName+  , elAttribs :: [Attr]+  , elContent :: [cnode]+  } deriving (Show, Typeable, Data, Generic, Functor, Foldable, Traversable)++instance NFData cnode => NFData (Element' cnode)++-- | XML attributes+data Attr     = Attr+  { attrKey :: !QName -- ^ Note that the default namespace doesn't apply to unprefixed names+  , attrVal :: !Text+  } deriving (Eq, Ord, Show, Typeable, Data, Generic)++instance NFData Attr++-- | XML CData+data CData    = CData+  { cdVerbatim :: !CDataKind+  , cdData     :: !Text+  } deriving (Show, Typeable, Data, Generic)++instance NFData CData++data CDataKind+  = CDataText      -- ^ Ordinary character data; pretty printer escapes &, < etc.+  | CDataVerbatim  -- ^ Unescaped character data; pretty printer embeds it in <![CDATA[..+  | CDataRaw       -- ^ As-is character data; pretty printer passes it along without any escaping or CDATA wrap-up.+  deriving (Eq, Show, Typeable, Data, Generic)++instance NFData CDataKind++-- | A <https://www.w3.org/TR/xml-names/#NT-NCName NCName>+--+-- NB: Among other properties this means that an 'NCName' shall never be the empty string.+--+-- @since 0.2.0+type NCName = ShortText++-- | XML (expanded) namespace-qualified names+--+-- Used by 'attrKey' and 'elName'.+data QName    = QName+  { qLName  :: !LName -- ^ Local name part+  , qURI    :: !URI -- ^ Invariant: the `qURI' field MUST always be populated with the proper namespace. Specifically, entities belonging to the <http://www.w3.org/2000/xmlns/> or <http://www.w3.org/XML/1998/namespace> must have the 'qURI' field set accordingly. The special empty 'URI' (see also 'isNullURI') is only allowed /iff/ 'qPrefix' is 'Nothing' (i.e. when the name is /unprefixed/)+  , qPrefix :: Maybe NCName+    -- ^ 'Nothing' denotes an unprefixed name; 'Just' denotes a prefixed name.+    --+    -- Invariant: MUST be a proper <https://www.w3.org/TR/xml-names/#NT-NCName NCName> (unless 'Nothing')+  } deriving (Show, Typeable, Data, Generic)++instance NFData QName++-- | Compares namespace URI and local name for equality (i.e. the namespace prefix is ignored)+--+-- @since 0.3.0+instance Eq QName where+  q1 == q2  = xn q1 == xn q2+    where+      xn (QName ln ns _) = (ns,ln)++-- | Compares namespace URI and local name for equality (i.e. the namespace prefix is effectively ignored)+--+-- The <http://www.w3.org/2000/xmlns/> namespace is considered less than any other namespace (including the null namespace)+--+-- @since 0.3.0+instance Ord QName where+  compare = comparing sortKey+    where+      sortKey (QName ln ns pfx) = (not isXmlns,ns,key2)+        where+          isXmlns = URI ns_xmlns_uri == ns+          key2+            | isXmlns = if isNothing pfx then LName mempty else ln+            | otherwise = ln++-- | XML local names+--+-- Invariant: MUST be a proper <https://www.w3.org/TR/xml-names/#NT-NCName NCName>+newtype LName = LName { unLName :: NCName }+  deriving (Ord, Eq, Typeable, Data, IsString, NFData, Generic)++-- due to the IsString instance we can just drop the constructor name+instance Show LName where+  showsPrec p (LName s) = showsPrec p s++-- | URIs resembling @anyURI@+--+-- Invariant: MUST be a valid @URI-reference@ as defined in <https://tools.ietf.org/html/rfc3986 RFC3986>+--+-- __NOTE__: The special empty 'URI' is valid for denoting 'QName's that aren't in any namespace.+newtype URI = URI { unURI :: ShortText }+  deriving (Ord, Eq, Typeable, Data, IsString, NFData, Generic)++-- | Test for /empty/ 'URI'+--+-- >>> isNullURI (URI mempty)+-- True+--+-- >>> isNullURI (URI "")+-- True+--+-- >>> isNullURI (URI " ")+-- False+--+-- @since 0.3.0+isNullURI :: URI -> Bool+isNullURI (URI u) = TS.null u++-- due to the IsString instance we can just drop the constructor name+instance Show URI where+  showsPrec p (URI s) = showsPrec p s++-- | Position expressed in number of code-points+--+-- A negative value denotes EOF+type Pos = Int++-- blank elements --------------------------------------------------------------++-- TODO: consider removing these in X-0.4.0++-- | Blank names+--+-- __NOTE__: This value is not a proper 'QName'.+blank_name :: QName+blank_name = QName+  { qLName  = LName mempty+  , qURI    = URI mempty+  , qPrefix = Nothing+  }++-- | Empty text-node+blank_cdata :: CData+blank_cdata = CData+  { cdVerbatim = CDataText+  , cdData     = mempty+  }++-- | Blank elements+--+-- __NOTE: This value is not a propler 'Element'.+blank_element :: Element+blank_element = Element+  { elName    = blank_name+  , elAttribs = mempty+  , elContent = mempty+  }
src/Text/XML/Types/Internal.hs view
@@ -29,7 +29,10 @@ module Text.XML.Types.Internal where  import           Common-import           Text.XML.Types+import qualified Data.Text           as T+import qualified Data.Text.Short     as TS+import           Text.XML.Types.Core+import           Utils  -- | Convenience class for converting to/from 'Content' values --@@ -92,3 +95,61 @@   toContent = either toContent toContent   fromContent c = (Left <$> fromContent c) <|> (Right <$> fromContent c)   toElem = either toElem toElem++-- | Convert a 'QName' to its text-representation, i.e.+--+-- > QName          ::= PrefixedName | UnprefixedName+-- > PrefixedName   ::= Prefix ':' LocalPart+-- > UnprefixedName ::= LocalPart+-- > Prefix         ::= NCName+-- > LocalPart      ::= NCName+--+-- See also 'NCName'+--+-- >>> qnameToText (QName "foo" "urn:example.org:bar" (Just "doo"))+-- "doo:foo"+--+-- >>> qnameToText (QName "foo" "urn:example.org:bar" Nothing)+-- "foo"+--+-- See also 'qnameFromText'+--+-- @since 0.3.1+qnameToText :: QName -> Text+qnameToText (QName (LName ln) _uri Nothing)    = TS.toText ln+qnameToText (QName (LName ln) _uri (Just pfx)) = TS.toText (mconcat [pfx, TS.singleton ':', ln])++-- | Decode a 'QName' from its text-representation (see 'qnameToText')+--+-- This is the inverse to the 'qnameToText' function. However,+-- 'qnameToText' is a lossy conversion, therefore this function needs+-- to reconstruct the namespace (i.e. 'qURI') with the help of a+-- lookup function provided in the first argument: The lookup+-- functions takes a 'ShortText' which can be either+--+-- - the empty string (i.e. @""@) which denotes an unprefixed name, or+-- - a non-empty @NCName@ string which denotes a prefixed name.+--+-- The result of this function shall be the respective namespace 'URI'+-- to associate with this QName. An empty 'URI' may be returned In+-- case of unprefixed names to denote the name being in no namespace.+--+-- Finally, this function returns 'Nothing' in case of syntax errors+-- or when the prefix lookup function returns an empty 'URI' (see+-- 'isNullURI') for a /prefixed/ name.+--+-- @since 0.3.1+qnameFromText :: (ShortText -> URI) -> Text -> Maybe QName+qnameFromText nslup txt+  = case T.split (==':') txt of+      [ln] | isName ln -> Just (QName (LName (TS.fromText ln)) (nslup mempty) Nothing)+      [ln,pfx] | isName ln, isName pfx -> do+                   let pfx' = TS.fromText pfx+                       uri  = nslup pfx'+                   guard (not (isNullURI uri))+                   pure (QName (LName (TS.fromText ln)) uri (Just pfx'))+      _ -> Nothing+  where+    isName t+      | Just (c,t') <- T.uncons t  = isNameStartChar c && T.all isNameChar t'+      | otherwise                  = False
src/Utils.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe              #-}  {- @@ -46,7 +47,7 @@ isS _      = False  isNCName :: String -> Bool-isNCName [] = False+isNCName []     = False isNCName (c:cs) = isNameStartChar c && c /= ':' && all (\c' -> isNameChar c' && c' /= ':') cs  -- |@@ -166,12 +167,62 @@ bUnlines (x:xs@(_:_)) = x <+> TLB.singleton '\n' <+> bUnlines xs  -{-# NOINLINE ns_xmlns_uri #-}-ns_xmlns_uri :: ShortText-ns_xmlns_uri = "http://www.w3.org/2000/xmlns/"+{-# INLINEABLE noDupes #-}+-- | Return 'True' if list contains duplicate values+noDupes :: Ord a => [a] -> Bool+noDupes []      = True+noDupes [_]     = True+noDupes [x,y]   = x /= y+noDupes [x,y,z] = x /= y && x /= z && y /= z+noDupes xs0+  = case sort xs0 of+      []     -> True -- unreachable+      (x:xs) -> go x xs+  where+    go _  []      = True+    go x1 (x2:xs)+      | x1 == x2  = False+      | otherwise = go x2 xs -{-# NOINLINE ns_xml_uri #-}-ns_xml_uri :: ShortText-ns_xml_uri = "http://www.w3.org/XML/1998/namespace"+-- decode numeric char-reference taking into account 'isChar'+decodeCharRefHex :: [Char] -> Maybe Char+decodeCharRefHex = go 0 . dropWhile (=='0') -- NB: U+00 is invalid+  where+    go acc _ | acc > 0x10ffff = Nothing+    go acc [] = let c = toEnum acc+                in if isChar c then Just c else Nothing+    go acc (c:cs)+      | n < 0     = Nothing+      | otherwise = go ((acc*16) + n) cs+      where+        n = c2n c +    c2n :: Char -> Int+    c2n c+      | fromEnum '0' <= n, n <= fromEnum '9' = n - fromEnum '0'+      | fromEnum 'a' <= n, n <= fromEnum 'f' = n - (fromEnum 'a' - 10)+      | fromEnum 'A' <= n, n <= fromEnum 'F' = n - (fromEnum 'A' - 10)+      | otherwise = -1+      where+        n = fromEnum c+++decodeCharRefDec :: [Char] -> Maybe Char+decodeCharRefDec = go 0 . dropWhile (=='0') -- NB: U+00 is invalid+  where+    go acc _ | acc > 0x10ffff = Nothing+    go acc [] = let c = toEnum acc+                in if isChar c then Just c else Nothing+    go acc (c:cs)+      | n < 0     = Nothing+      | otherwise = go ((acc*10) + n) cs+      where+        n = c2n c++    c2n :: Char -> Int+    c2n c+      | fromEnum '0' <= n, n <= fromEnum '9' = n - fromEnum '0'+      | otherwise = -1+      where+        n = fromEnum c