1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE StrictData #-}
3 {-# LANGUAGE UndecidableInstances #-}
4 module Symantic.XML.Write where
6 import Control.Applicative (Applicative(..), Alternative((<|>)))
8 import Data.Either (Either(..))
9 import Data.Eq (Eq(..))
10 import Data.Foldable (Foldable(..))
11 import Data.Function (($), (.), id)
12 import Data.Functor ((<$>), (<$))
14 import Data.Maybe (Maybe(..), fromMaybe)
15 import Data.Monoid (Monoid(..))
16 import Data.Semigroup (Semigroup(..))
17 import Data.String (String)
18 import Data.Traversable (Traversable(..))
19 import Data.Tuple (fst)
20 import Numeric.Natural (Natural)
21 import Prelude (Integer, error)
22 import System.IO (IO, FilePath)
23 import Text.Show (Show(..))
24 import qualified Control.Exception as Exn
25 import qualified Control.Monad.Trans.State as S
26 import qualified Data.ByteString.Lazy as BSL
27 import qualified Data.HashMap.Strict as HM
28 import qualified Data.HashSet as HS
29 import qualified Data.List as List
30 import qualified Data.Text as Text
31 import qualified Data.Text.Lazy as TL
32 import qualified Data.Text.Lazy.Builder as TLB
33 import qualified Data.Text.Lazy.Encoding as TL
34 import qualified System.IO.Error as IO
36 import Symantic.Base.CurryN
37 import Symantic.XML.Language
38 import Symantic.XML.RelaxNG.Language
41 newtype Write params k
43 { unWrite :: (WriteSyn -> k) -> params
46 write :: Write params BSL.ByteString -> params
47 write = runWrite defaultWriteInh
49 runWrite :: WriteInh -> Write params BSL.ByteString -> params
50 runWrite def (Write params) = params $ \syn ->
51 TL.encodeUtf8 $ TLB.toLazyText $
52 fromMaybe mempty $ writeSyn_result syn def
54 writeUtf8 :: FilePath -> Write params (IO (Maybe ErrorWrite)) -> params
55 writeUtf8 path (Write params) = params $ \syn ->
57 TL.encodeUtf8 $ TLB.toLazyText $
59 writeSyn_result syn defaultWriteInh in
60 (Nothing <$ BSL.writeFile path txt)
62 if IO.isAlreadyInUseError e
63 || IO.isPermissionError e
68 type ErrorWrite = IO.IOError
71 -- | Top-down inheritage.
74 { writeInh_namespaces :: Namespaces NCName
75 -- ^ 'Namespaces' from the parent element.
76 , writeInh_indent :: TLB.Builder
77 , writeInh_indent_delta :: TL.Text
80 defaultWriteInh :: WriteInh
81 defaultWriteInh = WriteInh
82 { writeInh_namespaces = defaultNamespaces
83 , writeInh_indent = mempty
84 , writeInh_indent_delta = " "
88 -- | Bottom-up synthesis to build 'element' or 'attribute'.
91 { writeSyn_attrs :: HM.HashMap QName TL.Text
92 , writeSyn_attr :: TL.Text
93 , writeSyn_namespaces_default :: Maybe Namespace
94 , writeSyn_namespaces_prefixes :: HM.HashMap Namespace NCName
95 , writeSyn_result :: WriteInh -> Maybe TLB.Builder
98 instance Semigroup WriteSyn where
100 { writeSyn_attrs = writeSyn_attrs x <> writeSyn_attrs y
101 , writeSyn_attr = writeSyn_attr x <> writeSyn_attr y
102 , writeSyn_namespaces_default = writeSyn_namespaces_default x <|> writeSyn_namespaces_default y
103 , writeSyn_namespaces_prefixes = writeSyn_namespaces_prefixes x <> writeSyn_namespaces_prefixes y
104 , writeSyn_result = writeSyn_result x <> writeSyn_result y
106 instance Monoid WriteSyn where
108 { writeSyn_attrs = mempty
109 , writeSyn_attr = mempty
110 , writeSyn_namespaces_default = Nothing
111 , writeSyn_namespaces_prefixes = mempty
112 , writeSyn_result = mempty
115 instance Emptyable Write where
116 empty = Write (\k -> k mempty)
117 instance Unitable Write where
118 unit = Write (\k () -> k mempty)
119 instance Voidable Write where
120 void a (Write x) = Write (\k -> x k a)
121 instance Dimapable Write where
122 dimap _a2b b2a (Write x) = Write $ \k b ->
124 instance Dicurryable Write where
125 dicurry (_::proxy args) _construct destruct (Write x) =
127 uncurryN @args (x k) (destruct r)
128 instance Composable Write where
129 Write x <.> Write y = Write $ \k ->
130 x (\mx -> y $ \my -> k (mx<>my))
131 instance Tupable Write where
132 Write x <:> Write y = Write $ \k (a,b) ->
133 x (\mx -> y (\my -> k (mx<>my)) b) a
134 instance Eitherable Write where
135 Write x <+> Write y = Write $ \k -> \case
138 instance Constant Write where
139 constant _a = Write $ \k _a -> k mempty
140 instance Optionable Write where
142 optional (Write x) = Write $ \k ->
147 instance Routable Write where
148 Write x <!> Write y = Write $ \k ->
151 instance Repeatable Write where
152 many0 (Write x) = Write $ \k -> \case
155 unWrite (many0 (Write x))
156 (\mas -> k (ma<>mas)) as) a
157 many1 (Write x) = Write $ \k -> \case
160 unWrite (many0 (Write x))
161 (\mas -> k (ma<>mas)) as) a
162 instance Textable Write where
163 type TextConstraint Write a = EncodeText a
164 text = Write $ \k v ->
165 let t = encodeText v in
168 , writeSyn_result = \_inh -> Just $ textify $ escapeText t
170 instance XML Write where
171 namespace nm ns = Write $ \k ->
173 Nothing -> mempty{writeSyn_namespaces_default=Just ns}
174 Just p -> mempty{writeSyn_namespaces_prefixes=HM.singleton ns p}
175 element elemQName (Write x) = Write $ \k ->
177 k mempty{ writeSyn_result = \inh ->
179 hasIndenting = not $ TL.null $ writeInh_indent_delta inh
181 (namespaces_default (writeInh_namespaces inh))
182 (writeSyn_namespaces_default syn)
184 HS.singleton (qNameSpace elemQName) <>
185 HS.delete xmlns_empty (HS.fromList (qNameSpace <$> HM.keys (writeSyn_attrs syn)))
186 -- The inherited namespaces,
187 -- including those declared at this element.
190 (writeSyn_namespaces_prefixes syn)
191 (namespaces_prefixes (writeInh_namespaces inh))
192 -- The namespaces used but not declared nor default,
193 -- with fresh prefixes.
196 (`S.evalState` HS.empty) $
198 (\() -> S.gets freshNCName)
199 (HS.toMap usedNS `HM.difference` inhNS)
201 (if defNS == namespaces_default (writeInh_namespaces inh)
203 else textifyAttr (PName Nothing "xmlns") (escapeAttr (unNamespace defNS))) <>
204 HM.foldrWithKey (\(Namespace ns) qNameLocal acc ->
205 textifyAttr (PName (Just "xmlns") qNameLocal) (escapeAttr ns) <> acc
207 (autoNS <> writeSyn_namespaces_prefixes syn)
209 { namespaces_prefixes = autoNS <> inhNS
210 , namespaces_default = defNS
212 write_elemPName = textify $ prefixifyQName scopeNS elemQName
214 foldMap (\(an, av) -> textifyAttr
215 (prefixifyQName scopeNS{namespaces_default=xmlns_empty} an)
217 List.sortOn fst $ -- This makes the rendition more predictible, but this is useless.
218 HM.toList (writeSyn_attrs syn)
219 write_elemChilds = writeSyn_result syn inh
220 { writeInh_namespaces = scopeNS
221 -- Disable indenting unless hasIndenting.
225 writeInh_indent inh <>
226 textify (writeInh_indent_delta inh)
228 , writeInh_indent_delta =
230 then writeInh_indent_delta inh
239 <> case write_elemChilds of
240 Nothing -> "/>" <> nl inh
244 <> (if hasIndenting then writeInh_indent inh else mempty)
245 <> "</"<>write_elemPName<>">"
248 attribute n@(QName ans aln) (Write x) = Write $ \k ->
250 if ans == xmlns_xmlns
251 then unWrite (namespace (Just aln) (Namespace (writeSyn_attr syn))) k
252 else if ans == xmlns_empty && aln == NCName "xmlns"
253 then unWrite (namespace Nothing (Namespace (writeSyn_attr syn))) k
254 else k mempty{writeSyn_attrs = HM.insert n (writeSyn_attr syn) (writeSyn_attrs syn)}
255 literal lit = Write $ \k ->
257 { writeSyn_attr = lit
258 , writeSyn_result = \_inh ->
259 Just $ textify $ escapeText lit
261 pi n = Write $ \k v ->
262 k mempty{ writeSyn_result = \inh ->
263 let s | TL.null v = ""
266 writeInh_indent inh <>
267 "<?"<>textify n<>s <>
268 textify (TL.replace "?>" "?>" v) <>
271 comment = Write $ \k v ->
272 k mempty{ writeSyn_result = \inh ->
274 writeInh_indent inh <>
275 "<!--"<>textify (TL.replace "-->" "-->" v)<>"-->"<>nl inh
277 cdata = Write $ \k v ->
278 k mempty{ writeSyn_result = \inh ->
280 writeInh_indent inh <>
281 "<[CDATA[["<>textify (TL.replace "]]>" "]]>" v)<>"]]>"<>nl inh
283 instance Permutable Write where
284 type Permutation Write = WritePerm Write
285 permutable = unWritePerm
287 noPerm = WritePerm empty
288 permWithDefault _a = WritePerm
289 instance Definable Write where
291 instance RelaxNG Write where
292 elementMatch nc x = Write $ \k n ->
293 if matchNameClass nc n
294 then error "elementMatch: given QName does not match expected NameClass"
295 else unWrite (element n x) k
296 attributeMatch nc x = Write $ \k n ->
297 if matchNameClass nc n
298 then error "attributeMatch: given QName does not match expected NameClass"
299 else unWrite (attribute n x) k
301 -- ** Type 'WritePerm'
302 newtype WritePerm repr xml k
304 { unWritePerm :: repr xml k }
305 instance Transformable (WritePerm repr) where
306 type UnTrans (WritePerm repr) = repr
308 unTrans = unWritePerm
309 instance Dimapable (WritePerm Write)
310 instance Composable (WritePerm Write)
311 instance Tupable (WritePerm Write)
313 nl :: WriteInh -> TLB.Builder
314 nl inh | TL.null (writeInh_indent_delta inh) = mempty
317 -- * Class 'EncodeText'
318 class EncodeText a where
319 encodeText :: a -> TL.Text
320 default encodeText :: Show a => a -> TL.Text
321 encodeText = TL.pack . show
322 instance EncodeText String where
324 instance EncodeText Text.Text where
325 encodeText = TL.fromStrict
326 instance EncodeText TL.Text where
328 instance EncodeText Bool where
332 instance EncodeText Int
333 instance EncodeText Integer
334 instance EncodeText Natural