{-# LANGUAGE DeriveDataTypeable #-}
module Text.XML.HXT.DOM.QualifiedName
( QName
, XName(unXN)
, NsEnv
, mkQName
, mkName
, mkNsName
, mkSNsName
, mkPrefixLocalPart
, equivQName
, equivUri
, equalQNameBy
, namePrefix
, localPart
, namespaceUri
, newXName
, nullXName
, isNullXName
, newQName
, mkQName'
, namePrefix'
, localPart'
, namespaceUri'
, setNamePrefix'
, setLocalPart'
, setNamespaceUri'
, qualifiedName
, qualifiedName'
, universalName
, universalUri
, buildUniversalName
, normalizeNsUri
, setNamespace
, isNCName
, isWellformedQualifiedName
, isWellformedQName
, isWellformedNSDecl
, isWellformedNameSpaceName
, isNameSpaceName
, isDeclaredNamespace
, xmlNamespaceXName
, xmlXName
, xmlnsNamespaceXName
, xmlnsXName
, xmlnsQN
, toNsEnv
)
where
import Control.Arrow ((***))
import Control.DeepSeq
import Control.FlatSeq
import Data.AssocList
import Data.Binary
import Data.Char (toLower)
import Data.IORef
import Data.List (isPrefixOf)
import qualified Data.Map as M
import Data.Typeable
import System.IO.Unsafe (unsafePerformIO)
import Text.XML.HXT.DOM.XmlKeywords (a_xml, a_xmlns,
xmlNamespace,
xmlnsNamespace)
import Data.Char.Properties.XMLCharProps (isXmlNCNameChar,
isXmlNCNameStartChar)
data XName = XN { _idXN :: ! Int
, unXN :: String
}
deriving (Typeable)
instance Eq XName where
(XN id1 _) == (XN id2 _) = id1 == id2
instance Ord XName where
compare (XN _ n1) (XN _ n2) = compare n1 n2
instance NFData XName where
rnf (XN _ s) = rnf s
instance WNFData XName where
rwnf (XN _ s) = rnf s
instance Binary XName where
put (XN _ s) = put s
get = do
s <- get
return $! newXName s
type NsEnv = AssocList XName XName
data QName = QN { localPart' :: ! XName
, namePrefix' :: ! XName
, namespaceUri' :: ! XName
}
deriving (Typeable)
instance Eq QName where
(QN lp1 px1 ns1) == (QN lp2 px2 ns2)
| ns1 /= ns2 = False
| not (isNullXName ns1) = lp1 == lp2
| otherwise = lp1 == lp2
&&
px1 == px2
instance Ord QName where
compare (QN lp1 px1 ns1) (QN lp2 px2 ns2)
| isNullXName ns1 && isNullXName ns2
= compare (px1, lp1) (px2, lp2)
| otherwise
= compare (lp1, ns1) (lp2, ns2)
instance NFData QName where
rnf x = seq x ()
instance WNFData QName
instance Show QName where
show = showQN
instance Binary QName where
put (QN lp px ns) = put (unXN px) >>
put (unXN lp) >>
put (unXN ns)
get = do
px <- get
lp <- get
ns <- get
return $! newNsName lp px ns
isNullXName :: XName -> Bool
isNullXName = (== nullXName)
{-# INLINE isNullXName #-}
namePrefix :: QName -> String
namePrefix = unXN . namePrefix'
{-# INLINE namePrefix #-}
localPart :: QName -> String
localPart = unXN . localPart'
{-# INLINE localPart #-}
namespaceUri :: QName -> String
namespaceUri = unXN . namespaceUri'
{-# INLINE namespaceUri #-}
setNamespaceUri' :: XName -> QName -> QName
setNamespaceUri' ns (QN lp px _ns) = newQName lp px ns
setLocalPart' :: XName -> QName -> QName
setLocalPart' lp (QN _lp px ns) = newQName lp px ns
setNamePrefix' :: XName -> QName -> QName
setNamePrefix' px (QN lp _px ns) = newQName lp px ns
qualifiedName :: QName -> String
qualifiedName (QN lp px _ns)
| isNullXName px = unXN lp
| otherwise = unXN px ++ (':' : unXN lp)
qualifiedName' :: QName -> String -> String
qualifiedName' (QN lp px _ns)
| isNullXName px = (unXN lp ++)
| otherwise = (unXN px ++) . (':' :) . (unXN lp ++)
universalName :: QName -> String
universalName = buildUniversalName (\ ns lp -> '{' : ns ++ '}' : lp)
universalUri :: QName -> String
universalUri = buildUniversalName (++)
buildUniversalName :: (String -> String -> String) -> QName -> String
buildUniversalName bf n@(QN _lp _px ns)
| isNullXName ns = localPart n
| otherwise = unXN ns `bf` localPart n
showQN :: QName -> String
showQN n
| null ns = show $ qualifiedName n
| otherwise = show $ "{" ++ ns ++ "}" ++ qualifiedName n
where
ns = namespaceUri n
mkQName' :: XName -> XName -> XName -> QName
mkQName' px lp ns = newQName lp px ns
{-# DEPRECATED mkQName' "use newQName instead with lp px ns param seq " #-}
mkPrefixLocalPart :: String -> String -> QName
mkPrefixLocalPart px lp
| null px = newLpName lp
| otherwise = newPxName lp px
mkName :: String -> QName
mkName n
| (':' `elem` n)
&&
not (null px)
= newPxName lp px
| otherwise = newLpName n
where
(px, (_ : lp)) = span (/= ':') n
mkQName :: String -> String -> String -> QName
mkQName px lp ns
| null ns = mkPrefixLocalPart px lp
| otherwise = newNsName lp px ns
mkSNsName :: String -> QName
mkSNsName = mkName
{-# DEPRECATED mkSNsName "use mkName instead" #-}
mkNsName :: String -> String -> QName
mkNsName n ns
| null ns = qn
| otherwise = setNamespaceUri' ns' qn
where
qn = mkName n
ns' = newXName ns
equivQName :: QName -> QName -> Bool
equivQName = equalQNameBy equivUri
equivUri :: String -> String -> Bool
equivUri x y = normalizeNsUri x == normalizeNsUri y
equalQNameBy :: (String -> String -> Bool) -> QName -> QName -> Bool
equalQNameBy equiv q1 q2 = localPart q1 == localPart q2
&&
(namespaceUri q1 `equiv` namespaceUri q2)
normalizeNsUri :: String -> String
normalizeNsUri = map toLower . stripSlash
where
stripSlash "" = ""
stripSlash s
| last s == '/' = init s
| otherwise = s
setNamespace :: NsEnv -> QName -> QName
setNamespace env n@(QN lp px _ns)
= maybe n (\ ns -> newQName lp px ns) . lookup px $ env
isNCName :: String -> Bool
isNCName [] = False
isNCName n = and ( zipWith ($)
(isXmlNCNameStartChar : repeat isXmlNCNameChar)
n
)
isWellformedQualifiedName :: String -> Bool
isWellformedQualifiedName s
| null lp = isNCName px
| otherwise = isNCName px && isNCName (tail lp)
where
(px, lp) = span (/= ':') s
isWellformedQName :: QName -> Bool
isWellformedQName (QN lp px _ns)
= (isNCName . unXN) lp
&&
( isNullXName px
||
(isNCName . unXN) px
)
isWellformedNSDecl :: QName -> Bool
isWellformedNSDecl n
= not (isNameSpaceName n)
||
isWellformedNameSpaceName n
isWellformedNameSpaceName :: QName -> Bool
isWellformedNameSpaceName n@(QN lp px _ns)
| isNullXName px = lp == xmlnsXName
| otherwise = px == xmlnsXName
&&
not (null lp')
&&
not (a_xml `isPrefixOf` lp')
where
lp' = localPart n
isNameSpaceName :: QName -> Bool
isNameSpaceName (QN lp px _ns)
| isNullXName px = lp == xmlnsXName
| otherwise = px == xmlnsXName
isDeclaredNamespace :: QName -> Bool
isDeclaredNamespace (QN _lp px ns)
| isNullXName px = True
| px == xmlnsXName = ns == xmlnsNamespaceXName
| px == xmlXName = ns == xmlNamespaceXName
| otherwise = not (isNullXName ns)
toNsEnv :: AssocList String String -> NsEnv
toNsEnv = map (newXName *** newXName)
data NameCache = NC { _newXN :: ! Int
, _xnCache :: ! (M.Map String XName)
, _qnCache :: ! (M.Map (XName, XName, XName) QName)
}
type ChangeNameCache r = NameCache -> (NameCache, r)
theNameCache :: IORef NameCache
theNameCache = unsafePerformIO (newIORef $ initialCache)
{-# NOINLINE theNameCache #-}
initialXNames :: [XName]
nullXName
, xmlnsNamespaceXName
, xmlnsXName
, xmlNamespaceXName
, xmlXName :: XName
initialXNames@
[ nullXName
, xmlnsNamespaceXName
, xmlnsXName
, xmlNamespaceXName
, xmlXName
] = zipWith XN [0..] $
[ ""
, xmlnsNamespace
, a_xmlns
, xmlNamespace
, a_xml
]
initialQNames :: [QName]
xmlnsQN :: QName
initialQNames@
[xmlnsQN] = [QN xmlnsXName nullXName xmlnsNamespaceXName]
initialCache :: NameCache
initialCache = NC
(length initialXNames)
(M.fromList $ map (\ xn -> (unXN xn, xn)) initialXNames)
(M.fromList $ map (\ qn@(QN lp px ns) -> ((lp, px, ns), qn)) initialQNames)
changeNameCache :: NFData r => ChangeNameCache r -> r
changeNameCache action = unsafePerformIO changeNameCache'
where
action' c =
let r = action c
in
fst r `seq` r
changeNameCache' =
do
res <- atomicModifyIORef theNameCache action'
return res
{-# NOINLINE changeNameCache #-}
newXName' :: String -> ChangeNameCache XName
newXName' n c@(NC nxn xm qm)
= case M.lookup n xm of
Just xn -> (c, xn)
Nothing -> let nxn' = nxn + 1 in
let xn = (XN nxn n) in
let xm' = M.insert n xn xm in
rnf xn `seq` (NC nxn' xm' qm, xn)
newQName' :: XName -> XName -> XName -> ChangeNameCache QName
newQName' lp px ns c@(NC nxn xm qm)
= case M.lookup q' qm of
Just qn ->
(c, qn)
Nothing -> let qm' = M.insert q' q qm in
q `seq` (NC nxn xm qm', q)
where
q' = (lp, px, ns)
q = QN lp px ns
andThen :: ChangeNameCache r1 ->
(r1 -> ChangeNameCache r2) -> ChangeNameCache r2
andThen a1 a2 c0 = let (c1, r1) = a1 c0 in
(a2 r1) c1
newXName :: String -> XName
newXName n = changeNameCache $
newXName' n
newQName :: XName -> XName -> XName -> QName
newQName lp px ns = lp `seq` px `seq` ns `seq`
( changeNameCache $
newQName' lp px ns
)
newLpName :: String -> QName
newLpName lp = changeNameCache $
newXName' lp `andThen` \ lp' ->
newQName' lp' nullXName nullXName
newPxName :: String -> String -> QName
newPxName lp px = changeNameCache $
newXName' lp `andThen` \ lp' ->
newXName' px `andThen` \ px' ->
newQName' lp' px' nullXName
newNsName :: String -> String -> String -> QName
newNsName lp px ns = changeNameCache $
newXName' lp `andThen` \ lp' ->
newXName' px `andThen` \ px' ->
newXName' ns `andThen` \ ns' ->
newQName' lp' px' ns'