2006-12-20 20:54:23 +00:00
|
|
|
{-
|
|
|
|
Copyright (C) 2006 John MacFarlane <jgm at berkeley dot edu>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
-}
|
|
|
|
|
2006-12-20 06:50:14 +00:00
|
|
|
{- |
|
|
|
|
Module : Text.Pandoc.Writers.HTML
|
|
|
|
Copyright : Copyright (C) 2006 John MacFarlane
|
|
|
|
License : GNU GPL, version 2 or above
|
|
|
|
|
|
|
|
Maintainer : John MacFarlane <jgm at berkeley dot edu>
|
2006-12-20 20:20:10 +00:00
|
|
|
Stability : alpha
|
2006-12-20 06:50:14 +00:00
|
|
|
Portability : portable
|
|
|
|
|
|
|
|
Conversion of 'Pandoc' documents to HTML.
|
|
|
|
-}
|
2006-10-17 14:22:29 +00:00
|
|
|
module Text.Pandoc.Writers.HTML (
|
2007-01-01 21:08:12 +00:00
|
|
|
writeHtml,
|
2006-10-17 14:22:29 +00:00
|
|
|
) where
|
|
|
|
import Text.Pandoc.Definition
|
|
|
|
import Text.Pandoc.Shared
|
2007-01-28 16:40:44 +00:00
|
|
|
import Text.Pandoc.Entities ( escapeSGMLString )
|
2006-12-20 18:16:07 +00:00
|
|
|
import Text.Regex ( mkRegex, matchRegex )
|
2006-10-17 14:22:29 +00:00
|
|
|
import Numeric ( showHex )
|
2006-12-20 18:16:07 +00:00
|
|
|
import Data.Char ( ord, toLower )
|
2006-12-19 23:13:03 +00:00
|
|
|
import Data.List ( isPrefixOf, partition )
|
2007-01-06 09:54:58 +00:00
|
|
|
import Text.PrettyPrint.HughesPJ hiding ( Str )
|
2006-10-17 14:22:29 +00:00
|
|
|
|
|
|
|
-- | Convert Pandoc document to string in HTML format.
|
|
|
|
writeHtml :: WriterOptions -> Pandoc -> String
|
2007-01-06 09:54:58 +00:00
|
|
|
writeHtml opts (Pandoc (Meta title authors date) blocks) =
|
|
|
|
let titlePrefix = writerTitlePrefix opts in
|
2006-12-20 06:50:14 +00:00
|
|
|
let topTitle = if not (null titlePrefix)
|
|
|
|
then [Str titlePrefix] ++ (if not (null title)
|
|
|
|
then [Str " - "] ++ title
|
|
|
|
else [])
|
|
|
|
else title in
|
2007-01-06 09:54:58 +00:00
|
|
|
let head = if (writerStandalone opts)
|
|
|
|
then htmlHeader opts (Meta topTitle authors date)
|
|
|
|
else empty
|
|
|
|
titleBlocks = if (writerStandalone opts) && (not (null title)) &&
|
|
|
|
(not (writerS5 opts))
|
2006-12-20 06:50:14 +00:00
|
|
|
then [RawHtml "<h1 class=\"title\">", Plain title,
|
2007-01-06 09:54:58 +00:00
|
|
|
RawHtml "</h1>"]
|
2006-12-20 06:50:14 +00:00
|
|
|
else []
|
2007-01-06 09:54:58 +00:00
|
|
|
foot = if (writerStandalone opts)
|
|
|
|
then text "</body>\n</html>"
|
|
|
|
else empty
|
2006-12-20 06:50:14 +00:00
|
|
|
blocks' = replaceReferenceLinks (titleBlocks ++ blocks)
|
|
|
|
(noteBlocks, blocks'') = partition isNoteBlock blocks'
|
2007-01-06 09:54:58 +00:00
|
|
|
before = writerIncludeBefore opts
|
|
|
|
after = writerIncludeAfter opts
|
|
|
|
body = (if null before then empty else text before) $$
|
|
|
|
vcat (map (blockToHtml opts) blocks'') $$
|
|
|
|
footnoteSection opts noteBlocks $$
|
|
|
|
(if null after then empty else text after) in
|
|
|
|
render $ head $$ body $$ foot $$ text ""
|
2006-10-17 14:22:29 +00:00
|
|
|
|
2006-12-20 06:50:14 +00:00
|
|
|
-- | Convert list of Note blocks to a footnote <div>.
|
|
|
|
-- Assumes notes are sorted.
|
2007-01-06 09:54:58 +00:00
|
|
|
footnoteSection :: WriterOptions -> [Block] -> Doc
|
|
|
|
footnoteSection opts notes =
|
2006-12-20 06:50:14 +00:00
|
|
|
if null notes
|
2007-01-06 09:54:58 +00:00
|
|
|
then empty
|
|
|
|
else inTags True "div" [("class","footnotes")] $
|
|
|
|
selfClosingTag "hr" [] $$ (inTagsIndented "ol"
|
|
|
|
(vcat $ map (blockToHtml opts) notes))
|
2006-12-19 23:13:03 +00:00
|
|
|
|
2006-10-17 14:22:29 +00:00
|
|
|
-- | Obfuscate a "mailto:" link using Javascript.
|
2007-01-06 09:54:58 +00:00
|
|
|
obfuscateLink :: WriterOptions -> [Inline] -> String -> Doc
|
|
|
|
obfuscateLink opts txt src =
|
2006-12-20 18:16:07 +00:00
|
|
|
let emailRegex = mkRegex "mailto:*([^@]*)@(.*)"
|
2007-01-06 09:54:58 +00:00
|
|
|
text' = render $ inlineListToHtml opts txt
|
2006-12-20 18:16:07 +00:00
|
|
|
src' = map toLower src in
|
|
|
|
case (matchRegex emailRegex src') of
|
|
|
|
(Just [name, domain]) ->
|
2007-01-22 22:52:39 +00:00
|
|
|
let domain' = substitute "." " dot " domain
|
2006-12-20 18:16:07 +00:00
|
|
|
at' = obfuscateChar '@' in
|
|
|
|
let linkText = if src' == ("mailto:" ++ text')
|
|
|
|
then "e"
|
|
|
|
else "'" ++ text' ++ "'"
|
|
|
|
altText = if src' == ("mailto:" ++ text')
|
|
|
|
then name ++ " at " ++ domain'
|
|
|
|
else text' ++ " (" ++ name ++ " at " ++
|
|
|
|
domain' ++ ")" in
|
2007-01-06 09:54:58 +00:00
|
|
|
if writerStrictMarkdown opts
|
|
|
|
then inTags False "a" [("href", obfuscateString src')] $
|
|
|
|
text $ obfuscateString text'
|
|
|
|
else inTags False "script" [("type", "text/javascript")]
|
|
|
|
(text ("\n<!--\nh='" ++
|
2006-12-30 22:51:49 +00:00
|
|
|
obfuscateString domain ++ "';a='" ++ at' ++ "';n='" ++
|
|
|
|
obfuscateString name ++ "';e=n+a+h;\n" ++
|
|
|
|
"document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\">'+" ++
|
2007-01-06 09:54:58 +00:00
|
|
|
linkText ++ "+'<\\/'+'a'+'>');\n// -->\n")) <>
|
|
|
|
inTagsSimple "noscript" (text (obfuscateString altText))
|
|
|
|
_ -> inTags False "a" [("href", src)] (text text') -- malformed email
|
2006-10-17 14:22:29 +00:00
|
|
|
|
|
|
|
-- | Obfuscate character as entity.
|
|
|
|
obfuscateChar :: Char -> String
|
2006-12-20 06:50:14 +00:00
|
|
|
obfuscateChar char =
|
|
|
|
let num = ord char in
|
|
|
|
let numstr = if even num then (show num) else ("x" ++ (showHex num "")) in
|
|
|
|
"&#" ++ numstr ++ ";"
|
2006-10-17 14:22:29 +00:00
|
|
|
|
2006-12-20 18:16:07 +00:00
|
|
|
-- | Obfuscate string using entities.
|
|
|
|
obfuscateString :: String -> String
|
|
|
|
obfuscateString = concatMap obfuscateChar
|
|
|
|
|
2007-01-06 09:54:58 +00:00
|
|
|
-- | Return an HTML header with appropriate bibliographic information.
|
|
|
|
htmlHeader :: WriterOptions -> Meta -> Doc
|
|
|
|
htmlHeader opts (Meta title authors date) =
|
|
|
|
let titletext = inTagsSimple "title" (wrap opts title)
|
2006-12-20 06:50:14 +00:00
|
|
|
authortext = if (null authors)
|
2007-01-06 09:54:58 +00:00
|
|
|
then empty
|
|
|
|
else selfClosingTag "meta" [("name", "author"),
|
|
|
|
("content",
|
2007-01-28 16:40:44 +00:00
|
|
|
joinWithSep ", " (map escapeSGMLString authors))]
|
2006-12-20 06:50:14 +00:00
|
|
|
datetext = if (date == "")
|
2007-01-06 09:54:58 +00:00
|
|
|
then empty
|
|
|
|
else selfClosingTag "meta" [("name", "date"),
|
2007-01-28 16:40:44 +00:00
|
|
|
("content", escapeSGMLString date)] in
|
2007-01-06 09:54:58 +00:00
|
|
|
text (writerHeader opts) $$ authortext $$ datetext $$ titletext $$
|
|
|
|
text "</head>\n<body>"
|
|
|
|
|
|
|
|
-- | Take list of inline elements and return wrapped doc.
|
|
|
|
wrap :: WriterOptions -> [Inline] -> Doc
|
|
|
|
wrap opts lst = fsep $ map (inlineListToHtml opts) (splitBy Space lst)
|
2006-10-17 14:22:29 +00:00
|
|
|
|
|
|
|
-- | Convert Pandoc block element to HTML.
|
2007-01-06 09:54:58 +00:00
|
|
|
blockToHtml :: WriterOptions -> Block -> Doc
|
|
|
|
blockToHtml opts Blank = text ""
|
|
|
|
blockToHtml opts Null = empty
|
|
|
|
blockToHtml opts (Plain lst) = wrap opts lst
|
|
|
|
blockToHtml opts (Para lst) = inTagsIndented "p" $ wrap opts lst
|
|
|
|
blockToHtml opts (BlockQuote blocks) =
|
|
|
|
if (writerS5 opts)
|
2006-12-20 06:50:14 +00:00
|
|
|
then -- in S5, treat list in blockquote specially
|
|
|
|
-- if default is incremental, make it nonincremental;
|
|
|
|
-- otherwise incremental
|
2007-01-06 09:54:58 +00:00
|
|
|
let inc = not (writerIncremental opts) in
|
2006-12-20 06:50:14 +00:00
|
|
|
case blocks of
|
2007-01-06 09:54:58 +00:00
|
|
|
[BulletList lst] -> blockToHtml (opts {writerIncremental =
|
2006-12-20 06:50:14 +00:00
|
|
|
inc}) (BulletList lst)
|
2007-01-06 09:54:58 +00:00
|
|
|
[OrderedList lst] -> blockToHtml (opts {writerIncremental =
|
2006-12-20 06:50:14 +00:00
|
|
|
inc}) (OrderedList lst)
|
2007-01-06 09:54:58 +00:00
|
|
|
otherwise -> inTagsIndented "blockquote" $
|
|
|
|
vcat $ map (blockToHtml opts) blocks
|
|
|
|
else inTagsIndented "blockquote" $ vcat $ map (blockToHtml opts) blocks
|
|
|
|
blockToHtml opts (Note ref lst) =
|
|
|
|
let contents = (vcat $ map (blockToHtml opts) lst) in
|
|
|
|
inTags True "li" [("id", "fn" ++ ref)] $
|
|
|
|
contents <> inTags False "a" [("href", "#fnref" ++ ref),
|
|
|
|
("class", "footnoteBacklink"),
|
|
|
|
("title", "Jump back to footnote " ++ ref)]
|
|
|
|
(text "↩")
|
|
|
|
blockToHtml opts (Key _ _) = empty
|
|
|
|
blockToHtml opts (CodeBlock str) =
|
2007-01-28 16:40:44 +00:00
|
|
|
text "<pre><code>" <> text (escapeSGMLString str) <> text "\n</code></pre>"
|
2007-01-06 09:54:58 +00:00
|
|
|
blockToHtml opts (RawHtml str) = text str
|
|
|
|
blockToHtml opts (BulletList lst) =
|
|
|
|
let attribs = if (writerIncremental opts)
|
|
|
|
then [("class","incremental")]
|
|
|
|
else [] in
|
|
|
|
inTags True "ul" attribs $ vcat $ map (listItemToHtml opts) lst
|
|
|
|
blockToHtml opts (OrderedList lst) =
|
|
|
|
let attribs = if (writerIncremental opts)
|
|
|
|
then [("class","incremental")]
|
|
|
|
else [] in
|
|
|
|
inTags True "ol" attribs $ vcat $ map (listItemToHtml opts) lst
|
|
|
|
blockToHtml opts HorizontalRule = selfClosingTag "hr" []
|
|
|
|
blockToHtml opts (Header level lst) =
|
|
|
|
let contents = wrap opts lst in
|
2006-12-20 06:50:14 +00:00
|
|
|
if ((level > 0) && (level <= 6))
|
2007-01-06 09:54:58 +00:00
|
|
|
then inTagsSimple ("h" ++ show level) contents
|
|
|
|
else inTagsSimple "p" contents
|
2007-01-15 19:52:42 +00:00
|
|
|
blockToHtml opts (Table caption aligns widths headers rows) =
|
|
|
|
let alignStrings = map alignmentToString aligns
|
|
|
|
captionDoc = if null caption
|
|
|
|
then empty
|
|
|
|
else inTagsSimple "caption"
|
|
|
|
(inlineListToHtml opts caption) in
|
|
|
|
inTagsIndented "table" $ captionDoc $$
|
|
|
|
(colHeadsToHtml opts alignStrings widths headers) $$
|
|
|
|
(vcat $ map (tableRowToHtml opts alignStrings) rows)
|
|
|
|
|
|
|
|
colHeadsToHtml opts alignStrings widths headers =
|
|
|
|
let heads = zipWith3
|
|
|
|
(\align width item -> tableItemToHtml opts "th" align width item)
|
|
|
|
alignStrings widths headers in
|
|
|
|
inTagsIndented "tr" $ vcat heads
|
|
|
|
|
|
|
|
alignmentToString alignment = case alignment of
|
|
|
|
AlignLeft -> "left"
|
|
|
|
AlignRight -> "right"
|
|
|
|
AlignCenter -> "center"
|
|
|
|
AlignDefault -> "left"
|
|
|
|
|
|
|
|
tableRowToHtml opts aligns cols =
|
|
|
|
inTagsIndented "tr" $ vcat $ zipWith3 (tableItemToHtml opts "td") aligns (repeat 0) cols
|
|
|
|
|
|
|
|
tableItemToHtml opts tag align width item =
|
|
|
|
let attrib = [("align", align)] ++
|
|
|
|
if (width /= 0)
|
|
|
|
then [("style", "{width: " ++
|
|
|
|
show (truncate (100*width)) ++ "%;}")]
|
|
|
|
else [] in
|
|
|
|
inTags False tag attrib $ vcat $ map (blockToHtml opts) item
|
2007-01-06 09:54:58 +00:00
|
|
|
|
|
|
|
listItemToHtml :: WriterOptions -> [Block] -> Doc
|
|
|
|
listItemToHtml opts list =
|
|
|
|
inTagsSimple "li" $ vcat $ map (blockToHtml opts) list
|
2006-10-17 14:22:29 +00:00
|
|
|
|
|
|
|
-- | Convert list of Pandoc inline elements to HTML.
|
2007-01-06 09:54:58 +00:00
|
|
|
inlineListToHtml :: WriterOptions -> [Inline] -> Doc
|
|
|
|
inlineListToHtml opts lst = hcat (map (inlineToHtml opts) lst)
|
2006-10-17 14:22:29 +00:00
|
|
|
|
|
|
|
-- | Convert Pandoc inline element to HTML.
|
2007-01-06 09:54:58 +00:00
|
|
|
inlineToHtml :: WriterOptions -> Inline -> Doc
|
|
|
|
inlineToHtml opts (Emph lst) =
|
|
|
|
inTagsSimple "em" (inlineListToHtml opts lst)
|
|
|
|
inlineToHtml opts (Strong lst) =
|
|
|
|
inTagsSimple "strong" (inlineListToHtml opts lst)
|
|
|
|
inlineToHtml opts (Code str) =
|
2007-01-28 16:40:44 +00:00
|
|
|
inTagsSimple "code" $ text (escapeSGMLString str)
|
2007-01-06 09:54:58 +00:00
|
|
|
inlineToHtml opts (Quoted SingleQuote lst) =
|
|
|
|
text "‘" <> (inlineListToHtml opts lst) <> text "’"
|
|
|
|
inlineToHtml opts (Quoted DoubleQuote lst) =
|
|
|
|
text "“" <> (inlineListToHtml opts lst) <> text "”"
|
|
|
|
inlineToHtml opts EmDash = text "—"
|
|
|
|
inlineToHtml opts EnDash = text "–"
|
|
|
|
inlineToHtml opts Ellipses = text "…"
|
|
|
|
inlineToHtml opts Apostrophe = text "’"
|
2007-01-28 16:40:44 +00:00
|
|
|
inlineToHtml opts (Str str) = text $ escapeSGMLString str
|
|
|
|
inlineToHtml opts (TeX str) = text $ escapeSGMLString str
|
2007-01-06 09:54:58 +00:00
|
|
|
inlineToHtml opts (HtmlInline str) = text str
|
|
|
|
inlineToHtml opts (LineBreak) = selfClosingTag "br" []
|
|
|
|
inlineToHtml opts Space = space
|
2007-01-28 00:04:43 +00:00
|
|
|
inlineToHtml opts (Link txt (Src src title)) =
|
2006-12-20 06:50:14 +00:00
|
|
|
if (isPrefixOf "mailto:" src)
|
2007-01-06 09:54:58 +00:00
|
|
|
then obfuscateLink opts txt src
|
2007-01-28 00:04:43 +00:00
|
|
|
else inTags False "a" ([("href", src)] ++
|
|
|
|
if null title then [] else [("title", title)])
|
2007-01-06 09:54:58 +00:00
|
|
|
(inlineListToHtml opts txt)
|
|
|
|
inlineToHtml opts (Link txt (Ref ref)) =
|
|
|
|
char '[' <> (inlineListToHtml opts txt) <> text "][" <>
|
|
|
|
(inlineListToHtml opts ref) <> char ']'
|
2006-12-20 06:50:14 +00:00
|
|
|
-- this is what markdown does, for better or worse
|
2007-01-28 00:04:43 +00:00
|
|
|
inlineToHtml opts (Image alt (Src source title)) =
|
|
|
|
let alternate = render $ inlineListToHtml opts alt in
|
2007-01-07 01:06:34 +00:00
|
|
|
selfClosingTag "img" $ [("src", source)] ++
|
|
|
|
(if null alternate then [] else [("alt", alternate)]) ++
|
|
|
|
[("title", title)] -- note: null title is included, as in Markdown.pl
|
2007-01-06 09:54:58 +00:00
|
|
|
inlineToHtml opts (Image alternate (Ref ref)) =
|
|
|
|
text "![" <> (inlineListToHtml opts alternate) <> text "][" <>
|
|
|
|
(inlineListToHtml opts ref) <> char ']'
|
|
|
|
inlineToHtml opts (NoteRef ref) =
|
|
|
|
inTags False "sup" [("class", "footnoteRef"), ("id", "fnref" ++ ref)]
|
|
|
|
(inTags False "a" [("href", "#fn" ++ ref)] $ text ref)
|