1 {-# LANGUAGE DuplicateRecordFields #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE MultiParamTypeClasses #-}
5 {-# LANGUAGE OverloadedStrings #-}
6 {-# LANGUAGE ScopedTypeVariables #-}
7 {-# LANGUAGE TypeApplications #-}
8 {-# LANGUAGE ViewPatterns #-}
9 {-# OPTIONS_GHC -fno-warn-orphans #-}
10 module Language.DTC.Write.HTML5 where
12 -- import Control.Monad.Trans.Class (MonadTrans(..))
13 -- import Data.Functor.Identity (Identity(..))
14 -- import Data.Sequence (Seq)
15 -- import Data.Set (Set)
16 -- import Data.Traversable (Traversable(..))
17 -- import qualified Data.Sequence as Seq
18 -- import qualified Data.TreeSeq.Strict as Tree
19 import Control.Applicative (Applicative(..))
20 import Control.Category
23 import Data.Char (Char)
24 import Data.Eq (Eq(..))
25 import Data.Foldable (Foldable(..), concat)
26 import Data.Function (($), const, flip, on)
27 import Data.Functor (Functor(..), (<$>))
28 import Data.Functor.Compose (Compose(..))
30 import Data.Map.Strict (Map)
31 import Data.Maybe (Maybe(..), maybe, mapMaybe, fromJust, maybeToList)
32 import Data.Monoid (Monoid(..))
33 import Data.Ord (Ord(..))
34 import Data.Semigroup (Semigroup(..))
35 import Data.String (String)
36 import Data.Text (Text)
37 import Data.TreeSeq.Strict (Tree(..), Trees)
38 import Data.Tuple (snd)
39 import System.FilePath (FilePath)
40 import Text.Blaze ((!))
41 import Text.Blaze.Html (Html)
42 import Text.Show (Show(..))
43 import qualified Control.Monad.Trans.State as S
44 import qualified Data.List as List
45 import qualified Data.Map.Strict as Map
46 import qualified Data.Sequence as Seq
47 import qualified Data.Strict.Maybe as Strict
48 import qualified Data.Text as Text
49 import qualified Data.Text.Lazy as TL
50 import qualified Data.TreeMap.Strict as TreeMap
51 import qualified Data.TreeSeq.Strict as Tree
52 import qualified Data.TreeSeq.Strict.Zipper as Tree
53 import qualified Text.Blaze.Html5 as H
54 import qualified Text.Blaze.Html5.Attributes as HA
55 import qualified Text.Blaze.Internal as H
57 import Text.Blaze.Utils
58 import Data.Locale hiding (localize, Index)
59 import qualified Data.Locale as Locale
61 import Language.DTC.Document (Document)
62 import Language.DTC.Write.XML ()
63 import qualified Language.DTC.Document as DTC
64 import qualified Language.DTC.Anchor as Anchor
66 (<&>) :: Functor f => f a -> (a -> b) -> f b
71 type Html5 = StateMarkup State ()
76 { state_styles :: Map FilePath CSS
77 , state_scripts :: Map FilePath Script
78 , state_localize :: MsgHtml5 -> Html5
79 , state_indexs :: Map DTC.Pos (DTC.Terms, Anchor.Irefs)
80 , state_rrefs :: Anchor.Rrefs
81 , state_figures :: Map Text (Map DTC.Pos DTC.Title)
82 , state_references :: Map DTC.Ident DTC.About
86 { state_styles = mempty
87 , state_scripts = mempty
88 , state_localize = html5ify . show
89 , state_indexs = mempty
90 , state_rrefs = mempty
91 , state_figures = mempty
92 , state_references = mempty
100 { keys_index :: Map DTC.Pos DTC.Terms
101 , keys_figure :: Map Text (Map DTC.Pos DTC.Title)
102 , keys_reference :: Map DTC.Ident DTC.About
105 keys :: Trees DTC.BodyKey DTC.BodyValue -> Keys
106 keys body = foldl' flt (Keys mempty mempty mempty) (Compose body)
109 DTC.Index{..} -> acc{keys_index =
110 Map.insert pos terms $ keys_index acc}
111 DTC.Figure{..} -> acc{keys_figure =
113 type_ (Map.singleton pos title) $
115 DTC.References{..} -> acc{keys_reference =
118 (DTC.id (r::DTC.Reference))
119 (DTC.about (r::DTC.Reference)))
124 -- ** Class 'Html5ify'
125 class Html5ify a where
126 html5ify :: a -> Html5
127 instance Html5ify Char where
128 html5ify = html5ify . H.toMarkup
129 instance Html5ify Text where
130 html5ify = html5ify . H.toMarkup
131 instance Html5ify String where
132 html5ify = html5ify . H.toMarkup
133 instance Html5ify H.Markup where
134 html5ify = Compose . return
135 instance Html5ify DTC.Title where
136 html5ify (DTC.Title t) = html5ify t
137 instance Html5ify DTC.Para where
138 html5ify = mapM_ html5ify
139 instance Html5ify DTC.Ident where
140 html5ify (DTC.Ident i) = html5ify i
143 Localize ls Html5 MsgHtml5 =>
145 LocaleIn ls -> Document -> Html
146 html5Document locale DTC.Document{..} = do
147 let Keys{..} = keys body
148 let (body',state_rrefs,state_indexs) =
149 let irefs = foldMap Anchor.irefsOfTerms keys_index in
150 let (body0, Anchor.State{state_irefs, state_rrefs=rrefs}) =
151 Anchor.anchorify body `S.runState`
152 Anchor.state{Anchor.state_irefs=irefs} in
154 (<$> keys_index) $ \terms ->
156 TreeMap.intersection const state_irefs $
157 Anchor.irefsOfTerms terms
158 let (html5Body, State{state_styles,state_scripts}) =
162 , state_figures = keys_figure
163 , state_references = keys_reference
165 liftStateMarkup $ S.modify $ \s -> s{state_localize = Locale.localize locale}
169 H.html ! HA.lang (attrValue $ countryCode locale) $ do
171 H.meta ! HA.httpEquiv "Content-Type"
172 ! HA.content "text/html; charset=UTF-8"
173 whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
175 H.toMarkup $ plainify $ List.head ts
176 forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
177 H.link ! HA.rel (attrValue rel)
178 ! HA.href (attrValue href)
179 H.meta ! HA.name "generator"
182 (`mapMaybe` toList body) $ \case
183 TreeN k@DTC.Section{} _ -> Just k
185 forM_ chapters $ \DTC.Section{..} ->
186 H.link ! HA.rel "Chapter"
187 ! HA.title (attrValue $ plainify title)
188 ! HA.href ("#"<>attrValue pos)
189 H.link ! HA.rel "stylesheet"
190 ! HA.type_ "text/css"
191 ! HA.href "style/dtc-html5.css"
192 forM_ state_styles $ \style ->
193 H.style ! HA.type_ "text/css" $
195 forM_ state_scripts $ \script ->
196 H.script ! HA.type_ "application/javascript" $
201 -- * Type 'BodyCursor'
202 -- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
203 type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
204 instance Html5ify DTC.Body where
206 forM_ (Tree.zippers body) $ \z ->
207 forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
210 instance Html5ify BodyCursor where
212 case Tree.current z of
213 TreeN k _ts -> html5BodyKey z k
214 Tree0 v -> html5BodyValue z v
216 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
217 html5BodyKey z = \case
219 H.section ! HA.class_ "section"
220 ! HA.id (attrValue pos) $$ do
221 html5CommonAttrs attrs $
222 H.table ! HA.class_ "section-header" $$
225 H.td ! HA.class_ "section-number" $$ do
226 html5SectionNumber $ DTC.posAncestors pos
227 H.td ! HA.class_ "section-title" $$ do
228 (case List.length $ DTC.posAncestors pos of
237 forM_ (Tree.axis_child `Tree.runAxis` z) $
239 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
240 html5BodyValue z = \case
241 DTC.Block b -> html5ify b
243 H.nav ! HA.class_ "toc"
244 ! HA.id (attrValue pos) $$ do
245 H.span ! HA.class_ "toc-name" $$
246 H.a ! HA.href (attrValue pos) $$
247 html5ify MsgHTML5_Table_of_Contents
249 forM_ (Tree.axis_following_sibling `Tree.runAxis` z) $
252 H.nav ! HA.class_ "tof"
253 ! HA.id (attrValue pos) $$
254 H.table ! HA.class_ "tof" $$
258 html5CommonAttrs attrs $
259 H.div ! HA.class_ ("figure " <> attrValue ("figure-"<>type_))
260 ! HA.id (attrValue pos) $$ do
261 H.table ! HA.class_ "figure-caption" $$
264 H.td ! HA.class_ "figure-number" $$ do
265 H.a ! HA.href ("#"<>attrValue pos) $$ do
267 html5ify $ DTC.posAncestors pos
268 html5ify $ MsgHTML5_Colon
270 H.td ! HA.class_ "figure-name" $$
272 H.div ! HA.class_ "figure-content" $$ do
275 (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . state_indexs
276 let chars = Anchor.termsByChar allTerms
277 H.div ! HA.class_ "index"
278 ! HA.id (attrValue pos) $$ do
279 H.nav ! HA.class_ "index-nav" $$ do
280 forM_ (Map.keys chars) $ \char ->
281 H.a ! HA.href ("#"<>(attrValue pos <> "." <> attrValue char)) $$
283 H.dl ! HA.class_ "index-chars" $$
284 forM_ (Map.toList chars) $ \(char,terms) -> do
286 let i = attrValue pos <> "." <> attrValue char in
288 ! HA.href ("#"<>i) $$
291 H.dl ! HA.class_ "index-term" $$ do
292 forM_ terms $ \aliases -> do
294 H.ul ! HA.class_ "index-aliases" $$
295 forM_ (List.take 1 aliases) $ \term ->
296 H.li ! HA.id (attrValue term) $$
300 List.sortBy (compare `on` DTC.section . snd) $
301 (`foldMap` aliases) $ \words ->
303 path <- Anchor.pathFromWords words
304 Strict.maybe Nothing (Just . ((words,) <$>) . List.reverse) $
305 TreeMap.lookup path refsByTerm in
307 (<$> anchs) $ \(term,DTC.Anchor{..}) ->
308 H.a ! HA.class_ "index-iref"
309 ! HA.href ("#"<>attrValue (term,count)) $$
310 html5ify $ DTC.posAncestors section
311 DTC.References{..} ->
312 html5CommonAttrs attrs $
313 H.div ! HA.class_ "references"
314 ! HA.id (attrValue pos) $$ do
318 instance Html5ify DTC.Words where
319 html5ify = html5ify . Anchor.plainifyWords
321 cleanPara :: DTC.Para -> DTC.Para
323 p >>= (`Tree.bindTrees` \case
324 TreeN DTC.Iref{} ls -> ls
325 TreeN DTC.Note{} _ -> mempty
328 html5ifyToC :: Maybe DTC.Nat -> BodyCursor -> Html5
329 html5ifyToC depth z =
330 case Tree.current z of
331 TreeN DTC.Section{..} _ts -> do
333 H.table ! HA.class_ "toc-entry" $$
336 H.td ! HA.class_ "section-number" $$
337 html5SectionRef $ DTC.posAncestors pos
338 H.td ! HA.class_ "section-title" $$
339 html5ify $ cleanPara $ DTC.unTitle title
340 when (maybe True (> DTC.Nat 1) depth && not (null sections)) $
343 html5ifyToC (depth >>= DTC.predNat)
349 `Tree.axis_filter_current` \case
350 TreeN DTC.Section{} _ -> True
353 html5ifyToF :: [Text] -> Html5
354 html5ifyToF types = do
355 figsByType <- liftStateMarkup $ S.gets state_figures
357 Map.foldMapWithKey (\ty -> ((ty,) <$>)) $
361 Map.intersection figsByType $
362 Map.fromList [(ty,()) | ty <- types]
363 forM_ (Map.toList figs) $ \(pos, (type_, title)) ->
365 H.td ! HA.class_ "figure-number" $$
366 H.a ! HA.href ("#"<>attrValue pos) $$ do
368 html5ify $ DTC.posAncestors pos
369 H.td ! HA.class_ "figure-name" $$
370 html5ify $ cleanPara $ DTC.unTitle title
372 instance Html5ify [DTC.Block] where
373 html5ify = mapM_ html5ify
374 instance Html5ify DTC.Block where
377 html5CommonAttrs attrs $
378 H.p ! HA.class_ "para"
379 ! HA.id (attrValue pos) $$ do
382 html5CommonAttrs attrs $
383 H.ol ! HA.class_ "ol"
384 ! HA.id (attrValue pos) $$ do
385 forM_ items $ \item ->
386 H.li $$ html5ify item
388 html5CommonAttrs attrs $
389 H.ul ! HA.class_ "ul"
390 ! HA.id (attrValue pos) $$ do
391 forM_ items $ \item ->
392 H.li $$ html5ify item
394 html5ify $ H.Comment (H.Text t) ()
395 instance Html5ify DTC.Lines where
399 DTC.BR -> html5ify H.br
400 DTC.Plain t -> html5ify t
403 DTC.B -> H.strong $$ html5ify ls
404 DTC.Code -> H.code $$ html5ify ls
405 DTC.Del -> H.del $$ html5ify ls
406 DTC.I -> H.i $$ html5ify ls
407 DTC.Sub -> H.sub $$ html5ify ls
408 DTC.Sup -> H.sup $$ html5ify ls
409 DTC.SC -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
410 DTC.U -> H.span ! HA.class_ "underline" $$ html5ify ls
413 H.span ! HA.class_ "q" $$ do
414 html5ify MsgHTML5_QuoteOpen
416 html5ify MsgHTML5_QuoteClose
418 H.a ! HA.class_ "eref"
419 ! HA.href (attrValue href) $$
421 then html5ify $ DTC.unURL href
425 Nothing -> html5ify ls
426 Just DTC.Anchor{..} ->
427 H.span ! HA.class_ "iref"
428 ! HA.id (attrValue (term,count)) $$
431 H.a ! HA.class_ "ref"
432 ! HA.href ("#"<>attrValue to) $$
437 when (not $ null ls) $ do
438 refs <- liftStateMarkup $ S.gets state_references
439 case Map.lookup to refs of
441 Just DTC.About{..} ->
442 forM_ (List.take 1 titles) $ \(DTC.Title title) -> do
443 html5ify $ TreeN DTC.Q $
446 Just u -> pure $ TreeN (DTC.Eref u) title
448 Nothing -> html5ify ls
450 H.a ! HA.class_ "rref"
451 ! HA.href ("#rref."<>attrValue to)
452 ! HA.id ("rref."<>attrValue to<>maybe "" (\DTC.Anchor{..} -> "."<>attrValue count) anchor) $$
455 instance Html5ify DTC.URL where
456 html5ify (DTC.URL url) =
457 H.a ! HA.class_ "eref"
458 ! HA.href (attrValue url) $$
461 instance AttrValue DTC.Words where
463 "iref" <> "." <> attrValue (Anchor.plainifyWords term)
464 instance AttrValue (DTC.Words,DTC.Nat1) where
465 attrValue (term,count) =
467 <> "." <> attrValue (Anchor.plainifyWords term)
468 <> "." <> attrValue count
469 instance Html5ify DTC.Date where
470 html5ify = html5ify . MsgHTML5_Date
471 instance Html5ify DTC.About where
472 html5ify DTC.About{..} =
473 html5CommasDot $ concat $
474 [ (<$> List.take 1 titles) $ \(DTC.Title title) ->
475 html5ify $ TreeN DTC.Q $
478 Just u -> pure $ TreeN (DTC.Eref u) title
479 , html5Entity <$> authors
480 , html5ify <$> maybeToList date
481 , html5Entity <$> maybeToList editor
482 , html5Serie <$> series
485 html5Serie DTC.Serie{..} = do
487 html5ify MsgHTML5_Colon
489 html5Entity DTC.Entity{url=mu, ..} =
490 html5ify @DTC.Lines $
492 _ | not (Text.null email) -> TreeN (DTC.Eref $ DTC.URL $ "mailto:"<>email) $ pure $ Tree0 $ DTC.Plain name
493 _ | Just u <- mu -> TreeN (DTC.Eref u) $ pure $ Tree0 $ DTC.Plain name
494 _ -> Tree0 $ DTC.Plain name
495 instance Html5ify DTC.Reference where
496 html5ify DTC.Reference{id=id_, ..} =
498 H.td ! HA.class_ "reference-key" $$
499 html5ify @DTC.Lines $ TreeN DTC.Rref{anchor=Nothing, to=id_} Seq.empty
500 H.td ! HA.class_ "reference-content" $$ do
502 rrefs <- liftStateMarkup $ S.gets state_rrefs
503 case Map.lookup id_ rrefs of
506 H.span ! HA.class_ "reference-rrefs" $$
508 (<$> List.reverse anchs) $ \DTC.Anchor{..} ->
509 H.a ! HA.class_ "reference-rref"
510 ! HA.href ("#rref."<>attrValue id_<>"."<>attrValue count) $$
511 html5ify $ DTC.posAncestors section
513 html5CommasDot :: [Html5] -> Html5
514 html5CommasDot [] = pure ()
515 html5CommasDot hs = do
516 sequence_ $ List.intersperse ", " hs
519 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
520 html5CommonAttrs DTC.CommonAttrs{id=id_, ..} =
521 Compose . (addClass . addId <$>) . getCompose
526 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
527 addId = maybe id (\(DTC.Ident i) ->
528 H.AddCustomAttribute "id" (H.Text i)) id_
530 html5SectionNumber :: DTC.PosPath -> Html5
531 html5SectionNumber = go mempty
533 go :: DTC.PosPath -> DTC.PosPath -> Html5
535 case Seq.viewl next of
536 Seq.EmptyL -> pure ()
537 a@(_n,rank) Seq.:< as -> do
538 H.a ! HA.href ("#"<>attrValue (prev Seq.|>a)) $$
540 when (not (null as) || null prev) $ do
544 html5SectionRef :: DTC.PosPath -> Html5
546 H.a ! HA.href ("#"<>attrValue as) $$
549 instance Html5ify DTC.PosPath where
557 Text.intercalate "." $
558 Text.pack . show . snd <$> as
560 instance AttrValue DTC.PosPath where
561 attrValue = attrValue . plainify
562 instance AttrValue DTC.Pos where
563 attrValue = attrValue . DTC.posAncestors
565 -- * Class 'Plainify'
566 class Plainify a where
567 plainify :: a -> TL.Text
568 instance Plainify TL.Text where
570 instance Plainify Text where
571 plainify = TL.fromStrict
572 instance Plainify DTC.Para where
573 plainify = foldMap plainify
574 instance Plainify DTC.Lines where
579 DTC.Plain p -> plainify p
582 DTC.B -> "*"<>plainify ls<>"*"
583 DTC.Code -> "`"<>plainify ls<>"`"
584 DTC.Del -> "-"<>plainify ls<>"-"
585 DTC.I -> "/"<>plainify ls<>"/"
587 DTC.Q -> "« "<>plainify ls<>" »"
588 DTC.SC -> plainify ls
589 DTC.Sub -> plainify ls
590 DTC.Sup -> plainify ls
591 DTC.U -> "_"<>plainify ls<>"_"
592 DTC.Eref{..} -> plainify ls
593 DTC.Iref{..} -> plainify ls
594 DTC.Ref{..} -> plainify ls
595 DTC.Rref{..} -> plainify ls
596 instance Plainify DTC.Title where
597 plainify (DTC.Title t) = plainify t
598 instance Plainify DTC.PosPath where
600 snd . foldl' (\(nParent,acc) (n,c) ->
612 instance Html5ify Int where
613 html5ify = html5ify . show
614 instance Html5ify DTC.Nat where
615 html5ify (DTC.Nat n) = html5ify n
616 instance Html5ify DTC.Nat1 where
617 html5ify (DTC.Nat1 n) = html5ify n
621 = MsgHTML5_Table_of_Contents
624 | MsgHTML5_QuoteClose
625 | MsgHTML5_Date DTC.Date
627 instance Html5ify MsgHtml5 where
629 loc <- liftStateMarkup $ S.gets state_localize
631 instance LocalizeIn FR Html5 MsgHtml5 where
633 MsgHTML5_Table_of_Contents -> "Sommaire"
634 MsgHTML5_Colon -> " :"
635 MsgHTML5_QuoteOpen -> "« "
636 MsgHTML5_QuoteClose -> " »"
637 MsgHTML5_Date DTC.Date{..} ->
639 List.intersperse " " $
641 [ maybe [] (pure . html5ify) day
654 9 -> pure "septembre"
656 11 -> pure "novembre"
657 12 -> pure "décembre"
661 instance LocalizeIn EN Html5 MsgHtml5 where
663 MsgHTML5_Table_of_Contents -> "Summary"
664 MsgHTML5_Colon -> ":"
665 MsgHTML5_QuoteOpen -> "“"
666 MsgHTML5_QuoteClose -> "”"
667 MsgHTML5_Date DTC.Date{..} ->
669 List.intersperse " " $
671 [ maybe [] (pure . html5ify) day
684 9 -> pure "September"
686 11 -> pure "November"
687 12 -> pure "December"