Replace Element and makeHierarchical with makeSections.

Text.Pandoc.Shared:

+ Remove `Element` type [API change]
+ Remove `makeHierarchicalize` [API change]
+ Add `makeSections` [API change]
+ Export `deLink` [API change]

Now that we have Divs, we can use them to represent the structure
of sections, and we don't need a special Element type.
`makeSections` reorganizes a block list, adding Divs with
class `section` around sections, and adding numbering
if needed.

This change also fixes some longstanding issues recognizing
section structure when the document contains Divs.
Closes #3057, see also #997.

All writers have been changed to use `makeSections`.
Note that in the process we have reverted the change
c1d058aeb1
made in response to #5168, which I'm not completely
sure was a good idea.

Lua modules have also been adjusted accordingly.
Existing lua filters that use `hierarchicalize` will
need to be rewritten to use `make_sections`.
This commit is contained in:
John MacFarlane 2019-09-07 11:23:12 -07:00
parent 1ccff3339d
commit 9f984ff26a
25 changed files with 477 additions and 633 deletions

View file

@ -1314,40 +1314,6 @@ Object equality is determined via
: delimiter of list numbers; one of `DefaultDelim`, `Period`,
`OneParen`, and `TwoParens` (string)
## Hierarchical Element {#type-ref-Element}
Hierarchical elements can be either *Sec* (sections) or *Blk*
(blocks). *Blk* elements are treated like
[Block](#type-ref-Block)s.
### Sec {#type-ref-Sec}
Section elements used to provide hierarchical information on
document contents.
**Objects of this type are read-only.**
`level`
: header level (integer)
`numbering`
: section numbering ([list](#module-pandoc.list) of integers)
`attr`
: header attributes ([Attr](#type-ref-Attr))
`label`
: header content ([list](#module-pandoc.list) of
[Inline](#type-ref-Inline)s)
`contents`
: list of contents in this section
([list](#module-pandoc.list) of [hierarchical
element](#Element)s)
`tag`, `t`
: constant `Sec` (string)
## ReaderOptions {#type-ref-ReaderOptions}
Pandoc reader options
@ -2392,23 +2358,23 @@ Returns:
- Whether the two objects represent the same element (boolean)
### hierarchicalize {#utils-hierarchicalize}
### make\_sections {#utils-make_sections}
`hierarchicalize (blocks)`
`make_sections (number_sections, base_level, blocks)`
Convert list of [Blocks](#Blocks) into an hierarchical list. An
hierarchical elements is either a normal block (but no Header),
or a `Sec` element. The latter has the following fields:
- level: level in the document hierarchy;
- numbering: list of integers of length `level`, specifying
the absolute position of the section in the document;
- attr: section attributes (see [Attr](#Attr));
- contents: nested list of hierarchical elements.
Converst list of [Blocks](#Blocks) into sections.
`Div`s will be created beginning at each `Header`
and containing following content until the next `Header`
of comparable level. If `number_sections` is true,
a `number` attribute will be added to each `Header`
containing the section number. If `base_level` is
non-null, `Header` levels will be reorganized so
that there are no gaps, and so that the base level
is the level specified.
Returns:
- List of hierarchical elements.
- List of [Blocks](#Blocks).
Usage:
@ -2416,9 +2382,7 @@ Usage:
pandoc.Header(2, pandoc.Str 'first'),
pandoc.Header(2, pandoc.Str 'second'),
}
local elements = pandoc.utils.hierarchicalize(blocks)
print(table.concat(elements[1].numbering, '.')) -- 0.1
print(table.concat(elements[2].numbering, '.')) -- 0.2
local newblocks = pandoc.utils.make_sections(true, 1, blocks)
### run\_json\_filter {#utils-run_json_filter}

View file

@ -22,12 +22,9 @@ module Text.Pandoc.Lua.Marshaling.AST
import Prelude
import Control.Applicative ((<|>))
import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
import Foreign.Lua.Userdata ( ensureUserdataMetatable, pushAnyWithMetatable
, metatableName)
import Text.Pandoc.Definition
import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor)
import Text.Pandoc.Lua.Marshaling.CommonState ()
import Text.Pandoc.Shared (Element (Blk, Sec))
import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Lua.Util as LuaUtil
@ -285,31 +282,3 @@ instance Pushable LuaListAttributes where
instance Peekable LuaListAttributes where
peek = defineHowTo "get ListAttributes value" .
fmap LuaListAttributes . Lua.peek
--
-- Hierarchical elements
--
instance Pushable Element where
push (Blk blk) = Lua.push blk
push sec = pushAnyWithMetatable pushElementMetatable sec
where
pushElementMetatable = ensureUserdataMetatable (metatableName sec) $
LuaUtil.addFunction "__index" indexElement
instance Peekable Element where
peek idx = Lua.ltype idx >>= \case
Lua.TypeUserdata -> Lua.peekAny idx
_ -> Blk <$> Lua.peek idx
indexElement :: Element -> String -> Lua Lua.NumResults
indexElement = \case
(Blk _) -> const (1 <$ Lua.pushnil) -- this shouldn't happen
(Sec lvl num attr label contents) -> fmap (return 1) . \case
"level" -> Lua.push lvl
"numbering" -> Lua.push num
"attr" -> Lua.push (LuaAttr attr)
"label" -> Lua.push label
"contents" -> Lua.push contents
"tag" -> Lua.push "Sec"
"t" -> Lua.push "Sec"
_ -> Lua.pushnil

View file

@ -19,7 +19,6 @@ import Text.Pandoc.Definition
import Text.Pandoc.Lua.Marshaling.AST (LuaAttr, LuaListAttributes)
import Text.Pandoc.Lua.Marshaling.Version ()
import Text.Pandoc.Lua.Util (addFunction)
import Text.Pandoc.Shared (Element (..))
import qualified Foreign.Lua as Lua
@ -38,7 +37,6 @@ pushCloneTable = do
addFunction "Attr" cloneAttr
addFunction "Block" cloneBlock
addFunction "Citation" cloneCitation
addFunction "Element" cloneElement
addFunction "Inline" cloneInline
addFunction "Meta" cloneMeta
addFunction "MetaValue" cloneMetaValue
@ -55,9 +53,6 @@ cloneBlock = return
cloneCitation :: Citation -> Lua Citation
cloneCitation = return
cloneElement :: Element -> Lua Element
cloneElement = return
cloneInline :: Inline -> Lua Inline
cloneInline = return

View file

@ -38,7 +38,7 @@ pushModule mbDatadir = do
Lua.newtable
addFunction "blocks_to_inlines" blocksToInlines
addFunction "equals" equals
addFunction "hierarchicalize" hierarchicalize
addFunction "make_sections" makeSections
addFunction "normalize_date" normalizeDate
addFunction "run_json_filter" (runJSONFilter mbDatadir)
addFunction "sha1" sha1
@ -55,9 +55,10 @@ blocksToInlines blks optSep = do
Nothing -> Shared.defaultBlocksSeparator
return $ B.toList (Shared.blocksToInlinesWithSep sep blks)
-- | Convert list of Pandoc blocks into (hierarchical) list of Elements.
hierarchicalize :: [Block] -> Lua [Shared.Element]
hierarchicalize = return . Shared.hierarchicalize
-- | Convert list of Pandoc blocks into sections using Divs.
makeSections :: Bool -> Lua.Optional Int -> [Block] -> Lua [Block]
makeSections number baselevel =
return . Shared.makeSections number (Lua.fromOptional baselevel)
-- | Parse a date and convert (if possible) to "YYYY-MM-DD" format. We
-- limit years to the range 1601-9999 (ISO 8601 accepts greater than

View file

@ -47,13 +47,13 @@ module Text.Pandoc.Shared (
extractSpaces,
removeFormatting,
deNote,
deLink,
stringify,
capitalize,
compactify,
compactifyDL,
linesToPara,
Element (..),
hierarchicalize,
makeSections,
uniqueIdent,
inlineListToIdentifier,
isHeaderBlock,
@ -104,11 +104,10 @@ import qualified Data.Bifunctor as Bifunctor
import Data.Char (isAlpha, isLower, isSpace, isUpper, toLower, isAlphaNum,
generalCategory, GeneralCategory(NonSpacingMark,
SpacingCombiningMark, EnclosingMark, ConnectorPunctuation))
import Data.Data (Data, Typeable)
import Data.List (find, intercalate, intersperse, stripPrefix, sortBy)
import Data.Ord (comparing)
import qualified Data.Map as M
import Data.Maybe (mapMaybe)
import Data.Maybe (mapMaybe, fromMaybe)
import Data.Monoid (Any (..))
import Data.Sequence (ViewL (..), ViewR (..), viewl, viewr)
import qualified Data.Set as Set
@ -366,6 +365,10 @@ deNote :: Inline -> Inline
deNote (Note _) = Str ""
deNote x = x
deLink :: Inline -> Inline
deLink (Link _ ils _) = Span nullAttr ils
deLink x = x
deQuote :: Inline -> Inline
deQuote (Quoted SingleQuote xs) =
Span ("",[],[]) (Str "\8216" : xs ++ [Str "\8217"])
@ -449,34 +452,6 @@ isPara :: Block -> Bool
isPara (Para _) = True
isPara _ = False
-- | Data structure for defining hierarchical Pandoc documents
data Element = Blk Block
| Sec Int [Int] Attr [Inline] [Element]
-- lvl num attributes label contents
deriving (Eq, Read, Show, Typeable, Data)
instance Walkable Inline Element where
walk f (Blk x) = Blk (walk f x)
walk f (Sec lev nums attr ils elts) = Sec lev nums attr (walk f ils) (walk f elts)
walkM f (Blk x) = Blk `fmap` walkM f x
walkM f (Sec lev nums attr ils elts) = do
ils' <- walkM f ils
elts' <- walkM f elts
return $ Sec lev nums attr ils' elts'
query f (Blk x) = query f x
query f (Sec _ _ _ ils elts) = query f ils `mappend` query f elts
instance Walkable Block Element where
walk f (Blk x) = Blk (walk f x)
walk f (Sec lev nums attr ils elts) = Sec lev nums attr (walk f ils) (walk f elts)
walkM f (Blk x) = Blk `fmap` walkM f x
walkM f (Sec lev nums attr ils elts) = do
ils' <- walkM f ils
elts' <- walkM f elts
return $ Sec lev nums attr ils' elts'
query f (Blk x) = query f x
query f (Sec _ _ _ ils elts) = query f ils `mappend` query f elts
-- | Convert Pandoc inline list to plain text identifier. HTML
-- identifiers must start with a letter, and may contain only
-- letters, digits, and the characters _-.
@ -504,37 +479,67 @@ inlineListToIdentifier exts =
| otherwise = c == '_' || c == '-' || c == '.'
spaceToDash = map (\c -> if isSpace c then '-' else c)
-- | Convert list of Pandoc blocks into (hierarchical) list of Elements
hierarchicalize :: [Block] -> [Element]
hierarchicalize blocks = S.evalState (hierarchicalizeWithIds blocks) []
hierarchicalizeWithIds :: [Block] -> S.State [Int] [Element]
hierarchicalizeWithIds [] = return []
hierarchicalizeWithIds (Header level attr@(_,classes,_) title':xs) = do
lastnum <- S.get
let lastnum' = take level lastnum
let newnum = case length lastnum' of
x | "unnumbered" `elem` classes -> []
| x >= level -> init lastnum' ++ [last lastnum' + 1]
| otherwise -> lastnum ++
replicate (level - length lastnum - 1) 0 ++ [1]
unless (null newnum) $ S.put newnum
let (sectionContents, rest) = break (headerLtEq level) xs
sectionContents' <- hierarchicalizeWithIds sectionContents
rest' <- hierarchicalizeWithIds rest
return $ Sec level newnum attr title' sectionContents' : rest'
hierarchicalizeWithIds (Div ("refs",classes',kvs')
(Header level (ident,classes,kvs) title' : xs):ys) =
hierarchicalizeWithIds (Header level (ident,"references":classes,kvs)
title' : Div ("refs",classes',kvs') xs : ys)
hierarchicalizeWithIds (x:rest) = do
rest' <- hierarchicalizeWithIds rest
return $ Blk x : rest'
-- | Put a list of Pandoc blocks into a hierarchical structure:
-- a list of sections (each a Div with class "section" and first
-- element a Header). If the 'numbering' parameter is True, Header
-- numbers are added via the number attribute on the header.
-- If the baseLevel parameter is Just n, Header levels are
-- adjusted to be gapless starting at level n.
makeSections :: Bool -> Maybe Int -> [Block] -> [Block]
makeSections numbering mbBaseLevel bs =
S.evalState (go bs) (mbBaseLevel, [])
where
go :: [Block] -> S.State (Maybe Int, [Int]) [Block]
go (Header level (ident,classes,kvs) title':xs) = do
(mbLevel, lastnum) <- S.get
let level' = fromMaybe level mbLevel
let lastnum' = take level' lastnum
let newnum =
if level' > 0
then case length lastnum' of
x | "unnumbered" `elem` classes -> []
| x >= level' -> init lastnum' ++ [last lastnum' + 1]
| otherwise -> lastnum ++
replicate (level' - length lastnum - 1) 0 ++ [1]
else []
unless (null newnum) $ S.modify $ \(mbl, _) -> (mbl, newnum)
let (sectionContents, rest) = break (headerLtEq level) xs
S.modify $ \(_, ln) -> (fmap (+ 1) mbLevel, ln)
sectionContents' <- go sectionContents
S.modify $ \(_, ln) -> (mbLevel, ln)
rest' <- go rest
let divattr = (ident, ["section"], [])
let attr = ("",classes,kvs ++
[("number", intercalate "." (map show newnum))
| numbering])
return $
Div divattr (Header level' attr title' : sectionContents') : rest'
go (Div (dident,dclasses,dkvs)
(Header level (ident,classes,kvs) title':ys) : xs) = do
inner <- go (Header level (ident,classes,kvs) title':ys)
let inner' =
case inner of
(Div (dident',dclasses',dkvs') zs@(Header{}:zs') : ws)
| null dident ->
Div (dident',dclasses' ++ dclasses,dkvs' ++ dkvs) zs : ws
| otherwise -> -- keep id on header so we don't lose anchor
Div (dident,dclasses ++ dclasses',dkvs ++ dkvs')
(Header level (dident',classes,kvs) title':zs') : ws
_ -> inner -- shouldn't happen
rest <- go xs
return $ inner' ++ rest
go (Div attr xs : rest) = do
xs' <- go xs
rest' <- go rest
return $ Div attr xs' : rest'
go (x:xs) = (x :) <$> go xs
go [] = return []
headerLtEq :: Int -> Block -> Bool
headerLtEq level (Header l _ _) = l <= level
headerLtEq level (Div ("",["references"],[]) (Header l _ _ : _)) = l <= level
headerLtEq _ _ = False
headerLtEq level (Header l _ _) = l <= level
headerLtEq level (Div _ (b:_)) = headerLtEq level b
headerLtEq _ _ = False
-- | Generate a unique identifier from a list of inlines.
-- Second argument is a list of already used identifiers.

View file

@ -28,7 +28,7 @@ getSlideLevel = go 6
nonHOrHR HorizontalRule = False
nonHOrHR _ = True
-- | Prepare a block list to be passed to hierarchicalize.
-- | Prepare a block list to be passed to makeSections.
prepSlides :: Int -> [Block] -> [Block]
prepSlides slideLevel = ensureStartWithH . splitHrule . extractRefsHeader
where splitHrule (HorizontalRule : Header n attr xs : ys)

View file

@ -84,7 +84,7 @@ pandocToAsciiDoc opts (Pandoc meta blocks) = do
(blockListToAsciiDoc opts)
(fmap chomp . inlineListToAsciiDoc opts)
meta
main <- vcat <$> mapM (elementToAsciiDoc 1 opts) (hierarchicalize blocks)
main <- blockListToAsciiDoc opts $ makeSections False (Just 1) blocks
st <- get
let context = defField "body" main
$ defField "toc"
@ -97,14 +97,6 @@ pandocToAsciiDoc opts (Pandoc meta blocks) = do
Nothing -> main
Just tpl -> renderTemplate tpl context
elementToAsciiDoc :: PandocMonad m
=> Int -> WriterOptions -> Element -> ADW m (Doc Text)
elementToAsciiDoc _ opts (Blk b) = blockToAsciiDoc opts b
elementToAsciiDoc nestlevel opts (Sec _lvl _num attr label children) = do
hdr <- blockToAsciiDoc opts (Header nestlevel attr label)
rest <- vcat <$> mapM (elementToAsciiDoc (nestlevel + 1) opts) children
return $ hdr $$ rest
-- | Escape special characters for AsciiDoc.
escapeString :: String -> String
escapeString = escapeStringUsing escs
@ -137,6 +129,11 @@ blockToAsciiDoc :: PandocMonad m
-> Block -- ^ Block element
-> ADW m (Doc Text)
blockToAsciiDoc _ Null = return empty
blockToAsciiDoc opts (Div (id',"section":_,_)
(Header level (_,cls,kvs) ils : xs)) = do
hdr <- blockToAsciiDoc opts (Header level (id',cls,kvs) ils)
rest <- blockListToAsciiDoc opts xs
return $ hdr $$ rest
blockToAsciiDoc opts (Plain inlines) = do
contents <- inlineListToAsciiDoc opts inlines
return $ contents <> blankline

View file

@ -65,8 +65,7 @@ pandocToConTeXt options (Pandoc meta blocks) = do
blockListToConTeXt
(fmap chomp . inlineListToConTeXt)
meta
body <- mapM (elementToConTeXt options) $ hierarchicalize blocks
let main = vcat body
main <- blockListToConTeXt $ makeSections False Nothing blocks
let layoutFromMargins = mconcat $ intersperse ("," :: Doc Text) $
mapMaybe (\(x,y) ->
((x <> "=") <>) <$> getField y metadata)
@ -147,18 +146,15 @@ toLabel z = concatMap go z
| x `elem` ("\\#[]\",{}%()|=" :: String) = "ux" ++ printf "%x" (ord x)
| otherwise = [x]
-- | Convert Elements to ConTeXt
elementToConTeXt :: PandocMonad m => WriterOptions -> Element -> WM m (Doc Text)
elementToConTeXt _ (Blk block) = blockToConTeXt block
elementToConTeXt opts (Sec level _ attr title' elements) = do
header' <- sectionHeader attr level title'
footer' <- sectionFooter attr level
innerContents <- mapM (elementToConTeXt opts) elements
return $ header' $$ vcat innerContents $$ footer'
-- | Convert Pandoc block element to ConTeXt.
blockToConTeXt :: PandocMonad m => Block -> WM m (Doc Text)
blockToConTeXt Null = return empty
blockToConTeXt (Div attr@(_,"section":_,_)
(Header level _ title' : xs)) = do
header' <- sectionHeader attr level title'
footer' <- sectionFooter attr level
innerContents <- blockListToConTeXt xs
return $ header' $$ innerContents $$ footer'
blockToConTeXt (Plain lst) = inlineListToConTeXt lst
-- title beginning with fig: indicates that the image is a figure
blockToConTeXt (Para [Image attr txt (src,'f':'i':'g':':':_)]) = do

View file

@ -78,7 +78,6 @@ writeDocbook5 opts d =
-- | Convert Pandoc document to string in Docbook format.
writeDocbook :: PandocMonad m => WriterOptions -> Pandoc -> DB m Text
writeDocbook opts (Pandoc meta blocks) = do
let elements = hierarchicalize blocks
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
@ -88,15 +87,15 @@ writeDocbook opts (Pandoc meta blocks) = do
TopLevelChapter -> 0
TopLevelSection -> 1
TopLevelDefault -> 1
let fromBlocks = blocksToDocbook opts .
makeSections False (Just startLvl)
auths' <- mapM (authorToDocbook opts) $ docAuthors meta
let meta' = B.setMeta "author" auths' meta
metadata <- metaToContext opts
(fmap vcat .
mapM (elementToDocbook opts startLvl) .
hierarchicalize)
(fromBlocks)
(inlinesToDocbook opts)
meta'
main <- vcat <$> mapM (elementToDocbook opts startLvl) elements
main <- fromBlocks blocks
let context = defField "body" main
$ defField "mathml" (case writerHTMLMathMethod opts of
MathML -> True
@ -107,34 +106,6 @@ writeDocbook opts (Pandoc meta blocks) = do
Nothing -> main
Just tpl -> renderTemplate tpl context
-- | Convert an Element to Docbook.
elementToDocbook :: PandocMonad m => WriterOptions -> Int -> Element -> DB m (Doc Text)
elementToDocbook opts _ (Blk block) = blockToDocbook opts block
elementToDocbook opts lvl (Sec _ _num (id',_,_) title elements) = do
version <- ask
-- Docbook doesn't allow sections with no content, so insert some if needed
let elements' = if null elements
then [Blk (Para [])]
else elements
tag = case lvl of
-1 -> "part"
0 -> "chapter"
n | n >= 1 && n <= 5 -> if version == DocBook5
then "section"
else "sect" ++ show n
_ -> "simplesect"
idName = if version == DocBook5
then "xml:id"
else "id"
idAttr = [(idName, writerIdentifierPrefix opts ++ id') | not (null id')]
nsAttr = if version == DocBook5 && lvl == 0 then [("xmlns", "http://docbook.org/ns/docbook"),("xmlns:xlink", "http://www.w3.org/1999/xlink")]
else []
attribs = nsAttr ++ idAttr
contents <- mapM (elementToDocbook opts (lvl + 1)) elements'
title' <- inlinesToDocbook opts title
return $ inTags True tag attribs $
inTagsSimple "title" title' $$ vcat contents
-- | Convert a list of Pandoc blocks to Docbook.
blocksToDocbook :: PandocMonad m => WriterOptions -> [Block] -> DB m (Doc Text)
blocksToDocbook opts = fmap vcat . mapM (blockToDocbook opts)
@ -184,6 +155,29 @@ blockToDocbook :: PandocMonad m => WriterOptions -> Block -> DB m (Doc Text)
blockToDocbook _ Null = return empty
-- Add ids to paragraphs in divs with ids - this is needed for
-- pandoc-citeproc to get link anchors in bibliographies:
blockToDocbook opts (Div (id',"section":_,_) (Header lvl _ ils : xs)) = do
version <- ask
-- Docbook doesn't allow sections with no content, so insert some if needed
let bs = if null xs
then [Para []]
else xs
tag = case lvl of
-1 -> "part"
0 -> "chapter"
n | n >= 1 && n <= 5 -> if version == DocBook5
then "section"
else "sect" ++ show n
_ -> "simplesect"
idName = if version == DocBook5
then "xml:id"
else "id"
idAttr = [(idName, writerIdentifierPrefix opts ++ id') | not (null id')]
nsAttr = if version == DocBook5 && lvl == 0 then [("xmlns", "http://docbook.org/ns/docbook"),("xmlns:xlink", "http://www.w3.org/1999/xlink")]
else []
attribs = nsAttr ++ idAttr
title' <- inlinesToDocbook opts ils
contents <- blocksToDocbook opts bs
return $ inTags True tag attribs $ inTagsSimple "title" title' $$ contents
blockToDocbook opts (Div (ident,_,_) [Para lst]) =
let attribs = [("id", ident) | not (null ident)] in
if hasLineBreaks lst
@ -197,7 +191,7 @@ blockToDocbook opts (Div (ident,_,_) bs) = do
then mempty
else selfClosingTag "anchor" [("id", ident)]) $$ contents
blockToDocbook _ h@Header{} = do
-- should not occur after hierarchicalize, except inside lists/blockquotes
-- should be handled by Div section above, except inside lists/blockquotes
report $ BlockNotRendered h
return empty
blockToDocbook opts (Plain lst) = inlinesToDocbook opts lst

View file

@ -47,7 +47,7 @@ import Text.Pandoc.MIME (MimeType, extensionFromMimeType, getMimeType,
getMimeTypeDef)
import Text.Pandoc.Options
import Text.Pandoc.Readers.Docx.StyleMap
import Text.Pandoc.Shared hiding (Element)
import Text.Pandoc.Shared
import Text.Pandoc.Walk
import Text.Pandoc.Writers.Math
import Text.Pandoc.Writers.Shared

View file

@ -26,7 +26,7 @@ import Control.Monad.State.Strict (State, StateT, evalState, evalStateT, get,
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Lazy.Char8 as B8
import Data.Char (isAlphaNum, isAscii, isDigit, toLower)
import Data.List (intercalate, isInfixOf, isPrefixOf)
import Data.List (isInfixOf, isPrefixOf)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, isNothing, mapMaybe, isJust)
import qualified Data.Set as Set
@ -47,9 +47,8 @@ import Text.Pandoc.MIME (MimeType, extensionFromMimeType, getMimeType)
import Text.Pandoc.Options (EPUBVersion (..), HTMLMathMethod (..),
ObfuscationMethod (NoObfuscation), WrapOption (..),
WriterOptions (..))
import Text.Pandoc.Shared (hierarchicalize, normalizeDate, renderTags',
import Text.Pandoc.Shared (makeSections, normalizeDate, renderTags',
safeRead, stringify, trim, uniqueIdent)
import qualified Text.Pandoc.Shared as S (Element (..))
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.UUID (getUUID)
import Text.Pandoc.Walk (query, walk, walkM)
@ -712,31 +711,34 @@ pandocToEPUB version opts doc = do
contentsEntry <- mkEntry "content.opf" contentsData
-- toc.ncx
let secs = hierarchicalize blocks'
let secs = makeSections True (Just 1) blocks'
let tocLevel = writerTOCDepth opts
let navPointNode :: PandocMonad m
=> (Int -> [Inline] -> String -> [Element] -> Element)
-> S.Element -> StateT Int m Element
navPointNode formatter (S.Sec _ nums (ident,_,_) ils children) = do
n <- get
modify (+1)
let showNums :: [Int] -> String
showNums = intercalate "." . map show
let tit = if writerNumberSections opts && not (null nums)
then Span ("", ["section-header-number"], [])
[Str (showNums nums)] : Space : ils
else ils
src <- case lookup ident reftable of
Just x -> return x
Nothing -> throwError $ PandocSomeError $ ident ++ " not found in reftable"
let isSec (S.Sec lev _ _ _ _) = lev <= tocLevel
isSec _ = False
let subsecs = filter isSec children
subs <- mapM (navPointNode formatter) subsecs
return $ formatter n tit src subs
navPointNode _ (S.Blk _) = throwError $ PandocSomeError "navPointNode encountered Blk"
-> Block -> StateT Int m [Element]
navPointNode formatter (Div (ident,"section":_,_)
(Header lvl (_,_,kvs) ils : children)) = do
if lvl > tocLevel
then return []
else do
n <- get
modify (+1)
let num = fromMaybe "" $ lookup "number" kvs
let tit = if writerNumberSections opts && not (null num)
then Span ("", ["section-header-number"], [])
[Str num] : Space : ils
else ils
src <- case lookup ident reftable of
Just x -> return x
Nothing -> throwError $ PandocSomeError $
ident ++ " not found in reftable"
subs <- concat <$> mapM (navPointNode formatter) children
return [formatter n tit src subs]
navPointNode formatter (Div _ bs) =
concat <$> mapM (navPointNode formatter) bs
navPointNode _ _ = return []
let navMapFormatter :: Int -> [Inline] -> String -> [Element] -> Element
navMapFormatter n tit src subs = unode "navPoint" !
@ -750,7 +752,8 @@ pandocToEPUB version opts doc = do
, unode "content" ! [("src", "text/title_page.xhtml")]
$ () ]
navMap <- lift $ evalStateT (mapM (navPointNode navMapFormatter) secs) 1
navMap <- lift $ evalStateT
(concat <$> mapM (navPointNode navMapFormatter) secs) 1
let tocData = UTF8.fromStringLazy $ ppTopElement $
unode "ncx" ! [("version","2005-1")
,("xmlns","http://www.daisy.org/z3986/2005/ncx/")] $
@ -800,7 +803,8 @@ pandocToEPUB version opts doc = do
clean x = x
let navtag = if epub3 then "nav" else "div"
tocBlocks <- lift $ evalStateT (mapM (navPointNode navXhtmlFormatter) secs) 1
tocBlocks <- lift $ evalStateT
(concat <$> mapM (navPointNode navXhtmlFormatter) secs) 1
let navBlocks = [RawBlock (Format "html")
$ showElement $ -- prettyprinting introduces bad spaces
unode navtag ! ([("epub:type","toc") | epub3] ++

View file

@ -39,9 +39,9 @@ import qualified Text.Pandoc.Class as P
import Text.Pandoc.Definition
import Text.Pandoc.Logging
import Text.Pandoc.Options (HTMLMathMethod (..), WriterOptions (..), def)
import Text.Pandoc.Shared (capitalize, isURI, orderedListMarkers, hierarchicalize)
import Text.Pandoc.Shared (capitalize, isURI, orderedListMarkers,
makeSections)
import Text.Pandoc.Writers.Shared (lookupMetaString)
import qualified Text.Pandoc.Shared as Shared (Element(Blk, Sec))
-- | Data to be written at the end of the document:
-- (foot)notes, URLs, references, images.
@ -162,28 +162,27 @@ docdate meta' = do
-- representation.
renderSections :: PandocMonad m => Int -> [Block] -> FBM m [Content]
renderSections level blocks = do
let elements = hierarchicalize blocks
let isSection Shared.Sec{} = True
let blocks' = makeSections False Nothing blocks
let isSection (Div (_,"section":_,_) (Header{}:_)) = True
isSection _ = False
let (initialBlocks, secs) = break isSection elements
let elements' = if null initialBlocks
then secs
else Shared.Sec 1 [] nullAttr mempty initialBlocks : secs
cMapM (renderSection level) elements'
let (initialBlocks, secs) = break isSection blocks'
let blocks'' = if null initialBlocks
then blocks'
else Div ("",["section"],[])
(Header 1 nullAttr mempty : initialBlocks) : secs
cMapM (renderSection level) blocks''
renderSection :: PandocMonad m => Int -> Shared.Element -> FBM m [Content]
renderSection _ (Shared.Blk block) = blockToXml block
renderSection lvl (Shared.Sec _ _num (id',_,_) title elements) = do
content <- cMapM (renderSection (lvl + 1)) elements
renderSection :: PandocMonad m => Int -> Block -> FBM m [Content]
renderSection lvl (Div (id',"section":_,_) (Header _ _ title : xs)) = do
title' <- if null title
then return []
else list . el "title" <$> formatTitle title
content <- cMapM (renderSection (lvl + 1)) xs
let sectionContent = if null id'
then el "section" (title' ++ content)
else el "section" ([uattr "id" id'], title' ++ content)
return [sectionContent]
renderSection _ b = blockToXml b
-- | Only <p> and <empty-line> are allowed within <title> in FB2.
formatTitle :: PandocMonad m => [Inline] -> FBM m [Content]
@ -334,7 +333,7 @@ blockToXml (DefinitionList defs) =
t <- wrap "strong" term
return (el "p" t : items)
blockToXml h@Header{} = do
-- should not occur after hierarchicalize, except inside lists/blockquotes
-- should not occur after makeSections, except inside lists/blockquotes
report $ BlockNotRendered h
return []
blockToXml HorizontalRule = return [ el "empty-line" () ]

View file

@ -32,9 +32,10 @@ import Prelude
import Control.Monad.State.Strict
import Data.Char (ord, toLower)
import Data.List (intercalate, intersperse, isPrefixOf, partition)
import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe)
import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe)
import qualified Data.Set as Set
import Data.String (fromString)
import Data.List.Split (splitWhen)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
@ -90,20 +91,20 @@ data WriterState = WriterState
, stMath :: Bool -- ^ Math is used in document
, stQuotes :: Bool -- ^ <q> tag is used
, stHighlighting :: Bool -- ^ Syntax highlighting is used
, stSecNum :: [Int] -- ^ Number of current section
, stElement :: Bool -- ^ Processing an Element
, stHtml5 :: Bool -- ^ Use HTML5
, stEPUBVersion :: Maybe EPUBVersion -- ^ EPUB version if for epub
, stSlideVariant :: HTMLSlideVariant
, stSlideLevel :: Int -- ^ Slide level
, stCodeBlockNum :: Int -- ^ Number of code block
}
defaultWriterState :: WriterState
defaultWriterState = WriterState {stNotes= [], stMath = False, stQuotes = False,
stHighlighting = False, stSecNum = [],
stElement = False, stHtml5 = False,
stHighlighting = False,
stHtml5 = False,
stEPUBVersion = Nothing,
stSlideVariant = NoSlides,
stSlideLevel = 1,
stCodeBlockNum = 0}
-- Helpers to render HTML with the appropriate function.
@ -243,6 +244,8 @@ pandocToHtml :: PandocMonad m
-> Pandoc
-> StateT WriterState m (Html, Context Text)
pandocToHtml opts (Pandoc meta blocks) = do
let slideLevel = fromMaybe (getSlideLevel blocks) $ writerSlideLevel opts
modify $ \st -> st{ stSlideLevel = slideLevel }
metadata <- metaToContext opts
(fmap renderHtml' . blockListToHtml opts)
(fmap renderHtml' . inlineListToHtml opts)
@ -250,17 +253,15 @@ pandocToHtml opts (Pandoc meta blocks) = do
let stringifyHTML = escapeStringForXML . stringify
let authsMeta = map stringifyHTML $ docAuthors meta
let dateMeta = stringifyHTML $ docDate meta
let slideLevel = fromMaybe (getSlideLevel blocks) $ writerSlideLevel opts
slideVariant <- gets stSlideVariant
let sects = hierarchicalize $
let sects = makeSections (writerNumberSections opts) Nothing $
if slideVariant == NoSlides
then blocks
else prepSlides slideLevel blocks
toc <- if writerTableOfContents opts && slideVariant /= S5Slides
then fmap renderHtml' <$> tableOfContents opts sects
else return Nothing
blocks' <- liftM (mconcat . intersperse (nl opts)) $
mapM (elementToHtml Nothing slideLevel opts) sects
blocks' <- blockListToHtml opts sects
st <- get
notes <- footnoteSection opts (reverse (stNotes st))
let thebody = blocks' >> notes
@ -380,130 +381,20 @@ listItemToHtml opts bls
return $ constr (checkbox >> isContents) >> bsContents
-- | Construct table of contents from list of elements.
tableOfContents :: PandocMonad m => WriterOptions -> [Element]
tableOfContents :: PandocMonad m => WriterOptions -> [Block]
-> StateT WriterState m (Maybe Html)
tableOfContents _ [] = return Nothing
tableOfContents opts sects = do
contents <- mapM (elementToListItem opts) sects
let tocList = catMaybes contents
if null tocList
then return Nothing
else Just <$> unordList opts tocList
-- | Convert section number to string
showSecNum :: [Int] -> String
showSecNum = intercalate "." . map show
-- | Converts an Element to a list item for a table of contents,
-- retrieving the appropriate identifier from state.
elementToListItem :: PandocMonad m => WriterOptions -> Element
-> StateT WriterState m (Maybe Html)
-- Don't include the empty headers created in slide shows
-- shows when an hrule is used to separate slides without a new title:
elementToListItem _ (Sec _ _ _ [Str "\0"] _) = return Nothing
elementToListItem opts (Sec lev num (id',classes,_) headerText subsecs)
| lev <= writerTOCDepth opts = do
let num' = zipWith (+) num (writerNumberOffset opts ++ repeat 0)
let sectnum = if writerNumberSections opts && not (null num) &&
"unnumbered" `notElem` classes
then (H.span ! A.class_ "toc-section-number"
$ toHtml $ showSecNum num') >> preEscapedString " "
else mempty
txt <- liftM (sectnum >>) $
inlineListToHtml opts $ walk (deLink . deNote) headerText
subHeads <- mapM (elementToListItem opts) subsecs >>= return . catMaybes
subList <- if null subHeads
then return mempty
else unordList opts subHeads
-- in reveal.js, we need #/apples, not #apples:
slideVariant <- gets stSlideVariant
let revealSlash = ['/' | slideVariant== RevealJsSlides]
return $ Just
$ if null id'
then H.a (toHtml txt) >> subList
else (H.a ! A.href (toValue $ "#" ++ revealSlash ++
writerIdentifierPrefix opts ++ id')
$ toHtml txt) >> subList
elementToListItem _ _ = return Nothing
deLink :: Inline -> Inline
deLink (Link _ ils _) = Span nullAttr ils
deLink x = x
-- | Convert an Element to Html.
elementToHtml :: PandocMonad m => Maybe Int -> Int -> WriterOptions -> Element
-> StateT WriterState m Html
elementToHtml _ _ opts (Blk block) = blockToHtml opts block
elementToHtml mbparentlevel slideLevel opts
(Sec level num (id',classes,keyvals) title' elements)
= do
slideVariant <- gets stSlideVariant
let slide = slideVariant /= NoSlides &&
(level <= slideLevel ||
-- we're missing a header at slide level (see #5168)
maybe False (< slideLevel) mbparentlevel)
let num' = zipWith (+) num (writerNumberOffset opts ++ repeat 0)
modify $ \st -> st{stSecNum = num'} -- update section number
html5 <- gets stHtml5
let titleSlide = slide && level < slideLevel
header' <- if title' == [Str "\0"] -- marker for hrule
then return mempty
else do
modify (\st -> st{ stElement = True})
let level' = if level <= slideLevel &&
slideVariant == SlidySlides
then 1 -- see #3566
else level
res <- blockToHtml opts
(Header level' (id',classes,keyvals) title')
modify (\st -> st{ stElement = False})
return res
let isSec Sec{} = True
isSec (Blk _) = False
let isPause (Blk x) = x == Para [Str ".",Space,Str ".",Space,Str "."]
isPause _ = False
let fragmentClass = case slideVariant of
RevealJsSlides -> "fragment"
_ -> "incremental"
let inDiv xs = Blk (RawBlock (Format "html") ("<div class=\""
++ fragmentClass ++ "\">")) :
(xs ++ [Blk (RawBlock (Format "html") "</div>")])
let (titleBlocks, innerSecs) =
if titleSlide
-- title slides have no content of their own
then ([x | Blk x <- elements],
filter isSec elements)
else case splitBy isPause elements of
[] -> ([],[])
(x:xs) -> ([],x ++ concatMap inDiv xs)
titleContents <- blockListToHtml opts titleBlocks
innerContents <- mapM (elementToHtml (Just level) slideLevel opts) innerSecs
let inNl x = mconcat $ nl opts : intersperse (nl opts) x ++ [nl opts]
let classes' = ["title-slide" | titleSlide] ++ ["slide" | slide] ++
["section" | (slide || writerSectionDivs opts) &&
not html5 ] ++
["level" ++ show level | slide || writerSectionDivs opts ]
++ classes
let secttag = if html5
then H5.section
else H.div
let attr = (id',classes',keyvals)
if titleSlide
then do
t <- addAttrs opts attr $ secttag $ header' <> titleContents
return $
(if slideVariant == RevealJsSlides && not (null innerContents)
-- revealjs doesn't like more than one level of section nesting:
&& isNothing mbparentlevel
then H5.section
else id) $ mconcat $ t : innerContents
else if writerSectionDivs opts || slide
then addAttrs opts attr
$ secttag $ inNl $ header' : innerContents
else do
t <- addAttrs opts attr header'
return $ mconcat $ intersperse (nl opts) (t : innerContents)
let opts' = case slideVariant of
RevealJsSlides ->
opts{ writerIdentifierPrefix =
'/' : writerIdentifierPrefix opts }
_ -> opts
case toTableOfContents opts sects of
bl@(BulletList (_:_)) -> Just <$> blockToHtml opts' bl
_ -> return Nothing
-- | Convert list of Note blocks to a footnote <div>.
-- Assumes notes are sorted.
@ -686,6 +577,16 @@ figure opts attr txt (s,tit) = do
else H.div ! A.class_ "figure" $ mconcat
[nl opts, img, nl opts, capt, nl opts]
showSecNum :: [Int] -> String
showSecNum = intercalate "." . map show
getNumber :: WriterOptions -> Attr -> String
getNumber opts (_,_,kvs) =
showSecNum $ zipWith (+) num (writerNumberOffset opts ++ repeat 0)
where
num = maybe [] (map (fromMaybe 0 . safeRead) . splitWhen (=='.')) $
lookup "number" kvs
-- | Convert Pandoc block element to HTML.
blockToHtml :: PandocMonad m => WriterOptions -> Block -> StateT WriterState m Html
blockToHtml _ Null = return mempty
@ -713,6 +614,73 @@ blockToHtml opts (LineBlock lns) =
else do
htmlLines <- inlineListToHtml opts $ intercalate [LineBreak] lns
return $ H.div ! A.class_ "line-block" $ htmlLines
blockToHtml opts (Div (ident, "section":dclasses, dkvs)
(Header level hattr ils : xs)) = do
slideVariant <- gets stSlideVariant
slideLevel <- gets stSlideLevel
let slide = slideVariant /= NoSlides &&
level <= slideLevel {- DROPPED old fix for #5168 here -}
html5 <- gets stHtml5
let titleSlide = slide && level < slideLevel
let level' = if level <= slideLevel && slideVariant == SlidySlides
then 1 -- see #3566
else level
header' <- if ils == [Str "\0"] -- marker for hrule
then return mempty
else blockToHtml opts (Header level' hattr ils)
let isSec (Div (_,"section":_,_) _) = True
isSec (Div _ zs) = any isSec zs
isSec _ = False
let isPause (Para [Str ".",Space,Str ".",Space,Str "."]) = True
isPause _ = False
let fragmentClass = case slideVariant of
RevealJsSlides -> "fragment"
_ -> "incremental"
let inDiv zs = (RawBlock (Format "html") ("<div class=\""
++ fragmentClass ++ "\">")) :
(zs ++ [RawBlock (Format "html") "</div>"])
let (titleBlocks, innerSecs) =
if titleSlide
-- title slides have no content of their own
then break isSec xs
else case splitBy isPause xs of
[] -> ([],[])
(z:zs) -> ([],z ++ concatMap inDiv zs)
titleContents <- blockListToHtml opts titleBlocks
innerContents <- blockListToHtml opts innerSecs
let classes' = ["title-slide" | titleSlide] ++ ["slide" | slide] ++
["section" | (slide || writerSectionDivs opts) &&
not html5 ] ++
["level" ++ show level | slide || writerSectionDivs opts ]
++ dclasses
let secttag = if html5
then H5.section
else H.div
let attr = (ident, classes', dkvs)
if titleSlide
then do
t <- addAttrs opts attr $ secttag $ header' <> titleContents
return $
(if slideVariant == RevealJsSlides && not (null innerSecs)
-- revealjs doesn't like more than one level of section nesting:
{- REMOVED && isNothing mbparentlevel -}
then H5.section
else id) $ t <> if null innerSecs
then mempty
else nl opts <> innerContents
else if writerSectionDivs opts || slide || not (null dclasses) ||
not (null dkvs)
then addAttrs opts attr
$ secttag
$ nl opts <> header' <> nl opts <>
if null innerSecs
then mempty
else innerContents <> nl opts
else do
t <- addAttrs opts attr header'
return $ t <> if null innerSecs
then mempty
else nl opts <> innerContents
blockToHtml opts (Div attr@(ident, classes, kvs') bs) = do
html5 <- gets stHtml5
slideVariant <- gets stSlideVariant
@ -826,14 +794,13 @@ blockToHtml opts (BlockQuote blocks) = do
return $ H.blockquote $ nl opts >> contents >> nl opts
blockToHtml opts (Header level attr@(_,classes,_) lst) = do
contents <- inlineListToHtml opts lst
secnum <- liftM stSecNum get
let secnum = getNumber opts attr
let contents' = if writerNumberSections opts && not (null secnum)
&& "unnumbered" `notElem` classes
then (H.span ! A.class_ "header-section-number" $ toHtml
$ showSecNum secnum) >> strToHtml " " >> contents
then (H.span ! A.class_ "header-section-number"
$ toHtml secnum) >> strToHtml " " >> contents
else contents
inElement <- gets stElement
(if inElement then return else addAttrs opts attr)
addAttrs opts attr
$ case level of
1 -> H.h1 contents'
2 -> H.h2 contents'

View file

@ -63,30 +63,27 @@ docToJATS opts (Pandoc meta blocks) = do
let isBackBlock (Div ("refs",_,_) _) = True
isBackBlock _ = False
let (backblocks, bodyblocks) = partition isBackBlock blocks
let elements = hierarchicalize bodyblocks
let backElements = hierarchicalize $ backblocks
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
-- The numbering here follows LaTeX's internal numbering
let startLvl = case writerTopLevelDivision opts of
TopLevelPart -> -1
TopLevelChapter -> 0
TopLevelSection -> 1
TopLevelDefault -> 1
let fromBlocks = blocksToJATS opts . makeSections False (Just startLvl)
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
metadata <- metaToContext opts
(fmap vcat .
mapM (elementToJATS opts startLvl) .
hierarchicalize)
fromBlocks
(fmap chomp . inlinesToJATS opts)
meta
main <- vcat <$> mapM (elementToJATS opts startLvl) elements
main <- fromBlocks bodyblocks
notes <- reverse . map snd <$> gets jatsNotes
backs <- mapM (elementToJATS opts startLvl) backElements
backs <- fromBlocks backblocks
let fns = if null notes
then mempty
else inTagsIndented "fn-group" $ vcat notes
let back = vcat backs $$ fns
let back = backs $$ fns
let date =
case getField "date" metadata of
Nothing -> NullVal
@ -116,18 +113,6 @@ docToJATS opts (Pandoc meta blocks) = do
Nothing -> main
Just tpl -> renderTemplate tpl context
-- | Convert an Element to JATS.
elementToJATS :: PandocMonad m => WriterOptions -> Int -> Element -> JATS m (Doc Text)
elementToJATS opts _ (Blk block) = blockToJATS opts block
elementToJATS opts lvl (Sec _ _num (id',_,kvs) title elements) = do
let idAttr = [("id", writerIdentifierPrefix opts ++ id') | not (null id')]
let otherAttrs = ["sec-type", "specific-use"]
let attribs = idAttr ++ [(k,v) | (k,v) <- kvs, k `elem` otherAttrs]
contents <- mapM (elementToJATS opts (lvl + 1)) elements
title' <- inlinesToJATS opts title
return $ inTags True "sec" attribs $
inTagsSimple "title" title' $$ vcat contents
-- | Convert a list of Pandoc blocks to JATS.
blocksToJATS :: PandocMonad m => WriterOptions -> [Block] -> JATS m (Doc Text)
blocksToJATS = wrappedBlocksToJATS (const False)
@ -225,6 +210,14 @@ codeAttr (ident,classes,kvs) = (lang, attr)
-- | Convert a Pandoc block element to JATS.
blockToJATS :: PandocMonad m => WriterOptions -> Block -> JATS m (Doc Text)
blockToJATS _ Null = return empty
blockToJATS opts (Div (id',"section":_,kvs) (Header _lvl _ ils : xs)) = do
let idAttr = [("id", writerIdentifierPrefix opts ++ id') | not (null id')]
let otherAttrs = ["sec-type", "specific-use"]
let attribs = idAttr ++ [(k,v) | (k,v) <- kvs, k `elem` otherAttrs]
title' <- inlinesToJATS opts ils
contents <- blocksToJATS opts xs
return $ inTags True "sec" attribs $
inTagsSimple "title" title' $$ contents
-- Bibliography reference:
blockToJATS opts (Div ('r':'e':'f':'-':_,_,_) [Para lst]) =
inlinesToJATS opts lst

View file

@ -168,9 +168,8 @@ pandocToLaTeX options (Pandoc meta blocks) = do
blocks''' <- if beamer
then toSlides blocks''
else return blocks''
body <- mapM (elementToLaTeX options) $ hierarchicalize blocks'''
main <- blockListToLaTeX $ makeSections False Nothing blocks'''
biblioTitle <- inlineListToLaTeX lastHeader
let main = vsep body
st <- get
titleMeta <- stringToLaTeX TextString $ stringify $ docTitle meta
authorsMeta <- mapM (stringToLaTeX TextString . stringify) $ docAuthors meta
@ -298,16 +297,6 @@ pandocToLaTeX options (Pandoc meta blocks) = do
Nothing -> main
Just tpl -> renderTemplate tpl context'
-- | Convert Elements to LaTeX
elementToLaTeX :: PandocMonad m => WriterOptions -> Element -> LW m (Doc Text)
elementToLaTeX _ (Blk block) = blockToLaTeX block
elementToLaTeX opts (Sec level _ (id',classes,_) title' elements) = do
modify $ \s -> s{stInHeading = True}
header' <- sectionHeader ("unnumbered" `elem` classes) id' level title'
modify $ \s -> s{stInHeading = False}
innerContents <- mapM (elementToLaTeX opts) elements
return $ vsep (header' : innerContents)
data StringContext = TextString
| URLString
| CodeString
@ -459,68 +448,16 @@ toSlides bs = do
opts <- gets stOptions
let slideLevel = fromMaybe (getSlideLevel bs) $ writerSlideLevel opts
let bs' = prepSlides slideLevel bs
concat `fmap` mapM (elementToBeamer slideLevel) (hierarchicalize bs')
walkM (elementToBeamer slideLevel) (makeSections False Nothing bs')
elementToBeamer :: PandocMonad m => Int -> Element -> LW m [Block]
elementToBeamer _slideLevel (Blk (Div attrs bs)) = do
-- make sure we support "blocks" inside divs
bs' <- concat `fmap` mapM (elementToBeamer 0) (hierarchicalize bs)
return [Div attrs bs']
elementToBeamer _slideLevel (Blk b) = return [b]
elementToBeamer slideLevel (Sec lvl _num (ident,classes,kvs) tit elts)
| lvl > slideLevel = do
bs <- concat `fmap` mapM (elementToBeamer slideLevel) elts
return $ Para ( RawInline "latex" "\\begin{block}{"
: tit ++ [RawInline "latex" "}"] )
: bs ++ [RawBlock "latex" "\\end{block}"]
| lvl < slideLevel = do
let isSec Sec{} = True
isSec _ = False
let (contentElts, secElts) = break isSec elts
let elts' = if null contentElts
then secElts
else Sec slideLevel [] nullAttr tit contentElts :
secElts
bs <- concat `fmap` mapM (elementToBeamer slideLevel) elts'
return $ Header lvl (ident,classes,kvs) tit : bs
| otherwise = do -- lvl == slideLevel
-- note: [fragile] is required or verbatim breaks
let hasCodeBlock (CodeBlock _ _) = [True]
hasCodeBlock _ = []
let hasCode (Code _ _) = [True]
hasCode _ = []
let fragile = "fragile" `elem` classes ||
not (null $ query hasCodeBlock elts ++ query hasCode elts)
let frameoptions = ["allowdisplaybreaks", "allowframebreaks", "fragile",
"b", "c", "t", "environment",
"label", "plain", "shrink", "standout",
"noframenumbering"]
let optionslist = ["fragile" | fragile
, isNothing (lookup "fragile" kvs)
, "fragile" `notElem` classes] ++
[k | k <- classes, k `elem` frameoptions] ++
[k ++ "=" ++ v | (k,v) <- kvs, k `elem` frameoptions]
let options = if null optionslist
then ""
else "[" ++ intercalate "," optionslist ++ "]"
let latex = RawInline (Format "latex")
slideTitle <-
if tit == [Str "\0"] -- marker for hrule
then return []
else return $ latex "{" : tit ++ [latex "}"]
ref <- toLabel ident
let slideAnchor = if null ident
then []
else [latex ("\n\\protect\\hypertarget{" ++
ref ++ "}{}")]
let slideStart = Para $
RawInline "latex" ("\\begin{frame}" ++ options) :
slideTitle ++ slideAnchor
let slideEnd = RawBlock "latex" "\\end{frame}"
-- now carve up slide into blocks if there are sections inside
bs <- concat `fmap` mapM (elementToBeamer slideLevel) elts
return $ slideStart : bs ++ [slideEnd]
-- this creates section slides and marks slides with class "slide","block"
elementToBeamer :: PandocMonad m => Int -> Block -> LW m Block
elementToBeamer slideLevel d@(Div (ident,dclasses,dkvs)
xs@(Header lvl _ _ : _))
| lvl > slideLevel = return $ Div (ident,"block":dclasses,dkvs) xs
| lvl < slideLevel = return d
| otherwise = return $ Div (ident,"slide":dclasses,dkvs) xs
elementToBeamer _ x = return x
isListBlock :: Block -> Bool
isListBlock (BulletList _) = True
@ -533,85 +470,87 @@ blockToLaTeX :: PandocMonad m
=> Block -- ^ Block to convert
-> LW m (Doc Text)
blockToLaTeX Null = return empty
blockToLaTeX (Div (identifier,classes,kvs) bs)
| "incremental" `elem` classes = do
let classes' = filter ("incremental"/=) classes
beamer <- gets stBeamer
if beamer
then do oldIncremental <- gets stIncremental
modify $ \s -> s{ stIncremental = True }
result <- blockToLaTeX $ Div (identifier,classes',kvs) bs
modify $ \s -> s{ stIncremental = oldIncremental }
return result
else blockToLaTeX $ Div (identifier,classes',kvs) bs
| "nonincremental" `elem` classes = do
let classes' = filter ("nonincremental"/=) classes
beamer <- gets stBeamer
if beamer
then do oldIncremental <- gets stIncremental
modify $ \s -> s{ stIncremental = False }
result <- blockToLaTeX $ Div (identifier,classes',kvs) bs
modify $ \s -> s{ stIncremental = oldIncremental }
return result
else blockToLaTeX $ Div (identifier,classes',kvs) bs
| identifier == "refs" = do
modify $ \st -> st{ stHasCslRefs = True
, stCslHangingIndent =
"hanging-indent" `elem` classes }
contents <- blockListToLaTeX bs
return $ "\\begin{cslreferences}" $$
contents $$
"\\end{cslreferences}"
| otherwise = do
beamer <- gets stBeamer
linkAnchor' <- hypertarget True identifier empty
-- see #2704 for the motivation for adding \leavevmode:
let linkAnchor =
case bs of
Para _ : _
| not (isEmpty linkAnchor')
-> "\\leavevmode" <> linkAnchor' <> "%"
_ -> linkAnchor'
let align dir txt = inCmd "begin" dir $$ txt $$ inCmd "end" dir
lang <- toLang $ lookup "lang" kvs
let wrapColumns = if beamer && "columns" `elem` classes
then \contents ->
inCmd "begin" "columns" <> brackets "T"
$$ contents
$$ inCmd "end" "columns"
else id
wrapColumn = if beamer && "column" `elem` classes
then \contents ->
let w = maybe "0.48" fromPct (lookup "width" kvs)
in inCmd "begin" "column" <>
braces (text w <> "\\textwidth")
$$ contents
$$ inCmd "end" "column"
else id
fromPct xs =
case reverse xs of
'%':ds -> case safeRead (reverse ds) of
Just digits -> showFl (digits / 100 :: Double)
Nothing -> xs
_ -> xs
wrapDir = case lookup "dir" kvs of
Just "rtl" -> align "RTL"
Just "ltr" -> align "LTR"
_ -> id
wrapLang txt = case lang of
Just lng -> let (l, o) = toPolyglossiaEnv lng
ops = if null o
then ""
else brackets $ text o
in inCmd "begin" (text l) <> ops
$$ blankline <> txt <> blankline
$$ inCmd "end" (text l)
Nothing -> txt
wrapNotes txt = if beamer && "notes" `elem` classes
then "\\note" <> braces txt -- speaker notes
else linkAnchor $$ txt
(wrapColumns . wrapColumn . wrapDir . wrapLang . wrapNotes)
<$> blockListToLaTeX bs
blockToLaTeX (Div attr@(identifier,"block":_,_) (Header _ _ ils : bs)) = do
ref <- toLabel identifier
let anchor = if null identifier
then empty
else cr <> "\\protect\\hypertarget" <>
braces (text ref) <> braces empty
title' <- inlineListToLaTeX ils
contents <- blockListToLaTeX bs
wrapDiv attr $ ("\\begin{block}" <> braces title' <> anchor) $$
contents $$ "\\end{block}"
blockToLaTeX (Div (identifier,"slide":dclasses,dkvs)
(Header _ (_,hclasses,hkvs) ils : bs)) = do
-- note: [fragile] is required or verbatim breaks
let hasCodeBlock (CodeBlock _ _) = [True]
hasCodeBlock _ = []
let hasCode (Code _ _) = [True]
hasCode _ = []
let classes = dclasses ++ hclasses
let kvs = dkvs ++ hkvs
let fragile = "fragile" `elem` classes ||
not (null $ query hasCodeBlock bs ++ query hasCode bs)
let frameoptions = ["allowdisplaybreaks", "allowframebreaks", "fragile",
"b", "c", "t", "environment",
"label", "plain", "shrink", "standout",
"noframenumbering"]
let optionslist = ["fragile" | fragile
, isNothing (lookup "fragile" kvs)
, "fragile" `notElem` classes] ++
[k | k <- classes, k `elem` frameoptions] ++
[k ++ "=" ++ v | (k,v) <- kvs, k `elem` frameoptions]
let options = if null optionslist
then empty
else brackets (text (intercalate "," optionslist))
slideTitle <- if ils == [Str "\0"] -- marker for hrule
then return empty
else braces <$> inlineListToLaTeX ils
ref <- toLabel identifier
let slideAnchor = if null identifier
then empty
else cr <> "\\protect\\hypertarget" <>
braces (text ref) <> braces empty
contents <- blockListToLaTeX bs >>= wrapDiv (identifier,classes,kvs)
return $ ("\\begin{frame}" <> options <> slideTitle <> slideAnchor) $$
contents $$
"\\end{frame}"
blockToLaTeX (Div (identifier@(_:_),dclasses,dkvs)
(Header lvl ("",hclasses,hkvs) ils : bs)) = do
-- move identifier from div to header
blockToLaTeX (Div ("",dclasses,dkvs)
(Header lvl (identifier,hclasses,hkvs) ils : bs))
blockToLaTeX (Div (identifier,classes,kvs) bs) = do
beamer <- gets stBeamer
oldIncremental <- gets stIncremental
if beamer && "incremental" `elem` classes
then modify $ \st -> st{ stIncremental = True }
else if beamer && "nonincremental" `elem` classes
then modify $ \st -> st { stIncremental = False }
else return ()
result <- if identifier == "refs"
then do
inner <- blockListToLaTeX bs
modify $ \st -> st{ stHasCslRefs = True
, stCslHangingIndent =
"hanging-indent" `elem` classes }
return $ "\\begin{cslreferences}" $$
inner $$
"\\end{cslreferences}"
else blockListToLaTeX bs
modify $ \st -> st{ stIncremental = oldIncremental }
linkAnchor' <- hypertarget True identifier empty
-- see #2704 for the motivation for adding \leavevmode:
let linkAnchor =
case bs of
Para _ : _
| not (isEmpty linkAnchor')
-> "\\leavevmode" <> linkAnchor' <> "%"
_ -> linkAnchor'
wrapNotes txt = if beamer && "notes" `elem` classes
then "\\note" <> braces txt -- speaker notes
else linkAnchor $$ txt
wrapNotes <$> wrapDiv (identifier,classes,kvs) result
blockToLaTeX (Plain lst) =
inlineListToLaTeX lst
-- title beginning with fig: indicates that the image is a figure
@ -1077,6 +1016,46 @@ sectionHeader unnumbered ident level lst = do
braces txtNoNotes
else empty
wrapDiv :: PandocMonad m => Attr -> Doc Text -> LW m (Doc Text)
wrapDiv (_,classes,kvs) t = do
beamer <- gets stBeamer
let align dir txt = inCmd "begin" dir $$ txt $$ inCmd "end" dir
lang <- toLang $ lookup "lang" kvs
let wrapColumns = if beamer && "columns" `elem` classes
then \contents ->
inCmd "begin" "columns" <> brackets "T"
$$ contents
$$ inCmd "end" "columns"
else id
wrapColumn = if beamer && "column" `elem` classes
then \contents ->
let w = maybe "0.48" fromPct (lookup "width" kvs)
in inCmd "begin" "column" <>
braces (text w <> "\\textwidth")
$$ contents
$$ inCmd "end" "column"
else id
fromPct xs =
case reverse xs of
'%':ds -> case safeRead (reverse ds) of
Just digits -> showFl (digits / 100 :: Double)
Nothing -> xs
_ -> xs
wrapDir = case lookup "dir" kvs of
Just "rtl" -> align "RTL"
Just "ltr" -> align "LTR"
_ -> id
wrapLang txt = case lang of
Just lng -> let (l, o) = toPolyglossiaEnv lng
ops = if null o
then ""
else brackets $ text o
in inCmd "begin" (text l) <> ops
$$ blankline <> txt <> blankline
$$ inCmd "end" (text l)
Nothing -> txt
return $ wrapColumns . wrapColumn . wrapDir . wrapLang $ t
hypertarget :: PandocMonad m => Bool -> String -> Doc Text -> LW m (Doc Text)
hypertarget _ "" x = return x
hypertarget addnewline ident x = do

View file

@ -13,14 +13,12 @@ Conversion of 'Pandoc' documents to OPML XML.
-}
module Text.Pandoc.Writers.OPML ( writeOPML) where
import Prelude
import Control.Monad.Except (throwError)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Text.Pandoc.Builder as B
import Text.Pandoc.Class (PandocMonad)
import Data.Time
import Text.Pandoc.Definition
import Text.Pandoc.Error
import Text.Pandoc.Options
import Text.DocLayout
import Text.Pandoc.Shared
@ -33,8 +31,7 @@ import Text.Pandoc.XML
-- | Convert Pandoc document to string in OPML format.
writeOPML :: PandocMonad m => WriterOptions -> Pandoc -> m Text
writeOPML opts (Pandoc meta blocks) = do
let elements = hierarchicalize blocks
colwidth = if writerWrapText opts == WrapAuto
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
meta' = B.setMeta "date" (B.str $ convertDate $ docDate meta) meta
@ -42,7 +39,8 @@ writeOPML opts (Pandoc meta blocks) = do
(writeMarkdown def . Pandoc nullMeta)
(\ils -> T.stripEnd <$> writeMarkdown def (Pandoc nullMeta [Plain ils]))
meta'
main <- (render colwidth . vcat) <$> mapM (elementToOPML opts) elements
let blocks' = makeSections False (Just 1) blocks
main <- (render colwidth . vcat) <$> mapM (blockToOPML opts) blocks'
let context = defField "body" main metadata
return $
(if writerPreferAscii opts then toEntities else id) $
@ -63,25 +61,18 @@ convertDate :: [Inline] -> String
convertDate ils = maybe "" showDateTimeRFC822 $
parseTimeM True defaultTimeLocale "%F" =<< normalizeDate (stringify ils)
-- | Convert an Element to OPML.
elementToOPML :: PandocMonad m => WriterOptions -> Element -> m (Doc Text)
elementToOPML _ (Blk _) = return empty
elementToOPML opts (Sec _ _num _ title elements) = do
let isBlk :: Element -> Bool
isBlk (Blk _) = True
isBlk _ = False
fromBlk :: PandocMonad m => Element -> m Block
fromBlk (Blk x) = return x
fromBlk _ = throwError $ PandocSomeError "fromBlk called on non-block"
(blocks, rest) = span isBlk elements
-- | Convert a Block to OPML.
blockToOPML :: PandocMonad m => WriterOptions -> Block -> m (Doc Text)
blockToOPML opts (Div (_,"section":_,_) (Header _ _ title : xs)) = do
let isSect (Div (_,"section":_,_) (Header{}:_)) = True
isSect _ = False
let (blocks, rest) = break isSect xs
htmlIls <- writeHtmlInlines title
md <- if null blocks
then return mempty
else do blks <- mapM fromBlk blocks
writeMarkdown def $ Pandoc nullMeta blks
else writeMarkdown def $ Pandoc nullMeta blocks
let attrs = ("text", T.unpack htmlIls) :
[("_note", T.unpack $ T.stripEnd md) | not (null blocks)]
o <- mapM (elementToOPML opts) rest
return $ inTags True "outline" attrs $ vcat o
rest' <- vcat <$> mapM (blockToOPML opts) rest
return $ inTags True "outline" attrs rest'
blockToOPML _ _ = return empty

View file

@ -39,6 +39,7 @@ module Text.Pandoc.Writers.Shared (
where
import Prelude
import Safe (lastMay)
import Data.Maybe (fromMaybe)
import Control.Monad (zipWithM)
import Data.Aeson (ToJSON (..), encode)
import Data.Char (chr, ord, isSpace)
@ -49,7 +50,7 @@ import qualified Text.Pandoc.Builder as Builder
import Text.Pandoc.Definition
import Text.Pandoc.Options
import Text.DocLayout
import Text.Pandoc.Shared (stringify, hierarchicalize, Element(..), deNote)
import Text.Pandoc.Shared (stringify, makeSections, deNote, deLink)
import Text.Pandoc.Walk (walk)
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.XML (escapeStringForXML)
@ -382,20 +383,28 @@ toTableOfContents :: WriterOptions
-> [Block]
-> Block
toTableOfContents opts bs =
BulletList $ map (elementToListItem opts) (hierarchicalize bs)
BulletList $ filter (not . null)
$ map (sectionToListItem opts)
$ makeSections (writerNumberSections opts) Nothing bs
-- | Converts an Element to a list item for a table of contents,
elementToListItem :: WriterOptions -> Element -> [Block]
elementToListItem opts (Sec lev _nums (ident,_,_) headerText subsecs)
= Plain headerLink : [BulletList listContents | not (null subsecs)
, lev < writerTOCDepth opts]
sectionToListItem :: WriterOptions -> Block -> [Block]
sectionToListItem opts (Div (ident,_,_)
(Header lev (_,_,kvs) ils : subsecs)) =
Plain headerLink : [BulletList listContents | not (null listContents)
, lev < writerTOCDepth opts]
where
headerText' = walk deNote headerText
num = fromMaybe "" $ lookup "number" kvs
addNumber = if null num
then id
else (Span ("",["toc-section-number"],[])
[Str num] :) . (Space :)
headerText' = addNumber $ walk (deLink . deNote) ils
headerLink = if null ident
then headerText'
else [Link nullAttr headerText' ('#':ident, "")]
listContents = map (elementToListItem opts) subsecs
elementToListItem _ (Blk _) = []
listContents = filter (not . null) $ map (sectionToListItem opts) subsecs
sectionToListItem _ _ = []
endsWithPlain :: [Block] -> Bool
endsWithPlain xs =

View file

@ -32,21 +32,20 @@ import Text.Pandoc.XML
-- | Convert Pandoc document to string in Docbook format.
writeTEI :: PandocMonad m => WriterOptions -> Pandoc -> m Text
writeTEI opts (Pandoc meta blocks) = do
let elements = hierarchicalize blocks
colwidth = if writerWrapText opts == WrapAuto
let colwidth = if writerWrapText opts == WrapAuto
then Just $ writerColumns opts
else Nothing
startLvl = case writerTopLevelDivision opts of
let startLvl = case writerTopLevelDivision opts of
TopLevelPart -> -1
TopLevelChapter -> 0
TopLevelSection -> 1
TopLevelDefault -> 1
let fromBlocks = blocksToTEI opts . makeSections False (Just startLvl)
metadata <- metaToContext opts
(fmap vcat .
mapM (elementToTEI opts startLvl) . hierarchicalize)
fromBlocks
(fmap chomp . inlinesToTEI opts)
meta
main <- vcat <$> mapM (elementToTEI opts startLvl) elements
main <- fromBlocks blocks
let context = defField "body" main
$ defField "mathml" (case writerHTMLMathMethod opts of
MathML -> True
@ -56,25 +55,6 @@ writeTEI opts (Pandoc meta blocks) = do
Nothing -> main
Just tpl -> renderTemplate tpl context
-- | Convert an Element to TEI.
elementToTEI :: PandocMonad m => WriterOptions -> Int -> Element -> m (Doc Text)
elementToTEI opts _ (Blk block) = blockToTEI opts block
elementToTEI opts lvl (Sec _ _num attr title elements) = do
-- TEI doesn't allow sections with no content, so insert some if needed
let elements' = if null elements
then [Blk (Para [])]
else elements
-- level numbering correspond to LaTeX internals
divType = case lvl of
n | n == -1 -> "part"
| n == 0 -> "chapter"
| n >= 1 && n <= 5 -> "level" ++ show n
| otherwise -> "section"
contents <- vcat <$> mapM (elementToTEI opts (lvl + 1)) elements'
titleContents <- inlinesToTEI opts title
return $ inTags True "div" (("type", divType) : idFromAttr opts attr) $
inTagsSimple "head" titleContents $$ contents
-- | Convert a list of Pandoc blocks to TEI.
blocksToTEI :: PandocMonad m => WriterOptions -> [Block] -> m (Doc Text)
blocksToTEI opts bs = vcat <$> mapM (blockToTEI opts) bs
@ -121,6 +101,22 @@ imageToTEI opts attr src = return $ selfClosingTag "graphic" $
-- | Convert a Pandoc block element to TEI.
blockToTEI :: PandocMonad m => WriterOptions -> Block -> m (Doc Text)
blockToTEI _ Null = return empty
blockToTEI opts (Div attr@(_,"section":_,_) (Header lvl _ ils : xs)) =
do
-- TEI doesn't allow sections with no content, so insert some if needed
let xs' = if null xs
then [Para []]
else xs
-- level numbering correspond to LaTeX internals
divType = case lvl of
n | n == -1 -> "part"
| n == 0 -> "chapter"
| n >= 1 && n <= 5 -> "level" ++ show n
| otherwise -> "section"
titleContents <- inlinesToTEI opts ils
contents <- blocksToTEI opts xs'
return $ inTags True "div" (("type", divType) : idFromAttr opts attr) $
inTagsSimple "head" titleContents $$ contents
-- Add ids to paragraphs in divs with ids - this is needed for
-- pandoc-citeproc to get link anchors in bibliographies:
blockToTEI opts (Div attr [Para lst]) = do
@ -128,7 +124,7 @@ blockToTEI opts (Div attr [Para lst]) = do
inTags False "p" attribs <$> inlinesToTEI opts lst
blockToTEI opts (Div _ bs) = blocksToTEI opts $ map plainToPara bs
blockToTEI _ h@Header{} = do
-- should not occur after hierarchicalize, except inside lists/blockquotes
-- should not occur after makeSections, except inside lists/blockquotes
report $ BlockNotRendered h
return empty
-- For TEI simple, text must be within containing block element, so

View file

@ -56,7 +56,6 @@ ok
^D
\begin{frame}{Slide one}
\protect\hypertarget{slide-one}{}
\begin{columns}[T]
\begin{column}{0.4\textwidth}
\begin{itemize}
@ -82,6 +81,5 @@ ok
ok
\end{column}
\end{columns}
\end{frame}
```

View file

@ -15,32 +15,28 @@ pandoc -t beamer
^D
\begin{frame}{Level 2 blocks}
\protect\hypertarget{level-2-blocks}{}
\begin{columns}[T]
\begin{column}{0.4\textwidth}
\begin{block}{Block one}
\protect\hypertarget{block-one}{}
\begin{itemize}
\tightlist
\item
Item
\end{itemize}
\end{block}
\end{column}
\begin{column}{0.6\textwidth}
\begin{block}{Block two}
\protect\hypertarget{block-two}{}
\begin{itemize}
\tightlist
\item
Item
\end{itemize}
\end{block}
\end{column}
\end{columns}
\end{frame}
```

View file

@ -13,7 +13,6 @@ content2
^D
\begin{frame}{title}
\protect\hypertarget{title}{}
\begin{columns}[T]
\begin{column}{0.08\textwidth}
content
@ -23,6 +22,5 @@ content
content2
\end{column}
\end{columns}
\end{frame}
```

View file

@ -37,8 +37,6 @@
[Para [Str "hi"], Para [], Para [], Para [Str "lo"]]
^D
<p>hi</p>
<p>lo</p>
```

View file

@ -39,20 +39,17 @@ return {
end)
},
group 'hierarchicalize' {
group 'make_sections' {
test('sanity check', function ()
local blks = {
pandoc.Header(1, {pandoc.Str 'First'}),
pandoc.Header(2, {pandoc.Str 'Second'}),
pandoc.Header(2, {pandoc.Str 'Third'}),
}
local hblks = utils.hierarchicalize(blks)
-- cannot create Elements directly; performing only an approximate
-- sanity checking instead of a full equality comparison.
assert.are_equal('Sec', hblks[1].t)
assert.are_equal('Sec', hblks[1].contents[1].t)
assert.are_equal(1, hblks[1].contents[2].numbering[1])
assert.are_equal(2, hblks[1].contents[2].numbering[2])
local hblks = utils.make_sections(true, 1, blks)
assert.are_equal('Div', hblks[1].t)
assert.are_equal('Header', hblks[1].content[1].t)
assert.are_equal('1', hblks[1].content[1].attributes['number'])
end)
},

View file

@ -442,7 +442,6 @@ Blah
<li>Escaped <code>$</code>: $73 <em>this should be emphasized</em> 23$.</li>
</ul>
<p>Heres a LaTeX table:</p>
<hr />
<h1 id="special-characters">Special Characters</h1>
<p>Here is some unicode:</p>

View file

@ -445,7 +445,6 @@ Blah
<li>Escaped <code>$</code>: $73 <em>this should be emphasized</em> 23$.</li>
</ul>
<p>Heres a LaTeX table:</p>
<hr />
<h1 id="special-characters">Special Characters</h1>
<p>Here is some unicode:</p>