1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE OverloadedLists #-}
4 {-# LANGUAGE TypeFamilies #-}
5 {-# LANGUAGE ViewPatterns #-}
6 {-# OPTIONS_GHC -fno-warn-orphans #-}
7 module Textphile.TCT.Write.XML where
9 import Control.Applicative (Applicative(..))
10 import Control.Monad (Monad(..))
12 import Data.Default.Class (Default(..))
13 import Data.Eq (Eq(..))
14 import Data.Foldable (Foldable(..))
15 import Data.Function (($), (.))
16 import Data.Functor ((<$>), (<$), ($>))
17 import GHC.Exts (fromList)
18 import Data.List.NonEmpty (NonEmpty(..))
19 import Data.Maybe (Maybe(..), isJust, maybe, maybeToList)
20 import Data.Monoid (Monoid(..))
21 import Data.Ord (Ord(..))
22 import Data.Semigroup (Semigroup(..))
23 import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|), (|>))
25 import Data.String (String, IsString(..))
26 import Data.TreeSeq.Strict (Tree(..), tree0)
27 import Prelude (Num(..), undefined)
28 import System.FilePath as FilePath
29 import qualified Data.Char as Char
30 import qualified Data.List as List
31 import qualified Data.Set as Set
32 import qualified Data.Sequence as Seq
33 import qualified Data.Text.Lazy as TL
34 import qualified Textphile.TCT.Write.Plain as Plain
35 import qualified Symantic.XML as XML
37 -- import Textphile.TCT.Debug
38 import Textphile.TCT as TCT hiding (Parser)
39 import Textphile.TCT.Utils
40 import Textphile.XML (XML, XMLs)
41 import Text.Blaze.DTC (xmlns_dtc)
42 import Text.Blaze.XML ()
46 -- NOTE: 'XmlNode' are still annotated with 'Sourced',
47 -- but nothing is done to preserve any ordering amongst them,
48 -- because 'Node's sometimes need to be reordered
49 -- (eg. about/title may have a title from the section before,
50 -- hence outside of about).
51 writeXML :: Roots -> XMLs
52 writeXML (tn@(Tree (Sourced src (NodeHeader (HeaderSection 1))) _ts) Seq.:<| rs) =
53 element src "head" (xmlifySection def tn) <|
55 writeXML roots = xmlify def roots
57 -- | Generate the content of <section> or <head>.
58 xmlifySection :: Inh -> Root -> XMLs
59 xmlifySection inh tn@(Tree (Sourced src _nt) _ts) =
64 { inh_para = List.repeat elementPara
67 (titles, content) = partitionSection tn
68 (attrs, body) = partitionAttrs content
70 case Seq.viewl titles of
72 title@(unTree -> src_title) :< subtitles ->
73 (xmlAttrs (attrs `defaultAttr` (src_title $> (fromString "id", getAttrId title))) <>) $
76 xmlify inh{inh_para=List.repeat elementTitle} title <>
80 subtitles >>= \subtitle@(unTree -> Sourced src_subtitle _) ->
82 element src_subtitle "alias" $
83 xmlAttrs [src_title $> (fromString "id", getAttrId subtitle)] <>
84 xmlify inh{inh_para=List.repeat elementTitle} subtitle
90 , inh_para :: [Inh -> Root -> XML]
92 instance Default Inh where
95 , inh_para = List.repeat elementPara
99 elementPara :: Inh -> Root -> XML
100 elementPara inh (Tree (Sourced src _) ts) = element src "para" $ xmlify inh ts
102 elementTitle :: Inh -> Root -> XML
103 elementTitle inh (Tree (Sourced src _) ts) =
104 element src "title" $
107 elementTitleWith :: Attrs -> Inh -> Root -> XML
108 elementTitleWith attrs inh (Tree (Sourced src _) ts) =
109 element src "title" $
110 xmlAttrs attrs <> xmlify inh ts
112 elementName :: Inh -> Root -> XML
113 elementName inh (Tree (Sourced src _) ts) =
117 attributeName :: Inh -> Root -> XML
118 attributeName _inh (Tree (Sourced src _) ts) =
119 Tree (Sourced src $ XML.NodeAttr $ XML.qName $ fromString "name") $
120 return $ tree0 $ Sourced src $
121 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
123 attributeId :: Inh -> Root -> XML
124 attributeId _inh (Tree (Sourced src _) ts) =
126 return $ tree0 $ Sourced src $
127 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
131 xmlify :: Inh -> a -> XMLs
132 instance Xmlify Roots where
134 case Seq.viewl roots of
136 selfR@(Tree cellSelf@(Sourced selfS nodeSelf) childrenR) :< fsR ->
138 ----------------------
139 -- NOTE: HeaderColon becomes parent
140 -- of any continuous following-sibling HeaderBar or HeaderGreat
141 NodeHeader (HeaderColon n _wh)
142 | (span, rest) <- spanlHeaderColon fsR
144 xmlify inh (Tree cellSelf (childrenR<>span)) <>
147 spanlHeaderColon :: Roots -> (Roots, Roots)
150 Tree (unSourced -> NodeHeader (HeaderBar m _)) _ -> m == n
151 Tree (unSourced -> NodeHeader (HeaderGreat m _)) _ -> m == n
153 ----------------------
154 -- NOTE: gather HeaderBrackets
155 NodeHeader HeaderBrackets{}
156 | (span,rest) <- spanlBrackets roots
158 (<| xmlify inh rest) $
159 element selfS "references" $
162 spanlBrackets :: Roots -> (Roots, Roots)
165 Tree (unSourced -> NodeHeader HeaderBrackets{}) _ -> True
167 ----------------------
168 -- NOTE: merge adjacent NodeText-s, shouldn't be needed, but just in case.
170 | Tree (cy@(unSourced -> NodeText y)) ys :< fsfsR <- Seq.viewl fsR ->
171 xmlify inh $ Tree (NodeText <$> (x <$ cellSelf) <> (y <$ cy)) (childrenR <> ys) <| fsfsR
172 ----------------------
175 -- NOTE: (Some Text)[Some Ref]
176 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
177 Seq.:<| fsfsR <- fsR ->
178 xmlifyPairBracket inh (Just $ Sourced selfS childrenR)
180 (Sourced bracketS bracketR) fsfsR
181 -- NOTE: (Some Text)@Some At@[Some Ref]
182 | Tree (Sourced atS (NodePair (PairAt False))) atR
183 Seq.:<| Tree (Sourced bracketS (NodePair PairBracket)) bracketR
184 Seq.:<| fsfsR <- fsR ->
185 xmlifyPairBracket inh (Just $ Sourced bracketS mempty)
186 (Just $ Sourced atS $ Plain.writePlain atR)
187 (Sourced bracketS bracketR) fsfsR
188 -- NOTE: (Some Text)@SomeAt[Some Ref]
189 | Tree (Sourced atS (NodeToken (TokenAt False textAt))) _
190 Seq.:<| Tree (Sourced bracketS (NodePair PairBracket)) bracketR
191 Seq.:<| fsfsR <- fsR ->
192 xmlifyPairBracket inh (Just $ Sourced bracketS mempty)
193 (Just $ Sourced atS textAt)
194 (Sourced bracketS bracketR) fsfsR
195 ----------------------
197 NodePair PairBracket ->
198 xmlifyPairBracket inh Nothing
200 (Sourced selfS childrenR) fsR
201 ----------------------
202 -- NOTE: @Some At@[Some Ref]
203 NodePair (PairAt False)
204 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
205 Seq.:<| fsfsR <- fsR ->
206 xmlifyPairBracket inh Nothing
207 (Just $ Sourced selfS $ Plain.writePlain childrenR)
208 (Sourced bracketS bracketR) fsfsR
209 ----------------------
210 -- NOTE: @SomeAt[Some Ref]
211 NodeToken (TokenAt False textAt)
212 | Tree (Sourced bracketS (NodePair PairBracket)) bracketR
213 Seq.:<| fsfsR <- fsR ->
214 xmlifyPairBracket inh Nothing
215 (Just $ Sourced selfS textAt)
216 (Sourced bracketS bracketR) fsfsR
217 ----------------------
218 -- NOTE: gather HeaderDash
219 _ | (span, rest) <- spanlItems (==HeaderDash) roots
221 (<| xmlify inh rest) $
223 span >>= xmlify inh{inh_para=List.repeat elementPara}
224 ----------------------
225 -- NOTE: gather HeaderDot
226 | (span,rest) <- spanlItems (\case HeaderDot{} -> True; _ -> False) roots
228 (<| xmlify inh rest) $
230 span >>= xmlify inh{inh_para=List.repeat elementPara}
232 spanlItems :: (Header -> Bool) -> Roots -> (Roots, Roots)
233 spanlItems liHeader =
234 Seq.spanl $ \(unTree -> (unSourced -> nod)) ->
236 NodeHeader (HeaderColon (Just (XML.NCName (TL.unpack -> "li"))) _wh) -> True
237 NodeHeader hdr -> liHeader hdr
238 NodePair (PairElem (XML.NCName (TL.unpack -> "li")) _as) -> True
240 ----------------------
241 NodePara | para:inh_para <- inh_para inh ->
243 xmlify inh{inh_para} fsR
244 ----------------------
245 -- NOTE: context-free Root
247 xmlify inh selfR `XML.union`
251 Maybe (Cell Roots) ->
252 Maybe (Cell TL.Text) ->
255 xmlifyPairBracket inh parenRM atRM
256 (Sourced bracketS bracketR) fsR =
257 case Plain.writePlain bracketR of
258 -- NOTE: [some://url]
259 _ | [unTree -> Sourced linkS (NodeToken (TokenLink link))] <- toList bracketR ->
260 (<| xmlify inh fsR) $
261 element elemS "eref" $
262 xmlAttrs ([Sourced linkS (fromString "to", link)] <> atAttrs) <>
263 xmlify inh (maybe mempty unSourced parenRM)
264 -- NOTE: @Some At@[Some Ref]
265 textPage | TL.any (=='/') textPage || isJust atRM ->
266 let page = TL.pack $ FilePath.normalise $ TL.unpack textPage in
267 (<| xmlify inh fsR) $
268 element elemS "page-ref" $
269 xmlAttrs ([Sourced bracketS (fromString "to", page)] <> atAttrs) <>
270 xmlify inh (maybe mempty unSourced parenRM)
273 (<| xmlify inh fsR) $
274 element elemS "ref" $
275 xmlAttrs [Sourced bracketS (fromString "to", textRef)] <>
278 Just (Sourced parenS parenR)
279 | null parenR -> -- NOTE: preserve empty parens
280 Seq.singleton $ tree0 (Sourced parenS $ XML.NodeText mempty)
281 | otherwise -> xmlify inh parenR
283 -- | Setting a correct Location improve error messages in parsing.
284 Sourced elemS () = sconcat $ fromList $ mconcat
285 [ maybeToList $ (() <$) <$> parenRM
286 , maybeToList $ (() <$) <$> atRM
287 , [Sourced bracketS ()]
289 atAttrs = case atRM of
290 Just (Sourced atS textAt) | not $ TL.null textAt -> [Sourced atS (fromString "at", textAt)]
292 instance FromPad () where
295 unionLocation :: Location -> Location -> Location
296 unionLocation (x:|xs) (y:|_ys) = (unionFileRange x y:|xs)
297 unionFileRange :: FileRange -> FileRange -> FileRange
298 unionFileRange (FileRange xf xb xe) (FileRange _yf yb ye) = FileRange xf xb ye
301 instance Xmlify Root where
302 xmlify inh tn@(Tree (Sourced src@(sn:|ssn) nod) ts) =
304 ----------------------
308 para:_ -> Seq.singleton $ para inh tn
309 ----------------------
315 element src "section" $
318 HeaderColon localName _wh ->
319 let (attrs, body) = partitionAttrs ts in
321 -- NOTE: disable 'inh_figure'
324 element src "about" $
326 xmlify inh'{inh_figure=False} body
327 -- NOTE: handle judgment
328 _ | Just lName <- localName
329 , lName`List.elem`elemsJudgment -> -- FIXME: not a special case so far.
338 "grades" -> List.repeat attributeId
339 "judges" -> List.repeat attributeId
340 _ -> List.repeat elementTitle
342 -- NOTE: in <figure> mode, unreserved elements become <figure>
343 _ | Just lName <- localName
345 && lName`List.notElem`elems || null name ->
347 element src "figure" $
348 -- xmlAttrs (setAttr (Sourced en en ("type", XML.unNCName lName)) attrs) <>
350 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_begin sn}:|ssn)
351 (fromString "type", XML.unNCName lName)) <>
353 [Tree0{}] -> xmlify inh'{inh_para = List.repeat elementPara} body
354 _ -> xmlify inh'{inh_para = elementTitle : List.repeat elementPara} body
355 -- NOTE: reserved elements
362 name = maybe mempty (TL.unpack . XML.unNCName) localName
366 "about" -> List.repeat elementTitle
367 "reference" -> List.repeat elementTitle
368 "serie" -> List.repeat attributeName
369 "author" -> List.repeat attributeName
370 "editor" -> List.repeat attributeName
371 "org" -> List.repeat attributeName
372 "note" -> List.repeat elementPara
376 HeaderBar localName wh ->
379 | inh_figure inh && lName`List.notElem`elems ->
381 Tree (Sourced src $ NodeHeader $ HeaderColon localName wh) ts
384 element src "artwork" $
386 (Seq.singleton $ Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
387 (fromString "type", maybe mempty XML.unNCName localName)) <>
388 xmlify inh{inh_para=[]} ts
390 HeaderGreat localName _wh ->
391 let (attrs,body) = partitionAttrs ts in
393 element src "quote" $
395 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
396 (fromString "type", maybe mempty XML.unNCName localName)) <>
397 xmlify inh{inh_para=List.repeat elementPara} body
399 HeaderEqual localName _wh ->
401 Tree (Sourced src $ XML.NodeAttr (XML.qName localName)) $
402 return $ tree0 $ Sourced src $ XML.NodeText $
404 Plain.text (Plain.setStart ts def{Plain.state_escape = False}) ts
409 let fileRange_end = (fileRange_begin sn)
410 { colNum= colNum (fileRange_begin sn) <> num (TL.length n) } in
412 (Seq.singleton $ Sourced (sn{fileRange_end}:|ssn)
413 (fromString "name", n)) <>
416 HeaderDash -> Seq.singleton $ element src "li" $ xmlify inh ts
419 Seq.singleton $ Tree0 $ Sourced src $
420 XML.NodeComment $ Plain.writePlain ts
422 HeaderBrackets ident ->
423 let (attrs, body) = partitionAttrs ts in
425 element src "reference" $
427 (setAttr (Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
428 (fromString "id",ident)) attrs) |>
429 element src "about" (
430 xmlify inh'{inh_para = List.repeat elementTitle} body
433 inh' = inh{inh_figure = False}
435 HeaderDotSlash _file -> xmlify inh ts
436 ----------------------
439 PairBracket | to <- Plain.writePlain ts
440 , TL.all (\c -> c/='[' && c/=']'
442 && not (Char.isSpace c)) to ->
445 xmlAttrs [Sourced src (fromString "to",to)]
446 PairStar -> Seq.singleton $ element src "b" $ xmlify inh ts
447 PairDash -> Seq.singleton $ element src "del" $ xmlify inh ts
448 PairUnderscore -> Seq.singleton $ element src "u" $ xmlify inh ts
449 PairSlash -> Seq.singleton $ element src "i" $ xmlify inh ts
450 PairBackquote -> Seq.singleton $ element src "code" $ xmlify inh ts
455 (Seq.viewl -> Tree0 (Sourced sl (NodeToken (TokenText l))) :< ls) ->
457 m :> Tree0 (Sourced sr (NodeToken (TokenText r0))) ->
459 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropWhile Char.isSpace l))))
460 Seq.<|(m Seq.|>Tree0 (Sourced sr (NodeToken (TokenText (TL.dropWhileEnd Char.isSpace r0)))))
463 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropAround Char.isSpace l)))) Seq.<| ls
464 (Seq.viewr -> rs :> Tree0 (Sourced sr (NodeToken (TokenText r0)))) ->
466 rs Seq.|> Tree0 (Sourced sr (NodeToken (TokenText (TL.dropAround Char.isSpace r0))))
470 element src (if isBackref then "tag-back" else "tag") $
471 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
472 -- xmlAttrs [Sourced src ("to",to)]
473 -- xmlify inh{inh_para=[]} ts
474 -- xmlAttrs [Sourced src ("to",Plain.writePlain ts)]
477 element src (if isBackref then "at-back" else "at") $
478 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
481 Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc n) $
482 xmlAttrs (Seq.fromList $ (\(_wh, ElemAttr{..}) ->
483 Sourced src (elemAttr_name, elemAttr_value)) <$> attrs) <>
486 Seq.singleton (Tree0 $ Sourced (sn{fileRange_end=bn'}:|ssn) $
487 XML.NodeText (XML.EscapedText $ pure $ XML.EscapedPlain open)) `XML.union`
488 xmlify inh ts `XML.union`
489 Seq.singleton (Tree0 $ Sourced (sn{fileRange_begin=en'}:|ssn) $
490 XML.NodeText $ XML.EscapedText $ pure $ XML.EscapedPlain close)
492 (open, close) = pairBorders pair ts
493 bn' = (fileRange_begin sn){colNum=num $ colInt (fileRange_begin sn) + int (TL.length open)}
494 en' = (fileRange_end sn){colNum=num $ colInt (fileRange_end sn) - int (TL.length close)}
495 ----------------------
496 NodeText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
497 ----------------------
500 TokenEscape c -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.EscapedText $ pure $ XML.escapeChar c
501 TokenText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
502 TokenAt b to -> Seq.singleton $ element src (if b then "at-back" else "at") $
503 xmlAttrs [Sourced src (fromString "to", to)]
504 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
505 TokenTag b to -> Seq.singleton $ element src (if b then "tag-back" else "tag") $
506 xmlAttrs [Sourced src (fromString "to", to)]
507 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
508 TokenLink lnk -> Seq.singleton $ element src "eref" $
509 xmlAttrs [Sourced src (fromString "to", lnk)]
510 ----------------------
513 element src "artwork" $
515 instance Xmlify a => Xmlify (Maybe a) where
518 Just a -> xmlify inh a
520 instance Xmlify (Seq (Cell (XML.QName,TL.Text))) where
521 xmlify _inh = xmlAttrs
526 element :: TCT.Location -> String -> XMLs -> XML
527 element src n = Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc $ fromString n)
529 -- | Reserved elements' name
530 elems :: Set ElemName -- TODO: use a @data@ or maybe a @data family@ instead of 'TL.Text'
531 elems = Set.fromList $ fromString <$>
631 elemsJudgment :: Set ElemName
632 elemsJudgment = Set.fromList $ fromString <$>
643 type Attrs = Seq (Cell (XML.NCName, TL.Text))
645 -- | Convenient alias, forcing the types
646 xmlAttrs :: Attrs -> XMLs
648 (<$>) $ \(Sourced src (n, v)) ->
649 Tree (Sourced src $ XML.NodeAttr (XML.qName n)) $
650 Seq.singleton $ tree0 $
652 XML.NodeText $ XML.escapeText v
654 -- | Extract section titles
655 partitionSection :: Root -> (Roots, Roots)
656 partitionSection (Tree (unSourced -> NodeHeader (HeaderSection lvlPar)) body) =
657 case Seq.viewl body of
659 title@(unTree -> Sourced (FileRange{fileRange_end=et}:|_) NodePara) :< rest ->
660 let (subtitles, content) = spanlSubtitles et rest in
661 (title <| (subtitles >>= subTrees), content)
663 spanlSubtitles ep ts =
665 sub@(unTree -> Sourced (FileRange{..}:|_) (NodeHeader (HeaderSection lvlSub))) :< rs
667 , lineInt fileRange_begin - lineInt ep <= 1 ->
668 let (subs, ts') = spanlSubtitles fileRange_end rs in
672 partitionSection _ = mempty
674 -- | Extract attributes
675 partitionAttrs :: Roots -> (Attrs, Roots)
676 partitionAttrs ts = (attrs, cs)
678 (as,cs) = (`Seq.partition` ts) $ \case
679 Tree (unSourced -> NodeHeader (HeaderEqual (XML.NCName n) _wh)) _cs -> not $ TL.null n
683 Tree (Sourced loc (NodeHeader (HeaderEqual n _wh))) a ->
685 where v = Plain.text (Plain.setStart a def{Plain.state_escape = False}) a
688 getAttrId :: Root -> TL.Text
689 getAttrId = Plain.writePlain . Seq.singleton
691 setAttr :: Cell (XML.NCName, TL.Text) -> Attrs -> Attrs
692 setAttr a@(unSourced -> (k, _v)) as =
693 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of
694 Just idx -> Seq.update idx a as
697 defaultAttr :: Attrs -> Cell (XML.NCName, TL.Text) -> Attrs
698 defaultAttr as a@(unSourced -> (k, _v)) =
699 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of