1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE InstanceSigs #-}
3 {-# LANGUAGE Rank2Types #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 module Symantic.XML.RelaxNG.Compact.Write where
7 import Control.Applicative (Applicative(..), Alternative(..))
8 import Control.Monad (forM)
10 import Data.Eq (Eq(..))
11 import Data.Foldable (Foldable(..))
12 import Data.Function (($), (.), id, const)
13 import Data.Functor ((<$>))
15 import Data.Maybe (Maybe(..), maybe, catMaybes, isNothing)
16 import Data.Monoid (Monoid(..))
17 import Data.Semigroup (Semigroup(..))
18 import Data.String (IsString(..), String)
19 import Numeric.Natural (Natural)
20 import Prelude (Integer)
21 import qualified Control.Monad.Trans.State.Strict as S
22 import qualified Data.HashMap.Strict as HM
23 import qualified Data.HashSet as HS
24 import qualified Data.List as List
25 import qualified Data.Map.Strict as Map
26 import qualified Data.Text as Text
27 import qualified Data.Text.Lazy as TL
28 import qualified Data.Text.Lazy.Builder as TLB
30 import Symantic.Base.Fixity
31 import Symantic.XML.Language
32 import Symantic.XML.RelaxNG.Language
34 -- | Get textual rendition of given 'RNCWriteSyn'.
35 writeRNC :: RNCWriteSyn a k -> TL.Text
36 writeRNC = TLB.toLazyText . runRNCWriteSyn
38 -- | Get textual rendition of given 'RNCWriteSyn'.
39 runRNCWriteSyn :: RNCWriteSyn a k -> TLB.Builder
40 runRNCWriteSyn RNCWriteSyn{..} =
43 [ [ "default namespace = \""<>textify (namespaces_default rncWriteInh_namespaces)<>"\"\n"
44 | not $ TL.null $ unNamespace (namespaces_default rncWriteInh_namespaces)
46 , [ "namespace "<>textify p<>" = \""<>textify n<>"\"\n"
47 | (Namespace n, NCName p) <-
48 HM.toList (namespaces_prefixes rncWriteInh_namespaces)
51 (\n v -> ((textify n<>" = "<>v<>"\n") :)) []
55 RNCWriteState{..} = rncWriteSyn_state $ RNCWriteState mempty mempty
56 defs :: Map.Map DefineName TLB.Builder
57 defs = Map.mapMaybe ($ inh) rncWriteState_defines
59 { rncWriteInh_namespaces
60 , rncWriteInh_op = (infixN0, SideL)
61 , rncWriteInh_pair = pairParen
63 rncWriteInh_namespaces :: Namespaces NCName
64 rncWriteInh_namespaces = rncWriteState_namespaces
65 { namespaces_prefixes =
66 (`S.evalState` HS.empty) $
67 forM prefixByNamespace $ \mp -> do
71 (freshNCName usedPrefixes)
72 (freshifyNCName usedPrefixes)
74 S.modify' $ HS.insert freshPrefix
77 prefixByNamespace :: HM.HashMap Namespace (Maybe NCName)
79 -- Add default prefixes if their 'Namespace' is used.
81 (HM.intersectionWith (<|>)
82 (namespaces_prefixes rncWriteState_namespaces)
83 (Just <$> namespaces_prefixes defaultNamespaces)) $
84 namespaces_prefixes rncWriteState_namespaces
86 -- * Type 'RNCWriteState'
90 { rncWriteState_namespaces :: Namespaces (Maybe NCName)
91 -- ^ The 'Namespaces' used throughout the 'RelaxNG' schema.
92 , rncWriteState_defines :: Map.Map DefineName (RNCWriteInh -> Maybe TLB.Builder)
93 -- ^ Used to avoid infinite recursion,
94 -- by looking up the 'DefineName' of 'define'.
97 -- * Type 'RNCWriteSyn'
98 -- | Synthetized (bottom-up) values.
101 { rncWriteSyn_state :: Chained RNCWriteState
102 , rncWriteSyn_schema :: RNCWriteInh -> Maybe TLB.Builder
104 instance IsString (RNCWriteSyn a k) where
105 fromString s = RNCWriteSyn
106 { rncWriteSyn_state = id
107 , rncWriteSyn_schema = const $
108 if List.null s then Nothing
109 else Just (textify s)
112 -- | Like the @State st ()@ monad, but without @()@.
113 -- The name comme from chained-attribute from Attribute Grammar.
114 type Chained a = a -> a
116 coerceRNCWriteSyn :: RNCWriteSyn a k -> RNCWriteSyn a' k'
117 coerceRNCWriteSyn RNCWriteSyn{..} = RNCWriteSyn{..}
118 {-# INLINE coerceRNCWriteSyn #-}
121 Semigroup s => IsString s =>
122 RNCWriteInh -> Infix -> Maybe s -> Maybe s
123 pairRNCWriteInh inh op s =
124 if isPairNeeded (rncWriteInh_op inh) op
125 then Just (fromString o<>" ")<>s<>Just (" "<>fromString c)
127 where (o,c) = rncWriteInh_pair inh
129 -- ** Type 'RNCWriteInh'
130 -- Inherited (top-down) values.
133 { rncWriteInh_namespaces :: Namespaces NCName
134 , rncWriteInh_op :: (Infix, Side)
135 , rncWriteInh_pair :: Pair
138 instance Emptyable RNCWriteSyn where
140 instance Unitable RNCWriteSyn where
142 instance Voidable RNCWriteSyn where
143 void _a = coerceRNCWriteSyn
144 instance Constant RNCWriteSyn where
146 instance Composable RNCWriteSyn where
147 x <.> y = RNCWriteSyn
148 (rncWriteSyn_state x . rncWriteSyn_state y) $ \inh ->
151 { rncWriteInh_op = (op, side)
152 , rncWriteInh_pair = pairParen
154 case rncWriteSyn_schema x (inh' SideL) of
155 Nothing -> rncWriteSyn_schema y (inh' SideR)
157 case rncWriteSyn_schema y (inh' SideR) of
160 pairRNCWriteInh inh op $
161 Just $ xw <> ", " <> yw
164 instance Tupable RNCWriteSyn where
165 x <:> y = coerceRNCWriteSyn x <.> coerceRNCWriteSyn y
166 instance Eitherable RNCWriteSyn where
167 x <+> y = RNCWriteSyn
168 (rncWriteSyn_state x . rncWriteSyn_state y) $ \inh ->
169 pairRNCWriteInh inh op $
170 rncWriteSyn_schema x inh
171 { rncWriteInh_op = (op, SideL)
172 , rncWriteInh_pair = pairParen
175 rncWriteSyn_schema y inh
176 { rncWriteInh_op = (op, SideR)
177 , rncWriteInh_pair = pairParen
179 where op = infixB SideL 3
180 instance Optionable RNCWriteSyn where
181 option = coerceRNCWriteSyn . optional . coerceRNCWriteSyn
182 optional w = w{ rncWriteSyn_schema = \inh ->
183 pairRNCWriteInh inh op $
184 rncWriteSyn_schema w inh
185 { rncWriteInh_op = (op, SideL)
186 , rncWriteInh_pair = pairParen
190 instance Dimapable RNCWriteSyn where
191 dimap _a2b _b2a = coerceRNCWriteSyn
192 instance Dicurryable RNCWriteSyn where
193 dicurry _args _constr _destr = coerceRNCWriteSyn
194 instance Repeatable RNCWriteSyn where
195 many0 w = w{ rncWriteSyn_schema = \inh ->
196 pairRNCWriteInh inh op $
197 rncWriteSyn_schema w inh
198 { rncWriteInh_op = (op, SideL)
199 , rncWriteInh_pair = pairParen
203 many1 w = w{ rncWriteSyn_schema = \inh ->
204 pairRNCWriteInh inh op $
205 rncWriteSyn_schema w inh
206 { rncWriteInh_op = (op, SideL)
207 , rncWriteInh_pair = pairParen
211 instance Textable RNCWriteSyn where
212 type TextConstraint RNCWriteSyn a = RNCText a
213 text :: forall a k. TextConstraint RNCWriteSyn a => RNCWriteSyn (a -> k) k
215 { rncWriteSyn_state = \st ->
217 (qNameSpace (rncText_qname @a))
218 (namespaces_prefixes (rncWriteState_namespaces st)) of
221 let ns = qNameSpace (rncText_qname @a) in
222 if ns == xmlns_empty then st else st
223 { rncWriteState_namespaces = (rncWriteState_namespaces st)
224 { namespaces_prefixes =
225 HM.insertWith (<|>) ns Nothing $
226 namespaces_prefixes (rncWriteState_namespaces st) } }
227 , rncWriteSyn_schema = \inh ->
228 let n = rncText_qname @a in
229 let t = if TL.null (unNamespace (qNameSpace n))
230 then textify (qNameLocal n)
231 else textify (prefixifyQName (rncWriteInh_namespaces inh) n)
232 in if null (rncText_params @a)
235 pairRNCWriteInh inh (infixN 8) $
237 t<>" {"<>Map.foldMapWithKey
238 (\k v -> " "<>textify k<>" = \""<>textify v<>"\"")
239 (rncText_params @a)<>" }"
241 instance XML RNCWriteSyn where
242 namespace mp ns = RNCWriteSyn
243 { rncWriteSyn_state = \st -> st
244 { rncWriteState_namespaces =
245 let nss = rncWriteState_namespaces st in
247 { namespaces_prefixes =
248 HM.insertWith (<|>) ns mp (namespaces_prefixes nss)
249 , namespaces_default =
252 else namespaces_default nss
255 , rncWriteSyn_schema = const Nothing
258 { rncWriteSyn_state = \st ->
259 rncWriteSyn_state w $ st
260 { rncWriteState_namespaces = (rncWriteState_namespaces st)
261 { namespaces_prefixes =
262 -- Insert this 'qNameSpace' even if this is the default namespace,
263 -- because the default namespace here may not end up
264 -- being the global default namespace
265 -- if there is a default 'namespace' declaration after this one.
266 -- at worse this will just add a superfluous ns# declaration
267 -- in the schema rendering.
268 HM.insertWith (<|>) (qNameSpace n) Nothing
269 (namespaces_prefixes (rncWriteState_namespaces st)) } }
270 , rncWriteSyn_schema = \inh ->
271 pairRNCWriteInh inh (infixN 8) $
273 <> textify (prefixifyQName (rncWriteInh_namespaces inh) n)
275 <> rncWriteSyn_schema w inh
276 { rncWriteInh_op = (infixN0, SideR)
277 , rncWriteInh_pair = pairBrace
282 { rncWriteSyn_state = \st ->
283 rncWriteSyn_state w $
284 if qNameSpace n == xmlns_empty then st else st
285 { rncWriteState_namespaces = (rncWriteState_namespaces st)
286 { namespaces_prefixes =
287 HM.insertWith (<|>) (qNameSpace n) Nothing
288 (namespaces_prefixes (rncWriteState_namespaces st)) } }
289 , rncWriteSyn_schema = \inh ->
290 pairRNCWriteInh inh (infixN 8) $
292 -- The namespace name for an unprefixed attribute name always has no value.
293 <> textify (prefixifyQName (rncWriteInh_namespaces inh){namespaces_default=xmlns_empty} n)
295 <> rncWriteSyn_schema w inh
296 { rncWriteInh_op = (infixN0, SideR)
297 , rncWriteInh_pair = pairBrace
301 literal lit = RNCWriteSyn
302 { rncWriteSyn_state = id
303 , rncWriteSyn_schema = \_inh -> Just ("\""<>textify lit<>"\"")
308 instance Definable RNCWriteSyn where
310 { rncWriteSyn_state = \st ->
311 let defs = rncWriteState_defines st in
312 case Map.lookup n defs of
314 rncWriteSyn_state w $ st
315 { rncWriteState_defines =
316 Map.insert n (rncWriteSyn_schema w) defs
319 , rncWriteSyn_schema = const $ Just $ textify n
321 instance Permutable RNCWriteSyn where
322 type Permutation RNCWriteSyn = RNCWriteSynPerm
323 permutable (RNCWriteSynPerm ps) = RNCWriteSyn
324 { rncWriteSyn_state = List.foldl' (.) id (rncWriteSyn_state <$> ps)
325 , rncWriteSyn_schema = case ps of
329 List.intersperse " & " $
330 catMaybes $ (<$> ps) $ \w ->
331 rncWriteSyn_schema w inh{rncWriteInh_op=(op, SideL)}
335 xs -> pairRNCWriteInh inh op $ Just $ mconcat xs
338 perm = RNCWriteSynPerm . pure
339 noPerm = RNCWriteSynPerm []
340 permWithDefault _def p = RNCWriteSynPerm
341 [coerceRNCWriteSyn (optional p)]
342 instance RelaxNG RNCWriteSyn where
343 elementMatch nc w = w
344 { rncWriteSyn_state = \st ->
345 rncWriteSyn_state w $ st
346 { rncWriteState_namespaces = (rncWriteState_namespaces st)
347 { namespaces_prefixes =
348 namespacesNameClass nc <>
349 namespaces_prefixes (rncWriteState_namespaces st)
351 , rncWriteSyn_schema = \inh ->
352 pairRNCWriteInh inh (infixN 8) $
354 <> textify (rncWriteInh_namespaces inh, (infixN0,SideL), nc)
356 <> rncWriteSyn_schema w inh
357 { rncWriteInh_op = (infixN 9, SideR)
358 , rncWriteInh_pair = pairBrace
361 attributeMatch nc w = w
362 { rncWriteSyn_state = \st ->
363 let nss = HM.delete xmlns_empty $ namespacesNameClass nc in
364 rncWriteSyn_state w $
365 if null nss then st else st
366 { rncWriteState_namespaces = (rncWriteState_namespaces st)
367 { namespaces_prefixes =
368 HM.unionWith (<|>) nss $
369 namespaces_prefixes (rncWriteState_namespaces st)
371 , rncWriteSyn_schema = \inh ->
372 pairRNCWriteInh inh (infixN 8) $
374 <> textify ( (rncWriteInh_namespaces inh){namespaces_default=xmlns_empty}
378 <> rncWriteSyn_schema w inh
379 { rncWriteInh_op = (infixN 9, SideR)
380 , rncWriteInh_pair = pairBrace
384 -- ** Type 'RNCWriteSynPerm'
385 newtype RNCWriteSynPerm a k
387 { rncWriteSynPerm_alternatives :: [RNCWriteSyn a k]
388 -- ^ Collect alternatives for rendering
389 -- them all at once in 'runPermutation'.
391 instance Composable RNCWriteSynPerm where
392 RNCWriteSynPerm x <.> RNCWriteSynPerm y =
394 (coerceRNCWriteSyn <$> x) <>
395 (coerceRNCWriteSyn <$> y)
396 instance Dimapable RNCWriteSynPerm where
397 dimap _a2b _b2a (RNCWriteSynPerm x) =
398 RNCWriteSynPerm (coerceRNCWriteSyn <$> x)
399 instance Tupable RNCWriteSynPerm where
400 RNCWriteSynPerm x <:> RNCWriteSynPerm y =
402 (coerceRNCWriteSyn <$> x) <>
403 (coerceRNCWriteSyn <$> y)
404 instance Definable RNCWriteSynPerm where
405 define n (RNCWriteSynPerm ps) =
406 RNCWriteSynPerm $ pure $
407 coerceRNCWriteSyn $ define n $
408 permutable $ RNCWriteSynPerm $
409 coerceRNCWriteSyn <$> ps
412 class RNCText a where
413 rncText_qname :: QName
414 rncText_params :: Map.Map TL.Text TL.Text
415 rncText_params = mempty
416 instance RNCText String where
417 rncText_qname = QName (Namespace "") "text"
418 instance RNCText Text.Text where
419 rncText_qname = QName (Namespace "") "text"
420 instance RNCText TL.Text where
421 rncText_qname = QName (Namespace "") "text"
422 instance RNCText Bool where
423 rncText_qname = QName xmlns_xsd "boolean"
424 instance RNCText Int where
425 rncText_qname = QName xmlns_xsd "int"
426 instance RNCText Integer where
427 rncText_qname = QName xmlns_xsd "integer"
428 instance RNCText Natural where
429 rncText_qname = QName xmlns_xsd "nonNegativeInteger"