1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE OverloadedLists #-}
4 {-# LANGUAGE ViewPatterns #-}
5 {-# OPTIONS_GHC -fno-warn-orphans #-}
6 module Hdoc.TCT.Write.XML where
8 import Control.Monad (Monad(..))
10 import Data.Default.Class (Default(..))
11 import Data.Eq (Eq(..))
12 import Data.Foldable (Foldable(..))
13 import Data.Function (($), (.))
14 import Data.Functor ((<$>), (<$), ($>))
15 import Data.List.NonEmpty (NonEmpty(..))
16 import Data.Maybe (Maybe(..), maybe)
17 import Data.Monoid (Monoid(..))
18 import Data.Ord (Ord(..))
19 import Data.Semigroup (Semigroup(..))
20 import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|), (|>))
22 import Data.String (String, IsString(..))
23 import Data.TreeSeq.Strict (Tree(..), tree0)
24 import Language.Symantic.XML (XML, XMLs)
25 import Prelude (Num(..), undefined)
26 import qualified Data.Char as Char
27 import qualified Data.List as List
28 import qualified Data.Set as Set
29 import qualified Data.Sequence as Seq
30 import qualified Data.Text.Lazy as TL
31 import qualified Hdoc.TCT.Write.Plain as Plain
32 import qualified Language.Symantic.XML as XML
34 -- import Hdoc.TCT.Debug
35 import Hdoc.TCT as TCT hiding (Parser)
37 import Text.Blaze.DTC (xmlns_dtc)
38 import Text.Blaze.XML ()
42 -- NOTE: 'XmlNode' are still annotated with 'Sourced',
43 -- but nothing is done to preserve any ordering amongst them,
44 -- because 'Node's sometimes need to be reordered
45 -- (eg. about/title may have a title from the section before,
46 -- hence outside of about).
47 writeXML :: Roots -> XMLs
48 writeXML (tn@(Tree (Sourced src (NodeHeader (HeaderSection 1))) _ts) Seq.:<| rs) =
49 element src "head" (xmlifySection def tn) <|
51 writeXML roots = xmlify def roots
53 -- | Generate the content of <section> or <head>.
54 xmlifySection :: Inh -> Root -> XMLs
55 xmlifySection inh tn@(Tree (Sourced src _nt) _ts) =
60 { inh_para = List.repeat elementPara
63 (titles, content) = partitionSection tn
64 (attrs, body) = partitionAttrs content
66 case Seq.viewl titles of
68 title@(unTree -> src_title) :< subtitles ->
69 (xmlAttrs (attrs `defaultAttr` (src_title $> (fromString "id", getAttrId title))) <>) $
72 xmlify inh{inh_para=List.repeat elementTitle} title <>
76 subtitles >>= \subtitle@(unTree -> Sourced src_subtitle _) ->
78 element src_subtitle "alias" $
79 xmlAttrs [src_title $> (fromString "id", getAttrId subtitle)] <>
80 xmlify inh{inh_para=List.repeat elementTitle} subtitle
86 , inh_para :: [Inh -> Root -> XML]
88 instance Default Inh where
91 , inh_para = List.repeat elementPara
95 elementPara :: Inh -> Root -> XML
96 elementPara inh (Tree (Sourced src _) ts) = element src "para" $ xmlify inh ts
98 elementTitle :: Inh -> Root -> XML
99 elementTitle inh (Tree (Sourced src _) ts) =
100 element src "title" $
103 elementTitleWith :: Attrs -> Inh -> Root -> XML
104 elementTitleWith attrs inh (Tree (Sourced src _) ts) =
105 element src "title" $
106 xmlAttrs attrs <> xmlify inh ts
108 elementName :: Inh -> Root -> XML
109 elementName inh (Tree (Sourced src _) ts) =
113 attributeName :: Inh -> Root -> XML
114 attributeName _inh (Tree (Sourced src _) ts) =
115 Tree (Sourced src $ XML.NodeAttr $ XML.qName $ fromString "name") $
116 return $ tree0 $ Sourced src $
117 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
119 attributeId :: Inh -> Root -> XML
120 attributeId _inh (Tree (Sourced src _) ts) =
122 return $ tree0 $ Sourced src $
123 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
127 xmlify :: Inh -> a -> XMLs
128 instance Xmlify Roots where
130 case Seq.viewl roots of
132 r@(Tree cr@(Sourced src nr) ts) :< rs ->
134 ----------------------
135 -- NOTE: HeaderColon becomes parent
136 -- of any continuous following-sibling HeaderBar or HeaderGreat
137 NodeHeader (HeaderColon n _wh)
138 | (span, rest) <- spanlHeaderColon rs
140 xmlify inh (Tree cr (ts<>span)) <>
143 spanlHeaderColon :: Roots -> (Roots, Roots)
146 Tree (unSourced -> NodeHeader (HeaderBar m _)) _ -> m == n
147 Tree (unSourced -> NodeHeader (HeaderGreat m _)) _ -> m == n
149 ----------------------
150 -- NOTE: gather HeaderBrackets
151 NodeHeader HeaderBrackets{}
152 | (span,rest) <- spanlBrackets roots
154 (<| xmlify inh rest) $
155 element src "references" $
158 spanlBrackets :: Roots -> (Roots, Roots)
161 Tree (unSourced -> NodeHeader HeaderBrackets{}) _ -> True
163 ----------------------
164 -- NOTE: merge adjacent NodeText-s, shouldn't be needed, but just in case.
166 | Tree (cy@(unSourced -> NodeText y)) ys :< rs' <- Seq.viewl rs ->
167 xmlify inh $ Tree (NodeText <$> (x <$ cr) <> (y <$ cy)) (ts <> ys) <| rs'
168 ----------------------
169 -- NOTE: detect (some text)[http://some.url] or (some text)[SomeRef]
171 | Tree (Sourced sb (NodePair PairBracket)) bracket :< rs' <- Seq.viewl rs ->
172 (<| xmlify inh rs') $
174 (toList -> [unTree -> Sourced sl (NodeToken (TokenLink lnk))]) ->
176 xmlAttrs [Sourced sl (fromString "to", lnk)] <>
180 xmlAttrs [Sourced sb (fromString "to", Plain.writePlain bracket)] <>
181 if null ts -- NOTE: preserve empty parens
182 then Seq.singleton $ tree0 (XML.NodeText mempty <$ cr)
184 ----------------------
185 -- NOTE: gather HeaderDash
186 _ | (span, rest) <- spanlItems (==HeaderDash) roots
188 (<| xmlify inh rest) $
190 span >>= xmlify inh{inh_para=List.repeat elementPara}
191 ----------------------
192 -- NOTE: gather HeaderDot
193 | (span,rest) <- spanlItems (\case HeaderDot{} -> True; _ -> False) roots
195 (<| xmlify inh rest) $
197 span >>= xmlify inh{inh_para=List.repeat elementPara}
199 spanlItems :: (Header -> Bool) -> Roots -> (Roots, Roots)
200 spanlItems liHeader =
201 Seq.spanl $ \(unTree -> (unSourced -> nod)) ->
203 NodeHeader (HeaderColon (Just (XML.NCName (TL.unpack -> "li"))) _wh) -> True
204 NodeHeader hdr -> liHeader hdr
205 NodePair (PairElem (XML.NCName (TL.unpack -> "li")) _as) -> True
207 ----------------------
208 NodePara | para:inh_para <- inh_para inh ->
210 xmlify inh{inh_para} rs
211 ----------------------
212 -- NOTE: context-free Root
214 xmlify inh r `XML.union`
216 instance Xmlify Root where
217 xmlify inh tn@(Tree (Sourced src@(sn:|ssn) nod) ts) =
219 ----------------------
223 para:_ -> Seq.singleton $ para inh tn
224 ----------------------
230 element src "section" $
233 HeaderColon localName _wh ->
234 let (attrs, body) = partitionAttrs ts in
236 -- NOTE: disable 'inh_figure'
239 element src "about" $
241 xmlify inh'{inh_figure=False} body
242 -- NOTE: handle judgment
243 _ | Just lName <- localName
244 , lName`List.elem`elemsJudgment -> -- FIXME: not a special case so far.
253 "grades" -> List.repeat attributeId
254 "judges" -> List.repeat attributeId
255 _ -> List.repeat elementTitle
257 -- NOTE: in <figure> mode, unreserved elements become <figure>
258 _ | Just lName <- localName
260 && lName`List.notElem`elems || null name ->
262 element src "figure" $
263 -- xmlAttrs (setAttr (Sourced en en ("type", XML.unNCName lName)) attrs) <>
265 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_begin sn}:|ssn)
266 (fromString "type", XML.unNCName lName)) <>
268 [Tree0{}] -> xmlify inh'{inh_para = List.repeat elementPara} body
269 _ -> xmlify inh'{inh_para = elementTitle : List.repeat elementPara} body
270 -- NOTE: reserved elements
277 name = maybe mempty (TL.unpack . XML.unNCName) localName
281 "about" -> List.repeat elementTitle
282 "reference" -> List.repeat elementTitle
283 "serie" -> List.repeat attributeName
284 "author" -> List.repeat attributeName
285 "editor" -> List.repeat attributeName
286 "org" -> List.repeat attributeName
287 "note" -> List.repeat elementPara
291 HeaderBar localName wh ->
294 | inh_figure inh && lName`List.notElem`elems ->
296 Tree (Sourced src $ NodeHeader $ HeaderColon localName wh) ts
299 element src "artwork" $
301 (Seq.singleton $ Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
302 (fromString "type", maybe mempty XML.unNCName localName)) <>
303 xmlify inh{inh_para=[]} ts
305 HeaderGreat localName _wh ->
306 let (attrs,body) = partitionAttrs ts in
308 element src "quote" $
310 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
311 (fromString "type", maybe mempty XML.unNCName localName)) <>
312 xmlify inh{inh_para=List.repeat elementPara} body
314 HeaderEqual localName _wh ->
316 Tree (Sourced src $ XML.NodeAttr (XML.qName localName)) $
317 return $ tree0 $ Sourced src $ XML.NodeText $
319 Plain.text (Plain.setStart ts def{Plain.state_escape = False}) ts
327 filePos_column (fileRange_begin sn) +
328 int (TL.length n) } in
330 (Seq.singleton $ Sourced (sn{fileRange_end}:|ssn)
331 (fromString "name", n)) <>
334 HeaderDash -> Seq.singleton $ element src "li" $ xmlify inh ts
337 Seq.singleton $ Tree0 $ Sourced src $
338 XML.NodeComment $ Plain.writePlain ts
340 HeaderBrackets ident ->
341 let (attrs, body) = partitionAttrs ts in
343 element src "reference" $
345 (setAttr (Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
346 (fromString "id",ident)) attrs) |>
347 element src "about" (
348 xmlify inh'{inh_para = List.repeat elementTitle} body
351 inh' = inh{inh_figure = False}
353 HeaderDotSlash _file -> xmlify inh ts
354 ----------------------
357 PairBracket | to <- Plain.writePlain ts
358 , TL.all (\c -> c/='[' && c/=']' && Char.isPrint c && not (Char.isSpace c)) to ->
361 xmlAttrs [Sourced src (fromString "to",to)]
362 PairStar -> Seq.singleton $ element src "b" $ xmlify inh ts
363 PairDash -> Seq.singleton $ element src "del" $ xmlify inh ts
364 PairUnderscore -> Seq.singleton $ element src "u" $ xmlify inh ts
365 PairSlash -> Seq.singleton $ element src "i" $ xmlify inh ts
366 PairBackquote -> Seq.singleton $ element src "code" $ xmlify inh ts
371 (Seq.viewl -> Tree0 (Sourced sl (NodeToken (TokenText l))) :< ls) ->
373 m :> Tree0 (Sourced sr (NodeToken (TokenText r))) ->
375 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropWhile Char.isSpace l))))
376 Seq.<|(m Seq.|>Tree0 (Sourced sr (NodeToken (TokenText (TL.dropWhileEnd Char.isSpace r)))))
379 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropAround Char.isSpace l)))) Seq.<| ls
380 (Seq.viewr -> rs :> Tree0 (Sourced sr (NodeToken (TokenText r)))) ->
382 rs Seq.|> Tree0 (Sourced sr (NodeToken (TokenText (TL.dropAround Char.isSpace r))))
386 element src (if isBackref then "tag-back" else "tag") $
387 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
388 -- xmlAttrs [Sourced src ("to",to)]
389 -- xmlify inh{inh_para=[]} ts
390 -- xmlAttrs [Sourced src ("to",Plain.writePlain ts)]
393 element src (if isBackref then "at-back" else "at") $
394 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
397 Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc n) $
398 xmlAttrs (Seq.fromList $ (\(_wh, ElemAttr{..}) ->
399 Sourced src (elemAttr_name, elemAttr_value)) <$> attrs) <>
402 Seq.singleton (Tree0 $ Sourced (sn{fileRange_end=bn'}:|ssn) $
403 XML.NodeText [XML.EscapedPlain open]) `XML.union`
404 xmlify inh ts `XML.union`
405 Seq.singleton (Tree0 $ Sourced (sn{fileRange_begin=en'}:|ssn) $
406 XML.NodeText [XML.EscapedPlain close])
408 (open, close) = pairBorders pair ts
409 bn' = (fileRange_begin sn){filePos_column=filePos_column (fileRange_begin sn) + int (TL.length open)}
410 en' = (fileRange_end sn){filePos_column=filePos_column (fileRange_end sn) - int (TL.length close)}
411 ----------------------
412 NodeText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
413 ----------------------
416 TokenEscape c -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText [XML.escapeChar c]
417 TokenText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
418 TokenAt b to -> Seq.singleton $ element src (if b then "at-back" else "at") $
419 xmlAttrs [Sourced src (fromString "to", to)]
420 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
421 TokenTag b to -> Seq.singleton $ element src (if b then "tag-back" else "tag") $
422 xmlAttrs [Sourced src (fromString "to", to)]
423 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
424 TokenLink lnk -> Seq.singleton $ element src "eref" $
425 xmlAttrs [Sourced src (fromString "to", lnk)]
426 ----------------------
429 element src "artwork" $
432 instance Xmlify (Seq (Cell (XML.QName,TL.Text))) where
433 xmlify _inh = xmlAttrs
438 element :: XML.FileSource -> String -> XMLs -> XML
439 element src n = Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc $ fromString n)
441 -- | Reserved elements' name
442 elems :: Set ElemName -- TODO: use a @data@ or maybe a @data family@ instead of 'TL.Text'
443 elems = Set.fromList $ fromString <$>
542 elemsJudgment :: Set ElemName
543 elemsJudgment = Set.fromList $ fromString <$>
554 type Attrs = Seq (Cell (XML.NCName, TL.Text))
556 -- | Convenient alias, forcing the types
557 xmlAttrs :: Attrs -> XMLs
559 (<$>) $ \(Sourced src (n, v)) ->
560 Tree (Sourced src $ XML.NodeAttr (XML.qName n)) $
561 Seq.singleton $ tree0 $
563 XML.NodeText $ XML.escapeText v
565 -- | Extract section titles
566 partitionSection :: Root -> (Roots, Roots)
567 partitionSection (Tree (unSourced -> NodeHeader (HeaderSection lvlPar)) body) =
568 case Seq.viewl body of
570 title@(unTree -> Sourced (FileRange{fileRange_end=et}:|_) NodePara) :< rest ->
571 let (subtitles, content) = spanlSubtitles et rest in
572 (title <| (subtitles >>= subTrees), content)
574 spanlSubtitles ep ts =
576 sub@(unTree -> Sourced (FileRange{..}:|_) (NodeHeader (HeaderSection lvlSub))) :< rs
578 , filePos_line fileRange_begin - filePos_line ep <= 1 ->
579 let (subs, ts') = spanlSubtitles fileRange_end rs in
583 partitionSection _ = mempty
585 -- | Extract attributes
586 partitionAttrs :: Roots -> (Attrs, Roots)
587 partitionAttrs ts = (attrs, cs)
589 (as,cs) = (`Seq.partition` ts) $ \case
590 Tree (unSourced -> NodeHeader (HeaderEqual (XML.NCName n) _wh)) _cs -> not $ TL.null n
594 Tree (Sourced loc (NodeHeader (HeaderEqual n _wh))) a ->
596 where v = Plain.text (Plain.setStart a def{Plain.state_escape = False}) a
599 getAttrId :: Root -> TL.Text
600 getAttrId = Plain.writePlain . Seq.singleton
602 setAttr :: Cell (XML.NCName, TL.Text) -> Attrs -> Attrs
603 setAttr a@(unSourced -> (k, _v)) as =
604 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of
605 Just idx -> Seq.update idx a as
608 defaultAttr :: Attrs -> Cell (XML.NCName, TL.Text) -> Attrs
609 defaultAttr as a@(unSourced -> (k, _v)) =
610 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of