1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE StrictData #-}
3 module Symantic.XML.Tree.Write where
6 import Data.Eq (Eq(..))
7 import Data.Foldable (Foldable(..), all)
8 import Data.Function (($), (.), const)
9 import Data.Maybe (Maybe(..))
10 import Data.Monoid (Monoid(..))
11 import Data.Semigroup (Semigroup(..))
12 import Data.String (IsString(..))
13 import Data.Traversable (Traversable(..))
14 import Data.Tuple (fst)
15 import System.IO (IO, FilePath)
16 import qualified Control.Monad.Trans.State as S
17 import qualified Data.ByteString.Lazy as BSL
18 import qualified Data.Char as Char
19 import qualified Data.HashMap.Strict as HM
20 import qualified Data.HashSet as HS
21 import qualified Data.List as List
22 import qualified Data.Text.Lazy as TL
23 import qualified Data.Text.Lazy.Builder as TLB
24 import qualified Data.Text.Lazy.Encoding as TL
25 import qualified Data.TreeSeq.Strict as TS
27 import Symantic.XML.Language
28 import Symantic.XML.Tree.Source
29 import Symantic.XML.Tree.Data
30 import Symantic.XML.Write
32 writeTree :: UnSource src => Trees src -> TL.Text
33 writeTree ts = TLB.toLazyText $ unTreeWrite (write_Trees ts) defaultWriteInh
35 writeTreeIndented :: UnSource src => TL.Text -> Trees src -> TL.Text
36 writeTreeIndented ind xs =
38 unTreeWrite (write_Trees xs) defaultWriteInh
39 { writeInh_indent_delta = ind }
41 writeFile :: FilePath -> TL.Text -> IO ()
42 writeFile fp = BSL.writeFile fp . TL.encodeUtf8
47 { unTreeWrite :: WriteInh -> TLB.Builder
49 instance Semigroup TreeWrite where
50 TreeWrite x <> TreeWrite y = TreeWrite (x <> y)
51 instance Monoid TreeWrite where
52 mempty = TreeWrite (const "")
54 instance IsString TreeWrite where
55 fromString = TreeWrite . const . fromString
57 write_Trees :: UnSource src => Trees src -> TreeWrite
58 write_Trees = foldMap write_Tree
60 write_Tree :: UnSource src => Tree src -> TreeWrite
61 write_Tree (TS.Tree node elemChilds) = TreeWrite $ \inh ->
63 NodeText et@(EscapedText t)
64 -- Remove spaces when indenting
65 | not $ TL.null (writeInh_indent_delta inh)
67 EscapedPlain p -> TL.all Char.isSpace p
70 | otherwise -> textify et
72 writeInh_indent inh <>
75 -- Special case: the value of the "xml" PI is parsed
77 "xml" -> foldMap (\case
80 NodePI n v -> " "<>textify n<>"=\""<>textify v<>"\""
85 where s | TL.null pv = ""
88 writeInh_indent inh <>
89 "<[CDATA[["<>textify (TL.replace "]]>" "]]>" t)<>"]]>"<>nl inh
91 writeInh_indent inh <>
92 "<!--"<>textify (TL.replace "-->" "-->" t)<>"-->"<>nl inh
93 NodeElem elemQName elemAttrs ->
102 <> (if hasIndenting then nl inh else mempty)
104 <> (if hasIndenting then writeInh_indent inh else mempty)
105 <> "</"<>write_elemPName<>">"
108 -- Empty NodeText do not count as a child
112 | NodeText (EscapedText t) <- unSource n ->
114 EscapedPlain p -> TL.null p
119 -- Follow xmllint --format rules to detect indenting:
120 -- if there is any NodeText it should only contain whites
122 (`all` elemChilds) $ \case
124 | NodeText (EscapedText t) <- unSource n ->
126 EscapedPlain p -> TL.all Char.isSpace p
131 HM.foldlWithKey' go (initUsedNS, initDeclNS) elemAttrs
133 initUsedNS = HS.singleton $ qNameSpace elemQName
134 initDeclNS = (writeInh_namespaces inh){namespaces_prefixes=mempty}
135 go acc@(uNS, dNS) an sav =
138 -- xmlns:prefix="namespace"
139 | qNameSpace an == xmlns_xmlns ->
140 let ns = unescapeAttr av in
142 { namespaces_prefixes =
145 -- Empty namespace means removal
146 -- of the prefix from scope.
147 else (`HM.insert` qNameLocal an))
149 (namespaces_prefixes dNS)
152 | qNameSpace an == xmlns_empty
153 , qNameLocal an == NCName "xmlns" ->
154 (uNS, dNS{namespaces_default = Namespace (unescapeAttr av)})
156 | qNameSpace an == xmlns_empty -> acc
157 -- {namespace}name="value"
158 | otherwise -> (HS.insert (qNameSpace an) uNS, dNS)
159 -- The inherited namespaces,
160 -- including those declared at this element.
163 (namespaces_prefixes declNS)
164 (namespaces_prefixes (writeInh_namespaces inh))
165 -- The namespaces used but not declared nor default,
166 -- with fresh prefixes.
168 HM.delete (namespaces_default declNS) $
169 (`S.evalState` HS.empty) $
171 (\() -> S.gets freshNCName)
172 (HS.toMap usedNS `HM.difference` inhNS)
174 foldMap (\(Namespace ns, qNameLocal) ->
175 textifyAttr (PName (Just "xmlns") qNameLocal) (escapeAttr ns)) $
178 scopeNS = declNS{ namespaces_prefixes = autoNS <> inhNS }
179 write_elemPName = textify $ prefixifyQName scopeNS elemQName
181 foldMap (\(an, av) -> textifyAttr
182 (prefixifyQName scopeNS{namespaces_default=xmlns_empty} an)
184 List.sortOn fst $ -- This makes the rendition more predictible, but this is useless.
186 write_elemChilds = unTreeWrite (write_Trees elemChilds) inh
187 { writeInh_namespaces = scopeNS
188 -- Disable indenting unless hasIndenting.
192 writeInh_indent inh <>
193 textify (writeInh_indent_delta inh)
195 , writeInh_indent_delta =
197 then writeInh_indent_delta inh