1 {-# LANGUAGE DataKinds #-}
2 {-# LANGUAGE DuplicateRecordFields #-}
3 {-# LANGUAGE ExistentialQuantification #-}
4 {-# LANGUAGE FlexibleInstances #-}
5 {-# LANGUAGE FlexibleContexts #-}
6 {-# LANGUAGE MultiParamTypeClasses #-}
7 {-# LANGUAGE OverloadedStrings #-}
8 {-# LANGUAGE ScopedTypeVariables #-}
9 {-# LANGUAGE TypeApplications #-}
10 {-# OPTIONS_GHC -fno-warn-orphans #-}
11 module Hdoc.DTC.Write.HTML5 where
13 import Control.Applicative (Applicative(..))
14 import Control.Category as Cat
17 import Data.Char (Char)
18 import Data.Default.Class (Default(..))
19 import Data.Either (Either(..))
20 import Data.Eq (Eq(..))
21 import Data.Foldable (Foldable(..), concat, any)
22 import Data.Function (($), const, on)
23 import Data.Functor ((<$>))
24 import Data.Functor.Compose (Compose(..))
26 import Data.Map.Strict (Map)
27 import Data.Maybe (Maybe(..), maybe, mapMaybe, fromJust, maybeToList)
28 import Data.Monoid (Monoid(..))
29 import Data.Ord (Ord(..))
30 import Data.Semigroup (Semigroup(..))
31 import Data.String (String, IsString(..))
32 import Data.Text (Text)
33 import Data.TreeSeq.Strict (Tree(..), tree0)
34 import Data.Tuple (snd)
36 import System.FilePath (FilePath)
37 import Text.Blaze ((!))
38 import Text.Blaze.Html (Html)
39 import Text.Show (Show(..))
40 import qualified Data.Char as Char
41 import qualified Control.Monad.Trans.State as S
42 import qualified Data.List as List
43 import qualified Data.Map.Strict as Map
44 import qualified Data.Sequence as Seq
45 import qualified Data.Strict.Maybe as Strict
46 import qualified Data.Text as Text
47 import qualified Data.ByteString.Lazy as BS
48 import qualified Data.Text.Lazy as TL
49 import qualified Data.Text.Lazy.Encoding as TL
50 import qualified Data.Text.Lazy.Builder as TL.Builder
51 import qualified Data.Text.Lazy.Builder.Int as TL.Builder
52 import qualified Data.TreeMap.Strict as TreeMap
53 import qualified Data.TreeSeq.Strict.Zipper as Tree
54 import qualified Text.Blaze.Html5 as H
55 import qualified Text.Blaze.Html5.Attributes as HA
56 import qualified Text.Blaze.Internal as H
58 import Text.Blaze.Utils
59 import Data.Locale hiding (Index)
61 import Hdoc.DTC.Document as DTC
62 import Hdoc.DTC.Write.Plain (Plainify(..))
63 import Hdoc.DTC.Write.XML ()
64 import qualified Hdoc.DTC.Anchor as Anchor
65 import qualified Hdoc.DTC.Write.Plain as Plain
67 writeHTML5 :: Config -> DTC.Document -> Html
68 writeHTML5 conf@Config{..} DTC.Document{..} = do
69 let Keys{..} = keys body `S.execState` def
70 let (body',state_rrefs,state_notes,state_indexs) =
71 let irefs = foldMap Anchor.irefsOfTerms keys_index in
72 let (body0, Anchor.State{state_irefs, state_rrefs=rrefs, state_notes=notes}) =
73 Anchor.anchorify body `S.runState`
74 def{Anchor.state_irefs=irefs} in
75 (body0,rrefs,notes,) $
76 (<$> keys_index) $ \terms ->
78 TreeMap.intersection const state_irefs $
79 Anchor.irefsOfTerms terms
80 let state_plainify = def{ Plain.state_l10n = loqualize config_locale}
81 let (html5Body, endState) =
86 , state_figures = keys_figure
87 , state_references = keys_reference
89 , state_l10n = loqualize config_locale
91 html5DocumentHead head
94 H.html ! HA.lang (attrify $ countryCode config_locale) $ do
95 html5Head conf endState head body
98 html5Head :: Config -> State -> Head -> Body -> Html
99 html5Head Config{..} State{..} Head{DTC.about=About{..}} body = do
101 H.meta ! HA.httpEquiv "Content-Type"
102 ! HA.content "text/html; charset=UTF-8"
103 unless (null titles) $ do
105 H.toMarkup $ Plain.text state_plainify $ List.head titles
106 forM_ links $ \Link{..} ->
108 "stylesheet" | URL "" <- href ->
109 H.style ! HA.type_ "text/css" $
110 H.toMarkup $ Plain.text def plain
112 H.link ! HA.rel (attrify rel)
113 ! HA.href (attrify href)
115 H.link ! HA.rel "self"
116 ! HA.href (attrify href)
117 unless (TL.null config_generator) $ do
118 H.meta ! HA.name "generator"
119 ! HA.content (attrify config_generator)
121 H.meta ! HA.name "keywords"
122 ! HA.content (attrify $ TL.intercalate ", " tags)
124 (`mapMaybe` toList body) $ \case
125 Tree k@BodySection{} _ -> Just k
127 forM_ chapters $ \case
129 H.link ! HA.rel "Chapter"
130 ! HA.title (attrify $ plainify title)
131 ! HA.href (refIdent $ identify pos)
133 unless (any (\DTC.Link{..} -> rel == "stylesheet" && href /= URL "") links) $ do
137 H.link ! HA.rel "stylesheet"
138 ! HA.type_ "text/css"
139 ! HA.href (attrify css)
141 H.style ! HA.type_ "text/css" $
142 -- NOTE: as a special case, H.style wraps its content into an External,
143 -- so it does not HTML-escape its content.
145 forM_ state_styles $ \style ->
146 H.style ! HA.type_ "text/css" $
148 unless (any (\DTC.Link{rel} -> rel == "script") links) $ do
149 forM_ state_scripts $ \script ->
150 H.script ! HA.type_ "application/javascript" $
153 html5DocumentHead :: Head -> Html5
154 html5DocumentHead Head{DTC.about=About{..}} = do
155 H.div ! HA.class_ "document-head" $$
159 H.td ! HA.class_ "left" $$ docHeaders
160 H.td ! HA.class_ "right" $$ docAuthors
161 unless (null titles) $
162 H.div ! HA.class_ "title" $$ do
163 forM_ titles $ \title ->
164 H.h1 $$ html5ify title
167 H.table ! HA.class_ "document-headers" $$
169 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
170 forM_ series $ \s@Serie{id=id_, name} ->
174 headerName $ html5ify name
175 headerValue $ html5ify id_
177 headerName $ html5ify name
179 H.a ! HA.href (attrify href) $$
181 forM_ links $ \Link{..} ->
182 unless (TL.null name) $
184 headerName $ html5ify name
185 headerValue $ html5ify $ Tree PlainEref{href} plain
188 headerName $ l10n_Header_Date loc
189 headerValue $ html5ify d
192 headerName $ l10n_Header_Address loc
193 headerValue $ html5ify $ tree0 $ PlainEref{href}
194 forM_ headers $ \Header{..} ->
196 headerName $ html5ify name
197 headerValue $ html5ify value
199 H.table ! HA.class_ "document-authors" $$
201 forM_ authors $ \a ->
203 H.td ! HA.class_ "author" $$
205 header :: Html5 -> Html5
206 header hdr = H.tr ! HA.class_ "header" $$ hdr
207 headerName :: Html5 -> Html5
209 H.td ! HA.class_ "header-name" $$ do
211 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
213 headerValue :: Html5 -> Html5
215 H.td ! HA.class_ "header-value" $$ do
222 , Loqualize locales (L10n Html5)
223 , Loqualize locales (Plain.L10n Plain.Plain)
226 { config_css :: Either FilePath TL.Text
227 , config_locale :: LocaleIn locales
228 , config_generator :: TL.Text
230 instance Default Config where
232 { config_css = Right "style/dtc-html5.css"
233 , config_locale = LocaleIn @'[EN] en_US
234 , config_generator = "https://hackage.haskell.org/package/hdoc"
238 type Html5 = StateMarkup State ()
239 instance IsString Html5 where
240 fromString = html5ify
245 { state_styles :: Map FilePath TL.Text
246 , state_scripts :: Map FilePath TL.Text
247 , state_indexs :: Map DTC.Pos (Terms, Anchor.Irefs)
248 , state_rrefs :: Anchor.Rrefs
249 , state_figures :: Map TL.Text (Map DTC.Pos (Maybe Title))
250 , state_references :: Map Ident About
251 , state_notes :: Anchor.Notes
252 , state_plainify :: Plain.State
253 , state_l10n :: Loqualization (L10n Html5)
255 instance Default State where
258 , state_scripts = def
261 , state_figures = def
262 , state_references = def
264 , state_plainify = def
265 , state_l10n = Loqualization EN_US
271 { keys_index :: Map DTC.Pos Terms
272 , keys_figure :: Map TL.Text (Map DTC.Pos (Maybe Title))
273 , keys_reference :: Map Ident About
275 instance Default Keys where
276 def = Keys mempty mempty mempty
280 keys :: a -> S.State Keys ()
281 instance KeysOf Body where
283 instance KeysOf (Tree BodyNode) where
286 BodySection{..} -> keys ts
287 BodyBlock b -> keys b
288 instance KeysOf DTC.Block where
290 BlockPara{} -> return ()
291 BlockBreak{} -> return ()
292 BlockToC{} -> return ()
293 BlockToF{} -> return ()
295 S.modify $ \s -> s{keys_index=
296 Map.insert pos terms $ keys_index s}
298 S.modify $ \s -> s{keys_figure=
300 type_ (Map.singleton pos mayTitle) $
302 BlockReferences{..} ->
303 S.modify $ \s -> s{keys_reference=
306 (DTC.id (r::DTC.Reference))
307 (DTC.about (r::DTC.Reference)))
311 -- * Class 'Html5ify'
312 class Html5ify a where
313 html5ify :: a -> Html5
314 instance Html5ify H.Markup where
315 html5ify = Compose . return
316 instance Html5ify Char where
317 html5ify = html5ify . H.toMarkup
318 instance Html5ify Text where
319 html5ify = html5ify . H.toMarkup
320 instance Html5ify TL.Text where
321 html5ify = html5ify . H.toMarkup
322 instance Html5ify String where
323 html5ify = html5ify . H.toMarkup
324 instance Html5ify Title where
325 html5ify (Title t) = html5ify t
326 instance Html5ify Ident where
327 html5ify (Ident i) = html5ify i
328 instance Html5ify Int where
329 html5ify = html5ify . show
330 instance Html5ify Nat where
331 html5ify (Nat n) = html5ify n
332 instance Html5ify Nat1 where
333 html5ify (Nat1 n) = html5ify n
334 instance Html5ify a => Html5ify (Maybe a) where
335 html5ify = foldMap html5ify
337 -- * Type 'BodyCursor'
338 -- | Cursor to navigate within a 'Body' according to many axis (like in XSLT).
339 type BodyCursor = Tree.Zipper BodyNode
340 instance Html5ify Body where
342 forM_ (Tree.zippers body) $ \z ->
343 forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
345 instance Html5ify BodyCursor
347 let Tree n _ts = Tree.current z in
349 BodyBlock BlockToC{..} -> do
350 H.nav ! HA.class_ "toc"
351 ! HA.id (attrify $ identify pos) $$ do
352 H.span ! HA.class_ "toc-name" $$
353 H.a ! HA.href (refIdent $ identify pos) $$ do
354 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
355 Plain.l10n_Table_of_Contents loc
357 forM_ (Tree.axis_following_sibling `Tree.runAxis` z) $
359 BodyBlock b -> html5ify b
360 BodySection{..} -> do
362 notes <- liftStateMarkup $ S.gets state_notes
364 p <- posParent $ posAncestors pos
365 let (ns, as) = Map.updateLookupWithKey (\_ _ -> Nothing) p notes
369 Just (secNotes, state_notes) -> do
370 liftStateMarkup $ S.modify' $ \s -> s{state_notes}
372 html5CommonAttrs attrs{classes="section":classes attrs} $
373 H.section ! HA.id (attrify $ identify pos) $$ do
374 forM_ aliases html5ify
376 ! HA.id (attrify $ escapeIdent $ identify title)
377 ! HA.class_ "section-header" $$
380 H.td ! HA.class_ "section-number" $$ do
381 html5SectionNumber $ DTC.posAncestors pos
382 H.td ! HA.class_ "section-title" $$ do
383 (case List.length $ DTC.posAncestors pos of
392 forM_ (Tree.axis_child `Tree.runAxis` z) $
394 notes <- liftStateMarkup $ S.gets state_notes
395 html5ify $ Map.lookup (posAncestors pos) notes
396 instance Html5ify [Anchor.Note] where
398 H.aside ! HA.class_ "notes" $$ do
402 forM_ (List.reverse notes) $ \Anchor.Note{..} ->
404 H.td ! HA.class_ "note-ref" $$ do
405 H.a ! HA.class_ "note-number"
406 ! HA.id ("note."<>attrify note_number)
407 ! HA.href ("#note."<>attrify note_number) $$ do
410 H.a ! HA.href ("#note-ref."<>attrify note_number) $$ do
413 html5ify note_content
414 instance Html5ify Block where
416 BlockPara para -> html5ify para
418 html5CommonAttrs attrs
419 { classes = "page-break":"print-only":classes attrs } $
421 H.p $$ " " -- NOTE: force page break
422 BlockToC{..} -> mempty -- NOTE: done in Html5ify BodyCursor
424 H.nav ! HA.class_ "tof"
425 ! HA.id (attrify $ identify pos) $$
426 H.table ! HA.class_ "tof" $$
430 html5CommonAttrs attrs
431 { classes = "figure":("figure-"<>type_):classes attrs
432 , DTC.id = Just $ Ident $ Plain.text def $ DTC.posAncestors pos
435 H.table ! HA.class_ "figure-caption" $$
439 then H.a ! HA.href (refIdent $ identify pos) $$ mempty
441 H.td ! HA.class_ "figure-number" $$ do
442 H.a ! HA.href (refIdent $ identify $ DTC.posAncestorsWithFigureNames pos) $$ do
444 html5ify $ DTC.posAncestorsWithFigureNames pos
445 forM_ mayTitle $ \title -> do
446 H.td ! HA.class_ "figure-colon" $$ do
447 unless (TL.null type_) $ do
448 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
450 H.td ! HA.class_ "figure-title" $$ do
452 H.div ! HA.class_ "figure-content" $$ do
454 BlockIndex{pos} -> do
455 (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . state_indexs
456 let chars = Anchor.termsByChar allTerms
457 H.div ! HA.class_ "index"
458 ! HA.id (attrify $ identify pos) $$ do
459 H.nav ! HA.class_ "index-nav" $$ do
460 forM_ (Map.keys chars) $ \char ->
461 H.a ! HA.href (refIdent (identify pos <> "." <> identify char)) $$
463 H.dl ! HA.class_ "index-chars" $$
464 forM_ (Map.toList chars) $ \(char,terms) -> do
466 let i = identify pos <> "." <> identify char
467 H.a ! HA.id (attrify i)
468 ! HA.href (refIdent i) $$
471 H.dl ! HA.class_ "index-term" $$ do
472 forM_ terms $ \aliases -> do
474 H.ul ! HA.class_ "index-aliases" $$
475 forM_ (List.take 1 aliases) $ \term -> do
476 H.li ! HA.id (attrify $ identifyIref term) $$
480 List.sortBy (compare `on` DTC.section . snd) $
481 (`foldMap` aliases) $ \words ->
483 path <- Anchor.pathFromWords words
484 Strict.maybe Nothing (Just . ((words,) <$>) . List.reverse) $
485 TreeMap.lookup path refsByTerm in
487 (<$> anchs) $ \(term,DTC.Anchor{..}) ->
488 H.a ! HA.class_ "index-iref"
489 ! HA.href (refIdent $ identifyIrefCount term count) $$
490 html5ify $ DTC.posAncestors section
491 BlockReferences{..} ->
492 html5CommonAttrs attrs
493 { classes = "references":classes attrs
494 , DTC.id = Just $ Ident $ Plain.text def $ DTC.posAncestors pos
500 html5ifyToC :: Maybe DTC.Nat -> BodyCursor -> Html5
501 html5ifyToC depth z =
502 let Tree n _ts = Tree.current z in
504 BodySection{..} -> do
506 H.table ! HA.class_ "toc-entry" $$
509 H.td ! HA.class_ "section-number" $$
510 html5SectionRef $ DTC.posAncestors pos
511 H.td ! HA.class_ "section-title" $$
512 html5ify $ cleanPlain $ unTitle title
513 when (maybe True (> Nat 1) depth && not (null sections)) $
516 html5ifyToC (depth >>= predNat)
522 `Tree.axis_filter_current` \case
523 Tree BodySection{} _ -> True
526 html5ifyToF :: [TL.Text] -> Html5
527 html5ifyToF types = do
528 figsByType <- liftStateMarkup $ S.gets state_figures
530 Map.foldMapWithKey (\ty -> ((ty,) <$>)) $
534 Map.intersection figsByType $
535 Map.fromList [(ty,()) | ty <- types]
536 forM_ (Map.toList figs) $ \(pos, (type_, title)) ->
538 H.td ! HA.class_ "figure-number" $$
539 H.a ! HA.href (refIdent $ identify pos) $$ do
541 html5ify $ DTC.posAncestors pos
543 H.td ! HA.class_ "figure-title" $$
544 html5ify $ cleanPlain $ unTitle ti
546 cleanPlain :: Plain -> Plain
549 Tree PlainIref{} ls -> cleanPlain ls
550 Tree PlainNote{} _ -> mempty
551 Tree n ts -> pure $ Tree n $ cleanPlain ts
553 instance Html5ify Para where
557 { classes="para":cls item
561 html5CommonAttrs attrs
562 { classes = "para":classes attrs
566 forM_ items $ \item ->
567 html5AttrClass (cls item) $
570 id_ = Just . Ident . Plain.text def . DTC.posAncestors
573 ParaArtwork{..} -> ["artwork", "artwork-"<>type_]
574 ParaQuote{..} -> ["quote", "quote-"<>type_]
578 instance Html5ify ParaItem where
580 ParaPlain p -> H.p $$ html5ify p
581 ParaArtwork{..} -> H.pre $$ do html5ify text
582 ParaQuote{..} -> H.div $$ do html5ify paras
583 ParaComment t -> html5ify $ H.Comment (H.String $ TL.unpack t) ()
587 forM_ items $ \ListItem{..} -> do
589 H.td ! HA.class_ "name" $$ do
592 H.td ! HA.class_ "value" $$
596 forM_ items $ \item -> do
598 H.dd $$ html5ify item
599 instance Html5ify [Para] where
600 html5ify = mapM_ html5ify
601 instance Html5ify Plain where
607 -- NOTE: gather adjacent PlainNotes
609 | (notes, rest) <- Seq.spanl (\case {Tree PlainNote{} _ -> True; _ -> False}) next -> do
610 H.sup ! HA.class_ "note-numbers" $$ do
612 forM_ notes $ \note -> do
621 instance Html5ify (Tree PlainNode)
622 where html5ify (Tree n ls) =
624 PlainBreak -> html5ify H.br
625 PlainText t -> html5ify t
626 PlainGroup -> html5ify ls
627 PlainB -> H.strong $$ html5ify ls
628 PlainCode -> H.code $$ html5ify ls
629 PlainDel -> H.del $$ html5ify ls
631 i <- liftStateMarkup $ do
632 i <- S.gets $ Plain.state_italic . state_plainify
635 (state_plainify s){Plain.state_italic=
638 H.em ! HA.class_ (if i then "even" else "odd") $$
643 (state_plainify s){Plain.state_italic=i}}
645 html5CommonAttrs attrs $
646 H.span $$ html5ify ls
647 PlainSub -> H.sub $$ html5ify ls
648 PlainSup -> H.sup $$ html5ify ls
649 PlainSC -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
650 PlainU -> H.span ! HA.class_ "underline" $$ html5ify ls
655 H.a ! HA.class_ "note-ref"
656 ! HA.id ("note-ref."<>attrify num)
657 ! HA.href ("#note."<>attrify num) $$
660 H.span ! HA.class_ "q" $$ do
661 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
662 Plain.l10n_Quote (html5ify $ Tree PlainI ls) loc
664 H.a ! HA.class_ "eref"
665 ! HA.href (attrify href) $$
667 then html5ify $ unURL href
671 Nothing -> html5ify ls
673 H.span ! HA.class_ "iref"
674 ! HA.id (attrify $ identifyIrefCount term count) $$
677 H.a ! HA.class_ "ref"
678 ! HA.href (refIdent $ escapeIdent to) $$
680 then html5ify $ unIdent to
683 refs <- liftStateMarkup $ S.gets state_references
684 case Map.lookup to refs of
687 H.span ! HA.class_ "rref-broken" $$
692 forM_ (List.take 1 titles) $ \(Title title) -> do
693 html5ify $ Tree PlainQ $
696 Just u -> pure $ Tree (PlainEref u) title
699 H.a ! HA.class_ "rref"
700 ! HA.href ("#rref."<>attrify to)
701 ! HA.id ("rref."<>attrify to<>maybe "" (\Anchor{..} -> "."<>attrify count) anchor) $$
704 instance Html5ify [Title] where
706 html5ify . fold . List.intersperse sep . toList
707 where sep = Title $ Seq.singleton $ tree0 $ PlainText " — "
708 instance Html5ify About where
709 html5ify About{..} = do
711 [ html5CommasDot $ concat $
713 , html5ify <$> authors
714 , html5ify <$> maybeToList date
715 , html5ify <$> maybeToList editor
716 , html5ify <$> series
719 H.span ! HA.class_ "print-only" $$ do
725 html5Titles :: [Title] -> [Html5]
726 html5Titles ts | null ts = []
727 html5Titles ts = [html5Title $ joinTitles ts]
729 joinTitles = fold . List.intersperse sep . toList
730 sep = Title $ Seq.singleton $ tree0 $ PlainText " — "
731 html5Title (Title title) =
732 html5ify $ Tree PlainQ $
735 Just u -> pure $ Tree (PlainEref u) title
736 instance Html5ify Serie where
737 html5ify s@Serie{id=id_, name} = do
738 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
742 Plain.l10n_Colon loc :: Html5
746 Tree PlainEref{href} $
748 [ tree0 $ PlainText $ name
749 , tree0 $ PlainText $ Plain.l10n_Colon loc
750 , tree0 $ PlainText id_
752 instance Html5ify Entity where
753 html5ify Entity{..} = do
755 _ | not (TL.null email) -> do
756 H.span ! HA.class_ "no-print" $$
758 Tree (PlainEref $ URL $ "mailto:"<>email) $
759 pure $ tree0 $ PlainText name
760 H.span ! HA.class_ "print-only" $$
762 Tree PlainGroup $ Seq.fromList
763 [ tree0 $ PlainText name
764 , tree0 $ PlainText " <"
765 , Tree (PlainEref $ URL $ "mailto:"<>email) $
766 pure $ tree0 $ PlainText email
767 , tree0 $ PlainText ">"
772 pure $ tree0 $ PlainText name
775 tree0 $ PlainText name
780 instance Html5ify Words where
781 html5ify = html5ify . Anchor.plainifyWords
782 instance Html5ify Alias where
783 html5ify Alias{id=id_, ..} = do
784 H.a ! HA.class_ "alias"
785 ! HA.id (attrify $ identify id_) $$
787 instance Html5ify URL where
789 H.a ! HA.class_ "eref"
790 ! HA.href (attrify url) $$
792 instance Html5ify Date where
794 Loqualization loc <- liftStateMarkup $ S.gets state_l10n
795 Plain.l10n_Date date loc
796 instance Html5ify Reference where
797 html5ify Reference{id=id_, ..} =
799 H.td ! HA.class_ "reference-key" $$
800 html5ify $ Tree PlainRref{anchor=Nothing, to=id_} Seq.empty
801 H.td ! HA.class_ "reference-content" $$ do
803 rrefs <- liftStateMarkup $ S.gets state_rrefs
804 case Map.lookup id_ rrefs of
807 H.span ! HA.class_ "reference-rrefs" $$
809 (<$> List.reverse anchs) $ \Anchor{..} ->
810 H.a ! HA.class_ "reference-rref"
811 ! HA.href ("#rref."<>attrify id_<>"."<>attrify count) $$
812 html5ify $ DTC.posAncestors section
813 instance Html5ify PosPath where
821 Text.intercalate "." $
822 Text.pack . show . snd <$> as
823 instance Html5ify Plain.Plain where
825 sp <- liftStateMarkup $ S.gets state_plainify
826 let (t,sp') = Plain.runPlain p sp
828 liftStateMarkup $ S.modify $ \s -> s{state_plainify=sp'}
830 html5CommasDot :: [Html5] -> Html5
831 html5CommasDot [] = pure ()
832 html5CommasDot hs = do
833 sequence_ $ List.intersperse ", " hs
836 html5Lines :: [Html5] -> Html5
837 html5Lines hs = sequence_ $ List.intersperse (html5ify H.br) hs
839 html5Words :: [Html5] -> Html5
840 html5Words hs = sequence_ $ List.intersperse " " hs
842 html5AttrClass :: [TL.Text] -> Html5 -> Html5
843 html5AttrClass = \case
847 (H.AddCustomAttribute "class"
848 (H.String $ TL.unpack $ TL.unwords cls) <$>) .
851 html5AttrId :: Ident -> Html5 -> Html5
852 html5AttrId (Ident id_) =
854 (H.AddCustomAttribute "id"
855 (H.String $ TL.unpack id_) <$>) .
858 html5CommonAttrs :: CommonAttrs -> Html5 -> Html5
859 html5CommonAttrs CommonAttrs{id=id_, ..} =
860 html5AttrClass classes .
861 maybe Cat.id html5AttrId id_
863 html5SectionNumber :: PosPath -> Html5
864 html5SectionNumber = go mempty
866 go :: PosPath -> PosPath -> Html5
868 case Seq.viewl next of
869 Seq.EmptyL -> pure ()
870 a@(_n,rank) Seq.:< as -> do
871 H.a ! HA.href (refIdent $ identify $ prev Seq.|> a) $$
873 when (not (null as) || null prev) $ do
877 html5SectionRef :: PosPath -> Html5
879 H.a ! HA.href (refIdent $ identify as) $$
882 -- * Class 'Identify'
883 class Identify a where
884 identify :: a -> Ident
885 instance Identify Char where
886 identify = Ident . TL.singleton
887 instance Identify String where
888 identify = Ident . TL.pack
889 instance Identify TL.Text where
891 instance Identify (Tree PlainNode) where
892 identify (Tree n ls) =
894 PlainBreak -> identify '\n'
895 PlainText t -> identify t
896 PlainGroup -> identify ls
897 PlainB -> identify ls
898 PlainCode -> identify ls
899 PlainDel -> identify ls
900 PlainI -> identify ls
901 PlainSpan{} -> identify ls
902 PlainSub -> identify ls
903 PlainSup -> identify ls
904 PlainSC -> identify ls
905 PlainU -> identify ls
907 PlainQ -> identify ls
908 PlainEref{} -> identify ls
909 PlainIref{} -> identify ls
910 PlainRef{} -> identify ls
911 PlainRref{..} -> identify to
912 instance Identify Ident where
913 identify (Ident p) = identify p
914 instance Identify Plain where
915 identify = foldMap identify
916 instance Identify Title where
917 identify (Title p) = identify p
918 instance Identify PosPath where
921 snd . foldl' (\(nameParent,acc) (name,rank) ->
923 (if TL.null $ unIdent acc then acc else acc <> ".") <>
924 (if name == nameParent
925 then identify (show rank)
926 else escapeIdentTail $ identify (show name)<>identify (show rank))
930 instance Identify DTC.Pos where
931 identify = identify . DTC.posAncestors
932 instance Identify Path where
933 identify (Path a) = identify a
934 instance Identify Int where
935 identify = fromString . show
936 instance Identify Nat where
937 identify (Nat a) = identify a
938 instance Identify Nat1 where
939 identify (Nat1 a) = identify a
940 instance Identify Anchor where
941 identify Anchor{..} = identify section <> "." <> identify count
943 refIdent :: Ident -> H.AttributeValue
944 refIdent i = "#"<>attrify i
946 escapeIdent :: Ident -> Ident
947 escapeIdent = escapeIdentHead . escapeIdentTail
948 escapeIdentHead :: Ident -> Ident
949 escapeIdentHead (Ident i) = Ident i
950 escapeIdentTail :: Ident -> Ident
951 escapeIdentTail (Ident i) =
954 (\c accum -> (<> accum) $ case c of
956 _ | Char.isAlphaNum c
961 enc = TL.encodeUtf8 $ TL.singleton c
962 bytes = BS.foldr (\b acc -> escape b<>acc) "" enc
963 escape = TL.Builder.toLazyText . TL.Builder.hexadecimal
966 identifyIref :: Words -> Ident
968 "iref" <> "." <> identify (Anchor.plainifyWords term)
969 identifyIrefCount :: Words -> Nat1 -> Ident
970 identifyIrefCount term count =
972 <> "." <> identify (Anchor.plainifyWords term)
973 <> "." <> identify count
976 instance Attrify Plain.Plain where
977 attrify p = attrify t
978 where (t,_) = Plain.runPlain p def
979 instance Attrify Ident where
980 attrify (Ident i) = attrify i
985 ( Plain.L10n msg lang
986 , Plain.L10n TL.Text lang
987 ) => L10n msg lang where
988 l10n_Header_Address :: FullLocale lang -> msg
989 l10n_Header_Date :: FullLocale lang -> msg
990 l10n_Header_Version :: FullLocale lang -> msg
991 l10n_Header_Origin :: FullLocale lang -> msg
992 l10n_Header_Source :: FullLocale lang -> msg
993 instance L10n Html5 EN where
994 l10n_Header_Address _loc = "Address"
995 l10n_Header_Date _loc = "Date"
996 l10n_Header_Origin _loc = "Origin"
997 l10n_Header_Source _loc = "Source"
998 l10n_Header_Version _loc = "Version"
999 instance L10n Html5 FR where
1000 l10n_Header_Address _loc = "Adresse"
1001 l10n_Header_Date _loc = "Date"
1002 l10n_Header_Origin _loc = "Origine"
1003 l10n_Header_Source _loc = "Source"
1004 l10n_Header_Version _loc = "Version"
1006 instance Plain.L10n Html5 EN where
1007 l10n_Colon loc = html5ify (Plain.l10n_Colon loc :: TL.Text)
1008 l10n_Table_of_Contents loc = html5ify (Plain.l10n_Table_of_Contents loc :: TL.Text)
1009 l10n_Date date loc = html5ify (Plain.l10n_Date date loc :: TL.Text)
1010 l10n_Quote msg _loc = do
1011 depth <- liftStateMarkup $ S.gets $ Plain.state_quote . state_plainify
1012 let (o,c) :: (Html5, Html5) =
1013 case unNat depth `mod` 3 of
1018 setDepth $ succNat depth
1024 liftStateMarkup $ S.modify' $ \s ->
1025 s{state_plainify=(state_plainify s){Plain.state_quote=d}}
1026 instance Plain.L10n Html5 FR where
1027 l10n_Colon loc = html5ify (Plain.l10n_Colon loc :: TL.Text)
1028 l10n_Table_of_Contents loc = html5ify (Plain.l10n_Table_of_Contents loc :: TL.Text)
1029 l10n_Date date loc = html5ify (Plain.l10n_Date date loc :: TL.Text)
1030 l10n_Quote msg _loc = do
1031 depth <- liftStateMarkup $ S.gets $ Plain.state_quote . state_plainify
1032 let (o,c) :: (Html5, Html5) =
1033 case unNat depth `mod` 3 of
1038 setDepth $ succNat depth
1044 liftStateMarkup $ S.modify' $ \s ->
1045 s{state_plainify=(state_plainify s){Plain.state_quote=d}}