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.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(..))
31 import Data.Map.Strict (Map)
32 import Data.Maybe (Maybe(..), maybe, mapMaybe, fromJust, listToMaybe)
33 import Data.Monoid (Monoid(..))
34 import Data.Ord (Ord(..))
35 import Data.Semigroup (Semigroup(..))
36 import Data.String (String)
37 import Data.Text (Text)
38 import Data.TreeSeq.Strict (Tree(..), Trees)
39 import Data.Tuple (snd)
40 import Prelude (Num(..))
41 import System.FilePath (FilePath)
42 import Text.Blaze ((!))
43 import Text.Blaze.Html (Html)
44 import Text.Show (Show(..))
45 import qualified Control.Monad.Trans.State as S
46 import qualified Data.List as List
47 import qualified Data.Map.Strict as Map
48 import qualified Data.Sequence as Seq
49 import qualified Data.Strict.Maybe as Strict
50 import qualified Data.Text as Text
51 import qualified Data.Text.Lazy as TL
52 import qualified Data.TreeMap.Strict as TreeMap
53 import qualified Data.TreeSeq.Strict as Tree
54 import qualified Data.TreeSeq.Strict.Zipper as Tree
55 import qualified Text.Blaze.Html5 as H
56 import qualified Text.Blaze.Html5.Attributes as HA
57 import qualified Text.Blaze.Internal as H
59 import Text.Blaze.Utils
60 import Data.Locale hiding (localize, Index)
61 import qualified Data.Locale as Locale
63 import Language.DTC.Document (Document)
64 import Language.DTC.Write.XML ()
65 import Language.XML (XmlName(..), XmlPos(..))
66 import qualified Language.DTC.Document as DTC
67 import qualified Language.DTC.Index as Index
68 -- import Debug.Trace (trace)
70 (<&>) :: Functor f => f a -> (a -> b) -> f b
75 type Html5 = StateMarkup State ()
80 { styles :: Map FilePath CSS
81 , scripts :: Map FilePath Script
82 , localize :: MsgHtml5 -> Html5
83 , indexs :: Map XmlPos (DTC.Terms, Index.Refs)
89 , localize = html5ify . show
98 { keys_index :: Map XmlPos DTC.Terms
101 keys :: Trees DTC.BodyKey DTC.BodyValue -> Keys
102 keys body = foldl' flt (Keys mempty) (Compose body)
105 DTC.Index{..} -> acc{keys_index =
106 Map.insert pos terms $ keys_index acc}
109 -- ** Class 'Html5ify'
110 class Html5ify a where
111 html5ify :: a -> Html5
112 instance Html5ify Char where
113 html5ify = html5ify . H.toMarkup
114 instance Html5ify Text where
115 html5ify = html5ify . H.toMarkup
116 instance Html5ify String where
117 html5ify = html5ify . H.toMarkup
118 instance Html5ify H.Markup where
119 html5ify = Compose . return
120 instance Html5ify DTC.Title where
121 html5ify (DTC.Title t) = html5ify t
122 instance Html5ify DTC.Para where
123 html5ify = mapM_ html5ify
124 instance Html5ify DTC.Ident where
125 html5ify (DTC.Ident i) = html5ify i
128 Localize ls Html5 MsgHtml5 =>
130 LocaleIn ls -> Document -> Html
131 html5Document locale DTC.Document{..} = do
132 let Keys{..} = keys body
134 case foldMap Index.refsOfTerms keys_index of
135 refs | null refs -> (body, mempty)
138 (Index.indexify body)
140 { Index.state_refs = refs
141 }) $ \Index.State{state_refs} ->
142 (<$> keys_index) $ \terms ->
144 TreeMap.intersection const state_refs $
145 Index.refsOfTerms terms
146 let (html5Body, State{styles,scripts}) =
147 runStateMarkup state{indexs} $ do
148 liftStateMarkup $ S.modify $ \s -> s{localize = Locale.localize locale}
152 H.html ! HA.lang (attrValue $ countryCode locale) $ do
154 H.meta ! HA.httpEquiv "Content-Type"
155 ! HA.content "text/html; charset=UTF-8"
156 whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
158 H.toMarkup $ plainify $ List.head ts
159 forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
160 H.link ! HA.rel (attrValue rel)
161 ! HA.href (attrValue href)
162 H.meta ! HA.name "generator"
165 (`mapMaybe` toList body) $ \case
166 TreeN k@DTC.Section{} _ -> Just k
168 forM_ chapters $ \DTC.Section{..} ->
169 H.link ! HA.rel "Chapter"
170 ! HA.title (attrValue $ plainify title)
171 ! HA.href ("#"<>attrValue pos)
172 H.link ! HA.rel "stylesheet"
173 ! HA.type_ "text/css"
174 ! HA.href "style/dtc-html5.css"
175 forM_ styles $ \style ->
176 H.style ! HA.type_ "text/css" $
178 forM_ scripts $ \script ->
179 H.script ! HA.type_ "application/javascript" $
184 -- * Type 'BodyCursor'
185 -- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
186 type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
187 instance Html5ify DTC.Body where
189 forM_ (Tree.zippers body) $ \z ->
190 forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
193 instance Html5ify BodyCursor where
195 case Tree.current z of
196 TreeN k _ts -> html5BodyKey z k
197 Tree0 v -> html5BodyValue z v
199 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
200 html5BodyKey z = \case
202 H.section ! HA.class_ "section"
203 ! HA.id (attrValue pos) $$ do
204 html5CommonAttrs attrs $
205 H.table ! HA.class_ "section-header" $$
208 H.td ! HA.class_ "section-number" $$ do
209 html5SectionNumber $ xmlPosAncestors pos
210 H.td ! HA.class_ "section-title" $$ do
211 (case List.length $ xmlPosAncestors pos of
220 forM_ (Tree.axis_child `Tree.runAxis` z) $
222 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
223 html5BodyValue z = \case
224 DTC.Block b -> html5ify b
226 H.nav ! HA.class_ "toc"
227 ! HA.id (attrValue pos) $$ do
228 H.span ! HA.class_ "toc-name" $$
229 H.a ! HA.href (attrValue pos) $$
230 html5ify MsgHTML5_Table_of_Contents
232 forM_ (Tree.axis_following_sibling `Tree.runAxis` z) $
234 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
236 H.nav ! HA.class_ "tof"
237 ! HA.id (attrValue pos) $$
238 H.table ! HA.class_ "tof" $$
240 forM_ (Tree.axis_preceding `Tree.runAxis` z) $
242 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
244 html5CommonAttrs attrs $
245 H.div ! HA.class_ ("figure " <> attrValue ("figure-"<>type_))
246 ! HA.id (attrValue pos) $$ do
247 H.table ! HA.class_ "figure-caption" $$
250 H.td ! HA.class_ "figure-number" $$ do
251 H.a ! HA.href ("#"<>attrValue pos) $$
253 html5ify $ MsgHTML5_Colon
255 H.td ! HA.class_ "figure-name" $$
257 H.div ! HA.class_ "figure-content" $$ do
260 (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . 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)) $$
268 H.dl ! HA.class_ "index-chars" $$
269 forM_ (Map.toList chars) $ \(char,terms) -> do
271 let i = attrValue pos <> "." <> attrValue char in
273 ! HA.href ("#"<>i) $$
276 H.dl ! HA.class_ "index-term" $$ do
277 forM_ terms $ \aliases -> do
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}) $$
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
291 List.intersperse ", " $
292 (<$> refs) $ \ref@Index.Ref{..} ->
293 H.a ! HA.href ("#"<>attrValue ref) $$
295 List.intercalate "." $
297 (<$> xmlPosAncestors section) $ \(_n,c) -> show c
299 instance Html5ify DTC.Words where
300 html5ify = html5ify . Index.plainifyWords
302 html5ToC :: Int -> BodyCursor -> Html5
304 case Tree.current z of
305 TreeN DTC.Section{..} _ts -> do
307 H.table ! HA.class_ "toc-entry" $$
310 H.td ! HA.class_ "section-number" $$
311 html5SectionRef $ xmlPosAncestors pos
312 H.td ! HA.class_ "section-title" $$
314 DTC.unTitle title >>= \ts -> Tree.bindTrees ts $ \case
315 TreeN DTC.Iref{} ls -> ls
316 TreeN DTC.Note{} _ -> mempty
321 `Tree.axis_filter_current` \case
322 TreeN DTC.Section{} _ -> True
324 when (depth > 0 && not (null sections)) $
330 html5ToF :: Int -> BodyCursor -> Html5
332 case Tree.current z of
337 H.td ! HA.class_ "figure-number" $$
338 H.a ! HA.href ("#"<>attrValue pos) $$
340 H.td ! HA.class_ "figure-name" $$
345 instance Html5ify [DTC.Block] where
346 html5ify = mapM_ html5ify
347 instance Html5ify DTC.Block where
350 html5CommonAttrs attrs $
351 H.p ! HA.class_ "para"
352 ! HA.id (attrValue pos) $$ do
355 html5CommonAttrs attrs $
356 H.ol ! HA.class_ "ol"
357 ! HA.id (attrValue pos) $$ do
358 forM_ items $ \item ->
359 H.li $$ html5ify item
361 html5CommonAttrs attrs $
362 H.ul ! HA.class_ "ul"
363 ! HA.id (attrValue pos) $$ do
364 forM_ items $ \item ->
365 H.li $$ html5ify item
367 html5CommonAttrs attrs $
368 H.div ! HA.class_ "rl"
369 ! HA.id (attrValue pos) $$ do
373 html5ify $ H.Comment (H.Text t) ()
374 instance Html5ify DTC.Lines where
378 DTC.BR -> html5ify H.br
379 DTC.Plain t -> html5ify t
382 DTC.B -> H.strong $$ html5ify ls
383 DTC.Code -> H.code $$ html5ify ls
384 DTC.Del -> H.del $$ html5ify ls
385 DTC.I -> H.i $$ html5ify ls
386 DTC.Sub -> H.sub $$ html5ify ls
387 DTC.Sup -> H.sup $$ html5ify ls
388 DTC.SC -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
389 DTC.U -> H.span ! HA.class_ "underline" $$ html5ify ls
392 H.span ! HA.class_ "q" $$ do
393 html5ify MsgHTML5_QuoteOpen
395 html5ify MsgHTML5_QuoteClose
397 H.a ! HA.class_ "eref"
398 ! HA.href (attrValue href) $$
401 H.span ! HA.class_ "iref"
402 ! HA.id (attrValue Index.Ref{term, count, section=def}) $$
405 H.a ! HA.class_ "ref"
406 ! HA.href ("#"<>attrValue to) $$
411 H.a ! HA.class_ "rref"
412 ! HA.href (attrValue to) $$
414 instance AttrValue Index.Ref where
415 attrValue Index.Ref{..} =
416 "iref" <> "." <> attrValue (Index.plainifyWords term) <>
418 then "." <> attrValue count
420 instance Html5ify DTC.About where
421 html5ify DTC.About{..} =
422 forM_ titles $ \(DTC.Title title) ->
423 html5ify $ Seq.singleton $ TreeN DTC.Q title
424 instance Html5ify DTC.Reference where
425 html5ify DTC.Reference{id=id_, ..} =
427 H.td ! HA.class_ "reference-key" $$
429 H.td ! HA.class_ "reference-content" $$
432 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
433 html5CommonAttrs DTC.CommonAttrs{id=id_, ..} =
434 Compose . (addClass . addId <$>) . getCompose
439 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
440 addId = maybe id (\(DTC.Ident i) ->
441 H.AddCustomAttribute "id" (H.Text i)) id_
443 html5SectionNumber :: [(XmlName,Int)] -> Html5
444 html5SectionNumber = go [] . List.reverse
446 go :: [(XmlName,Int)] -> [(XmlName,Int)] -> Html5
448 go rs (a@(_n,cnt):as) = do
449 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors (a:rs)) $$
451 when (not (null as) || null rs) $ do
455 html5SectionRef :: [(XmlName,Int)] -> Html5
457 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors as) $$
464 Text.intercalate "." $
465 Text.pack . show . snd <$> as
467 textXmlPosAncestors :: [(XmlName,Int)] -> Text
468 textXmlPosAncestors =
469 snd . foldr (\(n,c) (nParent,acc) ->
482 -- * Class 'Plainify'
483 class Plainify a where
484 plainify :: a -> TL.Text
485 instance Plainify TL.Text where
487 instance Plainify Text where
488 plainify = TL.fromStrict
489 instance Plainify DTC.Para where
490 plainify = foldMap plainify
491 instance Plainify DTC.Lines where
496 DTC.Plain p -> plainify p
499 DTC.B -> "*"<>plainify ls<>"*"
500 DTC.Code -> "`"<>plainify ls<>"`"
501 DTC.Del -> "-"<>plainify ls<>"-"
502 DTC.I -> "/"<>plainify ls<>"/"
504 DTC.Q -> "« "<>plainify ls<>" »"
505 DTC.SC -> plainify ls
506 DTC.Sub -> plainify ls
507 DTC.Sup -> plainify ls
508 DTC.U -> "_"<>plainify ls<>"_"
509 DTC.Eref{..} -> plainify ls
510 DTC.Iref{..} -> plainify ls
511 DTC.Ref{..} -> plainify ls
512 DTC.Rref{..} -> plainify ls
513 instance Plainify DTC.Title where
514 plainify (DTC.Title t) = plainify t
516 instance AttrValue XmlPos where
517 attrValue = attrValue . textXmlPosAncestors . xmlPosAncestors
521 = MsgHTML5_Table_of_Contents
524 | MsgHTML5_QuoteClose
526 instance Html5ify MsgHtml5 where
528 loc <- liftStateMarkup $ S.gets localize
530 instance LocalizeIn FR Html5 MsgHtml5 where
532 MsgHTML5_Table_of_Contents -> "Sommaire"
533 MsgHTML5_Colon -> " :"
534 MsgHTML5_QuoteOpen -> "« "
535 MsgHTML5_QuoteClose -> " »"
536 instance LocalizeIn EN Html5 MsgHtml5 where
538 MsgHTML5_Table_of_Contents -> "Summary"
539 MsgHTML5_Colon -> ":"
540 MsgHTML5_QuoteOpen -> "“"
541 MsgHTML5_QuoteClose -> "”"