]> Git — Sourcephile - doclang.git/blob - Language/DTC/Write/HTML5.hs
Fix ToF ordering.
[doclang.git] / Language / DTC / Write / HTML5.hs
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
11
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
21 import Control.Monad
22 import Data.Bool
23 import Data.Char (Char)
24 import Data.Default.Class (Default(..))
25 import Data.Eq (Eq(..))
26 import Data.Foldable (Foldable(..))
27 import Data.Function (($), const, flip, on)
28 import Data.Functor (Functor(..), (<$>))
29 import Data.Functor.Compose (Compose(..))
30 import Data.Map.Strict (Map)
31 import Data.Maybe (Maybe(..), maybe, mapMaybe, fromJust, listToMaybe)
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
56
57 import Text.Blaze.Utils
58 import Data.Locale hiding (localize, Index)
59 import qualified Data.Locale as Locale
60
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.Index as Index
65
66 (<&>) :: Functor f => f a -> (a -> b) -> f b
67 (<&>) = flip (<$>)
68 infixl 4 <&>
69
70 -- * Type 'Html5'
71 type Html5 = StateMarkup State ()
72
73 -- ** Type 'State'
74 data State
75 = 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, Index.Refs)
80 , state_figures :: Map Text (Map DTC.Pos DTC.Title)
81 }
82 state :: State
83 state = State
84 { state_styles = mempty
85 , state_scripts = mempty
86 , state_localize = html5ify . show
87 , state_indexs = mempty
88 , state_figures = mempty
89 }
90 type CSS = Text
91 type Script = Text
92
93 -- ** Type 'Keys'
94 data Keys
95 = Keys
96 { keys_index :: Map DTC.Pos DTC.Terms
97 , keys_figure :: Map Text (Map DTC.Pos DTC.Title)
98 } deriving (Show)
99
100 keys :: Trees DTC.BodyKey DTC.BodyValue -> Keys
101 keys body = foldl' flt (Keys mempty mempty) (Compose body)
102 where
103 flt acc = \case
104 DTC.Index{..} -> acc{keys_index =
105 Map.insert pos terms $ keys_index acc}
106 DTC.Figure{..} -> acc{keys_figure =
107 Map.insertWith (<>)
108 type_ (Map.singleton pos title) $
109 keys_figure acc}
110 _ -> acc
111
112 -- ** Class 'Html5ify'
113 class Html5ify a where
114 html5ify :: a -> Html5
115 instance Html5ify Char where
116 html5ify = html5ify . H.toMarkup
117 instance Html5ify Text where
118 html5ify = html5ify . H.toMarkup
119 instance Html5ify String where
120 html5ify = html5ify . H.toMarkup
121 instance Html5ify H.Markup where
122 html5ify = Compose . return
123 instance Html5ify DTC.Title where
124 html5ify (DTC.Title t) = html5ify t
125 instance Html5ify DTC.Para where
126 html5ify = mapM_ html5ify
127 instance Html5ify DTC.Ident where
128 html5ify (DTC.Ident i) = html5ify i
129
130 html5Document ::
131 Localize ls Html5 MsgHtml5 =>
132 Locales ls =>
133 LocaleIn ls -> Document -> Html
134 html5Document locale DTC.Document{..} = do
135 let Keys{..} = keys body
136 let (body',state_indexs) =
137 case foldMap Index.refsOfTerms keys_index of
138 refs | null refs -> (body, mempty)
139 | otherwise ->
140 (<$> S.runState
141 (Index.indexify body)
142 Index.state
143 { Index.state_refs = refs }) $ \Index.State{state_refs} ->
144 (<$> keys_index) $ \terms ->
145 (terms,) $
146 TreeMap.intersection const state_refs $
147 Index.refsOfTerms terms
148 let (html5Body, State{state_styles,state_scripts}) =
149 runStateMarkup state{state_indexs, state_figures=keys_figure} $ do
150 liftStateMarkup $ S.modify $ \s -> s{state_localize = Locale.localize locale}
151 html5ify body'
152
153 H.docType
154 H.html ! HA.lang (attrValue $ countryCode locale) $ do
155 H.head $ do
156 H.meta ! HA.httpEquiv "Content-Type"
157 ! HA.content "text/html; charset=UTF-8"
158 whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
159 H.title $
160 H.toMarkup $ plainify $ List.head ts
161 forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
162 H.link ! HA.rel (attrValue rel)
163 ! HA.href (attrValue href)
164 H.meta ! HA.name "generator"
165 ! HA.content "tct"
166 let chapters =
167 (`mapMaybe` toList body) $ \case
168 TreeN k@DTC.Section{} _ -> Just k
169 _ -> Nothing
170 forM_ chapters $ \DTC.Section{..} ->
171 H.link ! HA.rel "Chapter"
172 ! HA.title (attrValue $ plainify title)
173 ! HA.href ("#"<>attrValue pos)
174 H.link ! HA.rel "stylesheet"
175 ! HA.type_ "text/css"
176 ! HA.href "style/dtc-html5.css"
177 forM_ state_styles $ \style ->
178 H.style ! HA.type_ "text/css" $
179 H.toMarkup style
180 forM_ state_scripts $ \script ->
181 H.script ! HA.type_ "application/javascript" $
182 H.toMarkup script
183 H.body
184 html5Body
185
186 -- * Type 'BodyCursor'
187 -- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
188 type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
189 instance Html5ify DTC.Body where
190 html5ify body =
191 forM_ (Tree.zippers body) $ \z ->
192 forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
193 html5ify
194
195 instance Html5ify BodyCursor where
196 html5ify z =
197 case Tree.current z of
198 TreeN k _ts -> html5BodyKey z k
199 Tree0 v -> html5BodyValue z v
200
201 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
202 html5BodyKey z = \case
203 DTC.Section{..} ->
204 H.section ! HA.class_ "section"
205 ! HA.id (attrValue pos) $$ do
206 html5CommonAttrs attrs $
207 H.table ! HA.class_ "section-header" $$
208 H.tbody $$
209 H.tr $$ do
210 H.td ! HA.class_ "section-number" $$ do
211 html5SectionNumber $ DTC.posAncestors pos
212 H.td ! HA.class_ "section-title" $$ do
213 (case List.length $ DTC.posAncestors pos of
214 0 -> H.h1
215 1 -> H.h2
216 2 -> H.h3
217 3 -> H.h4
218 4 -> H.h5
219 5 -> H.h6
220 _ -> H.h6) $$
221 html5ify title
222 forM_ (Tree.axis_child `Tree.runAxis` z) $
223 html5ify
224 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
225 html5BodyValue z = \case
226 DTC.Block b -> html5ify b
227 DTC.ToC{..} -> do
228 H.nav ! HA.class_ "toc"
229 ! HA.id (attrValue pos) $$ do
230 H.span ! HA.class_ "toc-name" $$
231 H.a ! HA.href (attrValue pos) $$
232 html5ify MsgHTML5_Table_of_Contents
233 H.ul $$
234 forM_ (Tree.axis_following_sibling `Tree.runAxis` z) $
235 html5ifyToC depth
236 DTC.ToF{..} -> do
237 H.nav ! HA.class_ "tof"
238 ! HA.id (attrValue pos) $$
239 H.table ! HA.class_ "tof" $$
240 H.tbody $$
241 html5ifyToF types
242 DTC.Figure{..} ->
243 html5CommonAttrs attrs $
244 H.div ! HA.class_ ("figure " <> attrValue ("figure-"<>type_))
245 ! HA.id (attrValue pos) $$ do
246 H.table ! HA.class_ "figure-caption" $$
247 H.tbody $$
248 H.tr $$ do
249 H.td ! HA.class_ "figure-number" $$ do
250 H.a ! HA.href ("#"<>attrValue pos) $$ do
251 html5ify type_
252 html5ify $ DTC.posAncestors pos
253 html5ify $ MsgHTML5_Colon
254 " "
255 H.td ! HA.class_ "figure-name" $$
256 html5ify title
257 H.div ! HA.class_ "figure-content" $$ do
258 html5ify blocks
259 DTC.Index{pos} -> do
260 (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . state_indexs
261 let chars = Index.termsByChar allTerms
262 H.div ! HA.class_ "index"
263 ! HA.id (attrValue pos) $$ do
264 H.nav ! HA.class_ "index-nav" $$ do
265 forM_ (Map.keys chars) $ \char ->
266 H.a ! HA.href ("#"<>(attrValue pos <> "." <> attrValue char)) $$
267 html5ify char
268 H.dl ! HA.class_ "index-chars" $$
269 forM_ (Map.toList chars) $ \(char,terms) -> do
270 H.dt $$
271 let i = attrValue pos <> "." <> attrValue char in
272 H.a ! HA.id i
273 ! HA.href ("#"<>i) $$
274 html5ify char
275 H.dd $$
276 H.dl ! HA.class_ "index-term" $$ do
277 forM_ terms $ \aliases -> do
278 H.dt $$
279 H.ul ! HA.class_ "index-aliases" $$
280 forM_ (listToMaybe aliases) $ \term ->
281 H.li ! HA.id (attrValue Index.Ref{term, count=0, section=def}) $$
282 html5ify term
283 H.dd $$ do
284 let refs =
285 List.sortBy (compare `on` Index.section) $
286 (`foldMap` aliases) $ \words -> fromJust $ do
287 path <- Index.pathFromWords words
288 Strict.maybe Nothing (Just . List.reverse) $
289 TreeMap.lookup path refsByTerm
290 sequence_ $
291 List.intersperse ", " $
292 (<$> refs) $ \ref@Index.Ref{..} ->
293 H.a ! HA.href ("#"<>attrValue ref) $$
294 html5ify $
295 List.intercalate "." $
296 toList $
297 (<$> DTC.posAncestors section) $ \(_n,c) -> show c
298
299 instance Html5ify DTC.Words where
300 html5ify = html5ify . Index.plainifyWords
301
302 cleanPara :: DTC.Para -> DTC.Para
303 cleanPara p =
304 p >>= (`Tree.bindTrees` \case
305 TreeN DTC.Iref{} ls -> ls
306 TreeN DTC.Note{} _ -> mempty
307 h -> pure h)
308
309 html5ifyToC :: Maybe DTC.Nat -> BodyCursor -> Html5
310 html5ifyToC depth z =
311 case Tree.current z of
312 TreeN DTC.Section{..} _ts -> do
313 H.li $$ do
314 H.table ! HA.class_ "toc-entry" $$
315 H.tbody $$
316 H.tr $$ do
317 H.td ! HA.class_ "section-number" $$
318 html5SectionRef $ DTC.posAncestors pos
319 H.td ! HA.class_ "section-title" $$
320 html5ify $ cleanPara $ DTC.unTitle title
321 when (maybe True (> DTC.Nat 1) depth && not (null sections)) $
322 H.ul $$
323 forM_ sections $
324 html5ifyToC (depth >>= DTC.predNat)
325 _ -> pure ()
326 where
327 sections =
328 (`Tree.runAxis` z) $
329 Tree.axis_child
330 `Tree.axis_filter_current` \case
331 TreeN DTC.Section{} _ -> True
332 _ -> False
333
334 html5ifyToF :: [Text] -> Html5
335 html5ifyToF types = do
336 figsByType <- liftStateMarkup $ S.gets state_figures
337 let figs =
338 Map.foldMapWithKey (\ty -> ((ty,) <$>)) $
339 if null types
340 then figsByType
341 else
342 Map.intersection figsByType $
343 Map.fromList [(ty,()) | ty <- types]
344 forM_ (Map.toList figs) $ \(pos, (type_, title)) ->
345 H.tr $$ do
346 H.td ! HA.class_ "figure-number" $$
347 H.a ! HA.href ("#"<>attrValue pos) $$ do
348 html5ify type_
349 html5ify $ DTC.posAncestors pos
350 H.td ! HA.class_ "figure-name" $$
351 html5ify $ cleanPara $ DTC.unTitle title
352
353 instance Html5ify [DTC.Block] where
354 html5ify = mapM_ html5ify
355 instance Html5ify DTC.Block where
356 html5ify = \case
357 DTC.Para{..} ->
358 html5CommonAttrs attrs $
359 H.p ! HA.class_ "para"
360 ! HA.id (attrValue pos) $$ do
361 html5ify para
362 DTC.OL{..} ->
363 html5CommonAttrs attrs $
364 H.ol ! HA.class_ "ol"
365 ! HA.id (attrValue pos) $$ do
366 forM_ items $ \item ->
367 H.li $$ html5ify item
368 DTC.UL{..} ->
369 html5CommonAttrs attrs $
370 H.ul ! HA.class_ "ul"
371 ! HA.id (attrValue pos) $$ do
372 forM_ items $ \item ->
373 H.li $$ html5ify item
374 DTC.RL{..} ->
375 html5CommonAttrs attrs $
376 H.div ! HA.class_ "rl"
377 ! HA.id (attrValue pos) $$ do
378 H.table $$
379 forM_ refs html5ify
380 DTC.Comment t ->
381 html5ify $ H.Comment (H.Text t) ()
382 instance Html5ify DTC.Lines where
383 html5ify = \case
384 Tree0 v ->
385 case v of
386 DTC.BR -> html5ify H.br
387 DTC.Plain t -> html5ify t
388 TreeN k ls ->
389 case k of
390 DTC.B -> H.strong $$ html5ify ls
391 DTC.Code -> H.code $$ html5ify ls
392 DTC.Del -> H.del $$ html5ify ls
393 DTC.I -> H.i $$ html5ify ls
394 DTC.Sub -> H.sub $$ html5ify ls
395 DTC.Sup -> H.sup $$ html5ify ls
396 DTC.SC -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
397 DTC.U -> H.span ! HA.class_ "underline" $$ html5ify ls
398 DTC.Note -> ""
399 DTC.Q ->
400 H.span ! HA.class_ "q" $$ do
401 html5ify MsgHTML5_QuoteOpen
402 H.i $$ html5ify ls
403 html5ify MsgHTML5_QuoteClose
404 DTC.Eref{..} ->
405 H.a ! HA.class_ "eref"
406 ! HA.href (attrValue href) $$
407 html5ify ls
408 DTC.Iref{..} ->
409 H.span ! HA.class_ "iref"
410 ! HA.id (attrValue Index.Ref{term, count, section=def}) $$
411 html5ify ls
412 DTC.Ref{..} ->
413 H.a ! HA.class_ "ref"
414 ! HA.href ("#"<>attrValue to) $$
415 if null ls
416 then html5ify to
417 else html5ify ls
418 DTC.Rref{..} ->
419 H.a ! HA.class_ "rref"
420 ! HA.href (attrValue to) $$
421 html5ify ls
422 instance AttrValue Index.Ref where
423 attrValue Index.Ref{..} =
424 "iref" <> "." <> attrValue (Index.plainifyWords term) <>
425 if count > 0
426 then "." <> attrValue count
427 else ""
428 instance Html5ify DTC.About where
429 html5ify DTC.About{..} =
430 forM_ titles $ \(DTC.Title title) ->
431 html5ify $ Seq.singleton $ TreeN DTC.Q title
432 instance Html5ify DTC.Reference where
433 html5ify DTC.Reference{id=id_, ..} =
434 H.tr $$ do
435 H.td ! HA.class_ "reference-key" $$
436 html5ify id_
437 H.td ! HA.class_ "reference-content" $$
438 html5ify about
439
440 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
441 html5CommonAttrs DTC.CommonAttrs{id=id_, ..} =
442 Compose . (addClass . addId <$>) . getCompose
443 where
444 addClass =
445 case classes of
446 [] -> id
447 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
448 addId = maybe id (\(DTC.Ident i) ->
449 H.AddCustomAttribute "id" (H.Text i)) id_
450
451 html5SectionNumber :: DTC.PosPath -> Html5
452 html5SectionNumber = go mempty
453 where
454 go :: DTC.PosPath -> DTC.PosPath -> Html5
455 go prev next =
456 case Seq.viewl next of
457 Seq.EmptyL -> pure ()
458 a@(_n,rank) Seq.:< as -> do
459 H.a ! HA.href ("#"<>attrValue (prev Seq.|>a)) $$
460 html5ify $ show rank
461 when (not (null as) || null prev) $ do
462 html5ify '.'
463 go (prev Seq.|>a) as
464
465 html5SectionRef :: DTC.PosPath -> Html5
466 html5SectionRef as =
467 H.a ! HA.href ("#"<>attrValue as) $$
468 html5ify as
469
470 instance Html5ify DTC.PosPath where
471 html5ify ancs =
472 case toList ancs of
473 [(_n,c)] -> do
474 html5ify $ show c
475 html5ify '.'
476 as ->
477 html5ify $
478 Text.intercalate "." $
479 Text.pack . show . snd <$> as
480
481 instance AttrValue DTC.PosPath where
482 attrValue = attrValue . plainify
483 instance AttrValue DTC.Pos where
484 attrValue = attrValue . DTC.posAncestors
485
486 -- * Class 'Plainify'
487 class Plainify a where
488 plainify :: a -> TL.Text
489 instance Plainify TL.Text where
490 plainify = id
491 instance Plainify Text where
492 plainify = TL.fromStrict
493 instance Plainify DTC.Para where
494 plainify = foldMap plainify
495 instance Plainify DTC.Lines where
496 plainify = \case
497 Tree0 v ->
498 case v of
499 DTC.BR -> "\n"
500 DTC.Plain p -> plainify p
501 TreeN k ls ->
502 case k of
503 DTC.B -> "*"<>plainify ls<>"*"
504 DTC.Code -> "`"<>plainify ls<>"`"
505 DTC.Del -> "-"<>plainify ls<>"-"
506 DTC.I -> "/"<>plainify ls<>"/"
507 DTC.Note -> ""
508 DTC.Q -> "« "<>plainify ls<>" »"
509 DTC.SC -> plainify ls
510 DTC.Sub -> plainify ls
511 DTC.Sup -> plainify ls
512 DTC.U -> "_"<>plainify ls<>"_"
513 DTC.Eref{..} -> plainify ls
514 DTC.Iref{..} -> plainify ls
515 DTC.Ref{..} -> plainify ls
516 DTC.Rref{..} -> plainify ls
517 instance Plainify DTC.Title where
518 plainify (DTC.Title t) = plainify t
519 instance Plainify DTC.PosPath where
520 plainify =
521 snd . foldl' (\(nParent,acc) (n,c) ->
522 (n,
523 (if TL.null acc
524 then acc
525 else acc <> ".") <>
526 TL.pack
527 (if n == nParent
528 then show c
529 else show n<>show c)
530 )
531 )
532 ("","")
533
534 -- * Type 'MsgHtml5'
535 data MsgHtml5
536 = MsgHTML5_Table_of_Contents
537 | MsgHTML5_Colon
538 | MsgHTML5_QuoteOpen
539 | MsgHTML5_QuoteClose
540 deriving (Show)
541 instance Html5ify MsgHtml5 where
542 html5ify msg = do
543 loc <- liftStateMarkup $ S.gets state_localize
544 loc msg
545 instance LocalizeIn FR Html5 MsgHtml5 where
546 localizeIn _ = \case
547 MsgHTML5_Table_of_Contents -> "Sommaire"
548 MsgHTML5_Colon -> " :"
549 MsgHTML5_QuoteOpen -> "« "
550 MsgHTML5_QuoteClose -> " »"
551 instance LocalizeIn EN Html5 MsgHtml5 where
552 localizeIn _ = \case
553 MsgHTML5_Table_of_Contents -> "Summary"
554 MsgHTML5_Colon -> ":"
555 MsgHTML5_QuoteOpen -> "“"
556 MsgHTML5_QuoteClose -> "”"