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 Data.List.NonEmpty (NonEmpty(..))
17 import Data.Maybe (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.String (String, IsString(..))
24 import Data.TreeSeq.Strict (Tree(..), tree0)
25 import Language.Symantic.XML (XML, XMLs)
26 import Prelude (Num(..), undefined)
27 import qualified Data.Char as Char
28 import qualified Data.List as List
29 import qualified Data.Set as Set
30 import qualified Data.Sequence as Seq
31 import qualified Data.Text.Lazy as TL
32 import qualified Hdoc.TCT.Write.Plain as Plain
33 import qualified Language.Symantic.XML as XML
35 -- import Hdoc.TCT.Debug
36 import Hdoc.TCT as TCT hiding (Parser)
38 import Text.Blaze.DTC (xmlns_dtc)
39 import Text.Blaze.XML ()
43 -- NOTE: 'XmlNode' are still annotated with 'Sourced',
44 -- but nothing is done to preserve any ordering amongst them,
45 -- because 'Node's sometimes need to be reordered
46 -- (eg. about/title may have a title from the section before,
47 -- hence outside of about).
48 writeXML :: Roots -> XMLs
49 writeXML (tn@(Tree (Sourced src (NodeHeader (HeaderSection 1))) _ts) Seq.:<| rs) =
50 element src "head" (xmlifySection def tn) <|
52 writeXML roots = xmlify def roots
54 -- | Generate the content of <section> or <head>.
55 xmlifySection :: Inh -> Root -> XMLs
56 xmlifySection inh tn@(Tree (Sourced src _nt) _ts) =
61 { inh_para = List.repeat elementPara
64 (titles, content) = partitionSection tn
65 (attrs, body) = partitionAttrs content
67 case Seq.viewl titles of
69 title@(unTree -> src_title) :< subtitles ->
70 (xmlAttrs (attrs `defaultAttr` (src_title $> (fromString "id", getAttrId title))) <>) $
73 xmlify inh{inh_para=List.repeat elementTitle} title <>
77 subtitles >>= \subtitle@(unTree -> Sourced src_subtitle _) ->
79 element src_subtitle "alias" $
80 xmlAttrs [src_title $> (fromString "id", getAttrId subtitle)] <>
81 xmlify inh{inh_para=List.repeat elementTitle} subtitle
87 , inh_para :: [Inh -> Root -> XML]
89 instance Default Inh where
92 , inh_para = List.repeat elementPara
96 elementPara :: Inh -> Root -> XML
97 elementPara inh (Tree (Sourced src _) ts) = element src "para" $ xmlify inh ts
99 elementTitle :: Inh -> Root -> XML
100 elementTitle inh (Tree (Sourced src _) ts) =
101 element src "title" $
104 elementTitleWith :: Attrs -> Inh -> Root -> XML
105 elementTitleWith attrs inh (Tree (Sourced src _) ts) =
106 element src "title" $
107 xmlAttrs attrs <> xmlify inh ts
109 elementName :: Inh -> Root -> XML
110 elementName inh (Tree (Sourced src _) ts) =
114 attributeName :: Inh -> Root -> XML
115 attributeName _inh (Tree (Sourced src _) ts) =
116 Tree (Sourced src $ XML.NodeAttr $ XML.qName $ fromString "name") $
117 return $ tree0 $ Sourced src $
118 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
120 attributeId :: Inh -> Root -> XML
121 attributeId _inh (Tree (Sourced src _) ts) =
123 return $ tree0 $ Sourced src $
124 XML.NodeText $ XML.escapeText $ Plain.writePlain ts
128 xmlify :: Inh -> a -> XMLs
129 instance Xmlify Roots where
131 case Seq.viewl roots of
133 r0@(Tree cr@(Sourced src nr) ts) :< rs ->
135 ----------------------
136 -- NOTE: HeaderColon becomes parent
137 -- of any continuous following-sibling HeaderBar or HeaderGreat
138 NodeHeader (HeaderColon n _wh)
139 | (span, rest) <- spanlHeaderColon rs
141 xmlify inh (Tree cr (ts<>span)) <>
144 spanlHeaderColon :: Roots -> (Roots, Roots)
147 Tree (unSourced -> NodeHeader (HeaderBar m _)) _ -> m == n
148 Tree (unSourced -> NodeHeader (HeaderGreat m _)) _ -> m == n
150 ----------------------
151 -- NOTE: gather HeaderBrackets
152 NodeHeader HeaderBrackets{}
153 | (span,rest) <- spanlBrackets roots
155 (<| xmlify inh rest) $
156 element src "references" $
159 spanlBrackets :: Roots -> (Roots, Roots)
162 Tree (unSourced -> NodeHeader HeaderBrackets{}) _ -> True
164 ----------------------
165 -- NOTE: merge adjacent NodeText-s, shouldn't be needed, but just in case.
167 | Tree (cy@(unSourced -> NodeText y)) ys :< rs' <- Seq.viewl rs ->
168 xmlify inh $ Tree (NodeText <$> (x <$ cr) <> (y <$ cy)) (ts <> ys) <| rs'
169 ----------------------
170 -- NOTE: detect @some text@{some page/and more}
171 NodePair (PairAt False)
172 | Tree (Sourced _src (NodePair PairBracket)) bracket :< rs' <- Seq.viewl rs
173 , [Tree (Sourced srcPage (NodeToken (TokenText pageRef))) _] <- toList bracket
174 , '.':'/':page <- TL.unpack pageRef ->
175 (<| xmlify inh rs') $
176 element src "page-ref" $
178 [ Sourced src (fromString "at", Plain.writePlain ts)
179 , Sourced srcPage (fromString "page", TL.pack page)
182 if null ts -- NOTE: preserve empty parens
183 then Seq.singleton $ tree0 (XML.NodeText mempty <$ cr)
186 ----------------------
187 -- NOTE: detect (some text)[http://some.url] or (some text)[SomeRef]
189 | Tree (Sourced sb (NodePair PairBracket)) bracket :< rs' <- Seq.viewl rs ->
190 (<| xmlify inh rs') $
192 (toList -> [unTree -> Sourced sl (NodeToken tok)])
193 | TokenLink lnk <- tok ->
195 xmlAttrs [Sourced sl (fromString "to", lnk)] <>
199 xmlAttrs [Sourced sb (fromString "to", Plain.writePlain bracket)] <>
200 if null ts -- NOTE: preserve empty parens
201 then Seq.singleton $ tree0 (XML.NodeText mempty <$ cr)
203 ----------------------
204 -- NOTE: gather HeaderDash
205 _ | (span, rest) <- spanlItems (==HeaderDash) roots
207 (<| xmlify inh rest) $
209 span >>= xmlify inh{inh_para=List.repeat elementPara}
210 ----------------------
211 -- NOTE: gather HeaderDot
212 | (span,rest) <- spanlItems (\case HeaderDot{} -> True; _ -> False) roots
214 (<| xmlify inh rest) $
216 span >>= xmlify inh{inh_para=List.repeat elementPara}
218 spanlItems :: (Header -> Bool) -> Roots -> (Roots, Roots)
219 spanlItems liHeader =
220 Seq.spanl $ \(unTree -> (unSourced -> nod)) ->
222 NodeHeader (HeaderColon (Just (XML.NCName (TL.unpack -> "li"))) _wh) -> True
223 NodeHeader hdr -> liHeader hdr
224 NodePair (PairElem (XML.NCName (TL.unpack -> "li")) _as) -> True
226 ----------------------
227 NodePara | para:inh_para <- inh_para inh ->
229 xmlify inh{inh_para} rs
230 ----------------------
231 -- NOTE: context-free Root
233 xmlify inh r0 `XML.union`
235 instance Xmlify Root where
236 xmlify inh tn@(Tree (Sourced src@(sn:|ssn) nod) ts) =
238 ----------------------
242 para:_ -> Seq.singleton $ para inh tn
243 ----------------------
249 element src "section" $
252 HeaderColon localName _wh ->
253 let (attrs, body) = partitionAttrs ts in
255 -- NOTE: disable 'inh_figure'
258 element src "about" $
260 xmlify inh'{inh_figure=False} body
261 -- NOTE: handle judgment
262 _ | Just lName <- localName
263 , lName`List.elem`elemsJudgment -> -- FIXME: not a special case so far.
272 "grades" -> List.repeat attributeId
273 "judges" -> List.repeat attributeId
274 _ -> List.repeat elementTitle
276 -- NOTE: in <figure> mode, unreserved elements become <figure>
277 _ | Just lName <- localName
279 && lName`List.notElem`elems || null name ->
281 element src "figure" $
282 -- xmlAttrs (setAttr (Sourced en en ("type", XML.unNCName lName)) attrs) <>
284 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_begin sn}:|ssn)
285 (fromString "type", XML.unNCName lName)) <>
287 [Tree0{}] -> xmlify inh'{inh_para = List.repeat elementPara} body
288 _ -> xmlify inh'{inh_para = elementTitle : List.repeat elementPara} body
289 -- NOTE: reserved elements
296 name = maybe mempty (TL.unpack . XML.unNCName) localName
300 "about" -> List.repeat elementTitle
301 "reference" -> List.repeat elementTitle
302 "serie" -> List.repeat attributeName
303 "author" -> List.repeat attributeName
304 "editor" -> List.repeat attributeName
305 "org" -> List.repeat attributeName
306 "note" -> List.repeat elementPara
310 HeaderBar localName wh ->
313 | inh_figure inh && lName`List.notElem`elems ->
315 Tree (Sourced src $ NodeHeader $ HeaderColon localName wh) ts
318 element src "artwork" $
320 (Seq.singleton $ Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
321 (fromString "type", maybe mempty XML.unNCName localName)) <>
322 xmlify inh{inh_para=[]} ts
324 HeaderGreat localName _wh ->
325 let (attrs,body) = partitionAttrs ts in
327 element src "quote" $
329 (attrs `defaultAttr` Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
330 (fromString "type", maybe mempty XML.unNCName localName)) <>
331 xmlify inh{inh_para=List.repeat elementPara} body
333 HeaderEqual localName _wh ->
335 Tree (Sourced src $ XML.NodeAttr (XML.qName localName)) $
336 return $ tree0 $ Sourced src $ XML.NodeText $
338 Plain.text (Plain.setStart ts def{Plain.state_escape = False}) ts
346 filePos_column (fileRange_begin sn) +
347 int (TL.length n) } in
349 (Seq.singleton $ Sourced (sn{fileRange_end}:|ssn)
350 (fromString "name", n)) <>
353 HeaderDash -> Seq.singleton $ element src "li" $ xmlify inh ts
356 Seq.singleton $ Tree0 $ Sourced src $
357 XML.NodeComment $ Plain.writePlain ts
359 HeaderBrackets ident ->
360 let (attrs, body) = partitionAttrs ts in
362 element src "reference" $
364 (setAttr (Sourced (sn{fileRange_end=fileRange_end sn}:|ssn)
365 (fromString "id",ident)) attrs) |>
366 element src "about" (
367 xmlify inh'{inh_para = List.repeat elementTitle} body
370 inh' = inh{inh_figure = False}
372 HeaderDotSlash _file -> xmlify inh ts
373 ----------------------
376 PairBracket | to <- Plain.writePlain ts
377 , TL.all (\c -> c/='[' && c/=']'
379 && not (Char.isSpace c)) to ->
382 xmlAttrs [Sourced src (fromString "to",to)]
383 PairStar -> Seq.singleton $ element src "b" $ xmlify inh ts
384 PairDash -> Seq.singleton $ element src "del" $ xmlify inh ts
385 PairUnderscore -> Seq.singleton $ element src "u" $ xmlify inh ts
386 PairSlash -> Seq.singleton $ element src "i" $ xmlify inh ts
387 PairBackquote -> Seq.singleton $ element src "code" $ xmlify inh ts
392 (Seq.viewl -> Tree0 (Sourced sl (NodeToken (TokenText l))) :< ls) ->
394 m :> Tree0 (Sourced sr (NodeToken (TokenText r0))) ->
396 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropWhile Char.isSpace l))))
397 Seq.<|(m Seq.|>Tree0 (Sourced sr (NodeToken (TokenText (TL.dropWhileEnd Char.isSpace r0)))))
400 Tree0 (Sourced sl (NodeToken (TokenText (TL.dropAround Char.isSpace l)))) Seq.<| ls
401 (Seq.viewr -> rs :> Tree0 (Sourced sr (NodeToken (TokenText r0)))) ->
403 rs Seq.|> Tree0 (Sourced sr (NodeToken (TokenText (TL.dropAround Char.isSpace r0))))
407 element src (if isBackref then "tag-back" else "tag") $
408 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
409 -- xmlAttrs [Sourced src ("to",to)]
410 -- xmlify inh{inh_para=[]} ts
411 -- xmlAttrs [Sourced src ("to",Plain.writePlain ts)]
414 element src (if isBackref then "at-back" else "at") $
415 xmlAttrs [Sourced src (fromString "to", Plain.writePlain ts)]
418 Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc n) $
419 xmlAttrs (Seq.fromList $ (\(_wh, ElemAttr{..}) ->
420 Sourced src (elemAttr_name, elemAttr_value)) <$> attrs) <>
423 Seq.singleton (Tree0 $ Sourced (sn{fileRange_end=bn'}:|ssn) $
424 XML.NodeText (XML.EscapedText $ pure $ XML.EscapedPlain open)) `XML.union`
425 xmlify inh ts `XML.union`
426 Seq.singleton (Tree0 $ Sourced (sn{fileRange_begin=en'}:|ssn) $
427 XML.NodeText $ XML.EscapedText $ pure $ XML.EscapedPlain close)
429 (open, close) = pairBorders pair ts
430 bn' = (fileRange_begin sn){filePos_column=filePos_column (fileRange_begin sn) + int (TL.length open)}
431 en' = (fileRange_end sn){filePos_column=filePos_column (fileRange_end sn) - int (TL.length close)}
432 ----------------------
433 NodeText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
434 ----------------------
437 TokenEscape c -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.EscapedText $ pure $ XML.escapeChar c
438 TokenText t -> Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText $ XML.escapeText t
439 TokenAt b to -> Seq.singleton $ element src (if b then "at-back" else "at") $
440 xmlAttrs [Sourced src (fromString "to", to)]
441 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
442 TokenTag b to -> Seq.singleton $ element src (if b then "tag-back" else "tag") $
443 xmlAttrs [Sourced src (fromString "to", to)]
444 -- Seq.singleton $ Tree0 $ Sourced src $ XML.NodeText to
445 TokenLink lnk -> Seq.singleton $ element src "eref" $
446 xmlAttrs [Sourced src (fromString "to", lnk)]
447 ----------------------
450 element src "artwork" $
453 instance Xmlify (Seq (Cell (XML.QName,TL.Text))) where
454 xmlify _inh = xmlAttrs
459 element :: XML.FileSource -> String -> XMLs -> XML
460 element src n = Tree (Sourced src $ XML.NodeElem $ XML.QName xmlns_dtc $ fromString n)
462 -- | Reserved elements' name
463 elems :: Set ElemName -- TODO: use a @data@ or maybe a @data family@ instead of 'TL.Text'
464 elems = Set.fromList $ fromString <$>
564 elemsJudgment :: Set ElemName
565 elemsJudgment = Set.fromList $ fromString <$>
576 type Attrs = Seq (Cell (XML.NCName, TL.Text))
578 -- | Convenient alias, forcing the types
579 xmlAttrs :: Attrs -> XMLs
581 (<$>) $ \(Sourced src (n, v)) ->
582 Tree (Sourced src $ XML.NodeAttr (XML.qName n)) $
583 Seq.singleton $ tree0 $
585 XML.NodeText $ XML.escapeText v
587 -- | Extract section titles
588 partitionSection :: Root -> (Roots, Roots)
589 partitionSection (Tree (unSourced -> NodeHeader (HeaderSection lvlPar)) body) =
590 case Seq.viewl body of
592 title@(unTree -> Sourced (FileRange{fileRange_end=et}:|_) NodePara) :< rest ->
593 let (subtitles, content) = spanlSubtitles et rest in
594 (title <| (subtitles >>= subTrees), content)
596 spanlSubtitles ep ts =
598 sub@(unTree -> Sourced (FileRange{..}:|_) (NodeHeader (HeaderSection lvlSub))) :< rs
600 , filePos_line fileRange_begin - filePos_line ep <= 1 ->
601 let (subs, ts') = spanlSubtitles fileRange_end rs in
605 partitionSection _ = mempty
607 -- | Extract attributes
608 partitionAttrs :: Roots -> (Attrs, Roots)
609 partitionAttrs ts = (attrs, cs)
611 (as,cs) = (`Seq.partition` ts) $ \case
612 Tree (unSourced -> NodeHeader (HeaderEqual (XML.NCName n) _wh)) _cs -> not $ TL.null n
616 Tree (Sourced loc (NodeHeader (HeaderEqual n _wh))) a ->
618 where v = Plain.text (Plain.setStart a def{Plain.state_escape = False}) a
621 getAttrId :: Root -> TL.Text
622 getAttrId = Plain.writePlain . Seq.singleton
624 setAttr :: Cell (XML.NCName, TL.Text) -> Attrs -> Attrs
625 setAttr a@(unSourced -> (k, _v)) as =
626 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of
627 Just idx -> Seq.update idx a as
630 defaultAttr :: Attrs -> Cell (XML.NCName, TL.Text) -> Attrs
631 defaultAttr as a@(unSourced -> (k, _v)) =
632 case Seq.findIndexL (\(unSourced -> (n,_v)) -> n == k) as of