File: Namespaced.hs

package info (click to toggle)
haskell-hexpat 0.20.13-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 916 kB
  • sloc: ansic: 12,303; haskell: 3,457; xml: 1,109; makefile: 5; sh: 5
file content (157 lines) | stat: -rw-r--r-- 6,326 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
{-# LANGUAGE FlexibleContexts #-}
module Text.XML.Expat.Internal.Namespaced
      ( NName (..)
      , NAttributes
      , mkNName
      , mkAnNName
      , toNamespaced
      , fromNamespaced
      , xmlnsUri
      , xmlns
      ) where

import Text.XML.Expat.Internal.NodeClass
import Text.XML.Expat.Internal.Qualified
import Text.XML.Expat.SAX
import Control.DeepSeq
import qualified Data.Map as M
import qualified Data.Maybe as DM
import qualified Data.List as L

-- | A namespace-qualified tag.
--
-- NName has two components, a local part and an optional namespace. The local part is the
-- name of the tag. The namespace is the URI identifying collections of declared tags.
-- Tags with the same local part but from different namespaces are distinct. Unqualified tags
-- are those with no namespace. They are in the default namespace, and all uses of an
-- unqualified tag are equivalent.
data NName text =
    NName {
        nnNamespace :: Maybe text,
        nnLocalPart :: !text
    }
    deriving (Eq,Show)

instance NFData text => NFData (NName text) where
    rnf (NName ns loc) = rnf (ns, loc)

-- | Type shortcut for attributes with namespaced names
type NAttributes text = Attributes (NName text) text

-- | Make a new NName from a prefix and localPart.
mkNName :: text -> text -> NName text
mkNName prefix localPart = NName (Just prefix) localPart

-- | Make a new NName with no prefix.
mkAnNName :: text -> NName text
mkAnNName localPart = NName Nothing localPart

type NsPrefixMap text = M.Map (Maybe text) (Maybe text)
type PrefixNsMap text = M.Map (Maybe text) (Maybe text)

xmlUri :: (GenericXMLString text) => text
xmlUri = gxFromString "http://www.w3.org/XML/1998/namespace"
xml :: (GenericXMLString text) => text
xml = gxFromString "xml"

xmlnsUri :: (GenericXMLString text) => text
xmlnsUri = gxFromString "http://www.w3.org/2000/xmlns/"
xmlns :: (GenericXMLString text) => text
xmlns = gxFromString "xmlns"

baseNsBindings :: (GenericXMLString text, Ord text)
               => NsPrefixMap text
baseNsBindings = M.fromList
  [ (Nothing, Nothing) 
  , (Just xml, Just xmlUri)
  , (Just xmlns, Just xmlnsUri)
  ]

basePfBindings :: (GenericXMLString text, Ord text)
               => PrefixNsMap text
basePfBindings = M.fromList
   [ (Nothing, Nothing)
   , (Just xmlUri, Just xml)
   , (Just xmlnsUri, Just xmlns)
   ]

toNamespaced :: (NodeClass n c, GenericXMLString text, Ord text, Show text)
               => n c (QName text) text -> n c (NName text) text
toNamespaced = nodeWithNamespaces baseNsBindings

nodeWithNamespaces :: (NodeClass n c, GenericXMLString text, Ord text, Show text)
                   => NsPrefixMap text -> n c (QName text) text -> n c (NName text) text
nodeWithNamespaces bindings = modifyElement namespaceify
  where
    namespaceify (qname, qattrs, qchildren) = (nname, nattrs, nchildren)
      where
        for = flip map
        ffor = flip fmap
        (nsAtts, otherAtts) = L.partition ((== Just xmlns) . qnPrefix . fst) qattrs
        (dfAtt, normalAtts) = L.partition ((== QName Nothing xmlns) . fst) otherAtts
        nsMap  = M.fromList $ for nsAtts $ \((QName _ lp), uri) -> (Just lp, Just uri)
        -- fixme: when snd q is null, use Nothing
        dfMap  = M.fromList $ for dfAtt $ \q -> (Nothing, Just $ snd q)
        chldBs = M.unions [dfMap, nsMap, bindings]
    
        trans bs (QName pref qual) = case pref `M.lookup` bs of
          Nothing -> error 
                  $  "Namespace prefix referenced but never bound: '"
                  ++ (show . DM.fromJust) pref
                  ++ "'"
          Just mUri -> NName mUri qual
        nname       = trans chldBs qname
    
        -- attributes with no prefix are in the same namespace as the element
        attBs = M.insert Nothing (nnNamespace nname) chldBs
    
        transAt (qn, v) = (trans attBs qn, v)
    
        nNsAtts     = map transAt nsAtts
        nDfAtt      = map transAt dfAtt
        nNormalAtts = map transAt normalAtts
        nattrs      = concat [nNsAtts, nDfAtt, nNormalAtts]

        nchildren   = ffor qchildren $ nodeWithNamespaces chldBs

fromNamespaced :: (NodeClass n c, GenericXMLString text, Ord text, Functor c) =>
                  n c (NName text) text -> n c (QName text) text
fromNamespaced = nodeWithQualifiers 1 basePfBindings

nodeWithQualifiers :: (NodeClass n c, GenericXMLString text, Ord text, Functor c) =>
                      Int
                   -> PrefixNsMap text
                   -> n c (NName text) text
                   -> n c (QName text) text
nodeWithQualifiers cntr bindings = modifyElement namespaceify
  where
    namespaceify (nname, nattrs, nchildren) = (qname, qattrs, qchildren) 
      where
        for = flip map
        ffor = flip fmap
        (nsAtts, otherAtts) = L.partition ((== Just xmlnsUri) . nnNamespace . fst) nattrs
        (dfAtt, normalAtts) = L.partition ((== NName Nothing xmlns) . fst) otherAtts
        nsMap = M.fromList $ for nsAtts $ \((NName _ lp), uri) -> (Just uri, Just lp)
        dfMap = M.fromList $ for dfAtt  $ \(_, uri) -> (Just uri, Just xmlns)
        chldBs = M.unions [dfMap, nsMap, bindings]
    
        trans (i, bs, as) (NName nspace qual) =
          case nspace `M.lookup` bs of
               Nothing -> let
                            pfx = gxFromString $ "ns" ++ show i
                            bsN = M.insert nspace (Just pfx) bs
                            asN = (NName (Just xmlnsUri) pfx, DM.fromJust nspace) : as
                          in trans (i+1, bsN, asN) (NName nspace qual)
               Just pfx -> ((i, bs, as), QName pfx qual)
        transAt ibs (nn, v) = let (ibs', qn) = trans ibs nn
                              in  (ibs', (qn, v))
    
        ((i', bs', as'), qname) = trans (cntr, chldBs, []) nname
    
        ((i'',   bs'',   as''),   qNsAtts)     = L.mapAccumL transAt (i',    bs',    as')    nsAtts
        ((i''',  bs''',  as'''),  qDfAtt)      = L.mapAccumL transAt (i'',   bs'',   as'')   dfAtt
        ((i'''', bs'''', as''''), qNormalAtts) = L.mapAccumL transAt (i''',  bs''',  as''')  normalAtts
        (_,                       qas)         = L.mapAccumL transAt (i'''', bs'''', as'''') as''''
        qattrs = concat [qNsAtts, qDfAtt, qNormalAtts, qas]
    
        qchildren = ffor nchildren $ nodeWithQualifiers i'''' bs''''