{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Safe #-}
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"
xmlns_attr :: ShortText
-> 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))
xmlns_def_attr :: URI
-> 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))
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'))
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
where
xmlNamesNS' = TS.toText (unURI xmlNamesNS)
xmlnsNS' = TS.toText (unURI xmlnsNS)
isNotRsvd uri = not (uri == xmlNamesNS' || uri == xmlnsNS')
validURI = T.all validUriChar
validUriChar c = isAsciiLower c || isAsciiUpper c || isDigit c ||
c `elem` ("%-._~:/?#[]@!$&'()*+,;=" :: [Char])
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
, 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
qnameWF _ (QName _ uri (Just pfx))
| Just uri' <- lookup pfx curScope1 = uri == uri'
| otherwise = False
children :: [Element]
children = [ el | Elem el <- elContent curElement ]
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
, all (qnameWF False) cdataQName
, all (qnameWF False) attrQNames
, 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
qnameWF _ (QName _ uri (Just pfx))
| Just uri' <- lookup pfx curScope1 = uri == uri'
| otherwise = False
children :: [Element]
children = [ el | Elem el <- elContent curElement ]