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 :: FilePos
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=FilePos line _col, ..} <- composeLift RWS.get
117 composeLift $ RWS.put st{state_pos=FilePos (line + 1) 1}
119 H.a ! HA.id ("line-"<>attrify (line + 1)) $$ return ()
120 join $ composeLift $ RWS.asks reader_indent
122 composeLift $ RWS.modify $ \s@State{state_pos=FilePos line col} ->
123 s{state_pos=FilePos line (col + 1)}
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=FilePos line col} ->
135 s{state_pos=FilePos line $ col + int (TL.length h)}
139 -- NOTE: useless to increment the filePos_column for h,
140 -- since the following '\n' will reset the filePos_column.
143 instance Html5ify FilePos where
144 html5ify new@(FilePos lineNew colNew) = do
145 Reader{reader_indent} <- composeLift RWS.ask
146 st@State{state_pos=old@(FilePos lineOld colOld)} <- composeLift RWS.get
147 case lineOld`compare`lineNew of
149 forM_ [lineOld+1..lineNew] $ \lnum -> do
151 H.a ! HA.id ("line-"<>attrify lnum) $$ return ()
152 composeLift $ RWS.put st{state_pos=FilePos lineNew 1}
154 FilePos _lineMid colMid <- composeLift $ RWS.gets state_pos
155 html5 $ List.replicate (colNew - colMid) ' '
156 composeLift $ RWS.put st{state_pos=new}
157 EQ | colOld <= colNew -> do
158 composeLift $ RWS.put st{state_pos=new}
159 html5 $ List.replicate (colNew - colOld) ' '
160 _ -> error $ "html5ify: non-ascending FilePos:"
161 <> "\n old: " <> show old
162 <> "\n new: " <> show new
163 instance Html5ify Roots where
164 html5ify = mapM_ html5ify
165 instance Html5ify Root where
166 html5ify (Tree (Sourced (FileRange{fileRange_begin=bp}:|_) nod) ts) = do
169 ----------------------
170 NodeLower name attrs -> do
171 H.span ! HA.class_ (mconcat ["header header-lower"," header-name-",attrify name]) $$ do
172 H.span ! HA.class_ "header-mark" $$ html5ify '<'
173 H.span ! HA.class_ "header-name" $$ html5ify name
176 ----------------------
179 HeaderGreat n wh -> html5HeaderRepeated "" "" (maybe "" XML.unNCName n) wh ">" "" "great"
180 HeaderBar n wh -> html5HeaderRepeated "" "" (maybe "" XML.unNCName n) wh "|" "" "bar"
181 HeaderColon n wh -> html5Header "" "" (maybe "" XML.unNCName n) wh ":" "" "colon"
182 HeaderEqual n wh -> html5Header "" "" (XML.unNCName n) wh "=" "" "equal"
183 HeaderDot n -> html5Header "" "" n "" "." "" "dot"
184 HeaderDash -> html5Header "" "" "" "" "-" " " "dash"
185 HeaderDashDash -> html5Header "" "" "" "" "--" " " "dashdash"
186 HeaderBrackets n -> html5Header "[" "" n "" "]" "" "dashdash"
187 HeaderSection lvl -> do
189 H.span ! HA.class_ "section-title" $$ do
190 H.span ! HA.class_ "section-mark" $$ do
191 html5ify $ List.replicate lvl '#'
193 title :< _ -> h lvl $$ html5ify title
206 h n | n > 6 = H.span ! HA.class_ ("h h"<>attrify n)
208 HeaderDotSlash file -> do
209 ext <- composeLift $ RWS.asks reader_ext_html
213 H.a ! HA.class_ "header-dotslash"
214 ! HA.href (attrify $ file<>ext) $$
217 html5Head :: TL.Text -> White -> TL.Text -> White -> TL.Text -> White -> H.AttributeValue -> Html5
218 html5Head markBegin whmb name whn markEnd whme cl = do
219 H.span ! HA.class_ (mconcat $ ["header header-",cl] <>
220 if TL.null name then [] else [" header-name-",attrify name]) $$ do
221 when (markBegin/="") $
222 H.span ! HA.class_ "header-mark" $$ html5ify markBegin
225 H.span ! HA.class_ "header-name" $$ html5ify name
228 H.span ! HA.class_ "header-mark" $$ html5ify markEnd
230 html5Header markBegin whmb name whn markEnd whme cl = do
231 html5Head markBegin whmb name whn markEnd whme cl
232 H.span ! HA.class_ "header-value" $$
234 html5HeaderRepeated :: TL.Text -> White -> TL.Text -> White -> TL.Text -> White -> H.AttributeValue -> Html5
235 html5HeaderRepeated markBegin whmb name whn markEnd whme cl = do
236 localComposeRWS (\ro ->
237 ro{ reader_indent = do
239 FilePos _lineMid colMid <- composeLift $ RWS.gets state_pos
240 html5ify $ List.replicate (filePos_column bp - colMid) ' '
241 html5Head markBegin whmb name whn markEnd whme cl
243 html5Header markBegin whmb name whn markEnd whme cl
244 ----------------------
246 localComposeRWS (\ro ->
247 ro{ reader_indent = do
249 FilePos _lineMid colMid <- composeLift $ RWS.gets state_pos
250 html5ify $ List.replicate (filePos_column bp - colMid) ' '
253 ----------------------
255 localComposeRWS (\ro ->
256 ro{ reader_indent = do
258 FilePos _lineMid colMid <- composeLift $ RWS.gets state_pos
259 html5ify $ List.replicate (filePos_column bp - colMid) ' '
262 ----------------------
263 NodeToken t -> html5ify t <> html5ify ts
264 ----------------------
267 PairElem name attrs -> do
268 H.span ! HA.class_ ("pair-PairElem" <> " pair-elem-"<>attrify name) $$ do
269 H.span ! HA.class_ "pair-open" $$ o
270 unless (null ts) $ do
271 H.span ! HA.class_ "pair-content" $$ html5ify ts
272 H.span ! HA.class_ "pair-close" $$ c
275 H.span ! HA.class_ "elem-name" $$
280 ( "<"<>html5Name<>html5ify attrs<>"/>"
283 ( "<"<>html5Name<>html5ify attrs<>">"
284 , "</"<>html5Name<>">" )
286 H.span ! HA.class_ ("pair-"<>fromString (show pair)) $$ do
287 let (o,c) = pairBorders pair ts
288 H.span ! HA.class_ "pair-open" $$ html5ify o
289 H.span ! HA.class_ "pair-content" $$ em $ html5ify ts
290 H.span ! HA.class_ "pair-close" $$ html5ify c
296 || p == PairFrenchquote
297 || p == PairDoublequote -> do
298 Reader{reader_italic} <- composeLift RWS.ask
299 localComposeRWS (\ro -> ro{reader_italic = not reader_italic}) $
300 H.em ! HA.class_ (if reader_italic then "even" else "odd") $$ h
302 instance Html5ify Token where
305 TokenText t -> html5ify t
307 H.span ! HA.class_ "at" $$ do
309 H.span ! HA.class_ "at-back" $$
311 H.span ! HA.class_ "at-open" $$
315 H.span ! HA.class_ "tag" $$ do
317 H.span ! HA.class_ "tag-back" $$
319 H.span ! HA.class_ "tag-open" $$
322 TokenEscape c -> html5ify ['\\', c]
324 H.a ! HA.href (attrify l) $$
326 instance Html5ify ElemName where
327 html5ify = html5ify . show
328 instance Html5ify ElemAttrs where
329 html5ify = mapM_ html5ify
330 instance Html5ify (White,ElemAttr) where
331 html5ify (elemAttr_white,ElemAttr{..}) = do
332 html5ify elemAttr_white
333 H.span ! HA.class_ "attr-name" $$
334 html5ify elemAttr_name
335 html5ify elemAttr_open
336 H.span ! HA.class_ "attr-value" $$
337 html5ify elemAttr_value
338 html5ify elemAttr_close