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.Monad (Monad(..), sequence_, forM_, mapM_, when, (>=>))
22 import Data.Char (Char)
23 import Data.Default.Class (Default(..))
24 import Data.Eq (Eq(..))
25 import Data.Foldable (Foldable(..), concat, any)
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(..), mapMaybe)
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 Prelude (Num(..))
40 import System.FilePath (FilePath)
41 import Text.Blaze ((!))
42 import Text.Blaze.Html (Html)
43 import Text.Show (Show(..))
44 import qualified Control.Monad.Trans.State as S
45 import qualified Data.List as List
46 import qualified Data.Map.Strict as Map
47 import qualified Data.Set as Set
48 import qualified Data.Text as Text
49 import qualified Data.Text.Lazy as TL
50 import qualified Data.TreeSeq.Strict.Zipper as Tree
51 import qualified Text.Blaze.Html5 as H
52 import qualified Text.Blaze.Html5.Attributes as HA
53 import qualified Text.Blaze.Internal as H
55 import Text.Blaze.Utils
56 import Data.Locale hiding (localize, Index)
57 import qualified Data.Locale as Locale
59 import Language.DTC.Document (Document)
60 import Language.DTC.Write.XML ()
61 import Language.XML (XmlName(..), XmlPos(..))
62 import qualified Language.DTC.Document as DTC
63 import qualified Language.DTC.Index as Index
64 -- import Debug.Trace (trace)
66 (<&>) :: Functor f => f a -> (a -> b) -> f b
71 type Html5 = StateMarkup State ()
76 { styles :: Map FilePath CSS
77 , scripts :: Map FilePath Script
78 , localize :: MsgHtml5 -> Html5
79 , indexs :: Map XmlPos ( [[Index.Term]]
80 , Map Index.Term [Index.Ref] )
86 , localize = html5ify . show
95 { keys_index :: Map XmlPos [[Index.Term]]
98 keys :: Trees DTC.BodyKey DTC.BodyValue -> Keys
99 keys body = foldl' flt (Keys mempty) (Compose body)
102 DTC.Index{..} -> acc{keys_index =
103 Map.insert pos terms $ keys_index acc}
106 -- ** Class 'Html5ify'
107 class Html5ify a where
108 html5ify :: a -> Html5
109 instance Html5ify Char where
110 html5ify = html5ify . H.toMarkup
111 instance Html5ify Text where
112 html5ify = html5ify . H.toMarkup
113 instance Html5ify String where
114 html5ify = html5ify . H.toMarkup
115 instance Html5ify H.Markup where
116 html5ify = Compose . return
117 instance Html5ify DTC.Title where
118 html5ify (DTC.Title t) = html5ify t
119 instance Html5ify DTC.Ident where
120 html5ify (DTC.Ident i) = html5ify i
123 Localize ls Html5 MsgHtml5 =>
125 LocaleIn ls -> Document -> Html
126 html5Document locale DTC.Document{..} = do
127 let Keys{..} = keys body
132 let allTerms = (Set.fromList . concat) `foldMap` keys_index in
134 (Index.indexify body)
136 { Index.state_terms = Map.fromSet (const []) allTerms
137 }) $ \Index.State{state_terms} ->
138 (<$> keys_index) $ \terms ->
140 Map.intersection state_terms $
141 Map.fromSet (const ()) $
142 Set.fromList $ concat $
144 let (html5Body, State{styles,scripts}) =
145 runStateMarkup state{indexs} $ do
146 liftStateMarkup $ S.modify $ \s -> s{localize = Locale.localize locale}
150 H.html ! HA.lang (attrValue $ countryCode locale) $ do
152 H.meta ! HA.httpEquiv "Content-Type"
153 ! HA.content "text/html; charset=UTF-8"
154 whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
156 H.toMarkup $ plainify $ List.head ts
157 forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
158 H.link ! HA.rel (attrValue rel)
159 ! HA.href (attrValue href)
160 H.meta ! HA.name "generator"
163 (`mapMaybe` toList body) $ \case
164 TreeN k@DTC.Section{} _ -> Just k
166 forM_ chapters $ \DTC.Section{..} ->
167 H.link ! HA.rel "Chapter"
168 ! HA.title (attrValue $ plainify title)
169 ! HA.href ("#"<>attrValue pos)
170 H.link ! HA.rel "stylesheet"
171 ! HA.type_ "text/css"
172 ! HA.href "style/dtc-html5.css"
173 forM_ styles $ \style ->
174 H.style ! HA.type_ "text/css" $
176 forM_ scripts $ \script ->
177 H.script ! HA.type_ "application/javascript" $
182 -- * Type 'BodyCursor'
183 -- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
184 type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
185 instance Html5ify DTC.Body where
187 forM_ (Tree.zippers body >>= Tree.axis_repeat Tree.axis_following1) $
190 instance Html5ify BodyCursor where
192 case Tree.current z of
193 TreeN k _ts -> html5BodyKey z k
194 Tree0 v -> html5BodyValue z v
196 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
197 html5BodyKey z = \case
199 H.section ! HA.class_ "section"
200 ! HA.id (attrValue pos) $$ do
201 html5CommonAttrs attrs $
202 H.table ! HA.class_ "section-header" $$
205 H.td ! HA.class_ "section-number" $$ do
208 H.td ! HA.class_ "section-title" $$ do
209 (case List.length $ xmlPosAncestors pos of
218 forM_ (Tree.axis_child z) $
220 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
221 html5BodyValue z = \case
225 H.nav ! HA.class_ "toc"
226 ! HA.id (attrValue pos) $$ do
227 H.span ! HA.class_ "toc-name" $$
228 H.a ! HA.href (attrValue pos) $$
229 html5ify MsgHTML5_Table_of_Contents
231 forM_ (Tree.axis_following_sibling z) $
233 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
235 H.nav ! HA.class_ "tof"
236 ! HA.id (attrValue pos) $$
237 H.table ! HA.class_ "tof" $$
239 forM_ (Tree.axis_preceding z) $
241 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
243 html5CommonAttrs attrs $
244 H.div ! HA.class_ (attrValue $ "figure-"<>type_)
245 ! HA.id (attrValue pos) $$ do
246 H.table ! HA.class_ "figure-caption" $$
249 H.td ! HA.class_ "figure-number" $$ do
250 H.a ! HA.href ("#"<>attrValue pos) $$
253 H.td ! HA.class_ "figure-name" $$
255 H.div ! HA.class_ "figure-content" $$ do
258 (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . indexs
259 let chars = Index.aliasesByChar allTerms
260 H.div ! HA.class_ "index"
261 ! HA.id (attrValue pos) $$ do
262 H.nav ! HA.class_ "index-nav-chars" $$ do
263 forM_ (Map.keys chars) $ \char ->
264 H.a ! HA.href ("#"<>(attrValue pos <> "." <> attrValue char)) $$
267 forM_ (Map.toList chars) $ \(char,terms) -> do
269 let i = attrValue pos <> "." <> attrValue char in
271 ! HA.href ("#"<>i) $$
274 H.dl ! HA.class_ "index-char-refs" $$ do
275 forM_ terms $ \aliases -> do
277 forM_ aliases $ \term ->
279 H.li ! HA.id (attrValue Index.Ref{term, count=0, section=def}) $$
284 (compare `on` Index.section) $
285 (`foldMap` aliases) $ \term ->
286 refsByTerm Map.! term
288 List.intersperse ", " $
289 (<$> refs) $ \ref@Index.Ref{..} ->
290 H.a ! HA.href ("#"<>attrValue ref) $$
292 List.intercalate "." $
294 (<$> xmlPosAncestors section) $ \(_n,c) -> show c
296 html5ToC :: Int -> BodyCursor -> Html5
298 case Tree.current z of
299 TreeN DTC.Section{..} _ts -> do
301 H.table ! HA.class_ "toc-entry" $$
304 H.td ! HA.class_ "section-number" $$
305 html5SectionRef $ xmlPosAncestors pos
306 H.td ! HA.class_ "section-title" $$
308 DTC.unTitle title >>= \case
313 ($ z) $ Tree.axis_child
314 `Tree.axis_filter_current` \case
315 TreeN DTC.Section{} _ -> True
317 when (depth > 0 && not (null sections)) $
323 html5ToF :: Int -> BodyCursor -> Html5
325 case Tree.current z of
330 H.td ! HA.class_ "figure-number" $$
331 H.a ! HA.href ("#"<>attrValue pos) $$
333 H.td ! HA.class_ "figure-name" $$
338 instance Html5ify [DTC.Vertical] where
339 html5ify = mapM_ html5ify
340 instance Html5ify DTC.Vertical where
343 html5CommonAttrs attrs $
344 H.p ! HA.class_ "para"
345 ! HA.id (attrValue pos) $$ do
348 html5CommonAttrs attrs $
349 H.ol ! HA.class_ "ol"
350 ! HA.id (attrValue pos) $$ do
351 forM_ items $ \item ->
352 H.li $$ html5ify item
354 html5CommonAttrs attrs $
355 H.ul ! HA.class_ "ul"
356 ! HA.id (attrValue pos) $$ do
357 forM_ items $ \item ->
358 H.li $$ html5ify item
360 html5CommonAttrs attrs $
361 H.div ! HA.class_ "rl"
362 ! HA.id (attrValue pos) $$ do
366 html5ify $ H.Comment (H.Text t) ()
367 instance Html5ify DTC.Horizontal where
369 DTC.BR -> html5ify H.br
370 DTC.B hs -> H.strong $$ html5ify hs
371 DTC.Code hs -> H.code $$ html5ify hs
372 DTC.Del hs -> H.del $$ html5ify hs
373 DTC.I hs -> H.i $$ html5ify hs
376 H.span ! HA.class_ "q" $$ do
380 DTC.SC hs -> H.span ! HA.class_ "smallcaps" $$ html5ify hs
381 DTC.Sub hs -> H.sub $$ html5ify hs
382 DTC.Sup hs -> H.sup $$ html5ify hs
383 DTC.U hs -> H.span ! HA.class_ "underline" $$ html5ify hs
385 H.a ! HA.class_ "eref"
386 ! HA.href (attrValue href) $$
389 H.span ! HA.class_ "iref"
390 ! HA.id (attrValue Index.Ref{term, count, section=def}) $$
393 H.a ! HA.class_ "ref"
394 ! HA.href ("#"<>attrValue to) $$
399 H.a ! HA.class_ "rref"
400 ! HA.href (attrValue to) $$
402 DTC.Plain t -> html5ify t
403 instance AttrValue Index.Ref where
404 attrValue Index.Ref{..} =
405 "iref" <> "." <> attrValue term <>
407 then "." <> attrValue count
409 instance Html5ify [DTC.Horizontal] where
410 html5ify = mapM_ html5ify
411 instance Html5ify DTC.About where
412 html5ify DTC.About{..} =
413 forM_ titles $ \(DTC.Title title) ->
414 html5ify $ DTC.Q title
415 instance Html5ify DTC.Reference where
416 html5ify DTC.Reference{..} =
418 H.td ! HA.class_ "reference-key" $$
420 H.td ! HA.class_ "reference-content" $$
423 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
424 html5CommonAttrs DTC.CommonAttrs{..} =
425 Compose . (addClass . addId <$>) . getCompose
430 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
434 Just (DTC.Ident i) ->
435 H.AddCustomAttribute "id" (H.Text i)
437 html5SectionNumber :: [(XmlName,Int)] -> Html5
438 html5SectionNumber = go [] . List.reverse
440 go :: [(XmlName,Int)] -> [(XmlName,Int)] -> Html5
442 go rs (a@(_n,cnt):as) = do
443 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors (a:rs)) $$
448 html5SectionRef :: [(XmlName,Int)] -> Html5
450 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors as) $$
457 Text.intercalate "." $
458 Text.pack . show . snd <$> as
460 textXmlPosAncestors :: [(XmlName,Int)] -> Text
461 textXmlPosAncestors =
462 snd . foldr (\(n,c) (nParent,acc) ->
475 -- * Class 'Plainify'
476 class Plainify a where
477 plainify :: a -> TL.Text
478 instance Plainify DTC.Horizontal where
481 DTC.B hs -> "*"<>plainify hs<>"*"
482 DTC.Code hs -> "`"<>plainify hs<>"`"
483 DTC.Del hs -> "-"<>plainify hs<>"-"
484 DTC.I hs -> "/"<>plainify hs<>"/"
486 DTC.Q hs -> "« "<>plainify hs<>" »"
487 DTC.SC hs -> plainify hs
488 DTC.Sub hs -> plainify hs
489 DTC.Sup hs -> plainify hs
490 DTC.U hs -> "_"<>plainify hs<>"_"
491 DTC.Eref{..} -> plainify text
492 DTC.Iref{..} -> plainify text
493 DTC.Ref{..} -> plainify text
494 DTC.Rref{..} -> plainify text
495 DTC.Plain t -> TL.fromStrict t
496 instance Plainify [DTC.Horizontal] where
497 plainify = foldMap plainify
498 instance Plainify DTC.Title where
499 plainify (DTC.Title t) = plainify t
501 instance AttrValue XmlPos where
502 attrValue = attrValue . textXmlPosAncestors . xmlPosAncestors
506 = MsgHTML5_Table_of_Contents
508 instance Html5ify MsgHtml5 where
510 loc <- liftStateMarkup $ S.gets localize
512 instance LocalizeIn FR Html5 MsgHtml5 where
514 MsgHTML5_Table_of_Contents -> "Sommaire"
515 instance LocalizeIn EN Html5 MsgHtml5 where
517 MsgHTML5_Table_of_Contents -> "Summary"