git.iko.soy/typeable/pandoc
on master help sign in
Added an EPUB writer.

+ New writer module Text.Pandoc.Writers.EPUB + Stylesheet in epub.css + --epub-stylesheet command-line option. + New utility module Text.Pandoc.UUID to generate random UUIDs for EPUBs.

commitf1114733a6aeab12ec1c148488aeeafbf422f624 authorJohn MacFarlane <jgm@berkeley.edu> dateFri 2 Jul 2010, 22:07 (16 years ago) parentff4d94e
6 files changed+382−4
epub.css+30 pandoc.cabal+7−3 src/Text/Pandoc.hs+2 src/Text/Pandoc/UUID.hs+77 src/Text/Pandoc/Writers/EPUB.hs+247 src/pandoc.hs+19−1
epub.cssnew file+30
@@ -0,0 +1,30 @@
1+/* This defines styles and classes used in the book */
2+body { margin-left: 5%; margin-right: 5%; margin-top: 5%; margin-bottom: 5%; text-align: justify; font-size: medium; }
3+code { font-family: monospace; }
4+h1 { text-align: center; }
5+h2 { text-align: center; }
6+h3 { text-align: center; }
7+h4 { text-align: center; }
8+h5 { text-align: center; }
9+h6 { text-align: center; }
10+
11+/* for syntax highlighting produced by highlighting-kate */
12+table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
13+ { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
14+td.lineNumbers { text-align: right; background-color: #EBEBEB; color: black; padding-right: 5px; padding-left: 5px; }
15+td.sourceCode { padding-left: 5px; }
16+pre.sourceCode { }
17+pre.sourceCode span.Normal { }
18+pre.sourceCode span.Keyword { font-weight: bold; }
19+pre.sourceCode span.DataType { color: #800000; }
20+pre.sourceCode span.DecVal { color: #0000FF; }
21+pre.sourceCode span.BaseN { color: #0000FF; }
22+pre.sourceCode span.Float { color: #800080; }
23+pre.sourceCode span.Char { color: #FF00FF; }
24+pre.sourceCode span.String { color: #DD0000; }
25+pre.sourceCode span.Comment { color: #808080; font-style: italic; }
26+pre.sourceCode span.Others { }
27+pre.sourceCode span.Alert { color: green; font-weight: bold; }
28+pre.sourceCode span.Function { color: #000080; }
29+pre.sourceCode span.RegionMarker { }
30+pre.sourceCode span.Error { color: red; font-weight: bold; }
pandoc.cabal+7−3
@@ -18,8 +18,8 @@ Description: Pandoc is a Haskell library for converting from one markup
1818 this library. It can read markdown and (subsets of)
1919 reStructuredText, HTML, and LaTeX, and it can write
2020 markdown, reStructuredText, HTML, LaTeX, ConTeXt, Docbook,
21− OpenDocument, ODT, RTF, MediaWiki, groff man pages, and
22− S5 HTML slide shows.
21+ OpenDocument, ODT, RTF, MediaWiki, groff man pages, EPUB,
22+ and S5 HTML slide shows.
2323 .
2424 Pandoc extends standard markdown syntax with footnotes,
2525 embedded LaTeX, definition lists, tables, and other
@@ -44,6 +44,8 @@ Data-Files:
4444 templates/mediawiki.template, templates/rtf.template,
4545 -- data for ODT writer
4646 reference.odt,
47+ -- stylesheet for EPUB writer
48+ epub.css,
4749 -- data for LaTeXMathML writer
4850 data/LaTeXMathML.js,
4951 data/MathMLinHTML.js,
@@ -146,7 +148,7 @@ Library
146148 bytestring >= 0.9, zip-archive >= 0.1.1.4,
147149 utf8-string >= 0.3, old-time >= 1,
148150 HTTP >= 4000.0.5, texmath, xml >= 1.3.5 && < 1.4,
149− extensible-exceptions
151+ random, extensible-exceptions
150152 if impl(ghc >= 6.10)
151153 Build-depends: base >= 4 && < 5, syb
152154 else
@@ -183,9 +185,11 @@ Library
183185 Text.Pandoc.Writers.RTF,
184186 Text.Pandoc.Writers.S5,
185187 Text.Pandoc.Writers.ODT,
188+ Text.Pandoc.Writers.EPUB,
186189 Text.Pandoc.Templates
187190 Other-Modules: Text.Pandoc.XML,
188191 Text.Pandoc.UTF8,
192+ Text.Pandoc.UUID,
189193 Paths_pandoc
190194 Extensions: CPP
191195 if impl(ghc >= 6.12)
src/Text/Pandoc.hs+2
@@ -87,6 +87,7 @@ module Text.Pandoc
8787 , writeMediaWiki
8888 , writeRTF
8989 , writeODT
90+ , writeEPUB
9091 , prettyPandoc
9192 -- * Writer options used in writers
9293 , WriterOptions (..)
@@ -111,6 +112,7 @@ import Text.Pandoc.Writers.Texinfo
111112import Text.Pandoc.Writers.HTML
112113import Text.Pandoc.Writers.S5
113114import Text.Pandoc.Writers.ODT
115+import Text.Pandoc.Writers.EPUB
114116import Text.Pandoc.Writers.Docbook
115117import Text.Pandoc.Writers.OpenDocument
116118import Text.Pandoc.Writers.Man
src/Text/Pandoc/UUID.hsnew file+77
@@ -0,0 +1,77 @@
1+{-
2+Copyright (C) 2010 John MacFarlane <jgm@berkeley.edu>
3+
4+This program is free software; you can redistribute it and/or modify
5+it under the terms of the GNU General Public License as published by
6+the Free Software Foundation; either version 2 of the License, or
7+(at your option) any later version.
8+
9+This program is distributed in the hope that it will be useful,
10+but WITHOUT ANY WARRANTY; without even the implied warranty of
11+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+GNU General Public License for more details.
13+
14+You should have received a copy of the GNU General Public License
15+along with this program; if not, write to the Free Software
16+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17+-}
18+
19+{- |
20+ Module : Text.Pandoc.UUID
21+ Copyright : Copyright (C) 2010 John MacFarlane
22+ License : GNU GPL, version 2 or above
23+
24+ Maintainer : John MacFarlane <jgm@berkeley.edu>
25+ Stability : alpha
26+ Portability : portable
27+
28+UUID generation using Version 4 (random method) described
29+in RFC4122. See http://tools.ietf.org/html/rfc4122
30+-}
31+
32+module Text.Pandoc.UUID ( UUID, getRandomUUID ) where
33+
34+import Text.Printf ( printf )
35+import System.Random ( randomIO )
36+import Data.Word
37+import Data.Bits ( setBit, clearBit )
38+import Control.Monad ( liftM )
39+
40+data UUID = UUID Word8 Word8 Word8 Word8 Word8 Word8 Word8 Word8
41+ Word8 Word8 Word8 Word8 Word8 Word8 Word8 Word8
42+
43+instance Show UUID where
44+ show (UUID a b c d e f g h i j k l m n o p) =
45+ "urn:uuid:" ++
46+ printf "%02x" a ++
47+ printf "%02x" b ++
48+ printf "%02x" c ++
49+ printf "%02x" d ++
50+ "-" ++
51+ printf "%02x" e ++
52+ printf "%02x" f ++
53+ "-" ++
54+ printf "%02x" g ++
55+ printf "%02x" h ++
56+ "-" ++
57+ printf "%02x" i ++
58+ printf "%02x" j ++
59+ "-" ++
60+ printf "%02x" k ++
61+ printf "%02x" l ++
62+ printf "%02x" m ++
63+ printf "%02x" n ++
64+ printf "%02x" o ++
65+ printf "%02x" p
66+
67+getRandomUUID :: IO UUID
68+getRandomUUID = do
69+ let getRN :: a -> IO Word8
70+ getRN _ = liftM fromIntegral (randomIO :: IO Int)
71+ [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p] <- mapM getRN ([1..16] :: [Int])
72+ -- set variant
73+ let i' = i `setBit` 7 `clearBit` 6
74+ -- set version (0100 for random)
75+ let g' = g `clearBit` 7 `setBit` 6 `clearBit` 5 `clearBit` 4
76+ return $ UUID a b c d e f g' h i' j k l m n o p
77+
src/Text/Pandoc/Writers/EPUB.hsnew file+247
@@ -0,0 +1,247 @@
1+{-
2+Copyright (C) 2010 John MacFarlane <jgm@berkeley.edu>
3+
4+This program is free software; you can redistribute it and/or modify
5+it under the terms of the GNU General Public License as published by
6+the Free Software Foundation; either version 2 of the License, or
7+(at your option) any later version.
8+
9+This program is distributed in the hope that it will be useful,
10+but WITHOUT ANY WARRANTY; without even the implied warranty of
11+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+GNU General Public License for more details.
13+
14+You should have received a copy of the GNU General Public License
15+along with this program; if not, write to the Free Software
16+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17+-}
18+
19+{- |
20+ Module : Text.Pandoc.Writers.EPUB
21+ Copyright : Copyright (C) 2010 John MacFarlane
22+ License : GNU GPL, version 2 or above
23+
24+ Maintainer : John MacFarlane <jgm@berkeley.edu>
25+ Stability : alpha
26+ Portability : portable
27+
28+Conversion of 'Pandoc' documents to EPUB.
29+-}
30+module Text.Pandoc.Writers.EPUB ( writeEPUB ) where
31+import Data.IORef
32+import Data.Maybe ( fromMaybe, isNothing )
33+import Data.List ( findIndices, isPrefixOf )
34+import System.Environment ( getEnv )
35+import System.FilePath ( (</>), takeBaseName, takeExtension )
36+import qualified Data.ByteString.Lazy as B
37+import Data.ByteString.Lazy.UTF8 ( fromString )
38+import Codec.Archive.Zip
39+import System.Time
40+import Text.Pandoc.Shared hiding ( Element )
41+import Text.Pandoc.Definition
42+import Control.Monad (liftM)
43+import Text.XML.Light hiding (ppTopElement)
44+import Text.Pandoc.UUID
45+import Text.Pandoc.Writers.HTML
46+import Text.Pandoc.Writers.Markdown ( writePlain )
47+import Data.Char ( toLower )
48+
49+-- | Produce an EPUB file from a Pandoc document.
50+writeEPUB :: FilePath -- ^ Relative directory of source file
51+ -> String -- ^ EPUB stylesheet
52+ -> WriterOptions -- ^ Writer options
53+ -> Pandoc -- ^ Document to convert
54+ -> IO B.ByteString
55+writeEPUB sourceDir stylesheet opts doc = do
56+ (TOD epochtime _) <- getClockTime
57+ let opts' = opts{ writerEmailObfuscation = NoObfuscation
58+ , writerStandalone = True
59+ , writerWrapText = False }
60+ -- mimetype
61+ let mimetypeEntry = toEntry "mimetype" epochtime $ fromString "application/epub+zip"
62+ -- container.xml
63+ let containerData = fromString $ ppTopElement $
64+ unode "container" ! [("version","1.0")
65+ ,("xmlns","urn:oasis:names:tc:opendocument:xmlns:container")] $
66+ unode "rootfiles" $
67+ unode "rootfile" ! [("full-path","content.opf")
68+ ,("media-type","application/oebps-package+xml")] $ ()
69+ let containerEntry = toEntry "META-INF/container.xml" epochtime containerData
70+ -- stylesheet
71+ let stylesheetEntry = toEntry "stylesheet.css" epochtime $
72+ fromString stylesheet
73+ -- title page
74+ let vars = writerVariables opts'
75+ let tpContent = fromString $
76+ writeHtmlString opts'{writerTemplate = pageTemplate
77+ ,writerVariables = ("titlepage","yes"):vars} doc
78+ let tpEntry = toEntry "title_page.xhtml" epochtime tpContent
79+ -- handle pictures
80+ picEntriesRef <- newIORef ([] :: [Entry])
81+ Pandoc meta blocks <- liftM (processWith transformBlock) $
82+ processWithM (transformInline (writerHTMLMathMethod opts)
83+ sourceDir picEntriesRef) doc
84+ picEntries <- readIORef picEntriesRef
85+ -- body pages
86+ let isH1 (Header 1 _) = True
87+ isH1 _ = False
88+ let chunks = splitByIndices (dropWhile (==0) $ findIndices isH1 blocks) blocks
89+ let titleize (Header 1 xs : ys) = Pandoc meta{docTitle = xs} ys
90+ titleize xs = Pandoc meta xs
91+ let chapToHtml = writeHtmlString opts'{ writerTemplate = pageTemplate
92+ , writerHTMLMathMethod = PlainMath}
93+ let chapters = map titleize chunks
94+ let chapterToEntry :: Int -> Pandoc -> Entry
95+ chapterToEntry num chap = toEntry ("ch" ++ show num ++ ".xhtml")
96+ epochtime $ fromString $ chapToHtml chap
97+ let chapterEntries = zipWith chapterToEntry [1..] chapters
98+ -- contents.opf
99+ uuid <- getRandomUUID
100+ let chapterNode ent = unode "item" !
101+ [("id", takeBaseName $ eRelativePath ent),
102+ ("href", eRelativePath ent),
103+ ("media-type", "application/xhtml+xml")] $ ()
104+ let chapterRefNode ent = unode "itemref" !
105+ [("idref", takeBaseName $ eRelativePath ent)] $ ()
106+ let pictureNode ent = unode "item" !
107+ [("id", takeBaseName $ eRelativePath ent),
108+ ("href", eRelativePath ent),
109+ ("media-type", fromMaybe "application/octet-stream"
110+ $ imageTypeOf $ eRelativePath ent)] $ ()
111+ let plainify t = removeTrailingSpace $
112+ writePlain opts'{ writerStandalone = False } $
113+ Pandoc meta [Plain t]
114+ let plainTitle = plainify $ docTitle meta
115+ let plainAuthors = map plainify $ docAuthors meta
116+ lang <- catch (liftM (takeWhile (/='.')) $ getEnv "lang")
117+ (\_ -> return "en-US")
118+ let contentsData = fromString $ ppTopElement $
119+ unode "package" ! [("version","2.0")
120+ ,("xmlns","http://www.idpf.org/2007/opf")
121+ ,("unique-identifier","BookId")] $
122+ [ unode "metadata" ! [("xmlns:dc","http://purl.org/dc/elements/1.1/")
123+ ,("xmlns:opf","http://www.idpf.org/2007/opf")] $
124+ [ unode "dc:title" plainTitle
125+ , unode "dc:language" lang
126+ , unode "dc:identifier" ! [("id","BookId")] $ show uuid
127+ ] ++
128+ map (unode "dc:creator" ! [("opf:role","aut")]) plainAuthors
129+ , unode "manifest" $
130+ [ unode "item" ! [("id","ncx"), ("href","toc.ncx")
131+ ,("media-type","application/x-dtbncx+xml")] $ ()
132+ , unode "item" ! [("id","style"), ("href","stylesheet.css")
133+ ,("media-type","text/css")] $ ()
134+ ] ++
135+ map chapterNode (tpEntry : chapterEntries) ++
136+ map pictureNode picEntries
137+ , unode "spine" ! [("toc","ncx")] $
138+ map chapterRefNode (tpEntry : chapterEntries)
139+ ]
140+ let contentsEntry = toEntry "content.opf" epochtime contentsData
141+ -- toc.ncx
142+ let navPointNode ent n tit = unode "navPoint" !
143+ [("id", "navPoint-" ++ show n)
144+ ,("playOrder", show n)] $
145+ [ unode "navLabel" $ unode "text" tit
146+ , unode "content" ! [("src",
147+ eRelativePath ent)] $ ()
148+ ]
149+ let tocData = fromString $ ppTopElement $
150+ unode "ncx" ! [("version","2005-1")
151+ ,("xmlns","http://www.daisy.org/z3986/2005/ncx/")] $
152+ [ unode "head"
153+ [ unode "meta" ! [("name","dtb:uid")
154+ ,("content", show uuid)] $ ()
155+ , unode "meta" ! [("name","dtb:depth")
156+ ,("content", "1")] $ ()
157+ , unode "meta" ! [("name","dtb:totalPageCount")
158+ ,("content", "0")] $ ()
159+ , unode "meta" ! [("name","dtb:maxPageNumber")
160+ ,("content", "0")] $ ()
161+ ]
162+ , unode "docTitle" $ unode "text" $ plainTitle
163+ , unode "navMap" $ zipWith3 navPointNode (tpEntry : chapterEntries)
164+ [1..(length chapterEntries + 1)]
165+ ("Title Page" : map (\(Pandoc m _) ->
166+ plainify $ docTitle m) chapters)
167+ ]
168+ let tocEntry = toEntry "toc.ncx" epochtime tocData
169+ -- construct archive
170+ let archive = foldr addEntryToArchive emptyArchive
171+ (mimetypeEntry : containerEntry : stylesheetEntry : tpEntry :
172+ contentsEntry : tocEntry : (picEntries ++ chapterEntries) )
173+ return $ fromArchive archive
174+
175+transformInline :: HTMLMathMethod
176+ -> FilePath
177+ -> IORef [Entry]
178+ -> Inline
179+ -> IO Inline
180+transformInline _ _ _ (Image lab (src,_)) | isNothing (imageTypeOf src) =
181+ return (Emph lab)
182+transformInline _ sourceDir picsRef (Image lab (src,tit)) = do
183+ entries <- readIORef picsRef
184+ let newsrc = "images/img" ++ show (length entries) ++ takeExtension src
185+ catch (readEntry [] (sourceDir </> src) >>= \entry ->
186+ modifyIORef picsRef (entry{ eRelativePath = newsrc } :) >>
187+ return (Image lab (newsrc, tit)))
188+ (\_ -> return (Emph lab))
189+transformInline (MathML _) _ _ x@(Math _ _) = do
190+ let writeHtmlInline opts z = removeTrailingSpace $
191+ writeHtmlString opts $ Pandoc (Meta [] [] []) [Plain [z]]
192+ mathml = writeHtmlInline defaultWriterOptions{
193+ writerHTMLMathMethod = MathML Nothing } x
194+ fallback = writeHtmlInline defaultWriterOptions{
195+ writerHTMLMathMethod = PlainMath } x
196+ inOps = "<ops:switch xmlns:ops=\"http://www.idpf.org/2007/ops\">" ++
197+ "<ops:case required-namespace=\"http://www.w3.org/1998/Math/MathML\">" ++
198+ mathml ++ "</ops:case><ops:default>" ++ fallback ++ "</ops:default>" ++
199+ "</ops:switch>"
200+ return $ HtmlInline $ if "<math" `isPrefixOf` mathml then inOps else mathml
201+transformInline _ _ _ (HtmlInline _) = return $ Str ""
202+transformInline _ _ _ x = return x
203+
204+transformBlock :: Block -> Block
205+transformBlock (RawHtml _) = Null
206+transformBlock x = x
207+
208+(!) :: Node t => (t -> Element) -> [(String, String)] -> t -> Element
209+(!) f attrs n = add_attrs (map (\(k,v) -> Attr (unqual k) v) attrs) (f n)
210+
211+-- | Version of 'ppTopElement' that specifies UTF-8 encoding.
212+ppTopElement :: Element -> String
213+ppTopElement = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ++) . ppElement
214+
215+imageTypeOf :: FilePath -> Maybe String
216+imageTypeOf x = case drop 1 (map toLower (takeExtension x)) of
217+ "jpg" -> Just "image/jpeg"
218+ "jpeg" -> Just "image/jpeg"
219+ "jfif" -> Just "image/jpeg"
220+ "png" -> Just "image/png"
221+ "gif" -> Just "image/gif"
222+ "svg" -> Just "image/svg+xml"
223+ _ -> Nothing
224+
225+pageTemplate :: String
226+pageTemplate = unlines
227+ [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
228+ , "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
229+ , "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
230+ , "<head>"
231+ , "<title>$title$</title>"
232+ , "<link href=\"stylesheet.css\" type=\"text/css\" rel=\"stylesheet\" />"
233+ , "</head>"
234+ , "<body>"
235+ , "$if(titlepage)$"
236+ , "<h1 class=\"title\">$title$</h1>"
237+ , "$for(author)$"
238+ , "<h2 class=\"author\">$author$</h2>"
239+ , "$endfor$"
240+ , "$else$"
241+ , "<h1>$title$</h1>"
242+ , "$body$"
243+ , "$endif$"
244+ , "</body>"
245+ , "</html>"
246+ ]
247+
src/pandoc.hs+19−1
@@ -109,6 +109,7 @@ writers = [("native" , writeDoc)
109109 ,("docbook" , writeDocbook)
110110 ,("opendocument" , writeOpenDocument)
111111 ,("odt" , \_ _ -> "")
112+ ,("epub" , \_ _ -> "")
112113 ,("latex" , writeLaTeX)
113114 ,("latex+lhs" , writeLaTeX)
114115 ,("context" , writeConTeXt)
@@ -124,7 +125,7 @@ writers = [("native" , writeDoc)
124125 ]
125126
126127isNonTextOutput :: String -> Bool
127−isNonTextOutput = (`elem` ["odt"])
128+isNonTextOutput = (`elem` ["odt","epub"])
128129
129130-- | Writer for Pandoc native format.
130131writeDoc :: WriterOptions -> Pandoc -> String
@@ -157,6 +158,7 @@ data Opt = Opt
157158 , optSmart :: Bool -- ^ Use smart typography
158159 , optHTMLMathMethod :: HTMLMathMethod -- ^ Method to print HTML math
159160 , optReferenceODT :: Maybe FilePath -- ^ Path of reference.odt
161+ , optEPUBStylesheet :: Maybe String -- ^ EPUB stylesheet
160162 , optDumpArgs :: Bool -- ^ Output command-line arguments
161163 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
162164 , optStrict :: Bool -- ^ Use strict markdown syntax
@@ -197,6 +199,7 @@ defaultOpts = Opt
197199 , optSmart = False
198200 , optHTMLMathMethod = PlainMath
199201 , optReferenceODT = Nothing
202+ , optEPUBStylesheet = Nothing
200203 , optDumpArgs = False
201204 , optIgnoreArgs = False
202205 , optStrict = False
@@ -467,6 +470,14 @@ options =
467470 "FILENAME")
468471 "" -- "Path of custom reference.odt"
469472
473+ , Option "" ["epub-stylesheet"]
474+ (ReqArg
475+ (\arg opt -> do
476+ text <- UTF8.readFile arg
477+ return opt { optEPUBStylesheet = Just text })
478+ "FILENAME")
479+ "" -- "Path of epub.css"
480+
470481 , Option "D" ["print-default-template"]
471482 (ReqArg
472483 (\arg _ -> do
@@ -580,6 +591,7 @@ defaultWriterName x =
580591 ".texinfo" -> "texinfo"
581592 ".db" -> "docbook"
582593 ".odt" -> "odt"
594+ ".epub" -> "epub"
583595 ['.',y] | y `elem` ['1'..'9'] -> "man"
584596 _ -> "html"
585597
@@ -628,6 +640,7 @@ main = do
628640 , optSmart = smart
629641 , optHTMLMathMethod = mathMethod
630642 , optReferenceODT = referenceODT
643+ , optEPUBStylesheet = epubStylesheet
631644 , optDumpArgs = dumpArgs
632645 , optIgnoreArgs = ignoreArgs
633646 , optStrict = strict
@@ -686,6 +699,11 @@ main = do
686699 Nothing -> error ("Unknown reader: " ++ readerName')
687700
688701 writer <- case (lookup writerName' writers) of
702+ Just _ | writerName' == "epub" -> do
703+ epubstyle <- case epubStylesheet of
704+ Just s -> return s
705+ Nothing -> readDataFile datadir "epub.css"
706+ return (writeEPUB sourceDirRelative epubstyle)
689707 Just _ | writerName' == "odt" -> return
690708 (writeODT datadir sourceDirRelative referenceODT)
691709 Just r -> return $ \o d ->