1 {-# LANGUAGE FlexibleInstances #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
3 {-# LANGUAGE OverloadedStrings #-}
4 {-# LANGUAGE ViewPatterns #-}
5 module Hdoc.TCT.Write.HTML5 where
7 import Control.Monad (Monad(..), forM_, mapM_, join)
9 import Data.Char (Char)
10 import Data.Default.Class (Default(..))
11 import Data.Eq (Eq(..))
12 import Data.Foldable (Foldable(..))
13 import Data.Function (($), (.))
14 import Data.Functor.Compose (Compose(..))
15 import Data.List.NonEmpty (NonEmpty(..))
16 import Data.Maybe (Maybe(..), maybe)
17 import Data.Monoid (Monoid(..))
18 import Data.Ord (Ord(..), Ordering(..))
19 import Data.Semigroup (Semigroup(..))
20 import Data.Sequence (ViewL(..))
21 import Data.String (String, IsString(..))
22 import Prelude (Num(..), undefined, error)
23 import Text.Blaze ((!))
24 import Text.Blaze.Html (Html)
25 import Text.Show (Show(..))
26 import qualified Control.Monad.Trans.RWS.Strict as RWS
27 import qualified Data.List as List
28 import qualified Data.Sequence as Seq
29 import qualified Data.Text.Lazy as TL
30 import qualified Language.Symantic.XML as XML
31 import qualified Text.Blaze.Html5 as H
32 import qualified Text.Blaze.Html5.Attributes as HA
33 import qualified Text.Blaze.Internal as Blaze
35 -- import Hdoc.TCT.Debug
38 import Control.Monad.Utils
39 import Text.Blaze.Utils
40 import Text.Blaze.XML ()
41 import qualified Hdoc.TCT.Write.Plain as Plain
43 writeHTML5 :: Trees (Cell Node) -> Html
48 H.meta ! HA.httpEquiv "Content-Type"
49 ! HA.content "text/html; charset=UTF-8"
50 whenJust (titleFrom body) $ \t ->
52 H.toMarkup $ Plain.text def t
53 -- link ! rel "Chapter" ! title "SomeTitle">
54 H.link ! HA.rel "stylesheet"
56 ! HA.href "style/tct-html5.css"
57 let (html5Body, State{}, ()) =
58 runComposeRWS def def $
61 H.a ! HA.id "line-1" $ return ()
64 titleFrom :: Roots -> Maybe Root
67 Tree (unSourced -> NodeHeader HeaderSection{}) _ts -> True
70 Tree (unSourced -> NodeHeader (HeaderSection _lvl))
71 (Seq.viewl -> title:<_) -> Just title
75 type Html5 = ComposeRWS Reader Writer State Blaze.MarkupM ()
77 instance IsString Html5 where
78 fromString = mapM_ html5ify
80 html5 :: H.ToMarkup a => a -> Html5
81 html5 = Compose . return . H.toMarkup
85 { reader_indent :: Html5
86 , reader_italic :: Bool
87 , reader_ext_html :: String
88 } -- deriving (Eq, Show)
89 instance Default Reader where
92 , reader_italic = False
93 , reader_ext_html = ".html"
100 newtype State = State
101 { state_pos :: LineColumn
102 } -- deriving (Eq, Show)
103 instance Default State where
108 -- * Class 'Html5ify'
109 class Html5ify a where
110 html5ify :: a -> Html5
111 instance Html5ify () where
113 instance Html5ify Char where
116 st@State{state_pos=LineColumn line _col, ..} <- composeLift RWS.get
117 composeLift $ RWS.put st{state_pos=LineColumn (line <> pos1) pos1}
119 H.a ! HA.id ("line-"<>attrify (line <> pos1)) $$ return ()
120 join $ composeLift $ RWS.asks reader_indent
122 composeLift $ RWS.modify $ \s@State{state_pos=LineColumn line col} ->
123 s{state_pos=LineColumn line (col <> pos1)}
125 instance Html5ify String where
126 html5ify = mapM_ html5ify
127 instance Html5ify TL.Text where
131 let (h,ts) = TL.span (/='\n') t in
134 composeLift $ RWS.modify $ \s@State{state_pos} ->
135 s{state_pos=state_pos{colNum = colNum state_pos <> num (TL.length h)}}
139 -- NOTE: useless to increment the 'colNum' for h,
140 -- since the following '\n' will reset the 'colNum'.
143 instance Html5ify LineColumn where
145 Reader{reader_indent} <- composeLift RWS.ask
146 st@State{state_pos=old} <- composeLift RWS.get
147 let lineOld = lineInt old
148 let colOld = colInt old
149 case lineOld`compare`lineNew of
151 forM_ [lineOld+1..lineNew] $ \lnum -> do
153 H.a ! HA.id ("line-"<>attrify lnum) $$ return ()
154 composeLift $ RWS.put st{state_pos=LineColumn (lineNum new) pos1}
156 mid <- composeLift $ RWS.gets state_pos
157 html5 $ List.replicate (colNew - colInt mid) ' '
158 composeLift $ RWS.put st{state_pos=new}
159 EQ | colOld <= colNew -> do
160 composeLift $ RWS.put st{state_pos=new}
161 html5 $ List.replicate (colNew - colOld) ' '
162 _ -> error $ "html5ify: non-ascending LineColumn:"
163 <> "\n old: " <> show old
164 <> "\n new: " <> show new
166 lineNew = lineInt new
168 instance Html5ify Roots where
169 html5ify = mapM_ html5ify
170 instance Html5ify Root where
171 html5ify (Tree (Sourced (FileRange{fileRange_begin=bp}:|_) nod) ts) = do
174 ----------------------
175 NodeLower name attrs -> do
176 H.span ! HA.class_ (mconcat ["header header-lower"," header-name-",attrify name]) $$ do
177 H.span ! HA.class_ "header-mark" $$ html5ify '<'
178 H.span ! HA.class_ "header-name" $$ html5ify name
181 ----------------------
184 HeaderGreat n wh -> html5HeaderRepeated "" "" (maybe "" XML.unNCName n) wh ">" "" "great"
185 HeaderBar n wh -> html5HeaderRepeated "" "" (maybe "" XML.unNCName n) wh "|" "" "bar"
186 HeaderColon n wh -> html5Header "" "" (maybe "" XML.unNCName n) wh ":" "" "colon"
187 HeaderEqual n wh -> html5Header "" "" (XML.unNCName n) wh "=" "" "equal"
188 HeaderDot n -> html5Header "" "" n "" "." "" "dot"
189 HeaderDash -> html5Header "" "" "" "" "-" " " "dash"
190 HeaderDashDash -> html5Header "" "" "" "" "--" " " "dashdash"
191 HeaderBrackets n -> html5Header "[" "" n "" "]" "" "dashdash"
192 HeaderSection lvl -> do
194 H.span ! HA.class_ "section-title" $$ do
195 H.span ! HA.class_ "section-mark" $$ do
196 html5ify $ List.replicate lvl '#'
198 title :< _ -> h lvl $$ html5ify title
211 h n | n > 6 = H.span ! HA.class_ ("h h"<>attrify n)
213 HeaderDotSlash file -> do
214 ext <- composeLift $ RWS.asks reader_ext_html
218 H.a ! HA.class_ "header-dotslash"
219 ! HA.href (attrify $ file<>ext) $$
222 html5Head :: TL.Text -> White -> TL.Text -> White -> TL.Text -> White -> H.AttributeValue -> Html5
223 html5Head markBegin whmb name whn markEnd whme cl = do
224 H.span ! HA.class_ (mconcat $ ["header header-",cl] <>
225 if TL.null name then [] else [" header-name-",attrify name]) $$ do
226 when (markBegin/="") $
227 H.span ! HA.class_ "header-mark" $$ html5ify markBegin
230 H.span ! HA.class_ "header-name" $$ html5ify name
233 H.span ! HA.class_ "header-mark" $$ html5ify markEnd
235 html5Header markBegin whmb name whn markEnd whme cl = do
236 html5Head markBegin whmb name whn markEnd whme cl
237 H.span ! HA.class_ "header-value" $$
239 html5HeaderRepeated :: TL.Text -> White -> TL.Text -> White -> TL.Text -> White -> H.AttributeValue -> Html5
240 html5HeaderRepeated markBegin whmb name whn markEnd whme cl = do
241 localComposeRWS (\ro ->
242 ro{ reader_indent = do
244 midLC <- composeLift $ RWS.gets state_pos
245 html5ify $ List.replicate (colInt bp - colInt midLC) ' '
246 html5Head markBegin whmb name whn markEnd whme cl
248 html5Header markBegin whmb name whn markEnd whme cl
249 ----------------------
251 localComposeRWS (\ro ->
252 ro{ reader_indent = do
254 midLC <- composeLift $ RWS.gets state_pos
255 html5ify $ List.replicate (colInt bp - colInt midLC) ' '
258 ----------------------
260 localComposeRWS (\ro ->
261 ro{ reader_indent = do
263 midLC <- composeLift $ RWS.gets state_pos
264 html5ify $ List.replicate (colInt bp - colInt midLC) ' '
267 ----------------------
268 NodeToken t -> html5ify t <> html5ify ts
269 ----------------------
272 PairElem name attrs -> do
273 H.span ! HA.class_ ("pair-PairElem" <> " pair-elem-"<>attrify name) $$ do
274 H.span ! HA.class_ "pair-open" $$ o
275 unless (null ts) $ do
276 H.span ! HA.class_ "pair-content" $$ html5ify ts
277 H.span ! HA.class_ "pair-close" $$ c
280 H.span ! HA.class_ "elem-name" $$
285 ( "<"<>html5Name<>html5ify attrs<>"/>"
288 ( "<"<>html5Name<>html5ify attrs<>">"
289 , "</"<>html5Name<>">" )
291 H.span ! HA.class_ ("pair-"<>fromString (show pair)) $$ do
292 let (o,c) = pairBorders pair ts
293 H.span ! HA.class_ "pair-open" $$ html5ify o
294 H.span ! HA.class_ "pair-content" $$ em $ html5ify ts
295 H.span ! HA.class_ "pair-close" $$ html5ify c
301 || p == PairFrenchquote
302 || p == PairDoublequote -> do
303 Reader{reader_italic} <- composeLift RWS.ask
304 localComposeRWS (\ro -> ro{reader_italic = not reader_italic}) $
305 H.em ! HA.class_ (if reader_italic then "even" else "odd") $$ h
307 instance Html5ify Token where
310 TokenText t -> html5ify t
312 H.span ! HA.class_ "at" $$ do
314 H.span ! HA.class_ "at-back" $$
316 H.span ! HA.class_ "at-open" $$
320 H.span ! HA.class_ "tag" $$ do
322 H.span ! HA.class_ "tag-back" $$
324 H.span ! HA.class_ "tag-open" $$
327 TokenEscape c -> html5ify ['\\', c]
329 H.a ! HA.href (attrify l) $$
331 instance Html5ify ElemName where
332 html5ify = html5ify . show
333 instance Html5ify ElemAttrs where
334 html5ify = mapM_ html5ify
335 instance Html5ify (White,ElemAttr) where
336 html5ify (elemAttr_white,ElemAttr{..}) = do
337 html5ify elemAttr_white
338 H.span ! HA.class_ "attr-name" $$
339 html5ify elemAttr_name
340 html5ify elemAttr_open
341 H.span ! HA.class_ "attr-value" $$
342 html5ify elemAttr_value
343 html5ify elemAttr_close