1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE OverloadedLists #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE ViewPatterns #-}
6 {-# OPTIONS_GHC -fno-warn-orphans #-}
7 module Hdoc.TCT.Write.XML where
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 Data.List.NonEmpty (NonEmpty(..))
17 import Data.Maybe (Maybe(..))
18 import Data.Monoid (Monoid(..))
19 import Data.Ord (Ord(..))
20 import Data.Semigroup (Semigroup(..))
21 import Data.Sequence (Seq, ViewL(..), ViewR(..), (<|), (|>))
23 import Data.TreeSeq.Strict (Tree(..), tree0)
24 import Data.Tuple (uncurry)
25 import Prelude (Num(..), undefined)
26 import qualified Data.Char as Char
27 import qualified Data.List as List
28 import qualified Data.Sequence as Seq
29 import qualified Data.Text.Lazy as TL
30 import qualified Hdoc.TCT.Write.Plain as Plain
32 -- import Hdoc.TCT.Debug
34 import Hdoc.TCT as TCT hiding (Parser)
35 import Hdoc.XML (XML, XMLs)
36 import qualified Hdoc.XML as XML
37 import Text.Blaze.XML ()
41 -- NOTE: 'XmlNode' are still annotated with 'Cell',
42 -- but nothing is done to preserve any ordering amongst them,
43 -- because 'Node's sometimes need to be reordered
44 -- (eg. about/title may have a title from the section before,
45 -- hence outside of about).
46 writeXML :: Roots -> XMLs
47 writeXML (tn@(Tree (Cell ss (NodeHeader (HeaderSection 1))) _ts) Seq.:<| rs) =
48 element "head" (xmlifySection def tn) <|
51 element :: XML.Name -> XMLs -> XML
52 element n = Tree (Cell ss $ XML.NodeElem n)
53 writeXML roots = xmlify def roots
55 -- | Generate the content of <section> or <head>.
56 xmlifySection :: Inh -> Root -> XMLs
57 xmlifySection inh tn@(Tree (Cell ss _nt) _ts) =
61 element :: XML.Name -> XMLs -> XML
62 element n = Tree (Cell ss $ XML.NodeElem n)
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 -> ct) :< subtitles ->
73 (xmlAttrs (attrs `defaultAttr` (ct $> ("id",getAttrId title))) <>) $
76 xmlify inh{inh_para=List.repeat elementTitle} title <>
80 subtitles >>= \subtitle@(unTree -> cs) ->
82 Tree (cs $> XML.NodeElem "alias") $
83 xmlAttrs [ct $> ("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 c ts) = Tree (XML.NodeElem "para" <$ c) $ xmlify inh ts
102 elementTitle :: Inh -> Root -> XML
103 elementTitle inh (Tree c ts) = Tree (XML.NodeElem "title" <$ c) $ xmlify inh ts
104 elementTitleWith :: Attrs -> Inh -> Root -> XML
105 elementTitleWith attrs inh (Tree c ts) = Tree (XML.NodeElem "title" <$ c) $ xmlAttrs attrs <> xmlify inh ts
107 elementName :: Inh -> Root -> XML
108 elementName inh (Tree c ts) = Tree (XML.NodeElem "name" <$ c) $ xmlify inh ts
110 attributeName :: Inh -> Root -> XML
111 attributeName _inh (Tree c ts) = tree0 (XML.NodeAttr "name" (Plain.writePlain ts) <$ c)
113 attributeId :: Inh -> Root -> XML
114 attributeId _inh (Tree c ts) = tree0 (XML.NodeAttr "id" (Plain.writePlain ts) <$ c)
118 xmlify :: Inh -> a -> XMLs
119 instance Xmlify Roots where
121 case Seq.viewl roots of
123 r@(Tree cr@(Cell _sr nr) ts) :< rs ->
125 ----------------------
126 -- NOTE: HeaderColon becomes parent
127 -- of any continuous following-sibling HeaderBar or HeaderGreat
128 NodeHeader (HeaderColon n _wh)
129 | (span, rest) <- spanlHeaderColon rs
131 xmlify inh (Tree cr (ts<>span)) <>
134 spanlHeaderColon :: Roots -> (Roots, Roots)
137 Tree (unCell -> NodeHeader (HeaderBar m _)) _ -> m == n
138 Tree (unCell -> NodeHeader (HeaderGreat m _)) _ -> m == n
140 ----------------------
141 -- NOTE: gather HeaderBrackets
142 NodeHeader HeaderBrackets{}
143 | (span,rest) <- spanlBrackets roots
145 (<| xmlify inh rest) $
146 element "references" $
149 spanlBrackets :: Roots -> (Roots, Roots)
152 Tree (unCell -> NodeHeader HeaderBrackets{}) _ -> True
154 ----------------------
155 -- NOTE: merge adjacent NodeText-s, shouldn't be needed, but just in case.
157 | Tree (cy@(unCell -> NodeText y)) ys :< rs' <- Seq.viewl rs ->
158 xmlify inh $ Tree (NodeText <$> (x <$ cr) <> (y <$ cy)) (ts <> ys) <| rs'
159 ----------------------
160 -- NOTE: detect (some text)[http://some.url] or (some text)[SomeRef]
162 | Tree (Cell sb (NodePair PairBracket)) bracket :< rs' <- Seq.viewl rs ->
163 (<| xmlify inh rs') $
165 (toList -> [unTree -> Cell sl (NodeToken (TokenLink lnk))]) ->
167 xmlAttrs [Cell sl ("to",lnk)] <>
171 xmlAttrs [Cell sb ("to",Plain.writePlain bracket)] <>
172 if null ts -- NOTE: preserve empty parens
173 then Seq.singleton $ tree0 (XML.NodeText "" <$ cr)
175 ----------------------
176 -- NOTE: gather HeaderDash
177 _ | (span, rest) <- spanlItems (==HeaderDash) roots
179 (<| xmlify inh rest) $
181 span >>= xmlify inh{inh_para=List.repeat elementPara}
182 ----------------------
183 -- NOTE: gather HeaderDot
184 | (span,rest) <- spanlItems (\case HeaderDot{} -> True; _ -> False) roots
186 (<| xmlify inh rest) $
188 span >>= xmlify inh{inh_para=List.repeat elementPara}
190 spanlItems :: (Header -> Bool) -> Roots -> (Roots, Roots)
191 spanlItems liHeader =
192 Seq.spanl $ \(unTree -> (unCell -> nod)) ->
194 NodeHeader (HeaderColon "li" _wh) -> True
195 NodeHeader hdr -> liHeader hdr
196 NodePair (PairElem "li" _as) -> True
198 ----------------------
199 NodePara | para:inh_para <- inh_para inh ->
201 xmlify inh{inh_para} rs
202 ----------------------
203 -- NOTE: context-free Root
205 xmlify inh r `unionXml`
208 element :: XML.Name -> XMLs -> XML
209 element n = Tree (XML.NodeElem n <$ cr)
210 instance Xmlify Root where
211 xmlify inh tn@(Tree (Cell ss@(sn:|ssn) nod) ts) =
213 ----------------------
217 para:_ -> Seq.singleton $ para inh tn
218 ----------------------
228 let (attrs,body) = partitionAttrs ts in
230 -- NOTE: disable 'inh_figure'
235 xmlify inh'{inh_figure=False} body
236 -- NOTE: handle judgment
237 _ | n`List.elem`elemsJudgment -> -- FIXME: not a special case so far.
239 element (XML.localName n) $
246 "grades" -> List.repeat attributeId
247 "judges" -> List.repeat attributeId
248 _ -> List.repeat elementTitle
250 -- NOTE: in <figure> mode, unreserved elements become <figure>
251 _ | inh_figure inh && n`List.notElem`elems || TL.null n ->
254 -- xmlAttrs (setAttr (Cell en en ("type",n)) attrs) <>
255 xmlAttrs (attrs `defaultAttr` Cell (sn{span_end=span_begin sn}:|ssn) ("type", n)) <>
257 [Tree0{}] -> xmlify inh'{inh_para = List.repeat elementPara} body
258 _ -> xmlify inh'{inh_para = elementTitle : List.repeat elementPara} body
259 -- NOTE: reserved elements
262 element (XML.localName n) $
269 "about" -> List.repeat elementTitle
270 "reference" -> List.repeat elementTitle
271 "serie" -> List.repeat attributeName
272 "author" -> List.repeat attributeName
273 "editor" -> List.repeat attributeName
274 "org" -> List.repeat attributeName
275 "note" -> List.repeat elementPara
280 if inh_figure inh && n`List.notElem`elems || TL.null n
284 xmlAttrs (Seq.singleton $ Cell (sn{span_end=span_end sn}:|ssn) ("type", n)) <>
285 xmlify inh{inh_para=[]} ts
288 Tree (cell $ NodeHeader $ HeaderColon n wh) ts
292 let (attrs,body) = partitionAttrs ts in
294 xmlAttrs (attrs `defaultAttr` Cell (sn{span_end=span_end sn}:|ssn) ("type", n)) <>
295 xmlify inh{inh_para=List.repeat elementPara} body
299 Tree0 $ cell $ XML.NodeAttr (XML.localName n) $
300 Plain.text (Plain.setStart ts def{Plain.state_escape = False}) ts
305 let span_end = (span_begin sn){pos_column=pos_column (span_begin sn) + int (TL.length n)} in
306 xmlAttrs (Seq.singleton $ Cell (sn{span_end}:|ssn) ("name", n)) <>
309 HeaderDash -> Seq.singleton $ element "li" $ xmlify inh ts
312 Seq.singleton $ Tree0 $ cell $
313 XML.NodeComment $ Plain.writePlain ts
315 HeaderBrackets ident ->
316 let (attrs,body) = partitionAttrs ts in
318 element "reference" $
319 xmlAttrs (setAttr (Cell (sn{span_end=span_end sn}:|ssn) ("id",ident)) attrs) |>
321 xmlify inh'{inh_para = List.repeat elementTitle} body
324 inh' = inh{inh_figure = False}
326 HeaderDotSlash _file -> xmlify inh ts
327 ----------------------
330 PairBracket | to <- Plain.writePlain ts
331 , TL.all (\c -> c/='[' && c/=']' && Char.isPrint c && not (Char.isSpace c)) to ->
334 xmlAttrs [cell ("to",to)]
335 PairStar -> Seq.singleton $ element "b" $ xmlify inh ts
336 PairDash -> Seq.singleton $ element "del" $ xmlify inh ts
337 PairUnderscore -> Seq.singleton $ element "u" $ xmlify inh ts
338 PairSlash -> Seq.singleton $ element "i" $ xmlify inh ts
339 PairBackquote -> Seq.singleton $ element "code" $ xmlify inh ts
344 (Seq.viewl -> Tree0 (Cell sl (NodeToken (TokenText l))) :< ls) ->
346 m :> Tree0 (Cell sr (NodeToken (TokenText r))) ->
348 Tree0 (Cell sl (NodeToken (TokenText (TL.dropWhile Char.isSpace l))))
349 Seq.<|(m Seq.|>Tree0 (Cell sr (NodeToken (TokenText (TL.dropWhileEnd Char.isSpace r)))))
352 Tree0 (Cell sl (NodeToken (TokenText (TL.dropAround Char.isSpace l)))) Seq.<| ls
353 (Seq.viewr -> rs :> Tree0 (Cell sr (NodeToken (TokenText r)))) ->
355 rs Seq.|> Tree0 (Cell sr (NodeToken (TokenText (TL.dropAround Char.isSpace r))))
359 element (if isBackref then "tag-back" else "tag") $
360 xmlAttrs [cell ("to",Plain.writePlain ts)]
361 -- xmlAttrs [cell ("to",to)]
362 -- xmlify inh{inh_para=[]} ts
363 -- xmlAttrs [cell ("to",Plain.writePlain ts)]
366 element (if isBackref then "at-back" else "at") $
367 xmlAttrs [cell ("to",Plain.writePlain ts)]
368 PairElem name attrs ->
370 element (XML.localName name) $
371 xmlAttrs (Seq.fromList $ (\(_wh,ElemAttr{..}) ->
372 cell (XML.localName elemAttr_name,elemAttr_value)) <$> attrs) <>
375 Seq.singleton (Tree0 $ Cell (sn{span_end=bn'}:|ssn) $ XML.NodeText open) `unionXml`
376 xmlify inh ts `unionXml`
377 Seq.singleton (Tree0 $ Cell (sn{span_begin=en'}:|ssn) $ XML.NodeText close)
379 (open, close) = pairBorders pair ts
380 bn' = (span_begin sn){pos_column=pos_column (span_begin sn) + int (TL.length open)}
381 en' = (span_end sn){pos_column=pos_column (span_end sn) - int (TL.length close)}
382 ----------------------
383 NodeText t -> Seq.singleton $ Tree0 $ cell $ XML.NodeText t
384 ----------------------
387 TokenEscape c -> Seq.singleton $ Tree0 $ cell $ XML.NodeText $ TL.singleton c
388 TokenText t -> Seq.singleton $ Tree0 $ cell $ XML.NodeText t
389 TokenAt b to -> Seq.singleton $ element (if b then "at-back" else "at") $
390 xmlAttrs [cell ("to",to)] -- Seq.singleton $ Tree0 $ cell $ XML.NodeText to
391 TokenTag b to -> Seq.singleton $ element (if b then "tag-back" else "tag") $
392 xmlAttrs [cell ("to",to)] -- Seq.singleton $ Tree0 $ cell $ XML.NodeText to
393 TokenLink lnk -> Seq.singleton $ element "eref" $ xmlAttrs [cell ("to",lnk)]
394 ----------------------
402 element :: XML.Name -> XMLs -> XML
403 element n = Tree (cell $ XML.NodeElem n)
405 instance Xmlify (Seq (Cell (XML.Name,TL.Text))) where
406 xmlify _inh = xmlAttrs
411 -- | Reserved elements' name
412 elems :: Set TL.Text -- TODO: use a @data@ or maybe a @data family@ instead of 'TL.Text'
512 elemsJudgment :: Set TL.Text
525 type Attrs = Seq (Cell (XML.Name, TL.Text))
527 -- | Convenient alias, forcing the types
528 xmlAttrs :: Attrs -> XMLs
529 xmlAttrs = (Tree0 . (uncurry XML.NodeAttr <$>) <$>)
531 -- | Extract section titles
532 partitionSection :: Root -> (Roots, Roots)
533 partitionSection (Tree (unCell -> NodeHeader (HeaderSection lvlPar)) body) =
534 case Seq.viewl body of
536 title@(unTree -> Cell (Span{span_end=et}:|_) NodePara) :< rest ->
537 let (subtitles, content) = spanlSubtitles et rest in
538 (title <| (subtitles >>= subTrees), content)
540 spanlSubtitles ep ts =
542 sub@(unTree -> Cell (Span{..}:|_) (NodeHeader (HeaderSection lvlSub))) :< rs
544 , pos_line span_begin - pos_line ep <= 1 ->
545 let (subs, ts') = spanlSubtitles span_end rs in
549 partitionSection _ = mempty
551 -- | Extract attributes
552 partitionAttrs :: Roots -> (Seq (Cell (XML.Name, TL.Text)), Roots)
553 partitionAttrs ts = (attrs,cs)
555 (as,cs) = (`Seq.partition` ts) $ \case
556 Tree (unCell -> NodeHeader (HeaderEqual n _wh)) _cs -> not $ TL.null n
560 Tree (Cell loc (NodeHeader (HeaderEqual n _wh))) a ->
561 Cell loc (XML.localName n, v)
563 v = Plain.text (Plain.setStart a def{Plain.state_escape = False}) a
566 getAttrId :: Root -> TL.Text
567 getAttrId = Plain.writePlain . Seq.singleton
570 Cell (XML.Name, TL.Text) ->
571 Seq (Cell (XML.Name, TL.Text)) ->
572 Seq (Cell (XML.Name, TL.Text))
573 setAttr a@(unCell -> (k, _v)) as =
574 case Seq.findIndexL (\(unCell -> (n,_v)) -> n == k) as of
575 Just idx -> Seq.update idx a as
579 Seq (Cell (XML.Name, TL.Text)) ->
580 Cell (XML.Name, TL.Text) ->
581 Seq (Cell (XML.Name, TL.Text))
582 defaultAttr as a@(unCell -> (k, _v)) =
583 case Seq.findIndexL (\(unCell -> (n,_v)) -> n == k) as of
589 -- | Unify two 'XMLs', merging border 'XML.NodeText's if any.
590 unionXml :: XMLs -> XMLs -> XMLs
592 case (Seq.viewr x, Seq.viewl y) of
593 (xs :> x0, y0 :< ys) ->
595 ( Tree0 (Cell ssx@(Span {span_file=fx}:|_sx) (XML.NodeText tx))
596 , Tree0 (Cell ssy@(Span {span_file=fy}:|_sy) (XML.NodeText ty)) ) | fx == fy ->
598 Seq.singleton (Tree0 $ (XML.NodeText <$>) $ Cell ssx tx <> Cell ssy ty) `unionXml`
604 unionsXml :: Foldable f => f XMLs -> XMLs
605 unionsXml = foldl' unionXml mempty