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.Applicative (Applicative(..))
9 import Control.Monad (Monad(..))
11 import Data.Default.Class (Default(..))
12 import Data.Eq (Eq(..))
13 import Data.Foldable (Foldable(..))
14 import Data.Function (($), (.))
15 import Data.Functor ((<$>), (<$), ($>))
16 import GHC.Exts (fromList)
17 import Data.List.NonEmpty (NonEmpty(..))
18 import Data.Maybe (Maybe(..), isJust, maybe, maybeToList)
19 import Data.Monoid (Monoid(..))
20 import Data.Ord (Ord(..))
21 import Data.Semigroup (Semigroup(..))
22 import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|), (|>))
24 import Data.String (String, IsString(..))
25 import Data.TreeSeq.Strict (Tree(..), tree0)
26 import Prelude (Num(..), undefined)
27 import System.FilePath as FilePath
28 import qualified Data.Char as Char
29 import qualified Data.List as List
30 import qualified Data.Set as Set
31 import qualified Data.Sequence as Seq
32 import qualified Data.Text.Lazy as TL
33 import qualified Hdoc.TCT.Write.Plain as Plain
34 import qualified Language.Symantic.XML as XML
36 -- import Hdoc.TCT.Debug
37 import Hdoc.TCT as TCT hiding (Parser)
39 import Hdoc.XML (XML, XMLs)
40 import Text.Blaze.DTC (xmlns_dtc)
41 import Text.Blaze.XML ()
45 -- NOTE: 'XmlNode' are still annotated with 'Sourced',
46 -- but nothing is done to preserve any ordering amongst them,
47 -- because 'Node's sometimes need to be reordered
48 -- (eg. about/title may have a title from the section before,
49 -- hence outside of about).
50 writeXML :: Roots -> XMLs
51 writeXML (tn@(Tree (Sourced src (NodeHeader (HeaderSection 1))) _ts) Seq.:<| rs) =
52 element src "head" (xmlifySection def tn) <|
54 writeXML roots = xmlify def roots
56 -- | Generate the content of <section> or <head>.
57 xmlifySection :: Inh -> Root -> XMLs
58 xmlifySection inh tn@(Tree (Sourced src _nt) _ts) =
63 { inh_para = List.repeat elementPara
66 (titles, content) = partitionSection tn
67 (attrs, body) = partitionAttrs content
69 case Seq.viewl titles of
71 title@(unTree -> src_title) :< subtitles ->
72 (xmlAttrs (attrs `defaultAttr` (src_title $> (fromString "id", getAttrId title))) <>) $
75 xmlify inh{inh_para=List.repeat elementTitle} title <>
79 subtitles >>= \subtitle@(unTree -> Sourced src_subtitle _) ->
81 element src_subtitle "alias" $
82 xmlAttrs [src_title $> (fromString "id", getAttrId subtitle)] <>
83 xmlify inh{inh_para=List.repeat elementTitle} subtitle
89 , inh_para :: [Inh -> Root -> XML]
91 instance Default Inh where
94 , inh_para = List.repeat elementPara
98 elementPara :: Inh -> Root -> XML
99 elementPara inh (Tree (Sourced src _) ts) = element src "para" $ xmlify inh ts
101 elementTitle :: Inh -> Root -> XML
102 elementTitle inh (Tree (Sourced src _) ts) =
103 element src "title" $
106 elementTitleWith :: Attrs -> Inh -> Root -> XML
107 elementTitleWith attrs inh (Tree (Sourced src _) ts) =
108 element src "title" $
109 xmlAttrs attrs <> xmlify inh ts
111 elementName :: Inh -> Root -> XML
112 elementName inh (Tree (Sourced src _) ts) =
116 attributeName :: Inh -> Root -> XML
117 attributeName _inh (Tree (Sourced src _) ts) =
118 Tree (Sourced src $ XML.NodeAttr $ XML.qName $ fromString "name") $
119 return $ tree0 $ Sourced src $
120 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
122 attributeId :: Inh -> Root -> XML
123 attributeId _inh (Tree (Sourced src _) ts) =
125 return $ tree0 $ Sourced src $
126 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
130 xmlify :: Inh -> a -> XMLs
131 instance Xmlify Roots where
133 case Seq.viewl roots of
135 selfR@(Tree cellSelf@(Sourced selfS nodeSelf) childrenR) :< fsR ->
137 ----------------------
138 -- NOTE: HeaderColon becomes parent
139 -- of any continuous following-sibling HeaderBar or HeaderGreat
140 NodeHeader (HeaderColon n _wh)
141 | (span, rest) <- spanlHeaderColon fsR
143 xmlify inh (Tree cellSelf (childrenR<>span)) <>
146 spanlHeaderColon :: Roots -> (Roots, Roots)
149 Tree (unSourced -> NodeHeader (HeaderBar m _)) _ -> m == n
150 Tree (unSourced -> NodeHeader (HeaderGreat m _)) _ -> m == n
152 ----------------------
153 -- NOTE: gather HeaderBrackets
154 NodeHeader HeaderBrackets{}
155 | (span,rest) <- spanlBrackets roots
157 (<| xmlify inh rest) $
158 element selfS "references" $
161 spanlBrackets :: Roots -> (Roots, Roots)
164 Tree (unSourced -> NodeHeader HeaderBrackets{}) _ -> True
166 ----------------------
167 -- NOTE: merge adjacent NodeText-s, shouldn't be needed, but just in case.
169 | Tree (cy@(unSourced -> NodeText y)) ys :< fsfsR <- Seq.viewl fsR ->
170 xmlify inh $ Tree (NodeText <$> (x <$ cellSelf) <> (y <$ cy)) (childrenR <> ys) <| fsfsR
171 ----------------------
174 -- NOTE: (Some Text)[Some Ref]
175 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
176 Seq.:<| fsfsR <- fsR ->
177 xmlifyPairBracket inh (Just $ Sourced selfS childrenR)
179 (Sourced bracketS bracketR) fsfsR
180 -- NOTE: (Some Text)@Some At@[Some Ref]
181 | Tree (Sourced atS (NodePair (PairAt False))) atR
182 Seq.:<| Tree (Sourced bracketS (NodePair PairBracket)) bracketR
183 Seq.:<| fsfsR <- fsR ->
184 xmlifyPairBracket inh (Just $ Sourced bracketS mempty)
185 (Just $ Sourced atS $ Plain.writePlain atR)
186 (Sourced bracketS bracketR) fsfsR
187 -- NOTE: (Some Text)@SomeAt[Some Ref]
188 | Tree (Sourced atS (NodeToken (TokenAt False textAt))) _
189 Seq.:<| Tree (Sourced bracketS (NodePair PairBracket)) bracketR
190 Seq.:<| fsfsR <- fsR ->
191 xmlifyPairBracket inh (Just $ Sourced bracketS mempty)
192 (Just $ Sourced atS textAt)
193 (Sourced bracketS bracketR) fsfsR
194 ----------------------
196 NodePair PairBracket ->
197 xmlifyPairBracket inh Nothing
199 (Sourced selfS childrenR) fsR
200 ----------------------
201 -- NOTE: @Some At@[Some Ref]
202 NodePair (PairAt False)
203 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
204 Seq.:<| fsfsR <- fsR ->
205 xmlifyPairBracket inh Nothing
206 (Just $ Sourced selfS $ Plain.writePlain childrenR)
207 (Sourced bracketS bracketR) fsfsR
208 ----------------------
209 -- NOTE: @SomeAt[Some Ref]
210 NodeToken (TokenAt False textAt)
211 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
212 Seq.:<| fsfsR <- fsR ->
213 xmlifyPairBracket inh Nothing
214 (Just $ Sourced selfS textAt)
215 (Sourced bracketS bracketR) fsfsR
216 ----------------------
217 -- NOTE: gather HeaderDash
218 _ | (span, rest) <- spanlItems (==HeaderDash) roots
220 (<| xmlify inh rest) $
222 span >>= xmlify inh{inh_para=List.repeat elementPara}
223 ----------------------
224 -- NOTE: gather HeaderDot
225 | (span,rest) <- spanlItems (\case HeaderDot{} -> True; _ -> False) roots
227 (<| xmlify inh rest) $
229 span >>= xmlify inh{inh_para=List.repeat elementPara}
231 spanlItems :: (Header -> Bool) -> Roots -> (Roots, Roots)
232 spanlItems liHeader =
233 Seq.spanl $ \(unTree -> (unSourced -> nod)) ->
235 NodeHeader (HeaderColon (Just (XML.NCName (TL.unpack -> "li"))) _wh) -> True
236 NodeHeader hdr -> liHeader hdr
237 NodePair (PairElem (XML.NCName (TL.unpack -> "li")) _as) -> True
239 ----------------------
240 NodePara | para:inh_para <- inh_para inh ->
242 xmlify inh{inh_para} fsR
243 ----------------------
244 -- NOTE: context-free Root
246 xmlify inh selfR `XML.union`
250 Maybe (Cell Roots) ->
251 Maybe (Cell TL.Text) ->
254 xmlifyPairBracket inh parenRM atRM
255 (Sourced bracketS bracketR) fsR =
256 case Plain.writePlain bracketR of
257 -- NOTE: [some://url]
258 _ | [unTree -> Sourced linkS (NodeToken (TokenLink link))] <- toList bracketR ->
259 (<| xmlify inh fsR) $
260 element elemS "eref" $
261 xmlAttrs ([Sourced linkS (fromString "to", link)] <> atAttrs) <>
262 xmlify inh (maybe mempty unSourced parenRM)
263 -- NOTE: @Some At@[Some Ref]
264 textPage | TL.any (=='/') textPage || isJust atRM ->
265 let page = TL.pack $ FilePath.normalise $ TL.unpack textPage in
266 (<| xmlify inh fsR) $
267 element elemS "page-ref" $
268 xmlAttrs ([Sourced bracketS (fromString "to", page)] <> atAttrs) <>
269 xmlify inh (maybe mempty unSourced parenRM)
272 (<| xmlify inh fsR) $
273 element elemS "ref" $
274 xmlAttrs [Sourced bracketS (fromString "to", textRef)] <>
277 Just (Sourced parenS parenR)
278 | null parenR -> -- NOTE: preserve empty parens
279 Seq.singleton $ tree0 (Sourced parenS $ XML.NodeText mempty)
280 | otherwise -> xmlify inh parenR
282 -- | Setting a correct Location improve error messages in parsing.
283 Sourced elemS () = sconcat $ fromList $ mconcat
284 [ maybeToList $ (() <$) <$> parenRM
285 , maybeToList $ (() <$) <$> atRM
286 , [Sourced bracketS ()]
288 atAttrs = case atRM of
289 Just (Sourced atS textAt) | not $ TL.null textAt -> [Sourced atS (fromString "at", textAt)]
291 instance FromPad () where
294 unionLocation :: Location -> Location -> Location
295 unionLocation (x:|xs) (y:|_ys) = (unionFileRange x y:|xs)
296 unionFileRange :: FileRange -> FileRange -> FileRange
297 unionFileRange (FileRange xf xb xe) (FileRange _yf yb ye) = FileRange xf xb ye
300 instance Xmlify Root where
301 xmlify inh tn@(Tree (Sourced src@(sn:|ssn) nod) ts) =
303 ----------------------
307 para:_ -> Seq.singleton $ para inh tn
308 ----------------------
314 element src "section" $
317 HeaderColon localName _wh ->
318 let (attrs, body) = partitionAttrs ts in
320 -- NOTE: disable 'inh_figure'
323 element src "about" $
325 xmlify inh'{inh_figure=False} body
326 -- NOTE: handle judgment
327 _ | Just lName <- localName
328 , lName`List.elem`elemsJudgment -> -- FIXME: not a special case so far.
337 "grades" -> List.repeat attributeId
338 "judges" -> List.repeat attributeId
339 _ -> List.repeat elementTitle
341 -- NOTE: in <figure> mode, unreserved elements become <figure>
342 _ | Just lName <- localName
344 && lName`List.notElem`elems || null name ->
346 element src "figure" $
347 -- xmlAttrs (setAttr (Sourced en en ("type", XML.unNCName lName)) attrs) <>
349 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_begin sn}:|ssn)
350 (fromString "type", XML.unNCName lName)) <>
352 [Tree0{}] -> xmlify inh'{inh_para = List.repeat elementPara} body
353 _ -> xmlify inh'{inh_para = elementTitle : List.repeat elementPara} body
354 -- NOTE: reserved elements
361 name = maybe mempty (TL.unpack . XML.unNCName) localName
365 "about" -> List.repeat elementTitle
366 "reference" -> List.repeat elementTitle
367 "serie" -> List.repeat attributeName
368 "author" -> List.repeat attributeName
369 "editor" -> List.repeat attributeName
370 "org" -> List.repeat attributeName
371 "note" -> List.repeat elementPara
375 HeaderBar localName wh ->
378 | inh_figure inh && lName`List.notElem`elems ->
380 Tree (Sourced src $ NodeHeader $ HeaderColon localName wh) ts
383 element src "artwork" $
385 (Seq.singleton $ Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
386 (fromString "type", maybe mempty XML.unNCName localName)) <>
387 xmlify inh{inh_para=[]} ts
389 HeaderGreat localName _wh ->
390 let (attrs,body) = partitionAttrs ts in
392 element src "quote" $
394 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
395 (fromString "type", maybe mempty XML.unNCName localName)) <>
396 xmlify inh{inh_para=List.repeat elementPara} body
398 HeaderEqual localName _wh ->
400 Tree (Sourced src $ XML.NodeAttr (XML.qName localName)) $
401 return $ tree0 $ Sourced src $ XML.NodeText $
403 Plain.text (Plain.setStart ts def{Plain.state_escape = False}) ts
408 let fileRange_end = (fileRange_begin sn)
409 { colNum= colNum (fileRange_begin sn) <> num (TL.length n) } in
411 (Seq.singleton $ Sourced (sn{fileRange_end}:|ssn)
412 (fromString "name", n)) <>
415 HeaderDash -> Seq.singleton $ element src "li" $ xmlify inh ts
418 Seq.singleton $ Tree0 $ Sourced src $
419 XML.NodeComment $ Plain.writePlain ts
421 HeaderBrackets ident ->
422 let (attrs, body) = partitionAttrs ts in
424 element src "reference" $
426 (setAttr (Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
427 (fromString "id",ident)) attrs) |>
428 element src "about" (
429 xmlify inh'{inh_para = List.repeat elementTitle} body
432 inh' = inh{inh_figure = False}
434 HeaderDotSlash _file -> xmlify inh ts
435 ----------------------
438 PairBracket | to <- Plain.writePlain ts
439 , TL.all (\c -> c/='[' && c/=']'
441 && not (Char.isSpace c)) to ->
444 xmlAttrs [Sourced src (fromString "to",to)]
445 PairStar -> Seq.singleton $ element src "b" $ xmlify inh ts
446 PairDash -> Seq.singleton $ element src "del" $ xmlify inh ts
447 PairUnderscore -> Seq.singleton $ element src "u" $ xmlify inh ts
448 PairSlash -> Seq.singleton $ element src "i" $ xmlify inh ts
449 PairBackquote -> Seq.singleton $ element src "code" $ xmlify inh ts
454 (Seq.viewl -> Tree0 (Sourced sl (NodeToken (TokenText l))) :< ls) ->
456 m :> Tree0 (Sourced sr (NodeToken (TokenText r0))) ->
458 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropWhile Char.isSpace l))))
459 Seq.<|(m Seq.|>Tree0 (Sourced sr (NodeToken (TokenText (TL.dropWhileEnd Char.isSpace r0)))))
462 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropAround Char.isSpace l)))) Seq.<| ls
463 (Seq.viewr -> rs :> Tree0 (Sourced sr (NodeToken (TokenText r0)))) ->
465 rs Seq.|> Tree0 (Sourced sr (NodeToken (TokenText (TL.dropAround Char.isSpace r0))))
469 element src (if isBackref then "tag-back" else "tag") $
470 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
471 -- xmlAttrs [Sourced src ("to",to)]
472 -- xmlify inh{inh_para=[]} ts
473 -- xmlAttrs [Sourced src ("to",Plain.writePlain ts)]
476 element src (if isBackref then "at-back" else "at") $
477 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
480 Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc n) $
481 xmlAttrs (Seq.fromList $ (\(_wh, ElemAttr{..}) ->
482 Sourced src (elemAttr_name, elemAttr_value)) <$> attrs) <>
485 Seq.singleton (Tree0 $ Sourced (sn{fileRange_end=bn'}:|ssn) $
486 XML.NodeText (XML.EscapedText $ pure $ XML.EscapedPlain open)) `XML.union`
487 xmlify inh ts `XML.union`
488 Seq.singleton (Tree0 $ Sourced (sn{fileRange_begin=en'}:|ssn) $
489 XML.NodeText $ XML.EscapedText $ pure $ XML.EscapedPlain close)
491 (open, close) = pairBorders pair ts
492 bn' = (fileRange_begin sn){colNum=num $ colInt (fileRange_begin sn) + int (TL.length open)}
493 en' = (fileRange_end sn){colNum=num $ colInt (fileRange_end sn) - int (TL.length close)}
494 ----------------------
495 NodeText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
496 ----------------------
499 TokenEscape c -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.EscapedText $ pure $ XML.escapeChar c
500 TokenText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
501 TokenAt b to -> Seq.singleton $ element src (if b then "at-back" else "at") $
502 xmlAttrs [Sourced src (fromString "to", to)]
503 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
504 TokenTag b to -> Seq.singleton $ element src (if b then "tag-back" else "tag") $
505 xmlAttrs [Sourced src (fromString "to", to)]
506 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
507 TokenLink lnk -> Seq.singleton $ element src "eref" $
508 xmlAttrs [Sourced src (fromString "to", lnk)]
509 ----------------------
512 element src "artwork" $
514 instance Xmlify a => Xmlify (Maybe a) where
517 Just a -> xmlify inh a
519 instance Xmlify (Seq (Cell (XML.QName,TL.Text))) where
520 xmlify _inh = xmlAttrs
525 element :: TCT.Location -> String -> XMLs -> XML
526 element src n = Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc $ fromString n)
528 -- | Reserved elements' name
529 elems :: Set ElemName -- TODO: use a @data@ or maybe a @data family@ instead of 'TL.Text'
530 elems = Set.fromList $ fromString <$>
630 elemsJudgment :: Set ElemName
631 elemsJudgment = Set.fromList $ fromString <$>
642 type Attrs = Seq (Cell (XML.NCName, TL.Text))
644 -- | Convenient alias, forcing the types
645 xmlAttrs :: Attrs -> XMLs
647 (<$>) $ \(Sourced src (n, v)) ->
648 Tree (Sourced src $ XML.NodeAttr (XML.qName n)) $
649 Seq.singleton $ tree0 $
651 XML.NodeText $ XML.escapeText v
653 -- | Extract section titles
654 partitionSection :: Root -> (Roots, Roots)
655 partitionSection (Tree (unSourced -> NodeHeader (HeaderSection lvlPar)) body) =
656 case Seq.viewl body of
658 title@(unTree -> Sourced (FileRange{fileRange_end=et}:|_) NodePara) :< rest ->
659 let (subtitles, content) = spanlSubtitles et rest in
660 (title <| (subtitles >>= subTrees), content)
662 spanlSubtitles ep ts =
664 sub@(unTree -> Sourced (FileRange{..}:|_) (NodeHeader (HeaderSection lvlSub))) :< rs
666 , lineInt fileRange_begin - lineInt ep <= 1 ->
667 let (subs, ts') = spanlSubtitles fileRange_end rs in
671 partitionSection _ = mempty
673 -- | Extract attributes
674 partitionAttrs :: Roots -> (Attrs, Roots)
675 partitionAttrs ts = (attrs, cs)
677 (as,cs) = (`Seq.partition` ts) $ \case
678 Tree (unSourced -> NodeHeader (HeaderEqual (XML.NCName n) _wh)) _cs -> not $ TL.null n
682 Tree (Sourced loc (NodeHeader (HeaderEqual n _wh))) a ->
684 where v = Plain.text (Plain.setStart a def{Plain.state_escape = False}) a
687 getAttrId :: Root -> TL.Text
688 getAttrId = Plain.writePlain . Seq.singleton
690 setAttr :: Cell (XML.NCName, TL.Text) -> Attrs -> Attrs
691 setAttr a@(unSourced -> (k, _v)) as =
692 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of
693 Just idx -> Seq.update idx a as
696 defaultAttr :: Attrs -> Cell (XML.NCName, TL.Text) -> Attrs
697 defaultAttr as a@(unSourced -> (k, _v)) =
698 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of