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(..), mapMaybe, fromJust)
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.Ident where
123 html5ify (DTC.Ident i) = html5ify i
126 Localize ls Html5 MsgHtml5 =>
128 LocaleIn ls -> Document -> Html
129 html5Document locale DTC.Document{..} = do
130 let Keys{..} = keys body
132 case foldMap Index.refsOfTerms keys_index of
133 refs | null refs -> (body, mempty)
136 (Index.indexify body)
138 { Index.state_refs = refs
139 }) $ \Index.State{state_refs} ->
140 (<$> keys_index) $ \terms ->
142 TreeMap.intersection const state_refs $
143 Index.refsOfTerms terms
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) $ \z ->
188 forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
191 instance Html5ify BodyCursor where
193 case Tree.current z of
194 TreeN k _ts -> html5BodyKey z k
195 Tree0 v -> html5BodyValue z v
197 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
198 html5BodyKey z = \case
200 H.section ! HA.class_ "section"
201 ! HA.id (attrValue pos) $$ do
202 html5CommonAttrs attrs $
203 H.table ! HA.class_ "section-header" $$
206 H.td ! HA.class_ "section-number" $$ do
209 H.td ! HA.class_ "section-title" $$ do
210 (case List.length $ xmlPosAncestors pos of
219 forM_ (Tree.axis_child `Tree.runAxis` z) $
221 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
222 html5BodyValue z = \case
223 DTC.Block b -> html5ify b
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 `Tree.runAxis` 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 `Tree.runAxis` 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.termsByChar 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) $ \words -> fromJust $ do
286 path <- Index.pathFromWords words
287 Strict.maybe Nothing Just $
288 TreeMap.lookup path refsByTerm
290 List.intersperse ", " $
291 (<$> refs) $ \ref@Index.Ref{..} ->
292 H.a ! HA.href ("#"<>attrValue ref) $$
294 List.intercalate "." $
296 (<$> xmlPosAncestors section) $ \(_n,c) -> show c
298 instance Html5ify DTC.Words where
299 html5ify = html5ify . Index.plainifyWords
301 html5ToC :: Int -> BodyCursor -> Html5
303 case Tree.current z of
304 TreeN DTC.Section{..} _ts -> do
306 H.table ! HA.class_ "toc-entry" $$
309 H.td ! HA.class_ "section-number" $$
310 html5SectionRef $ xmlPosAncestors pos
311 H.td ! HA.class_ "section-title" $$
313 DTC.unTitle title >>= \ts -> Tree.bindTrees ts $ \case
314 TreeN DTC.Iref{} ls -> ls
315 TreeN DTC.Note{} _ -> mempty
320 `Tree.axis_filter_current` \case
321 TreeN DTC.Section{} _ -> True
323 when (depth > 0 && not (null sections)) $
329 html5ToF :: Int -> BodyCursor -> Html5
331 case Tree.current z of
336 H.td ! HA.class_ "figure-number" $$
337 H.a ! HA.href ("#"<>attrValue pos) $$
339 H.td ! HA.class_ "figure-name" $$
344 instance Html5ify [DTC.Block] where
345 html5ify = mapM_ html5ify
346 instance Html5ify DTC.Block where
349 html5CommonAttrs attrs $
350 H.p ! HA.class_ "para"
351 ! HA.id (attrValue pos) $$ do
354 html5CommonAttrs attrs $
355 H.ol ! HA.class_ "ol"
356 ! HA.id (attrValue pos) $$ do
357 forM_ items $ \item ->
358 H.li $$ html5ify item
360 html5CommonAttrs attrs $
361 H.ul ! HA.class_ "ul"
362 ! HA.id (attrValue pos) $$ do
363 forM_ items $ \item ->
364 H.li $$ html5ify item
366 html5CommonAttrs attrs $
367 H.div ! HA.class_ "rl"
368 ! HA.id (attrValue pos) $$ do
372 html5ify $ H.Comment (H.Text t) ()
373 instance Html5ify DTC.Lines where
374 html5ify = mapM_ $ \case
377 DTC.BR -> html5ify H.br
378 DTC.Plain t -> html5ify t
381 DTC.B -> H.strong $$ html5ify ls
382 DTC.Code -> H.code $$ html5ify ls
383 DTC.Del -> H.del $$ html5ify ls
384 DTC.I -> H.i $$ html5ify ls
385 DTC.Sub -> H.sub $$ html5ify ls
386 DTC.Sup -> H.sup $$ html5ify ls
387 DTC.SC -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
388 DTC.U -> H.span ! HA.class_ "underline" $$ html5ify ls
391 H.span ! HA.class_ "q" $$ do
396 H.a ! HA.class_ "eref"
397 ! HA.href (attrValue href) $$
400 H.span ! HA.class_ "iref"
401 ! HA.id (attrValue Index.Ref{term, count, section=def}) $$
404 H.a ! HA.class_ "ref"
405 ! HA.href ("#"<>attrValue to) $$
410 H.a ! HA.class_ "rref"
411 ! HA.href (attrValue to) $$
413 instance AttrValue Index.Ref where
414 attrValue Index.Ref{..} =
415 "iref" <> "." <> attrValue (Index.plainifyWords term) <>
417 then "." <> attrValue count
419 instance Html5ify DTC.About where
420 html5ify DTC.About{..} =
421 forM_ titles $ \(DTC.Title title) ->
422 html5ify $ Seq.singleton $ TreeN DTC.Q title
423 instance Html5ify DTC.Reference where
424 html5ify DTC.Reference{id=id_, ..} =
426 H.td ! HA.class_ "reference-key" $$
428 H.td ! HA.class_ "reference-content" $$
431 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
432 html5CommonAttrs DTC.CommonAttrs{id=id_, ..} =
433 Compose . (addClass . addId <$>) . getCompose
438 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
442 Just (DTC.Ident i) ->
443 H.AddCustomAttribute "id" (H.Text i)
445 html5SectionNumber :: [(XmlName,Int)] -> Html5
446 html5SectionNumber = go [] . List.reverse
448 go :: [(XmlName,Int)] -> [(XmlName,Int)] -> Html5
450 go rs (a@(_n,cnt):as) = do
451 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors (a:rs)) $$
456 html5SectionRef :: [(XmlName,Int)] -> Html5
458 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors as) $$
465 Text.intercalate "." $
466 Text.pack . show . snd <$> as
468 textXmlPosAncestors :: [(XmlName,Int)] -> Text
469 textXmlPosAncestors =
470 snd . foldr (\(n,c) (nParent,acc) ->
483 -- * Class 'Plainify'
484 class Plainify a where
485 plainify :: a -> TL.Text
486 instance Plainify TL.Text where
488 instance Plainify Text where
489 plainify = TL.fromStrict
490 instance Plainify DTC.Lines where
491 plainify = foldMap $ \case
495 DTC.Plain p -> plainify p
498 DTC.B -> "*"<>plainify ls<>"*"
499 DTC.Code -> "`"<>plainify ls<>"`"
500 DTC.Del -> "-"<>plainify ls<>"-"
501 DTC.I -> "/"<>plainify ls<>"/"
503 DTC.Q -> "« "<>plainify ls<>" »"
504 DTC.SC -> plainify ls
505 DTC.Sub -> plainify ls
506 DTC.Sup -> plainify ls
507 DTC.U -> "_"<>plainify ls<>"_"
508 DTC.Eref{..} -> plainify ls
509 DTC.Iref{..} -> plainify ls
510 DTC.Ref{..} -> plainify ls
511 DTC.Rref{..} -> plainify ls
512 instance Plainify DTC.Title where
513 plainify (DTC.Title t) = plainify t
515 instance AttrValue XmlPos where
516 attrValue = attrValue . textXmlPosAncestors . xmlPosAncestors
520 = MsgHTML5_Table_of_Contents
522 instance Html5ify MsgHtml5 where
524 loc <- liftStateMarkup $ S.gets localize
526 instance LocalizeIn FR Html5 MsgHtml5 where
528 MsgHTML5_Table_of_Contents -> "Sommaire"
529 instance LocalizeIn EN Html5 MsgHtml5 where
531 MsgHTML5_Table_of_Contents -> "Summary"