git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #1516 from mpickering/epubmetadata

EPUB improvements

commite690fe4a3e13ba1720c2012c9909a6aaf2d8ec8d authorJohn MacFarlane <jgm@berkeley.edu> dateMon 11 Aug 2014, 08:14 (12 years ago) parent270e9a2e02360d
3 files changed+72−26
README+18 src/Text/Pandoc/Readers/EPUB.hs+7−6 src/Text/Pandoc/Writers/EPUB.hs+47−20
README+18
@@ -2972,43 +2972,60 @@ The following fields are recognized:
29722972 `GTIN-13`, `UPC`, `ISMN-10`, `DOI`, `LCCN`, `GTIN-14`,
29732973 `ISBN-13`, `Legal deposit number`, `URN`, `OCLC`,
29742974 `ISMN-13`, `ISBN-A`, `JP`, `OLCC`.
2975+
29752976`title`
29762977 ~ Either a string value, or an object with fields `file-as` and
29772978 `type`, or a list of such objects. Valid values for `type` are
29782979 `main`, `subtitle`, `short`, `collection`, `edition`, `extended`.
2980+
29792981`creator`
29802982 ~ Either a string value, or an object with fields `role`, `file-as`,
29812983 and `text`, or a list of such objects. Valid values for `role` are
29822984 [marc relators](http://www.loc.gov/marc/relators/relaterm.html), but
29832985 pandoc will attempt to translate the human-readable versions
29842986 (like "author" and "editor") to the appropriate marc relators.
2987+
29852988`contributor`
29862989 ~ Same format as `creator`.
2990+
29872991`date`
29882992 ~ A string value in `YYYY-MM-DD` format. (Only the year is necessary.)
29892993 Pandoc will attempt to convert other common date formats.
2994+
29902995`language`
29912996 ~ A string value in [RFC5646] format. Pandoc will default to the local
29922997 language if nothing is specified.
2998+
29932999`subject`
29943000 ~ A string value or a list of such values.
3001+
29953002`description`
29963003 ~ A string value.
3004+
29973005`type`
29983006 ~ A string value.
3007+
29993008`format`
30003009 ~ A string value.
3010+
30013011`relation`
30023012 ~ A string value.
3013+
30033014`coverage`
30043015 ~ A string value.
3016+
30053017`rights`
30063018 ~ A string value.
3019+
30073020`cover-image`
30083021 ~ A string value (path to cover image).
3022+
30093023`stylesheet`
30103024 ~ A string value (path to CSS stylesheet).
30113025
3026+`page-progression-direction`
3027+ ~ Either `ltr` or `rtl`. Specifies the synonymous spine [attribute][EPUBspine].
3028+
30123029Literate Haskell support
30133030========================
30143031
@@ -3126,3 +3143,4 @@ Rosenthal.
31263143[InDesign ICML]: https://www.adobe.com/content/dam/Adobe/en/devnet/indesign/cs55-docs/IDML/idml-specification.pdf
31273144[txt2tags]: http://txt2tags.org/
31283145[EPUB]: http://idpf.org/epub
3146+[EPUBspine]: http://www.idpf.org/epub/301/spec/epub-publications.html#sec-spine-elem
src/Text/Pandoc/Readers/EPUB.hs+7−6
@@ -14,7 +14,7 @@ import Text.Pandoc.Walk (walk, query)
1414import Text.Pandoc.Generic(bottomUp)
1515import Text.Pandoc.Readers.HTML (readHtml)
1616import Text.Pandoc.Options ( ReaderOptions(..), readerTrace)
17−import Text.Pandoc.Shared (escapeURI, collapseFilePath)
17+import Text.Pandoc.Shared (escapeURI, collapseFilePath, addMetaField)
1818import Text.Pandoc.MediaBag (MediaBag, insertMedia)
1919import Text.Pandoc.Compat.Except (MonadError, throwError, runExcept, Except)
2020import qualified Text.Pandoc.Builder as B
@@ -155,7 +155,7 @@ parseMeta content = do
155155-- http://www.idpf.org/epub/30/spec/epub30-publications.html#sec-metadata-elem
156156parseMetaItem :: Element -> Meta -> Meta
157157parseMetaItem e@(stripNamespace . elName -> field) meta =
158− B.setMeta (renameMeta field) (B.str $ strContent e) meta
158+ addMetaField (renameMeta field) (B.str $ strContent e) meta
159159
160160renameMeta :: String -> String
161161renameMeta "creator" = "author"
@@ -169,10 +169,10 @@ getManifest archive = do
169169 ns <- mkE "xmlns not in namespaces" (lookup "xmlns" namespaces)
170170 as <- liftM ((map attrToPair) . elAttribs)
171171 (findElementE (QName "rootfile" (Just ns) Nothing) docElem)
172− root <- mkE "Root not found" (lookup "full-path" as)
173− let rootdir = dropFileName root
172+ manifestFile <- mkE "Root not found" (lookup "full-path" as)
173+ let rootdir = dropFileName manifestFile
174174 --mime <- lookup "media-type" as
175− manifest <- findEntryByPathE root archive
175+ manifest <- findEntryByPathE manifestFile archive
176176 liftM ((,) rootdir) (parseXMLDocE . UTF8.toStringLazy . fromEntry $ manifest)
177177
178178-- Fixup
@@ -272,7 +272,8 @@ findAttrE :: MonadError String m => QName -> Element -> m String
272272findAttrE q e = mkE "findAttr" $ findAttr q e
273273
274274findEntryByPathE :: MonadError String m => FilePath -> Archive -> m Entry
275−findEntryByPathE path a = mkE ("No entry on path: " ++ path) $ findEntryByPath path a
275+findEntryByPathE (normalise -> path) a =
276+ mkE ("No entry on path: " ++ path) $ findEntryByPath path a
276277
277278parseXMLDocE :: MonadError String m => String -> m Element
278279parseXMLDocE doc = mkE "Unable to parse XML doc" $ parseXMLDoc doc
src/Text/Pandoc/Writers/EPUB.hs+47−20
@@ -1,4 +1,4 @@
1−{-# LANGUAGE PatternGuards, CPP, ScopedTypeVariables #-}
1+{-# LANGUAGE PatternGuards, CPP, ScopedTypeVariables, ViewPatterns #-}
22{-
33Copyright (C) 2010-2014 John MacFarlane <jgm@berkeley.edu>
44
@@ -29,7 +29,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2929Conversion of 'Pandoc' documents to EPUB.
3030-}
3131module Text.Pandoc.Writers.EPUB ( writeEPUB ) where
32−import Data.IORef
32+import Data.IORef ( IORef, newIORef, readIORef, modifyIORef )
3333import qualified Data.Map as M
3434import Data.Maybe ( fromMaybe )
3535import Data.List ( isPrefixOf, isInfixOf, intercalate )
@@ -40,27 +40,35 @@ import qualified Data.ByteString.Lazy as B
4040import qualified Data.ByteString.Lazy.Char8 as B8
4141import qualified Text.Pandoc.UTF8 as UTF8
4242import Text.Pandoc.SelfContained ( makeSelfContained )
43−import Codec.Archive.Zip
43+import Codec.Archive.Zip ( emptyArchive, addEntryToArchive, eRelativePath, fromEntry , Entry, toEntry, fromArchive)
4444import Control.Applicative ((<$>))
45−import Data.Time.Clock.POSIX
46−import Data.Time
47−import System.Locale
48−import Text.Pandoc.Shared hiding ( Element )
49−import qualified Text.Pandoc.Shared as Shared
45+import Data.Time.Clock.POSIX ( getPOSIXTime )
46+import Data.Time (getCurrentTime,UTCTime, formatTime)
47+import System.Locale ( defaultTimeLocale )
48+import Text.Pandoc.Shared ( trimr, renderTags', safeRead, uniqueIdent, trim
49+ , normalizeDate, readDataFile, stringify, warn
50+ , hierarchicalize, fetchItem' )
51+import qualified Text.Pandoc.Shared as S (Element(..))
5052import Text.Pandoc.Builder (fromList, setMeta)
51−import Text.Pandoc.Options
53+import Text.Pandoc.Options ( WriterOptions(..)
54+ , HTMLMathMethod(..)
55+ , EPUBVersion(..)
56+ , ObfuscationMethod(NoObfuscation) )
5257import Text.Pandoc.Definition
53−import Text.Pandoc.Walk
54−import Control.Monad.State
55−import Text.XML.Light hiding (ppTopElement)
56−import Text.Pandoc.UUID
57−import Text.Pandoc.Writers.HTML
58+import Text.Pandoc.Walk (walk, walkM)
59+import Control.Monad.State (modify, get, execState, State, put, evalState)
60+import Control.Monad (foldM, when, mplus, liftM)
61+import Text.XML.Light ( unode, Element(..), unqual, Attr(..), add_attrs
62+ , strContent, lookupAttr, Node(..), QName(..), parseXML
63+ , onlyElems, node, ppElement)
64+import Text.Pandoc.UUID (getRandomUUID)
65+import Text.Pandoc.Writers.HTML (writeHtmlString, writeHtml)
5866import Data.Char ( toLower, isDigit, isAlphaNum )
5967import Network.URI ( unEscapeString )
6068import Text.Pandoc.MIME (getMimeType)
6169import qualified Control.Exception as E
6270import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
63−import Text.HTML.TagSoup
71+import Text.HTML.TagSoup (Tag(TagOpen), fromAttrib, parseTags)
6472
6573-- A Chapter includes a list of blocks and maybe a section
6674-- number offset. Note, some chapters are unnumbered. The section
@@ -86,6 +94,7 @@ data EPUBMetadata = EPUBMetadata{
8694 , epubRights :: Maybe String
8795 , epubCoverImage :: Maybe String
8896 , epubStylesheet :: Maybe Stylesheet
97+ , epubPageDirection :: ProgressionDirection
8998 } deriving Show
9099
91100data Stylesheet = StylesheetPath FilePath
@@ -114,6 +123,8 @@ data Title = Title{
114123 , titleType :: Maybe String
115124 } deriving Show
116125
126+data ProgressionDirection = LTR | RTL | Default deriving Show
127+
117128dcName :: String -> QName
118129dcName n = QName n Nothing (Just "dc")
119130
@@ -288,6 +299,7 @@ metadataFromMeta opts meta = EPUBMetadata{
288299 , epubRights = rights
289300 , epubCoverImage = coverImage
290301 , epubStylesheet = stylesheet
302+ , epubPageDirection = pageDirection
291303 }
292304 where identifiers = getIdentifier meta
293305 titles = getTitle meta
@@ -310,6 +322,14 @@ metadataFromMeta opts meta = EPUBMetadata{
310322 stylesheet = (StylesheetContents <$> writerEpubStylesheet opts) `mplus`
311323 ((StylesheetPath . metaValueToString) <$>
312324 lookupMeta "stylesheet" meta)
325+ pageDirection = maybe Default stringToPageDirection
326+ (lookupMeta "page-progression-direction" meta)
327+ stringToPageDirection (metaValueToString -> s) =
328+ case s of
329+ "ltr" -> LTR
330+ "rtl" -> RTL
331+ _ -> Default
332+
313333
314334-- | Produce an EPUB file from a Pandoc document.
315335writeEPUB :: WriterOptions -- ^ Writer options
@@ -374,6 +394,12 @@ writeEPUB opts doc@(Pandoc meta _) = do
374394 let mkFontEntry f = mkEntry (takeFileName f) `fmap` B.readFile f
375395 fontEntries <- mapM mkFontEntry $ writerEpubFonts opts'
376396
397+ -- set page progression direction
398+ let progressionDirection = case epubPageDirection metadata of
399+ LTR -> "ltr"
400+ RTL -> "rtl"
401+ Default -> "default"
402+
377403 -- body pages
378404
379405 -- add level 1 header to beginning if none there
@@ -493,7 +519,8 @@ writeEPUB opts doc@(Pandoc meta _) = do
493519 (pictureNode x)]) ++
494520 map pictureNode picEntries ++
495521 map fontNode fontEntries
496− , unode "spine" ! [("toc","ncx")] $
522+ , unode "spine" ! [("toc","ncx")
523+ ,("page-progression-direction", progressionDirection)] $
497524 case epubCoverImage metadata of
498525 Nothing -> []
499526 Just _ -> [ unode "itemref" !
@@ -524,8 +551,8 @@ writeEPUB opts doc@(Pandoc meta _) = do
524551 let tocLevel = writerTOCDepth opts
525552
526553 let navPointNode :: (Int -> String -> String -> [Element] -> Element)
527− -> Shared.Element -> State Int Element
528− navPointNode formatter (Sec _ nums (ident,_,_) ils children) = do
554+ -> S.Element -> State Int Element
555+ navPointNode formatter (S.Sec _ nums (ident,_,_) ils children) = do
529556 n <- get
530557 modify (+1)
531558 let showNums :: [Int] -> String
@@ -537,12 +564,12 @@ writeEPUB opts doc@(Pandoc meta _) = do
537564 let src = case lookup ident reftable of
538565 Just x -> x
539566 Nothing -> error (ident ++ " not found in reftable")
540− let isSec (Sec lev _ _ _ _) = lev <= tocLevel
567+ let isSec (S.Sec lev _ _ _ _) = lev <= tocLevel
541568 isSec _ = False
542569 let subsecs = filter isSec children
543570 subs <- mapM (navPointNode formatter) subsecs
544571 return $ formatter n tit src subs
545− navPointNode _ (Blk _) = error "navPointNode encountered Blk"
572+ navPointNode _ (S.Blk _) = error "navPointNode encountered Blk"
546573
547574 let navMapFormatter :: Int -> String -> String -> [Element] -> Element
548575 navMapFormatter n tit src subs = unode "navPoint" !