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 qualified Data.TreeSeq.Strict as Tree
15 -- import qualified Data.Sequence as Seq
16 import Control.Applicative (Applicative(..), (<*))
17 import Control.Monad (Monad(..), forM_, mapM, mapM_, when, (>=>))
19 import Data.Char (Char)
20 import Data.Eq (Eq(..))
21 import Data.Foldable (Foldable(..))
22 import Data.Function (($), (.), id, const, flip)
23 import Data.Functor (Functor(..), (<$>), (<$), ($>))
24 import Data.Functor.Compose (Compose(..))
26 import Data.Map.Strict (Map)
27 import Data.Maybe (Maybe(..), mapMaybe)
28 import Data.Monoid (Monoid(..))
29 import Data.Ord (Ord(..))
30 import Data.Semigroup (Semigroup(..))
31 import Data.Sequence (Seq)
32 import Data.String (String)
33 import Data.Text (Text)
34 -- import Data.Traversable (Traversable(..))
35 import Data.TreeSeq.Strict (Tree(..), Trees)
36 import Data.Tuple (snd)
37 import Prelude (Num(..))
38 import Text.Blaze ((!))
39 import Text.Blaze.Html (Html)
40 import Text.Show (Show(..))
41 import qualified Control.Monad.Trans.State as S
42 import qualified Data.List as List
43 import qualified Data.Text as Text
44 import qualified Data.Text.Lazy as TL
45 import qualified Data.TreeSeq.Strict.Zipper as Tree
46 import qualified Text.Blaze.Html5 as H
47 import qualified Text.Blaze.Html5.Attributes as HA
48 import qualified Text.Blaze.Internal as H
49 import qualified Data.Map.Strict as Map
50 import qualified Data.Set as Set
51 import System.FilePath (FilePath)
54 import Text.Blaze.Utils
55 import Data.Locale hiding (localize, Index)
56 import qualified Data.Locale as Locale
58 import Language.DTC.Document (Document)
59 import qualified Language.DTC.Write.Index as Index
60 import Language.DTC.Write.XML ()
61 import Language.XML (XmlName(..), XmlPos(..))
62 import qualified Language.DTC.Document as DTC
63 -- import Debug.Trace (trace)
65 (<&>) :: Functor f => f a -> (a -> b) -> f b
70 type Html5 = StateMarkup StateHtml5 ()
72 -- ** Type 'StateHtml5'
75 { styles :: Map FilePath CSS
76 , scripts :: Map FilePath Script
77 , localize :: MsgHtml5 -> Html5
78 , indexs :: Map XmlPos (Map Index.Term Index.Count)
80 stateHtml5 :: StateHtml5
81 stateHtml5 = StateHtml5
84 , localize = html5ify . show
93 { keys_index :: Map XmlPos (Set Index.Term)
96 keys :: Trees DTC.BodyKey DTC.BodyValue -> Keys
97 keys body = foldl' flt (Keys mempty) (Compose body)
100 DTC.Index{..} -> acc{keys_index = Map.insert pos (Set.fromList terms) $ keys_index acc}
103 -- ** Class 'Html5ify'
104 class Html5ify a where
105 html5ify :: a -> Html5
106 instance Html5ify Char where
107 html5ify = html5ify . H.toMarkup
108 instance Html5ify Text where
109 html5ify = html5ify . H.toMarkup
110 instance Html5ify String where
111 html5ify = html5ify . H.toMarkup
112 instance Html5ify H.Markup where
113 html5ify = Compose . return
114 instance Html5ify DTC.Title where
115 html5ify (DTC.Title t) = html5ify t
116 instance Html5ify DTC.Ident where
117 html5ify (DTC.Ident i) = html5ify i
120 Localize ls Html5 MsgHtml5 =>
122 LocaleIn ls -> Document -> Html
123 html5Document locale doc@DTC.Document{head} = do
124 let Keys{..} = keys $ DTC.body (doc::DTC.Document)
126 let allTerms = fold keys_index in
128 (Index.indexify $ DTC.body (doc::DTC.Document)) Index.stateIndex
129 { Index.stateIndex_terms = Map.fromSet (const 0) allTerms
130 }) $ \Index.StateIndex{..} ->
131 (<$> keys_index) $ \ts ->
132 Map.intersection stateIndex_terms $
133 Map.fromSet (const ()) ts
134 let (html5Body, StateHtml5{styles,scripts}) =
135 runStateMarkup stateHtml5{indexs} $ do
136 liftStateMarkup $ S.modify $ \s -> s{localize = Locale.localize locale}
140 H.html ! HA.lang (attrValue $ countryCode locale) $ do
142 H.meta ! HA.httpEquiv "Content-Type"
143 ! HA.content "text/html; charset=UTF-8"
144 whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
146 H.toMarkup $ plainify $ List.head ts
147 forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
148 H.link ! HA.rel (attrValue rel)
149 ! HA.href (attrValue href)
150 H.meta ! HA.name "generator"
153 (`mapMaybe` toList body) $ \case
154 TreeN k@DTC.Section{} _ -> Just k
156 forM_ chapters $ \DTC.Section{..} ->
157 H.link ! HA.rel "Chapter"
158 ! HA.title (attrValue $ plainify title)
159 ! HA.href ("#"<>attrValue pos)
160 H.link ! HA.rel "stylesheet"
161 ! HA.type_ "text/css"
162 ! HA.href "style/dtc-html5.css"
163 forM_ styles $ \style ->
164 H.style ! HA.type_ "text/css" $
166 forM_ scripts $ \script ->
167 H.script ! HA.type_ "application/javascript" $
172 -- * Type 'BodyCursor'
173 -- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
174 type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
175 instance Html5ify DTC.Body where
177 forM_ (Tree.zippers body >>= Tree.axis_following_sibling) $
180 instance Html5ify BodyCursor where
182 case Tree.current z of
183 TreeN k _ts -> html5BodyKey z k
184 Tree0 v -> html5BodyValue z v
186 html5BodyKey :: BodyCursor -> DTC.BodyKey -> Html5
187 html5BodyKey z = \case
189 H.section ! HA.class_ "section"
190 ! HA.id (attrValue pos) $$ do
191 html5CommonAttrs attrs $
192 H.table ! HA.class_ "section-header" $$
195 H.td ! HA.class_ "section-number" $$ do
198 H.td ! HA.class_ "section-title" $$ do
200 forM_ (Tree.axis_child z) $
202 html5BodyValue :: BodyCursor -> DTC.BodyValue -> Html5
203 html5BodyValue z = \case
207 H.nav ! HA.class_ "toc"
208 ! HA.id (attrValue pos) $$ do
209 H.span ! HA.class_ "toc-name" $$
210 H.a ! HA.href (attrValue pos) $$
211 html5ify MsgHTML5_Table_of_Contents
213 forM_ (Tree.axis_following_sibling z) $
215 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
217 H.nav ! HA.class_ "tof"
218 ! HA.id (attrValue pos) $$
219 H.table ! HA.class_ "tof" $$
221 forM_ (Tree.axis_preceding z) $
223 where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
225 html5CommonAttrs attrs $
226 H.div ! HA.class_ (attrValue $ "figure-"<>type_)
227 ! HA.id (attrValue pos) $$ do
228 H.table ! HA.class_ "figure-caption" $$
231 H.td ! HA.class_ "figure-number" $$ do
232 H.a ! HA.href ("#"<>attrValue pos) $$
235 H.td ! HA.class_ "figure-name" $$
237 H.div ! HA.class_ "figure-content" $$ do
240 idxs <- liftStateMarkup $ S.gets indexs
241 let chars = Index.termsByChar $ idxs Map.! pos
242 H.div ! HA.class_ "index"
243 ! HA.id (attrValue pos) $$ do
244 H.nav ! HA.class_ "index-nav-chars" $$ do
245 forM_ (Map.keys chars) $ \char ->
246 H.a ! HA.href ("#"<>(attrValue pos <> "." <> attrValue char)) $$
249 forM_ (Map.toList chars) $ \(char,terms) -> do
251 let i = attrValue pos <> "." <> attrValue char in
253 ! HA.href ("#"<>i) $$
256 H.dl ! HA.class_ "index-char-refs" $$
257 forM_ (Map.toList terms) $ \(term,count) -> do
258 H.dt ! HA.id (attrValue Index.Ref{term, count}) $$
261 forM_ [0..count-1] $ \c -> do
262 H.a ! HA.href ("#"<>attrValue Index.Ref{term, count=c}) $$
268 forM_ terms $ \term ->
269 H.ul ! HA.class_ "index" $$
275 let r = Tree.current <$> (Tree.axis_root @[] >=> Tree.axis_preceding_sibling_first $ z)
276 let (ts,StateIndex{..}) = S.runState (indexify r) stateIndex
279 -- liftStateMarkup $ S.gets
282 html5ToC :: Int -> BodyCursor -> Html5
284 case Tree.current z of
285 TreeN DTC.Section{..} _ts -> do
287 H.table ! HA.class_ "toc-entry" $$
291 html5SectionRef $ xmlPosAncestors pos
296 forM_ (Tree.axis_child z) $
299 html5ToF :: Int -> BodyCursor -> Html5
301 case Tree.current z of
306 H.td ! HA.class_ "figure-number" $$
307 H.a ! HA.href ("#"<>attrValue pos) $$
309 H.td ! HA.class_ "figure-name" $$
314 instance Html5ify [DTC.Vertical] where
315 html5ify = mapM_ html5ify
316 instance Html5ify DTC.Vertical where
319 html5CommonAttrs attrs $
320 H.div ! HA.class_ "para"
321 ! HA.id (attrValue pos) $$ do
324 html5CommonAttrs attrs $
325 H.ol ! HA.class_ "ol"
326 ! HA.id (attrValue pos) $$ do
327 forM_ items $ \item ->
328 H.li $$ html5ify item
330 html5CommonAttrs attrs $
331 H.ul ! HA.class_ "ul"
332 ! HA.id (attrValue pos) $$ do
333 forM_ items $ \item ->
334 H.li $$ html5ify item
336 html5CommonAttrs attrs $
337 H.div ! HA.class_ "rl"
338 ! HA.id (attrValue pos) $$ do
342 html5ify $ H.Comment (H.Text t) ()
343 instance Html5ify DTC.Horizontal where
345 DTC.BR -> html5ify H.br
346 DTC.B hs -> H.strong $$ html5ify hs
347 DTC.Code hs -> H.code $$ html5ify hs
348 DTC.Del hs -> H.del $$ html5ify hs
349 DTC.I hs -> H.i $$ html5ify hs
355 DTC.SC hs -> html5ify hs
356 DTC.Sub hs -> H.sub $$ html5ify hs
357 DTC.Sup hs -> H.sup $$ html5ify hs
358 DTC.U hs -> H.span ! HA.class_ "underline" $$ html5ify hs
360 H.a ! HA.class_ "eref"
361 ! HA.href (attrValue href) $$
364 H.a ! HA.class_ "iref"
365 ! HA.href (attrValue Index.Ref{term, count}) $$
368 H.a ! HA.class_ "ref"
369 ! HA.href ("#"<>attrValue to) $$
374 H.a ! HA.class_ "rref"
375 ! HA.href (attrValue to) $$
377 DTC.Plain t -> Compose $ return $ H.toMarkup t
378 instance AttrValue Index.Ref where
379 attrValue Index.Ref{..} = "iref" <> "." <> attrValue term <> "." <> attrValue count
380 instance Html5ify [DTC.Horizontal] where
381 html5ify = mapM_ html5ify
382 instance Html5ify DTC.About where
383 html5ify DTC.About{..} =
384 forM_ titles $ \(DTC.Title title) ->
385 html5ify $ DTC.Q title
386 instance Html5ify DTC.Reference where
387 html5ify DTC.Reference{..} =
389 H.td ! HA.class_ "reference-key" $$
391 H.td ! HA.class_ "reference-content" $$
394 html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
395 html5CommonAttrs DTC.CommonAttrs{..} =
396 Compose . (addClass . addId <$>) . getCompose
401 _ -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
405 Just (DTC.Ident i) ->
406 H.AddCustomAttribute "id" (H.Text i)
408 html5SectionNumber :: [(XmlName,Int)] -> Html5
409 html5SectionNumber = go [] . List.reverse
411 go :: [(XmlName,Int)] -> [(XmlName,Int)] -> Html5
413 go rs (a@(_n,cnt):as) = do
414 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors (a:rs)) $$
419 html5SectionRef :: [(XmlName,Int)] -> Html5
421 H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors as) $$
428 Text.intercalate "." $
429 Text.pack . show . snd <$> as
431 textXmlPosAncestors :: [(XmlName,Int)] -> Text
432 textXmlPosAncestors =
433 snd . foldr (\(n,c) (nParent,acc) ->
446 -- * Class 'Plainify'
447 class Plainify a where
448 plainify :: a -> TL.Text
449 instance Plainify DTC.Horizontal where
452 DTC.B hs -> "*"<>plainify hs<>"*"
453 DTC.Code hs -> "`"<>plainify hs<>"`"
454 DTC.Del hs -> "-"<>plainify hs<>"-"
455 DTC.I hs -> "/"<>plainify hs<>"/"
457 DTC.Q hs -> "« "<>plainify hs<>" »"
458 DTC.SC hs -> plainify hs
459 DTC.Sub hs -> plainify hs
460 DTC.Sup hs -> plainify hs
461 DTC.U hs -> "_"<>plainify hs<>"_"
462 DTC.Eref{..} -> plainify text
463 DTC.Iref{..} -> plainify text
464 DTC.Ref{..} -> plainify text
465 DTC.Rref{..} -> plainify text
466 DTC.Plain t -> TL.fromStrict t
467 instance Plainify [DTC.Horizontal] where
468 plainify = foldMap plainify
469 instance Plainify DTC.Title where
470 plainify (DTC.Title t) = plainify t
472 instance AttrValue XmlPos where
473 attrValue = attrValue . textXmlPosAncestors . xmlPosAncestors
477 = MsgHTML5_Table_of_Contents
479 instance Html5ify MsgHtml5 where
481 loc <- liftStateMarkup $ S.gets localize
483 instance LocalizeIn FR Html5 MsgHtml5 where
485 MsgHTML5_Table_of_Contents -> "Sommaire"
486 instance LocalizeIn EN Html5 MsgHtml5 where
488 MsgHTML5_Table_of_Contents -> "Summary"