Cosmetic changes.
[doclang.git] / Language / DTC / Write / HTML5.hs
index 9cf2108298d106ee750b3c30fd7051f1b95972c3..b394d6fd25c657f8f97e6f1a9f0263a1a56493a4 100644 (file)
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE DisambiguateRecordFields #-}
 {-# LANGUAGE DuplicateRecordFields #-}
-{-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE ViewPatterns #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
--- | Render a DTC source file in HTML5.
 module Language.DTC.Write.HTML5 where
 
 -- import Control.Monad.Trans.Class (MonadTrans(..))
--- import Data.Bool
--- import Data.Functor.Compose (Compose(..))
 -- import Data.Functor.Identity (Identity(..))
--- import Data.Map.Strict (Map)
--- import Data.String (IsString(..))
--- import Prelude (Num(..), undefined)
--- import qualified Control.Monad.Trans.State as S
--- import qualified Data.Map.Strict as Map
-import Control.Monad (forM_, mapM_, when, (>=>))
-import Data.Eq (Eq(..))
-import Data.Ord (Ord(..))
-import Data.Foldable (Foldable(..))
-import Data.Function (($), (.))
-import Data.Functor ((<$>))
+-- import Data.Sequence (Seq)
+-- import Data.Set (Set)
+-- import Data.Traversable (Traversable(..))
+-- import qualified Data.Sequence as Seq
+-- import qualified Data.TreeSeq.Strict as Tree
+import Control.Applicative (Applicative(..))
+import Control.Category
+import Control.Monad
+import Data.Bool
+import Data.Char (Char)
+import Data.Default.Class (Default(..))
+import Data.Foldable (Foldable(..), concat)
+import Data.Function (($), const, flip, on)
+import Data.Functor (Functor(..), (<$>))
+import Data.Functor.Compose (Compose(..))
 import Data.Int (Int)
-import Data.Maybe (Maybe(..))
+import Data.Map.Strict (Map)
+import Data.Maybe (Maybe(..), maybe, mapMaybe, fromJust, maybeToList)
 import Data.Monoid (Monoid(..))
+import Data.Ord (Ord(..))
 import Data.Semigroup (Semigroup(..))
-import Data.Sequence (Seq)
+import Data.String (String)
 import Data.Text (Text)
+import Data.TreeSeq.Strict (Tree(..), Trees)
 import Data.Tuple (snd)
-import Prelude (Num(..))
+import System.FilePath (FilePath)
 import Text.Blaze ((!))
 import Text.Blaze.Html (Html)
 import Text.Show (Show(..))
-import Data.TreeSeq.Strict (Tree(..))
+import qualified Control.Monad.Trans.State as S
+import qualified Data.Char as Char
 import qualified Data.List as List
+import qualified Data.Map.Strict as Map
+import qualified Data.Sequence as Seq
+import qualified Data.Strict.Maybe as Strict
 import qualified Data.Text as Text
 import qualified Data.Text.Lazy as TL
+import qualified Data.TreeMap.Strict as TreeMap
+import qualified Data.TreeSeq.Strict as Tree
+import qualified Data.TreeSeq.Strict.Zipper as Tree
 import qualified Text.Blaze.Html5 as H
 import qualified Text.Blaze.Html5.Attributes  as HA
 import qualified Text.Blaze.Internal as H
--- import qualified Data.TreeSeq.Strict as Tree
-import qualified Data.TreeSeq.Strict.Zipper as Tree
 
 import Text.Blaze.Utils
+import Data.Locale hiding (localize, Index)
+import qualified Data.Locale as Locale
 
-import Data.Locale
-import Language.DTC.Document (Document)
 import Language.DTC.Write.XML ()
-import Language.XML (XmlName(..), XmlPos(..))
+import Language.DTC.Write.Plain (Plain, Plainify(..))
+import qualified Language.DTC.Write.Plain as Plain
 import qualified Language.DTC.Document as DTC
+import qualified Language.DTC.Anchor as Anchor
 
--- import Debug.Trace (trace)
+(<&>) :: Functor f => f a -> (a -> b) -> f b
+(<&>) = flip (<$>)
+infixl 4 <&>
 
-instance H.ToMarkup DTC.Ident where
-       toMarkup (DTC.Ident i) = H.toMarkup i
-instance H.ToMarkup DTC.Title where
-       toMarkup (DTC.Title t) = html5Horizontals t
-instance AttrValue XmlPos where
-       attrValue = attrValue . textXmlPosAncestors . xmlPosAncestors
+-- * Type 'Html5'
+type Html5 = StateMarkup State ()
 
--- * Type 'InhHtml5'
-data InhHtml5
- =   InhHtml5
- {   inhHtml5_localize :: MsgHtml5 -> Html
- }
-inhHtml5 :: InhHtml5
-inhHtml5 = InhHtml5
- { inhHtml5_localize = localizeIn @EN EN_US
+-- * Type 'State'
+data State
+ =   State
+ {   state_styles     :: Map FilePath CSS
+ ,   state_scripts    :: Map FilePath Script
+ ,   state_indexs     :: Map DTC.Pos (DTC.Terms, Anchor.Irefs)
+ ,   state_rrefs      :: Anchor.Rrefs
+ ,   state_figures    :: Map Text (Map DTC.Pos (Maybe DTC.Title))
+ ,   state_references :: Map DTC.Ident DTC.About
+ ,   state_notes      :: Anchor.Notes
+ ,   state_plainify   :: Plain.State
  }
+instance Default State where
+       def = State
+        { state_styles     = mempty
+        , state_scripts    = mempty
+        , state_indexs     = mempty
+        , state_rrefs      = mempty
+        , state_figures    = mempty
+        , state_references = mempty
+        , state_notes      = mempty
+        , state_plainify   = def
+        }
+type CSS = Text
+type Script = Text
 
--- * Type 'MsgHtml5'
-data MsgHtml5
- =   MsgHTML5_Table_of_Contents
-instance LocalizeIn FR Html MsgHtml5 where
-       localizeIn _ MsgHTML5_Table_of_Contents = "Sommaire"
-instance LocalizeIn EN Html MsgHtml5 where
-       localizeIn _ MsgHTML5_Table_of_Contents = "Table of Contents"
-
-{- NOTE: composing state and markups
-type HtmlM st = Compose (S.State st) H.MarkupM
-instance Monad (HtmlM st) where
-       return = pure
-       Compose sma >>= a2csmb =
-               Compose $ sma >>= \ma ->
-                       case ma >>= H.Empty . a2csmb of
-                        H.Append _ma (H.Empty csmb) ->
-                               H.Append ma <$> getCompose csmb
-                        _ -> undefined
-
-($$) :: (Html -> Html) -> HTML -> HTML
-($$) f m = Compose $ f <$> getCompose m
-infixr 0 $$
--}
+-- * Type 'Keys'
+data Keys
+ = Keys
+ { keys_index     :: Map DTC.Pos DTC.Terms
+ , keys_figure    :: Map Text (Map DTC.Pos (Maybe DTC.Title))
+ , keys_reference :: Map DTC.Ident DTC.About
+ } deriving (Show)
+instance Default Keys where
+       def = Keys mempty mempty mempty
 
-unMarkupValue :: H.MarkupM a -> b -> H.MarkupM b
-unMarkupValue = \case
- H.Parent x0 x1 x2 m          -> H.Parent x0 x1 x2 . unMarkupValue m
- H.CustomParent x0 m          -> H.CustomParent x0 . unMarkupValue m
- H.Leaf x0 x1 x2 _            -> H.Leaf x0 x1 x2
- H.CustomLeaf x0 x1 _         -> H.CustomLeaf x0 x1
- H.Content x0 _               -> H.Content x0
- H.Comment x0 _               -> H.Comment x0
- H.Append x0 m                -> H.Append x0 . unMarkupValue m
- H.AddAttribute x0 x1 x2 m    -> H.AddAttribute x0 x1 x2 . unMarkupValue m
- H.AddCustomAttribute x0 x1 m -> H.AddCustomAttribute x0 x1 . unMarkupValue m
- H.Empty _                    -> H.Empty
+-- ** Class 'KeysOf'
+class KeysOf a where
+       keys :: a -> S.State Keys ()
+instance KeysOf (Trees DTC.BodyKey DTC.BodyValue) where
+       keys = mapM_ keys
+instance KeysOf (Tree DTC.BodyKey DTC.BodyValue) where
+       keys = \case
+        TreeN k ts ->
+               case k of
+                DTC.Section{..} ->
+                       keys ts
+        Tree0 v ->
+               case v of
+                DTC.Index{..} ->
+                       S.modify $ \s -> s{keys_index=
+                               Map.insert pos terms $ keys_index s}
+                DTC.Figure{..} ->
+                       S.modify $ \s -> s{keys_figure=
+                               Map.insertWith (<>)
+                                type_ (Map.singleton pos title) $
+                               keys_figure s}
+                DTC.References{..} ->
+                       S.modify $ \s -> s{keys_reference=
+                               foldr
+                                (\r -> Map.insert
+                                        (DTC.id    (r::DTC.Reference))
+                                        (DTC.about (r::DTC.Reference)))
+                                (keys_reference s)
+                                refs}
+                DTC.ToC{} -> return ()
+                DTC.ToF{} -> return ()
+                DTC.Block{} -> return ()
 
-markupValue :: H.MarkupM a -> a
-markupValue m0 = case m0 of
- H.Parent _ _ _ m1           -> markupValue m1
- H.CustomParent _ m1         -> markupValue m1
- H.Leaf _ _ _ x              -> x
- H.CustomLeaf _ _ x          -> x
- H.Content _ x               -> x
- H.Comment _ x               -> x
- H.Append _ m1               -> markupValue m1
- H.AddAttribute _ _ _ m1     -> markupValue m1
- H.AddCustomAttribute _ _ m1 -> markupValue m1
- H.Empty x                   -> x
+-- * Class 'Html5ify'
+class Html5ify a where
+       html5ify :: a -> Html5
+instance Html5ify Char where
+       html5ify = html5ify . H.toMarkup
+instance Html5ify Text where
+       html5ify = html5ify . H.toMarkup
+instance Html5ify TL.Text where
+       html5ify = html5ify . H.toMarkup
+instance Html5ify String where
+       html5ify = html5ify . H.toMarkup
+instance Html5ify H.Markup where
+       html5ify = Compose . return
+instance Html5ify DTC.Title where
+       html5ify (DTC.Title t) = html5ify t
+instance Html5ify DTC.Para where
+       html5ify = mapM_ html5ify
+instance Html5ify DTC.Ident where
+       html5ify (DTC.Ident i) = html5ify i
+instance Html5ify Int where
+       html5ify = html5ify . show
+instance Html5ify DTC.Nat where
+       html5ify (DTC.Nat n) = html5ify n
+instance Html5ify DTC.Nat1 where
+       html5ify (DTC.Nat1 n) = html5ify n
 
 html5Document ::
- Localize ls Html MsgHtml5 =>
- LocaleIn ls -> Document -> Html
-html5Document loc DTC.Document{..} = do
-       let inh = InhHtml5
-                { inhHtml5_localize = localize loc
-                }
+ Localize ls Plain Plain.L10n =>
+ Locales ls =>
+ LocaleIn ls -> DTC.Document -> Html
+html5Document locale DTC.Document{..} = do
+       let Keys{..} = keys body `S.execState` def
+       let (body',state_rrefs,state_notes,state_indexs) =
+               let irefs = foldMap Anchor.irefsOfTerms keys_index in
+               let (body0, Anchor.State{state_irefs, state_rrefs=rrefs, state_notes=notes}) =
+                       Anchor.anchorify body `S.runState`
+                       def{Anchor.state_irefs=irefs} in
+               (body0,rrefs,notes,) $
+               (<$> keys_index) $ \terms ->
+                       (terms,) $
+                       TreeMap.intersection const state_irefs $
+                       Anchor.irefsOfTerms terms
+       let state_plainify = def
+               { Plain.state_localize = Locale.localize locale }
+       let (html5Body, State{state_styles,state_scripts}) =
+               runStateMarkup def
+                { state_indexs
+                , state_rrefs
+                , state_notes
+                , state_figures    = keys_figure
+                , state_references = keys_reference
+                , state_plainify
+                } $ html5ify body'
+       
        H.docType
-       H.html $ do
+       H.html ! HA.lang (attrify $ countryCode locale) $ do
                H.head $ do
                        H.meta ! HA.httpEquiv "Content-Type"
                               ! HA.content "text/html; charset=UTF-8"
                        whenSome (DTC.titles $ DTC.about (head :: DTC.Head)) $ \ts ->
-                               let t = H.toMarkup $ List.head $ ts <> [DTC.Title [DTC.Plain ""]] in
-                               H.title $ H.toMarkup t
-                       -- link ! rel "Chapter" ! title "SomeTitle">
+                               H.title $
+                                       H.toMarkup $ Plain.text state_plainify $ List.head ts
+                       forM_ (DTC.links $ DTC.about (head :: DTC.Head)) $ \DTC.Link{rel, href} ->
+                               H.link ! HA.rel (attrify rel)
+                                      ! HA.href (attrify href)
+                       H.meta ! HA.name "generator"
+                              ! HA.content "tct"
+                       let chapters =
+                               (`mapMaybe` toList body) $ \case
+                                TreeN k@DTC.Section{} _ -> Just k
+                                _ -> Nothing
+                       forM_ chapters $ \DTC.Section{..} ->
+                               H.link ! HA.rel "Chapter"
+                                      ! HA.title (attrify $ plainify title)
+                                      ! HA.href ("#"<>attrify pos)
                        H.link ! HA.rel "stylesheet"
                               ! HA.type_ "text/css"
                               ! HA.href "style/dtc-html5.css"
-               H.body $
-                       html5Body inh body
-
--- * Type 'BodyZip'
-type BodyZip = Tree.Zipper DTC.BodyKey (Seq DTC.BodyValue)
+                       forM_ state_styles $ \style ->
+                               H.style ! HA.type_ "text/css" $
+                                       H.toMarkup style
+                       forM_ state_scripts $ \script ->
+                               H.script ! HA.type_ "application/javascript" $
+                                       H.toMarkup script
+               H.body
+                       html5Body
 
-html5Body :: InhHtml5 -> DTC.Body -> Html
-html5Body inh body =
-       forM_ (Tree.zippers body) $
-               html5BodyZipper inh
-
-html5BodyZipper :: InhHtml5 -> BodyZip -> Html
-html5BodyZipper inh z =
+-- * Type 'BodyCursor'
+-- | Cursor to navigate within a 'DTC.Body' according to many axis (like in XSLT).
+type BodyCursor = Tree.Zipper DTC.BodyKey DTC.BodyValue
+instance Html5ify DTC.Body where
+       html5ify body =
+               forM_ (Tree.zippers body) $ \z ->
+                       forM_ (Tree.axis_repeat Tree.axis_following_sibling_nearest `Tree.runAxis` z) $
+                               html5ify
+instance Html5ify BodyCursor
+ where html5ify z =
        case Tree.current z of
-        TreeN k _ts -> html5BodyKey inh z k
-        Tree0 vs -> forM_ vs $ html5BodyValue inh z
-
-html5BodyKey :: InhHtml5 -> BodyZip -> DTC.BodyKey -> Html
-html5BodyKey inh z = \case
-        DTC.Section{..} ->
-               H.section
-                ! HA.class_ "section"
-                ! HA.id (attrValue pos) $ do
+        TreeN k _ts ->
+               case k of
+                DTC.Section{..} ->
+                       H.section ! HA.class_ "section"
+                                 ! HA.id (attrify pos) $$ do
+                               html5CommonAttrs attrs $
+                                       H.table ! HA.class_ "section-header" $$
+                                               H.tbody $$
+                                                       H.tr $$ do
+                                                               H.td ! HA.class_ "section-number" $$ do
+                                                                       html5SectionNumber $ DTC.posAncestors pos
+                                                               H.td ! HA.class_ "section-title" $$ do
+                                                                       (case List.length $ DTC.posAncestors pos of
+                                                                        0 -> H.h1
+                                                                        1 -> H.h2
+                                                                        2 -> H.h3
+                                                                        3 -> H.h4
+                                                                        4 -> H.h5
+                                                                        5 -> H.h6
+                                                                        _ -> H.h6) $$
+                                                                               html5ify title
+                               forM_ (Tree.axis_child `Tree.runAxis` z) $
+                                       html5ify
+                               notes <- liftStateMarkup $ S.gets state_notes
+                               case Map.lookup pos notes of
+                                Nothing -> return ()
+                                Just ns ->
+                                       H.aside ! HA.class_ "notes" $$ do
+                                               Compose $ pure H.hr
+                                               H.table $$
+                                                       H.tbody $$
+                                                               forM_ ns $ \(num,para) ->
+                                                                       H.tr $$ do
+                                                                               H.td ! HA.class_ "note-ref" $$ do
+                                                                                       H.a ! HA.class_ "note-number"
+                                                                                           ! HA.id   ("note."<>attrify num)
+                                                                                           ! HA.href ("#note."<>attrify num) $$ do
+                                                                                               html5ify num
+                                                                                       ". "::Html5
+                                                                                       H.a ! HA.href ("#note-ref."<>attrify num) $$ do
+                                                                                               "↑"
+                                                                               H.td $$
+                                                                                       html5ify para
+        Tree0 v ->
+               case v of
+                DTC.Block b -> html5ify b
+                DTC.ToC{..} -> do
+                       H.nav ! HA.class_ "toc"
+                             ! HA.id (attrify pos) $$ do
+                               H.span ! HA.class_ "toc-name" $$
+                                       H.a ! HA.href (attrify pos) $$
+                                               html5ify Plain.L10n_Table_of_Contents
+                               H.ul $$
+                                       forM_ (Tree.axis_following_sibling `Tree.runAxis` z) $
+                                               html5ifyToC depth
+                DTC.ToF{..} -> do
+                       H.nav ! HA.class_ "tof"
+                             ! HA.id (attrify pos) $$
+                               H.table ! HA.class_ "tof" $$
+                                       H.tbody $$
+                                               html5ifyToF types
+                DTC.Figure{..} ->
+                       html5CommonAttrs attrs $
+                       H.div ! HA.class_ ("figure " <> attrify ("figure-"<>type_))
+                             ! HA.id (attrify pos) $$ do
+                               H.table ! HA.class_ "figure-caption" $$
+                                       H.tbody $$
+                                               H.tr $$ do
+                                                       H.td ! HA.class_ "figure-number" $$ do
+                                                               H.a ! HA.href ("#"<>attrify pos) $$ do
+                                                                       html5ify type_
+                                                                       html5ify $ DTC.posAncestors pos
+                                                       forM_ title $ \ti -> do
+                                                               html5ify $ Plain.L10n_Colon
+                                                               H.td ! HA.class_ "figure-title" $$
+                                                                       html5ify ti
+                               H.div ! HA.class_ "figure-content" $$ do
+                                       html5ify blocks
+                DTC.Index{pos} -> do
+                       (allTerms,refsByTerm) <- liftStateMarkup $ S.gets $ (Map.! pos) . state_indexs
+                       let chars = Anchor.termsByChar allTerms
+                       H.div ! HA.class_ "index"
+                             ! HA.id (attrify pos) $$ do
+                               H.nav ! HA.class_ "index-nav" $$ do
+                                       forM_ (Map.keys chars) $ \char ->
+                                               H.a ! HA.href ("#"<>(attrify pos <> "." <> attrify char)) $$
+                                                       html5ify char
+                               H.dl ! HA.class_ "index-chars" $$
+                                       forM_ (Map.toList chars) $ \(char,terms) -> do
+                                               H.dt $$
+                                                       let i = attrify pos <> "." <> attrify char in
+                                                       H.a ! HA.id i
+                                                           ! HA.href ("#"<>i) $$
+                                                               html5ify char
+                                               H.dd $$
+                                                       H.dl ! HA.class_ "index-term" $$ do
+                                                       forM_ terms $ \aliases -> do
+                                                               H.dt $$
+                                                                       H.ul ! HA.class_ "index-aliases" $$
+                                                                               forM_ (List.take 1 aliases) $ \term ->
+                                                                                       H.li ! HA.id (attrifyIref term) $$
+                                                                                               html5ify term
+                                                               H.dd $$
+                                                                       let anchs =
+                                                                               List.sortBy (compare `on` DTC.section . snd) $
+                                                                               (`foldMap` aliases) $ \words ->
+                                                                                       fromJust $ do
+                                                                                               path <- Anchor.pathFromWords words
+                                                                                               Strict.maybe Nothing (Just . ((words,) <$>) . List.reverse) $
+                                                                                                       TreeMap.lookup path refsByTerm in
+                                                                       html5CommasDot $
+                                                                       (<$> anchs) $ \(term,DTC.Anchor{..}) ->
+                                                                               H.a ! HA.class_ "index-iref"
+                                                                                   ! HA.href ("#"<>attrifyIrefCount term count) $$
+                                                                                       html5ify $ DTC.posAncestors section
+                DTC.References{..} ->
                        html5CommonAttrs attrs $
-                               H.table ! HA.class_ "section-header" $
-                                       H.tbody $
-                                               H.tr $ do
-                                                       H.td ! HA.class_ "section-number" $ do
-                                                               html5SectionNumber $ xmlPosAncestors pos
-                                                       H.td ! HA.class_ "section-title" $ do
-                                                               H.toMarkup title
-                       forM_ (Tree.axis_child z) $
-                               html5BodyZipper inh
+                       H.div ! HA.class_ "references"
+                             ! HA.id (attrify pos) $$ do
+                               H.table $$
+                                       forM_ refs html5ify
+instance Html5ify DTC.Words where
+       html5ify = html5ify . Anchor.plainifyWords
 
-html5BodyValue :: InhHtml5 -> BodyZip -> DTC.BodyValue -> Html
-html5BodyValue InhHtml5{..} z = \ case
-        DTC.Vertical v -> do
-               html5Vertical v
-        DTC.ToC{..} -> do
-               H.nav ! HA.class_ "toc"
-                     ! HA.id (attrValue pos) $ do
-                       H.span ! HA.class_ "toc-name" $
-                               H.a ! HA.href (attrValue pos) $
-                                       inhHtml5_localize MsgHTML5_Table_of_Contents
-                       H.ul $
-                               forM_ (Tree.axis_following_sibling z) $
-                                       html5ToC d
-               where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
-        DTC.ToF{..} -> do
-               H.nav ! HA.class_ "tof"
-                     ! HA.id (attrValue pos) $
-                       H.table ! HA.class_ "tof" $
-                               H.tbody $
-                                       forM_ (Tree.axis_preceding z) $
-                                               html5ToF d
-               where d = case depth of { Just (DTC.Nat n) -> n; _ -> 0 }
-        DTC.Figure{..} ->
-               html5CommonAttrs attrs $
-               H.div ! HA.class_ (attrValue $ "figure-"<>type_)
-                     ! HA.id (attrValue pos) $ do
-                       H.table ! HA.class_ "figure-caption" $
-                               H.tbody $
-                                       H.tr $ do
-                                               H.td ! HA.class_ "figure-number" $ do
-                                                       H.a ! HA.href "" $ H.toMarkup type_
-                                                       ": "
-                                               H.td ! HA.class_ "figure-name" $
-                                                       H.toMarkup title
-                       H.div ! HA.class_ "figure-content" $ do
-                               html5Verticals verts
+cleanPara :: DTC.Para -> DTC.Para
+cleanPara p =
+       p >>= (`Tree.bindTrees` \case
+        TreeN DTC.Iref{} ls -> ls
+        TreeN DTC.Note{} _  -> mempty
+        h -> pure h)
 
-html5ToC :: Int -> BodyZip -> Html
-html5ToC depth z =
+html5ifyToC :: Maybe DTC.Nat -> BodyCursor -> Html5
+html5ifyToC depth z =
        case Tree.current z of
         TreeN DTC.Section{..} _ts -> do
-               H.li $ do
-                       H.table ! HA.class_ "toc-entry" $
-                               H.tbody $
-                                       H.tr $ do
-                                               H.td $
-                                                       html5SectionRef $ xmlPosAncestors pos
-                                               H.td $
-                                                       H.toMarkup title
-                       when (depth > 0) $
-                               H.ul $
-                                       forM_ (Tree.axis_child z) $
-                                               html5ToC (depth - 1)
-        _ -> mempty
-
-html5ToF :: Int -> BodyZip -> Html
-html5ToF depth z =
-       case Tree.current z of
-        Tree0 bs ->
-               forM_ bs $ \case
-                DTC.Figure{..} ->
-                       H.tr $ do
-                               H.td ! HA.class_ "figure-number" $
-                                       H.a ! HA.href (attrValue pos) $
-                                               H.toMarkup type_
-                               H.td ! HA.class_ "figure-name" $
-                                       H.toMarkup title
-                _ -> mempty
-        _ -> mempty
-
-textXmlPosAncestors :: [(XmlName,Int)] -> Text
-textXmlPosAncestors =
-       snd . foldr (\(n,c) (nParent,acc) ->
-               (n,
-                       (if Text.null acc
-                               then acc
-                               else acc <> ".") <>
-                       Text.pack
-                        (if n == nParent
-                               then show c
-                               else show n<>show c)
-               )
-        ) ("","")
-
-html5SectionNumber :: [(XmlName,Int)] -> Html
-html5SectionNumber = go [] . List.reverse
+               H.li $$ do
+                       H.table ! HA.class_ "toc-entry" $$
+                               H.tbody $$
+                                       H.tr $$ do
+                                               H.td ! HA.class_ "section-number" $$
+                                                       html5SectionRef $ DTC.posAncestors pos
+                                               H.td ! HA.class_ "section-title" $$
+                                                       html5ify $ cleanPara $ DTC.unTitle title
+                       when (maybe True (> DTC.Nat 1) depth && not (null sections)) $
+                               H.ul $$
+                                       forM_ sections $
+                                               html5ifyToC (depth >>= DTC.predNat)
+        _ -> pure ()
        where
-       go :: [(XmlName,Int)] -> [(XmlName,Int)] -> Html
-       go _rs [] = mempty
-       go rs (a@(_n,cnt):as) = do
-               H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors (a:rs)) $
-                       H.toMarkup $ show cnt
-               H.toMarkup '.'
-               go (a:rs) as
-
-html5SectionRef :: [(XmlName,Int)] -> Html
-html5SectionRef as =
-       H.a ! HA.href (attrValue $ "#"<>textXmlPosAncestors as) $
-               case as of
-                [(_n,c)] -> do
-                       H.toMarkup $ show c
-                       H.toMarkup '.'
-                _ ->
-                       H.toMarkup $
-                               Text.intercalate "." $
-                               Text.pack . show . snd <$> as
+       sections =
+               (`Tree.runAxis` z) $
+               Tree.axis_child
+               `Tree.axis_filter_current` \case
+                TreeN DTC.Section{} _ -> True
+                _ -> False
 
-html5Verticals :: [DTC.Vertical] -> Html
-html5Verticals = foldMap html5Vertical
+html5ifyToF :: [Text] -> Html5
+html5ifyToF types = do
+       figsByType <- liftStateMarkup $ S.gets state_figures
+       let figs =
+               Map.foldMapWithKey (\ty -> ((ty,) <$>)) $
+               if null types
+               then figsByType
+               else
+                       Map.intersection figsByType $
+                       Map.fromList [(ty,()) | ty <- types]
+       forM_ (Map.toList figs) $ \(pos, (type_, title)) ->
+               H.tr $$ do
+                       H.td ! HA.class_ "figure-number" $$
+                               H.a ! HA.href ("#"<>attrify pos) $$ do
+                                       html5ify type_
+                                       html5ify $ DTC.posAncestors pos
+                       forM_ title $ \ti ->
+                               H.td ! HA.class_ "figure-title" $$
+                                       html5ify $ cleanPara $ DTC.unTitle ti
 
-html5Vertical :: DTC.Vertical -> Html
-html5Vertical = \case
+instance Html5ify [DTC.Block] where
+       html5ify = mapM_ html5ify
+instance Html5ify DTC.Block where
+       html5ify = \case
         DTC.Para{..} ->
                html5CommonAttrs attrs $
-               H.div ! HA.class_ "para"
-                     ! HA.id (attrValue pos) $ do
-                       html5Horizontals horis
+               H.p ! HA.class_ "para"
+                   ! HA.id (attrify pos) $$ do
+                       html5ify para
         DTC.OL{..} ->
                html5CommonAttrs attrs $
                H.ol ! HA.class_ "ol"
-                    ! HA.id (attrValue pos) $ do
+                    ! HA.id (attrify pos) $$ do
                        forM_ items $ \item ->
-                               H.li $ html5Verticals item
+                               H.li $$ html5ify item
         DTC.UL{..} ->
                html5CommonAttrs attrs $
                H.ul ! HA.class_ "ul"
-                    ! HA.id (attrValue pos) $ do
+                    ! HA.id (attrify pos) $$ do
                        forM_ items $ \item ->
-                               H.li $ html5Verticals item
-        DTC.RL{..} ->
-               html5CommonAttrs attrs $
-               H.div ! HA.class_ "rl"
-                     ! HA.id (attrValue pos) $ do
-                       H.table $
-                               forM_ refs html5Reference
+                               H.li $$ html5ify item
         DTC.Comment t ->
-               H.Comment (H.Text t) ()
-       {-
-        Index{..} -> 
-        Artwork{..} -> 
-       -}
+               html5ify $ H.Comment (H.Text t) ()
+instance Html5ify DTC.Lines where
+       html5ify = \case
+        Tree0 v ->
+               case v of
+                DTC.BR      -> html5ify H.br
+                DTC.Plain t -> html5ify t
+        TreeN k ls ->
+               case k of
+                DTC.B    -> H.strong $$ html5ify ls
+                DTC.Code -> H.code   $$ html5ify ls
+                DTC.Del  -> H.del    $$ html5ify ls
+                DTC.I    -> do
+                       i <- liftStateMarkup $ do
+                               i <- S.gets $ Plain.state_italic . state_plainify
+                               S.modify $ \s ->
+                                       s{state_plainify=
+                                               (state_plainify s){Plain.state_italic=
+                                                       not i}}
+                               return i
+                       H.em ! HA.class_ (if i then "even" else "odd") $$
+                               html5ify ls
+                       liftStateMarkup $
+                               S.modify $ \s ->
+                                       s{state_plainify=
+                                               (state_plainify s){Plain.state_italic=i}}
+                DTC.Sub  -> H.sub    $$ html5ify ls
+                DTC.Sup  -> H.sup    $$ html5ify ls
+                DTC.SC   -> H.span ! HA.class_ "smallcaps" $$ html5ify ls
+                DTC.U    -> H.span ! HA.class_ "underline" $$ html5ify ls
+                DTC.Note{..} ->
+                       case number of
+                        Nothing -> ""
+                        Just num ->
+                               H.sup ! HA.class_ "note-number" $$
+                                       H.a ! HA.class_ "note-ref"
+                                           ! HA.id ("note-ref."<>attrify num)
+                                           ! HA.href ("#note."<>attrify num) $$
+                                               html5ify num
+                DTC.Q -> do
+                       depth <- liftStateMarkup $ do
+                               depth <- S.gets $ Plain.state_quote . state_plainify
+                               S.modify $ \s -> s{state_plainify=
+                                       (state_plainify s){Plain.state_quote=
+                                               DTC.succNat depth}}
+                               return depth
+                       H.span ! HA.class_ "q" $$ do
+                               html5ify $ Plain.L10n_QuoteOpen depth
+                               html5ify $ TreeN DTC.I ls
+                               html5ify $ Plain.L10n_QuoteClose depth
+                       liftStateMarkup $
+                               S.modify $ \s ->
+                                       s{state_plainify=
+                                               (state_plainify s){Plain.state_quote = depth}}
+                DTC.Eref{..} ->
+                       H.a ! HA.class_ "eref"
+                           ! HA.href (attrify href) $$
+                               if null ls
+                               then html5ify $ DTC.unURL href
+                               else html5ify ls
+                DTC.Iref{..} ->
+                       case anchor of
+                        Nothing -> html5ify ls
+                        Just DTC.Anchor{..} ->
+                               H.span ! HA.class_ "iref"
+                                      ! HA.id (attrifyIrefCount term count) $$
+                                       html5ify ls
+                DTC.Ref{..} ->
+                       H.a ! HA.class_ "ref"
+                           ! HA.href ("#"<>attrify to) $$
+                       if null ls
+                       then html5ify to
+                       else html5ify ls
+                DTC.Rref{..} -> do
+                       refs <- liftStateMarkup $ S.gets state_references
+                       case Map.lookup to refs of
+                        Nothing -> do
+                               "["::Html5
+                               H.span ! HA.class_ "rref-broken" $$
+                                       html5ify to
+                               "]"
+                        Just DTC.About{..} -> do
+                               when (not $ null ls) $
+                                       forM_ (List.take 1 titles) $ \(DTC.Title title) -> do
+                                               html5ify $ TreeN DTC.Q $
+                                                       case url of
+                                                        Nothing -> title
+                                                        Just u -> pure $ TreeN (DTC.Eref u) title
+                                               " "::Html5
+                               "["::Html5
+                               H.a ! HA.class_ "rref"
+                                   ! HA.href ("#rref."<>attrify to)
+                                   ! HA.id ("rref."<>attrify to<>maybe "" (\DTC.Anchor{..} -> "."<>attrify count) anchor) $$
+                                       html5ify to
+                               "]"
+instance Html5ify DTC.URL where
+       html5ify (DTC.URL url) =
+               H.a ! HA.class_ "eref"
+                   ! HA.href (attrify url) $$
+                       html5ify url
+instance Html5ify DTC.Date where
+       html5ify = html5ify . Plain.L10n_Date
+instance Html5ify DTC.About where
+       html5ify DTC.About{..} =
+               html5CommasDot $ concat $
+                [ html5Titles titles
+                , html5Entity <$> authors
+                , html5ify    <$> maybeToList date
+                , html5Entity <$> maybeToList editor
+                , html5Serie  <$> series
+                ]
+               where
+               html5Titles :: [DTC.Title] -> [Html5]
+               html5Titles ts | null ts = []
+               html5Titles ts = [html5Title $ fold $ List.intersperse t $ toList ts]
+                       where t = DTC.Title $ Seq.singleton $ Tree0 $ DTC.Plain " — "
+               html5Title (DTC.Title title) =
+                       html5ify $ TreeN DTC.Q $
+                               case url of
+                                Nothing -> title
+                                Just u -> pure $ TreeN (DTC.Eref u) title
+               html5SerieHref href DTC.Serie{..} = do
+                       sp <- liftStateMarkup $ S.gets state_plainify
+                       html5ify $
+                               TreeN DTC.Eref{href} $
+                               Seq.fromList
+                                [ Tree0 $ DTC.Plain $ name
+                                , Tree0 $ DTC.Plain $ TL.toStrict $ Plain.text sp Plain.L10n_Colon
+                                , Tree0 $ DTC.Plain key
+                                ]
+               html5Serie s@DTC.Serie{name="RFC", key} | Text.all Char.isDigit key =
+                       html5SerieHref (DTC.URL $ "https://tools.ietf.org/html/rfc"<>key) s
+               html5Serie s@DTC.Serie{name="DOI", key} =
+                       html5SerieHref (DTC.URL $ "https://dx.doi.org/"<>key) s
+               html5Serie DTC.Serie{..} = do
+                       html5ify name
+                       html5ify Plain.L10n_Colon
+                       html5ify key
+               html5Entity DTC.Entity{url=mu, ..} = do
+                       html5ify @DTC.Lines $
+                               case () of
+                                _ | not (Text.null email) ->
+                                       TreeN (DTC.Eref $ DTC.URL $ "mailto:"<>email) $
+                                               pure $ Tree0 $ DTC.Plain name
+                                _ | Just u <- mu  ->
+                                       TreeN (DTC.Eref u) $
+                                               pure $ Tree0 $ DTC.Plain name
+                                _ -> Tree0 $ DTC.Plain name
+                       forM_ org $ \o -> do
+                               " ("::Html5
+                               html5Entity o
+                               ")"::Html5
+instance Html5ify DTC.Reference where
+       html5ify DTC.Reference{id=id_, ..} =
+               H.tr $$ do
+                       H.td ! HA.class_ "reference-key" $$
+                               html5ify @DTC.Lines $ TreeN DTC.Rref{anchor=Nothing, to=id_} Seq.empty
+                       H.td ! HA.class_ "reference-content" $$ do
+                               html5ify about
+                               rrefs <- liftStateMarkup $ S.gets state_rrefs
+                               case Map.lookup id_ rrefs of
+                                Nothing -> pure ()
+                                Just anchs ->
+                                       H.span ! HA.class_ "reference-rrefs" $$
+                                               html5CommasDot $
+                                               (<$> List.reverse anchs) $ \DTC.Anchor{..} ->
+                                                       H.a ! HA.class_ "reference-rref"
+                                                           ! HA.href ("#rref."<>attrify id_<>"."<>attrify count) $$
+                                                               html5ify $ DTC.posAncestors section
+instance Html5ify DTC.PosPath where
+       html5ify ancs =
+               case toList ancs of
+                [(_n,c)] -> do
+                       html5ify $ show c
+                       html5ify '.'
+                as ->
+                       html5ify $
+                               Text.intercalate "." $
+                               Text.pack . show . snd <$> as
+instance Html5ify Plain where
+       html5ify p = do
+               sp <- liftStateMarkup $ S.gets state_plainify
+               let (t,sp') = Plain.runPlain p sp
+               html5ify t
+               liftStateMarkup $ S.modify $ \s -> s{state_plainify=sp'}
 
-html5Reference :: DTC.Reference -> Html
-html5Reference DTC.Reference{..} =
-       H.tr $ do
-               H.td ! HA.class_ "reference-key" $
-                       H.toMarkup id
-               H.td ! HA.class_ "reference-content" $
-                       html5About about
+html5CommasDot :: [Html5] -> Html5
+html5CommasDot [] = pure ()
+html5CommasDot hs = do
+       sequence_ $ List.intersperse ", " hs
+       "."
 
-html5About :: DTC.About -> Html
-html5About DTC.About{..} =
-       forM_ titles $ \(DTC.Title title) -> do
-               html5Horizontal $ DTC.Q title
-       {-
-       authors
-       editor
-       date
-       version
-       keywords
-       links
-       series
-       includes
-       -}
+html5CommonAttrs :: DTC.CommonAttrs -> Html5 -> Html5
+html5CommonAttrs DTC.CommonAttrs{id=id_, ..} =
+       Compose . (addClass . addId <$>) . getCompose
+       where
+       addClass =
+               case classes of
+                [] -> id
+                _  -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)
+       addId = maybe id (\(DTC.Ident i) ->
+               H.AddCustomAttribute "id" (H.Text i)) id_
+
+html5SectionNumber :: DTC.PosPath -> Html5
+html5SectionNumber = go mempty
+       where
+       go :: DTC.PosPath -> DTC.PosPath -> Html5
+       go prev next =
+               case Seq.viewl next of
+                Seq.EmptyL -> pure ()
+                a@(_n,rank) Seq.:< as -> do
+                       H.a ! HA.href ("#"<>attrify (prev Seq.|>a)) $$
+                               html5ify $ show rank
+                       when (not (null as) || null prev) $ do
+                               html5ify '.'
+                               go (prev Seq.|>a) as
 
-html5CommonAttrs :: DTC.CommonAttrs -> Html -> Html
-html5CommonAttrs DTC.CommonAttrs{..} =
-       (case classes of
-        [] -> \x -> x
-        _  -> H.AddCustomAttribute "class" (H.Text $ Text.unwords classes)) .
-       case id of
-        Nothing -> \x -> x
-        Just (DTC.Ident i) ->
-                       H.AddCustomAttribute "id" (H.Text i)
+html5SectionRef :: DTC.PosPath -> Html5
+html5SectionRef as =
+       H.a ! HA.href ("#"<>attrify as) $$
+               html5ify as
 
-html5Horizontal :: DTC.Horizontal -> Html
-html5Horizontal = \case
-        DTC.BR       -> H.br
-        DTC.B    hs  -> H.strong $ html5Horizontals hs
-        DTC.Code hs  -> H.code   $ html5Horizontals hs
-        DTC.Del  hs  -> H.del    $ html5Horizontals hs
-        DTC.I    hs  -> H.i      $ html5Horizontals hs
-        DTC.Note _   -> ""
-        DTC.Q    hs  -> "« "<>H.i (html5Horizontals hs)<>" »"
-        DTC.SC   hs  -> html5Horizontals hs
-        DTC.Sub  hs  -> H.sub $ html5Horizontals hs
-        DTC.Sup  hs  -> H.sup $ html5Horizontals hs
-        DTC.U    hs  -> H.span ! HA.class_ "underline" $ html5Horizontals hs
-        DTC.Eref{..} -> H.a ! HA.class_ "eref" ! HA.href (attrValue href) $ html5Horizontals text
-        DTC.Iref{..} -> H.a ! HA.class_ "iref" ! HA.href (attrValue to)   $ html5Horizontals text
-        DTC.Ref{..}  ->
-               H.a ! HA.class_ "ref"
-                   ! HA.href ("#"<>attrValue to) $
-               if null text
-               then H.toMarkup to
-               else html5Horizontals text
-        DTC.Rref{..} -> H.a ! HA.class_ "rref" ! HA.href (attrValue to)   $ html5Horizontals text
-        DTC.Plain t  -> H.toMarkup t
 
-html5Horizontals :: [DTC.Horizontal] -> Html
-html5Horizontals = mapM_ html5Horizontal
+-- * 'Attrify'
+instance Attrify DTC.Anchor where
+       attrify DTC.Anchor{..} =
+               attrify section
+                <> "." <> attrify count
+instance Attrify Plain where
+       attrify p =
+               let (t,_) = Plain.runPlain p def in
+               attrify t
+instance Attrify DTC.PosPath where
+       attrify = attrify . plainify
+instance Attrify DTC.Pos where
+       attrify = attrify . DTC.posAncestors
 
-textHorizontal :: DTC.Horizontal -> TL.Text
-textHorizontal = \case
-        DTC.BR       -> "\n"
-        DTC.B    hs  -> "*"<>textHorizontals hs<>"*"
-        DTC.Code hs  -> "`"<>textHorizontals hs<>"`"
-        DTC.Del  hs  -> "-"<>textHorizontals hs<>"-"
-        DTC.I    hs  -> "/"<>textHorizontals hs<>"/"
-        DTC.Note _   -> ""
-        DTC.Q    hs  -> "« "<>textHorizontals hs<>" »"
-        DTC.SC   hs  -> textHorizontals hs
-        DTC.Sub  hs  -> textHorizontals hs
-        DTC.Sup  hs  -> textHorizontals hs
-        DTC.U    hs  -> "_"<>textHorizontals hs<>"_"
-        DTC.Eref{..} -> textHorizontals text
-        DTC.Iref{..} -> textHorizontals text
-        DTC.Ref{..}  -> textHorizontals text
-        DTC.Rref{..} -> textHorizontals text
-        DTC.Plain t  -> TL.fromStrict t
+attrifyIref :: DTC.Words -> H.AttributeValue
+attrifyIref term =
+       "iref" <> "." <> attrify (Anchor.plainifyWords term)
+attrifyIrefCount :: DTC.Words -> DTC.Nat1 -> H.AttributeValue
+attrifyIrefCount term count =
+       "iref"
+        <> "." <> attrify (Anchor.plainifyWords term)
+        <> "." <> attrify count
 
-textHorizontals :: [DTC.Horizontal] -> TL.Text
-textHorizontals = foldMap textHorizontal
+-- * Type 'L10n'
+instance Html5ify Plain.L10n where
+       html5ify = html5ify . plainify
+instance Localize ls Plain Plain.L10n => Localize ls Html5 Plain.L10n where
+       localize loc a = html5ify (Locale.localize loc a::Plain)
+instance LocalizeIn FR Html5 Plain.L10n where
+       localizeIn loc = html5ify @Plain . localizeIn loc
+instance LocalizeIn EN Html5 Plain.L10n where
+       localizeIn loc = html5ify @Plain . localizeIn loc