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