aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Writers/Docx.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Text/Pandoc/Writers/Docx.hs')
-rw-r--r--src/Text/Pandoc/Writers/Docx.hs1302
1 files changed, 0 insertions, 1302 deletions
diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs
deleted file mode 100644
index 56aa29211..000000000
--- a/src/Text/Pandoc/Writers/Docx.hs
+++ /dev/null
@@ -1,1302 +0,0 @@
-{-# LANGUAGE ScopedTypeVariables, PatternGuards, ViewPatterns, DeriveFunctor #-}
-{-
-Copyright (C) 2012-2015 John MacFarlane <jgm@berkeley.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
--}
-
-{- |
- Module : Text.Pandoc.Writers.Docx
- Copyright : Copyright (C) 2012-2015 John MacFarlane
- License : GNU GPL, version 2 or above
-
- Maintainer : John MacFarlane <jgm@berkeley.edu>
- Stability : alpha
- Portability : portable
-
-Conversion of 'Pandoc' documents to docx.
--}
-module Text.Pandoc.Writers.Docx ( writeDocx ) where
-import Data.List ( intercalate, isPrefixOf, isSuffixOf )
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as BL
-import qualified Data.ByteString.Lazy.Char8 as BL8
-import qualified Data.Map as M
-import qualified Data.Set as Set
-import qualified Text.Pandoc.UTF8 as UTF8
-import Codec.Archive.Zip
-import Data.Time.Clock.POSIX
-import Text.Pandoc.Compat.Time
-import Text.Pandoc.Definition
-import Text.Pandoc.Generic
-import Text.Pandoc.ImageSize
-import Text.Pandoc.Shared hiding (Element)
-import Text.Pandoc.Writers.Shared (fixDisplayMath)
-import Text.Pandoc.Options
-import Text.Pandoc.Writers.Math
-import Text.Pandoc.Highlighting ( highlight )
-import Text.Pandoc.Walk
-import Text.Pandoc.Error (PandocError)
-import Text.XML.Light as XML
-import Text.TeXMath
-import Text.Pandoc.Readers.Docx.StyleMap
-import Control.Monad.Reader
-import Control.Monad.State
-import Skylighting
-import Control.Monad.Except (runExceptT)
-import System.Random (randomR)
-import Text.Printf (printf)
-import Data.Monoid ((<>))
-import qualified Data.Text as T
-import Text.Pandoc.MIME (MimeType, getMimeType, getMimeTypeDef,
- extensionFromMimeType)
-import Control.Applicative ((<|>))
-import Data.Maybe (fromMaybe, mapMaybe, maybeToList, isNothing)
-import Data.Char (ord, isSpace, toLower)
-import Text.Pandoc.Class (PandocMonad, report)
-import qualified Text.Pandoc.Class as P
-import Text.Pandoc.Logging
-
-data ListMarker = NoMarker
- | BulletMarker
- | NumberMarker ListNumberStyle ListNumberDelim Int
- deriving (Show, Read, Eq, Ord)
-
-listMarkerToId :: ListMarker -> String
-listMarkerToId NoMarker = "990"
-listMarkerToId BulletMarker = "991"
-listMarkerToId (NumberMarker sty delim n) =
- '9' : '9' : styNum : delimNum : show n
- where styNum = case sty of
- DefaultStyle -> '2'
- Example -> '3'
- Decimal -> '4'
- LowerRoman -> '5'
- UpperRoman -> '6'
- LowerAlpha -> '7'
- UpperAlpha -> '8'
- delimNum = case delim of
- DefaultDelim -> '0'
- Period -> '1'
- OneParen -> '2'
- TwoParens -> '3'
-
-data WriterEnv = WriterEnv{ envTextProperties :: [Element]
- , envParaProperties :: [Element]
- , envRTL :: Bool
- , envListLevel :: Int
- , envListNumId :: Int
- , envInDel :: Bool
- , envChangesAuthor :: String
- , envChangesDate :: String
- , envPrintWidth :: Integer
- }
-
-defaultWriterEnv :: WriterEnv
-defaultWriterEnv = WriterEnv{ envTextProperties = []
- , envParaProperties = []
- , envRTL = False
- , envListLevel = -1
- , envListNumId = 1
- , envInDel = False
- , envChangesAuthor = "unknown"
- , envChangesDate = "1969-12-31T19:00:00Z"
- , envPrintWidth = 1
- }
-
-data WriterState = WriterState{
- stFootnotes :: [Element]
- , stSectionIds :: Set.Set String
- , stExternalLinks :: M.Map String String
- , stImages :: M.Map FilePath (String, String, Maybe MimeType, Element, B.ByteString)
- , stLists :: [ListMarker]
- , stInsId :: Int
- , stDelId :: Int
- , stStyleMaps :: StyleMaps
- , stFirstPara :: Bool
- , stTocTitle :: [Inline]
- , stDynamicParaProps :: [String]
- , stDynamicTextProps :: [String]
- }
-
-defaultWriterState :: WriterState
-defaultWriterState = WriterState{
- stFootnotes = defaultFootnotes
- , stSectionIds = Set.empty
- , stExternalLinks = M.empty
- , stImages = M.empty
- , stLists = [NoMarker]
- , stInsId = 1
- , stDelId = 1
- , stStyleMaps = defaultStyleMaps
- , stFirstPara = False
- , stTocTitle = [Str "Table of Contents"]
- , stDynamicParaProps = []
- , stDynamicTextProps = []
- }
-
-type WS m = ReaderT WriterEnv (StateT WriterState m)
-
-mknode :: Node t => String -> [(String,String)] -> t -> Element
-mknode s attrs =
- add_attrs (map (\(k,v) -> Attr (nodename k) v) attrs) . node (nodename s)
-
-nodename :: String -> QName
-nodename s = QName{ qName = name, qURI = Nothing, qPrefix = prefix }
- where (name, prefix) = case break (==':') s of
- (xs,[]) -> (xs, Nothing)
- (ys, _:zs) -> (zs, Just ys)
-
-toLazy :: B.ByteString -> BL.ByteString
-toLazy = BL.fromChunks . (:[])
-
-renderXml :: Element -> BL.ByteString
-renderXml elt = BL8.pack "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" <>
- UTF8.fromStringLazy (showElement elt)
-
-renumIdMap :: Int -> [Element] -> M.Map String String
-renumIdMap _ [] = M.empty
-renumIdMap n (e:es)
- | Just oldId <- findAttr (QName "Id" Nothing Nothing) e =
- M.insert oldId ("rId" ++ (show n)) (renumIdMap (n+1) es)
- | otherwise = renumIdMap n es
-
-replaceAttr :: (QName -> Bool) -> String -> [XML.Attr] -> [XML.Attr]
-replaceAttr _ _ [] = []
-replaceAttr f val (a:as) | f (attrKey a) =
- (XML.Attr (attrKey a) val) : (replaceAttr f val as)
- | otherwise = a : (replaceAttr f val as)
-
-renumId :: (QName -> Bool) -> (M.Map String String) -> Element -> Element
-renumId f renumMap e
- | Just oldId <- findAttrBy f e
- , Just newId <- M.lookup oldId renumMap =
- let attrs' = replaceAttr f newId (elAttribs e)
- in
- e { elAttribs = attrs' }
- | otherwise = e
-
-renumIds :: (QName -> Bool) -> (M.Map String String) -> [Element] -> [Element]
-renumIds f renumMap = map (renumId f renumMap)
-
--- | Certain characters are invalid in XML even if escaped.
--- See #1992
-stripInvalidChars :: String -> String
-stripInvalidChars = filter isValidChar
-
--- | See XML reference
-isValidChar :: Char -> Bool
-isValidChar (ord -> c)
- | c == 0x9 = True
- | c == 0xA = True
- | c == 0xD = True
- | 0x20 <= c && c <= 0xD7FF = True
- | 0xE000 <= c && c <= 0xFFFD = True
- | 0x10000 <= c && c <= 0x10FFFF = True
- | otherwise = False
-
-metaValueToInlines :: MetaValue -> [Inline]
-metaValueToInlines (MetaString s) = [Str s]
-metaValueToInlines (MetaInlines ils) = ils
-metaValueToInlines (MetaBlocks bs) = query return bs
-metaValueToInlines (MetaBool b) = [Str $ show b]
-metaValueToInlines _ = []
-
-
-
-writeDocx :: (PandocMonad m)
- => WriterOptions -- ^ Writer options
- -> Pandoc -- ^ Document to convert
- -> m BL.ByteString
-writeDocx opts doc@(Pandoc meta _) = do
- let datadir = writerUserDataDir opts
- let doc' = walk fixDisplayMath $ doc
- username <- P.lookupEnv "USERNAME"
- utctime <- P.getCurrentTime
- distArchive <- (toArchive . BL.fromStrict) <$>
- P.readDataFile datadir "reference.docx"
- refArchive <- case writerReferenceDoc opts of
- Just f -> toArchive <$> P.readFileLazy f
- Nothing -> return distArchive
-
- parsedDoc <- parseXml refArchive distArchive "word/document.xml"
- let wname f qn = qPrefix qn == Just "w" && f (qName qn)
- let mbsectpr = filterElementName (wname (=="sectPr")) parsedDoc
-
- -- Gets the template size
- let mbpgsz = mbsectpr >>= (filterElementName (wname (=="pgSz")))
- let mbAttrSzWidth = (elAttribs <$> mbpgsz) >>= (lookupAttrBy ((=="w") . qName))
-
- let mbpgmar = mbsectpr >>= (filterElementName (wname (=="pgMar")))
- let mbAttrMarLeft = (elAttribs <$> mbpgmar) >>= (lookupAttrBy ((=="left") . qName))
- let mbAttrMarRight = (elAttribs <$> mbpgmar) >>= (lookupAttrBy ((=="right") . qName))
-
- -- Get the avaible area (converting the size and the margins to int and
- -- doing the difference
- let pgContentWidth = (-) <$> (read <$> mbAttrSzWidth ::Maybe Integer)
- <*> (
- (+) <$> (read <$> mbAttrMarRight ::Maybe Integer)
- <*> (read <$> mbAttrMarLeft ::Maybe Integer)
- )
-
- -- styles
- let stylepath = "word/styles.xml"
- styledoc <- parseXml refArchive distArchive stylepath
-
- -- parse styledoc for heading styles
- let styleMaps = getStyleMaps styledoc
-
- let tocTitle = fromMaybe (stTocTitle defaultWriterState) $
- metaValueToInlines <$> lookupMeta "toc-title" meta
-
- let initialSt = defaultWriterState {
- stStyleMaps = styleMaps
- , stTocTitle = tocTitle
- }
-
- let isRTLmeta = case lookupMeta "dir" meta of
- Just (MetaString "rtl") -> True
- Just (MetaInlines [Str "rtl"]) -> True
- _ -> False
-
- let env = defaultWriterEnv {
- envRTL = isRTLmeta
- , envChangesAuthor = fromMaybe "unknown" username
- , envChangesDate = formatTime defaultTimeLocale "%FT%XZ" utctime
- , envPrintWidth = (maybe 420 (\x -> quot x 20) pgContentWidth)
- }
-
-
- ((contents, footnotes), st) <- runStateT
- (runReaderT
- (writeOpenXML opts{writerWrapText = WrapNone} doc')
- env)
- initialSt
- let epochtime = floor $ utcTimeToPOSIXSeconds utctime
- let imgs = M.elems $ stImages st
-
- -- create entries for images in word/media/...
- let toImageEntry (_,path,_,_,img) = toEntry ("word/" ++ path) epochtime $ toLazy img
- let imageEntries = map toImageEntry imgs
-
- let stdAttributes =
- [("xmlns:w","http://schemas.openxmlformats.org/wordprocessingml/2006/main")
- ,("xmlns:m","http://schemas.openxmlformats.org/officeDocument/2006/math")
- ,("xmlns:r","http://schemas.openxmlformats.org/officeDocument/2006/relationships")
- ,("xmlns:o","urn:schemas-microsoft-com:office:office")
- ,("xmlns:v","urn:schemas-microsoft-com:vml")
- ,("xmlns:w10","urn:schemas-microsoft-com:office:word")
- ,("xmlns:a","http://schemas.openxmlformats.org/drawingml/2006/main")
- ,("xmlns:pic","http://schemas.openxmlformats.org/drawingml/2006/picture")
- ,("xmlns:wp","http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing")]
-
-
- parsedRels <- parseXml refArchive distArchive "word/_rels/document.xml.rels"
- let isHeaderNode e = findAttr (QName "Type" Nothing Nothing) e == Just "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
- let isFooterNode e = findAttr (QName "Type" Nothing Nothing) e == Just "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
- let headers = filterElements isHeaderNode parsedRels
- let footers = filterElements isFooterNode parsedRels
-
- let extractTarget = findAttr (QName "Target" Nothing Nothing)
-
- -- we create [Content_Types].xml and word/_rels/document.xml.rels
- -- from scratch rather than reading from reference.docx,
- -- because Word sometimes changes these files when a reference.docx is modified,
- -- e.g. deleting the reference to footnotes.xml or removing default entries
- -- for image content types.
-
- -- [Content_Types].xml
- let mkOverrideNode (part', contentType') = mknode "Override"
- [("PartName",part'),("ContentType",contentType')] ()
- let mkImageOverride (_, imgpath, mbMimeType, _, _) =
- mkOverrideNode ("/word/" ++ imgpath,
- fromMaybe "application/octet-stream" mbMimeType)
- let mkMediaOverride imgpath =
- mkOverrideNode ('/':imgpath, getMimeTypeDef imgpath)
- let overrides = map mkOverrideNode (
- [("/word/webSettings.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml")
- ,("/word/numbering.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml")
- ,("/word/settings.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml")
- ,("/word/theme/theme1.xml",
- "application/vnd.openxmlformats-officedocument.theme+xml")
- ,("/word/fontTable.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml")
- ,("/docProps/app.xml",
- "application/vnd.openxmlformats-officedocument.extended-properties+xml")
- ,("/docProps/core.xml",
- "application/vnd.openxmlformats-package.core-properties+xml")
- ,("/word/styles.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml")
- ,("/word/document.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")
- ,("/word/footnotes.xml",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml")
- ] ++
- map (\x -> (maybe "" ("/word/" ++) $ extractTarget x,
- "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml")) headers ++
- map (\x -> (maybe "" ("/word/" ++) $ extractTarget x,
- "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml")) footers) ++
- map mkImageOverride imgs ++
- map mkMediaOverride [ eRelativePath e | e <- zEntries refArchive
- , "word/media/" `isPrefixOf` eRelativePath e ]
-
- let defaultnodes = [mknode "Default"
- [("Extension","xml"),("ContentType","application/xml")] (),
- mknode "Default"
- [("Extension","rels"),("ContentType","application/vnd.openxmlformats-package.relationships+xml")] ()]
- let contentTypesDoc = mknode "Types" [("xmlns","http://schemas.openxmlformats.org/package/2006/content-types")] $ defaultnodes ++ overrides
- let contentTypesEntry = toEntry "[Content_Types].xml" epochtime
- $ renderXml contentTypesDoc
-
- -- word/_rels/document.xml.rels
- let toBaseRel (url', id', target') = mknode "Relationship"
- [("Type",url')
- ,("Id",id')
- ,("Target",target')] ()
- let baserels' = map toBaseRel
- [("http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
- "rId1",
- "numbering.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
- "rId2",
- "styles.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings",
- "rId3",
- "settings.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings",
- "rId4",
- "webSettings.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable",
- "rId5",
- "fontTable.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
- "rId6",
- "theme/theme1.xml")
- ,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
- "rId7",
- "footnotes.xml")
- ]
-
- let idMap = renumIdMap (length baserels' + 1) (headers ++ footers)
- let renumHeaders = renumIds (\q -> qName q == "Id") idMap headers
- let renumFooters = renumIds (\q -> qName q == "Id") idMap footers
- let baserels = baserels' ++ renumHeaders ++ renumFooters
- let toImgRel (ident,path,_,_,_) = mknode "Relationship" [("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"),("Id",ident),("Target",path)] ()
- let imgrels = map toImgRel imgs
- let toLinkRel (src,ident) = mknode "Relationship" [("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"),("Id",ident),("Target",src),("TargetMode","External") ] ()
- let linkrels = map toLinkRel $ M.toList $ stExternalLinks st
- let reldoc = mknode "Relationships" [("xmlns","http://schemas.openxmlformats.org/package/2006/relationships")] $ baserels ++ imgrels ++ linkrels
- let relEntry = toEntry "word/_rels/document.xml.rels" epochtime
- $ renderXml reldoc
-
-
- -- adjust contents to add sectPr from reference.docx
- let sectpr = case mbsectpr of
- Just sectpr' -> let cs = renumIds
- (\q -> qName q == "id" && qPrefix q == Just "r")
- idMap
- (elChildren sectpr')
- in
- add_attrs (elAttribs sectpr') $ mknode "w:sectPr" [] cs
- Nothing -> (mknode "w:sectPr" [] ())
-
- -- let sectpr = fromMaybe (mknode "w:sectPr" [] ()) mbsectpr'
- let contents' = contents ++ [sectpr]
- let docContents = mknode "w:document" stdAttributes
- $ mknode "w:body" [] contents'
-
-
-
- -- word/document.xml
- let contentEntry = toEntry "word/document.xml" epochtime
- $ renderXml docContents
-
- -- footnotes
- let notes = mknode "w:footnotes" stdAttributes footnotes
- let footnotesEntry = toEntry "word/footnotes.xml" epochtime $ renderXml notes
-
- -- footnote rels
- let footnoteRelEntry = toEntry "word/_rels/footnotes.xml.rels" epochtime
- $ renderXml $ mknode "Relationships" [("xmlns","http://schemas.openxmlformats.org/package/2006/relationships")]
- linkrels
-
- -- styles
-
- -- We only want to inject paragraph and text properties that
- -- are not already in the style map. Note that keys in the stylemap
- -- are normalized as lowercase.
- let newDynamicParaProps = filter
- (\sty -> isNothing $ M.lookup (toLower <$> sty) $ getMap $ sParaStyleMap styleMaps)
- (stDynamicParaProps st)
-
- newDynamicTextProps = filter
- (\sty -> isNothing $ M.lookup (toLower <$> sty) $ getMap $ sCharStyleMap styleMaps)
- (stDynamicTextProps st)
-
- let newstyles = map newParaPropToOpenXml newDynamicParaProps ++
- map newTextPropToOpenXml newDynamicTextProps ++
- (case writerHighlightStyle opts of
- Nothing -> []
- Just sty -> (styleToOpenXml styleMaps sty))
- let styledoc' = styledoc{ elContent = elContent styledoc ++
- map Elem newstyles }
- let styleEntry = toEntry stylepath epochtime $ renderXml styledoc'
-
- -- construct word/numbering.xml
- let numpath = "word/numbering.xml"
- numbering <- parseXml refArchive distArchive numpath
- newNumElts <- mkNumbering (stLists st)
- let allElts = onlyElems (elContent numbering) ++ newNumElts
- let numEntry = toEntry numpath epochtime $ renderXml numbering{ elContent =
- -- we want all the abstractNums first, then the nums,
- -- otherwise things break:
- [Elem e | e <- allElts
- , qName (elName e) == "abstractNum" ] ++
- [Elem e | e <- allElts
- , qName (elName e) == "num" ] }
- let docPropsPath = "docProps/core.xml"
- let docProps = mknode "cp:coreProperties"
- [("xmlns:cp","http://schemas.openxmlformats.org/package/2006/metadata/core-properties")
- ,("xmlns:dc","http://purl.org/dc/elements/1.1/")
- ,("xmlns:dcterms","http://purl.org/dc/terms/")
- ,("xmlns:dcmitype","http://purl.org/dc/dcmitype/")
- ,("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance")]
- $ mknode "dc:title" [] (stringify $ docTitle meta)
- : mknode "dc:creator" [] (intercalate "; " (map stringify $ docAuthors meta))
- : (\x -> [ mknode "dcterms:created" [("xsi:type","dcterms:W3CDTF")] x
- , mknode "dcterms:modified" [("xsi:type","dcterms:W3CDTF")] x
- ]) (formatTime defaultTimeLocale "%FT%XZ" utctime)
- let docPropsEntry = toEntry docPropsPath epochtime $ renderXml docProps
-
- let relsPath = "_rels/.rels"
- let rels = mknode "Relationships" [("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships")]
- $ map (\attrs -> mknode "Relationship" attrs ())
- [ [("Id","rId1")
- ,("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument")
- ,("Target","word/document.xml")]
- , [("Id","rId4")
- ,("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties")
- ,("Target","docProps/app.xml")]
- , [("Id","rId3")
- ,("Type","http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties")
- ,("Target","docProps/core.xml")]
- ]
- let relsEntry = toEntry relsPath epochtime $ renderXml rels
-
- -- we use dist archive for settings.xml, because Word sometimes
- -- adds references to footnotes or endnotes we don't have...
- -- we do, however, copy some settings over from reference
- let settingsPath = "word/settings.xml"
- settingsList = [ "w:autoHyphenation"
- , "w:consecutiveHyphenLimit"
- , "w:hyphenationZone"
- , "w:doNotHyphenateCap"
- ]
- settingsEntry <- copyChildren refArchive distArchive settingsPath epochtime settingsList
-
- let entryFromArchive arch path =
- maybe (fail $ path ++ " missing in reference docx")
- return
- (findEntryByPath path arch `mplus` findEntryByPath path distArchive)
- docPropsAppEntry <- entryFromArchive refArchive "docProps/app.xml"
- themeEntry <- entryFromArchive refArchive "word/theme/theme1.xml"
- fontTableEntry <- entryFromArchive refArchive "word/fontTable.xml"
- webSettingsEntry <- entryFromArchive refArchive "word/webSettings.xml"
- headerFooterEntries <- mapM (entryFromArchive refArchive) $
- mapMaybe (fmap ("word/" ++) . extractTarget)
- (headers ++ footers)
- let miscRelEntries = [ e | e <- zEntries refArchive
- , "word/_rels/" `isPrefixOf` (eRelativePath e)
- , ".xml.rels" `isSuffixOf` (eRelativePath e)
- , eRelativePath e /= "word/_rels/document.xml.rels"
- , eRelativePath e /= "word/_rels/footnotes.xml.rels" ]
- let otherMediaEntries = [ e | e <- zEntries refArchive
- , "word/media/" `isPrefixOf` eRelativePath e ]
-
- -- Create archive
- let archive = foldr addEntryToArchive emptyArchive $
- contentTypesEntry : relsEntry : contentEntry : relEntry :
- footnoteRelEntry : numEntry : styleEntry : footnotesEntry :
- docPropsEntry : docPropsAppEntry : themeEntry :
- fontTableEntry : settingsEntry : webSettingsEntry :
- imageEntries ++ headerFooterEntries ++
- miscRelEntries ++ otherMediaEntries
- return $ fromArchive archive
-
-
-newParaPropToOpenXml :: String -> Element
-newParaPropToOpenXml s =
- let styleId = filter (not . isSpace) s
- in mknode "w:style" [ ("w:type", "paragraph")
- , ("w:customStyle", "1")
- , ("w:styleId", styleId)]
- [ mknode "w:name" [("w:val", s)] ()
- , mknode "w:basedOn" [("w:val","BodyText")] ()
- , mknode "w:qFormat" [] ()
- ]
-
-newTextPropToOpenXml :: String -> Element
-newTextPropToOpenXml s =
- let styleId = filter (not . isSpace) s
- in mknode "w:style" [ ("w:type", "character")
- , ("w:customStyle", "1")
- , ("w:styleId", styleId)]
- [ mknode "w:name" [("w:val", s)] ()
- , mknode "w:basedOn" [("w:val","BodyTextChar")] ()
- ]
-
-styleToOpenXml :: StyleMaps -> Style -> [Element]
-styleToOpenXml sm style =
- maybeToList parStyle ++ mapMaybe toStyle alltoktypes
- where alltoktypes = enumFromTo KeywordTok NormalTok
- toStyle toktype | hasStyleName (show toktype) (sCharStyleMap sm) = Nothing
- | otherwise = Just $
- mknode "w:style" [("w:type","character"),
- ("w:customStyle","1"),("w:styleId",show toktype)]
- [ mknode "w:name" [("w:val",show toktype)] ()
- , mknode "w:basedOn" [("w:val","VerbatimChar")] ()
- , mknode "w:rPr" [] $
- [ mknode "w:color" [("w:val",tokCol toktype)] ()
- | tokCol toktype /= "auto" ] ++
- [ mknode "w:shd" [("w:val","clear"),("w:fill",tokBg toktype)] ()
- | tokBg toktype /= "auto" ] ++
- [ mknode "w:b" [] () | tokFeature tokenBold toktype ] ++
- [ mknode "w:i" [] () | tokFeature tokenItalic toktype ] ++
- [ mknode "w:u" [] () | tokFeature tokenUnderline toktype ]
- ]
- tokStyles = tokenStyles style
- tokFeature f toktype = maybe False f $ lookup toktype tokStyles
- tokCol toktype = maybe "auto" (drop 1 . fromColor)
- $ (tokenColor =<< lookup toktype tokStyles)
- `mplus` defaultColor style
- tokBg toktype = maybe "auto" (drop 1 . fromColor)
- $ (tokenBackground =<< lookup toktype tokStyles)
- `mplus` backgroundColor style
- parStyle | hasStyleName "Source Code" (sParaStyleMap sm) = Nothing
- | otherwise = Just $
- mknode "w:style" [("w:type","paragraph"),
- ("w:customStyle","1"),("w:styleId","SourceCode")]
- [ mknode "w:name" [("w:val","Source Code")] ()
- , mknode "w:basedOn" [("w:val","Normal")] ()
- , mknode "w:link" [("w:val","VerbatimChar")] ()
- , mknode "w:pPr" []
- $ mknode "w:wordWrap" [("w:val","off")] ()
- : ( maybe [] (\col -> [mknode "w:shd" [("w:val","clear"),("w:fill",drop 1 $ fromColor col)] ()])
- $ backgroundColor style )
- ]
-
-copyChildren :: (PandocMonad m) => Archive -> Archive -> String -> Integer -> [String] -> m Entry
-copyChildren refArchive distArchive path timestamp elNames = do
- ref <- parseXml refArchive distArchive path
- dist <- parseXml distArchive distArchive path
- return $ toEntry path timestamp $ renderXml dist{
- elContent = elContent dist ++ copyContent ref
- }
- where
- strName QName{qName=name, qPrefix=prefix}
- | Just p <- prefix = p++":"++name
- | otherwise = name
- shouldCopy = (`elem` elNames) . strName
- cleanElem el@Element{elName=name} = Elem el{elName=name{qURI=Nothing}}
- copyContent = map cleanElem . filterChildrenName shouldCopy
-
--- this is the lowest number used for a list numId
-baseListId :: Int
-baseListId = 1000
-
-mkNumbering :: (PandocMonad m) => [ListMarker] -> m [Element]
-mkNumbering lists = do
- elts <- mapM mkAbstractNum (ordNub lists)
- return $ elts ++ zipWith mkNum lists [baseListId..(baseListId + length lists - 1)]
-
-mkNum :: ListMarker -> Int -> Element
-mkNum marker numid =
- mknode "w:num" [("w:numId",show numid)]
- $ mknode "w:abstractNumId" [("w:val",listMarkerToId marker)] ()
- : case marker of
- NoMarker -> []
- BulletMarker -> []
- NumberMarker _ _ start ->
- map (\lvl -> mknode "w:lvlOverride" [("w:ilvl",show (lvl :: Int))]
- $ mknode "w:startOverride" [("w:val",show start)] ()) [0..6]
-
-mkAbstractNum :: (PandocMonad m) => ListMarker -> m Element
-mkAbstractNum marker = do
- gen <- P.newStdGen
- let (nsid, _) = randomR (0x10000000 :: Integer, 0xFFFFFFFF :: Integer) gen
- return $ mknode "w:abstractNum" [("w:abstractNumId",listMarkerToId marker)]
- $ mknode "w:nsid" [("w:val", printf "%8x" nsid)] ()
- : mknode "w:multiLevelType" [("w:val","multilevel")] ()
- : map (mkLvl marker) [0..6]
-
-mkLvl :: ListMarker -> Int -> Element
-mkLvl marker lvl =
- mknode "w:lvl" [("w:ilvl",show lvl)] $
- [ mknode "w:start" [("w:val",start)] ()
- | marker /= NoMarker && marker /= BulletMarker ] ++
- [ mknode "w:numFmt" [("w:val",fmt)] ()
- , mknode "w:lvlText" [("w:val",lvltxt)] ()
- , mknode "w:lvlJc" [("w:val","left")] ()
- , mknode "w:pPr" []
- [ mknode "w:tabs" []
- $ mknode "w:tab" [("w:val","num"),("w:pos",show $ lvl * step)] ()
- , mknode "w:ind" [("w:left",show $ lvl * step + hang),("w:hanging",show hang)] ()
- ]
- ]
- where (fmt, lvltxt, start) =
- case marker of
- NoMarker -> ("bullet"," ","1")
- BulletMarker -> ("bullet",bulletFor lvl,"1")
- NumberMarker st de n -> (styleFor st lvl
- ,patternFor de ("%" ++ show (lvl + 1))
- ,show n)
- step = 720
- hang = 480
- bulletFor 0 = "\x2022" -- filled circle
- bulletFor 1 = "\x2013" -- en dash
- bulletFor 2 = "\x2022" -- hyphen bullet
- bulletFor 3 = "\x2013"
- bulletFor 4 = "\x2022"
- bulletFor 5 = "\x2013"
- bulletFor _ = "\x2022"
- styleFor UpperAlpha _ = "upperLetter"
- styleFor LowerAlpha _ = "lowerLetter"
- styleFor UpperRoman _ = "upperRoman"
- styleFor LowerRoman _ = "lowerRoman"
- styleFor Decimal _ = "decimal"
- styleFor DefaultStyle 1 = "decimal"
- styleFor DefaultStyle 2 = "lowerLetter"
- styleFor DefaultStyle 3 = "lowerRoman"
- styleFor DefaultStyle 4 = "decimal"
- styleFor DefaultStyle 5 = "lowerLetter"
- styleFor DefaultStyle 6 = "lowerRoman"
- styleFor _ _ = "decimal"
- patternFor OneParen s = s ++ ")"
- patternFor TwoParens s = "(" ++ s ++ ")"
- patternFor _ s = s ++ "."
-
-getNumId :: (PandocMonad m) => WS m Int
-getNumId = (((baseListId - 1) +) . length) `fmap` gets stLists
-
-
-makeTOC :: (PandocMonad m) => WriterOptions -> WS m [Element]
-makeTOC opts | writerTableOfContents opts = do
- let depth = "1-"++(show (writerTOCDepth opts))
- let tocCmd = "TOC \\o \""++depth++"\" \\h \\z \\u"
- tocTitle <- gets stTocTitle
- title <- withParaPropM (pStyleM "TOC Heading") (blocksToOpenXML opts [Para tocTitle])
- return $
- [mknode "w:sdt" [] ([
- mknode "w:sdtPr" [] (
- mknode "w:docPartObj" [] (
- [mknode "w:docPartGallery" [("w:val","Table of Contents")] (),
- mknode "w:docPartUnique" [] ()]
- ) -- w:docPartObj
- ), -- w:sdtPr
- mknode "w:sdtContent" [] (title++[
- mknode "w:p" [] (
- mknode "w:r" [] ([
- mknode "w:fldChar" [("w:fldCharType","begin"),("w:dirty","true")] (),
- mknode "w:instrText" [("xml:space","preserve")] tocCmd,
- mknode "w:fldChar" [("w:fldCharType","separate")] (),
- mknode "w:fldChar" [("w:fldCharType","end")] ()
- ]) -- w:r
- ) -- w:p
- ])
- ])] -- w:sdt
-makeTOC _ = return []
-
-
--- | Convert Pandoc document to two lists of
--- OpenXML elements (the main document and footnotes).
-writeOpenXML :: (PandocMonad m) => WriterOptions -> Pandoc -> WS m ([Element], [Element])
-writeOpenXML opts (Pandoc meta blocks) = do
- let tit = docTitle meta ++ case lookupMeta "subtitle" meta of
- Just (MetaBlocks [Plain xs]) -> LineBreak : xs
- _ -> []
- let auths = docAuthors meta
- let dat = docDate meta
- let abstract' = case lookupMeta "abstract" meta of
- Just (MetaBlocks bs) -> bs
- Just (MetaInlines ils) -> [Plain ils]
- _ -> []
- let subtitle' = case lookupMeta "subtitle" meta of
- Just (MetaBlocks [Plain xs]) -> xs
- Just (MetaBlocks [Para xs]) -> xs
- Just (MetaInlines xs) -> xs
- _ -> []
- title <- withParaPropM (pStyleM "Title") $ blocksToOpenXML opts [Para tit | not (null tit)]
- subtitle <- withParaPropM (pStyleM "Subtitle") $ blocksToOpenXML opts [Para subtitle' | not (null subtitle')]
- authors <- withParaProp (pCustomStyle "Author") $ blocksToOpenXML opts $
- map Para auths
- date <- withParaPropM (pStyleM "Date") $ blocksToOpenXML opts [Para dat | not (null dat)]
- abstract <- if null abstract'
- then return []
- else withParaProp (pCustomStyle "Abstract") $ blocksToOpenXML opts abstract'
- let convertSpace (Str x : Space : Str y : xs) = Str (x ++ " " ++ y) : xs
- convertSpace (Str x : Str y : xs) = Str (x ++ y) : xs
- convertSpace xs = xs
- let blocks' = bottomUp convertSpace blocks
- doc' <- (setFirstPara >> blocksToOpenXML opts blocks')
- notes' <- reverse `fmap` gets stFootnotes
- toc <- makeTOC opts
- let meta' = title ++ subtitle ++ authors ++ date ++ abstract ++ toc
- return (meta' ++ doc', notes')
-
--- | Convert a list of Pandoc blocks to OpenXML.
-blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Element]
-blocksToOpenXML opts bls = concat `fmap` mapM (blockToOpenXML opts) bls
-
-pCustomStyle :: String -> Element
-pCustomStyle sty = mknode "w:pStyle" [("w:val",sty)] ()
-
-pStyleM :: (PandocMonad m) => String -> WS m XML.Element
-pStyleM styleName = do
- styleMaps <- gets stStyleMaps
- let sty' = getStyleId styleName $ sParaStyleMap styleMaps
- return $ mknode "w:pStyle" [("w:val",sty')] ()
-
-rCustomStyle :: String -> Element
-rCustomStyle sty = mknode "w:rStyle" [("w:val",sty)] ()
-
-rStyleM :: (PandocMonad m) => String -> WS m XML.Element
-rStyleM styleName = do
- styleMaps <- gets stStyleMaps
- let sty' = getStyleId styleName $ sCharStyleMap styleMaps
- return $ mknode "w:rStyle" [("w:val",sty')] ()
-
-getUniqueId :: (PandocMonad m) => m String
--- the + 20 is to ensure that there are no clashes with the rIds
--- already in word/document.xml.rel
-getUniqueId = (show . (+ 20)) <$> P.newUniqueHash
-
--- | Key for specifying user-defined docx styles.
-dynamicStyleKey :: String
-dynamicStyleKey = "custom-style"
-
--- | Convert a Pandoc block element to OpenXML.
-blockToOpenXML :: (PandocMonad m) => WriterOptions -> Block -> WS m [Element]
-blockToOpenXML opts blk = withDirection $ blockToOpenXML' opts blk
-
-blockToOpenXML' :: (PandocMonad m) => WriterOptions -> Block -> WS m [Element]
-blockToOpenXML' _ Null = return []
-blockToOpenXML' opts (Div (ident,classes,kvs) bs)
- | Just sty <- lookup dynamicStyleKey kvs = do
- modify $ \s -> s{stDynamicParaProps = sty : (stDynamicParaProps s)}
- withParaPropM (pStyleM sty) $ blocksToOpenXML opts bs
- | Just "rtl" <- lookup "dir" kvs = do
- let kvs' = filter (("dir", "rtl")/=) kvs
- local (\env -> env { envRTL = True }) $
- blockToOpenXML opts (Div (ident,classes,kvs') bs)
- | Just "ltr" <- lookup "dir" kvs = do
- let kvs' = filter (("dir", "ltr")/=) kvs
- local (\env -> env { envRTL = False }) $
- blockToOpenXML opts (Div (ident,classes,kvs') bs)
-blockToOpenXML' opts (Div (_,["references"],_) bs) = do
- let (hs, bs') = span isHeaderBlock bs
- header <- blocksToOpenXML opts hs
- -- We put the Bibliography style on paragraphs after the header
- rest <- withParaPropM (pStyleM "Bibliography") $ blocksToOpenXML opts bs'
- return (header ++ rest)
-blockToOpenXML' opts (Div _ bs) = blocksToOpenXML opts bs
-blockToOpenXML' opts (Header lev (ident,_,_) lst) = do
- setFirstPara
- paraProps <- withParaPropM (pStyleM ("Heading "++show lev)) $
- getParaProps False
- contents <- inlinesToOpenXML opts lst
- usedIdents <- gets stSectionIds
- let bookmarkName = if null ident
- then uniqueIdent lst usedIdents
- else ident
- modify $ \s -> s{ stSectionIds = Set.insert bookmarkName $ stSectionIds s }
- id' <- (lift . lift) getUniqueId
- let bookmarkStart = mknode "w:bookmarkStart" [("w:id", id')
- ,("w:name",bookmarkName)] ()
- let bookmarkEnd = mknode "w:bookmarkEnd" [("w:id", id')] ()
- return [mknode "w:p" [] (paraProps ++ [bookmarkStart, bookmarkEnd] ++ contents)]
-blockToOpenXML' opts (Plain lst) = withParaProp (pCustomStyle "Compact")
- $ blockToOpenXML opts (Para lst)
--- title beginning with fig: indicates that the image is a figure
-blockToOpenXML' opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) = do
- setFirstPara
- let prop = pCustomStyle $
- if null alt
- then "Figure"
- else "FigureWithCaption"
- paraProps <- local (\env -> env { envParaProperties = prop : envParaProperties env }) (getParaProps False)
- contents <- inlinesToOpenXML opts [Image attr alt (src,tit)]
- captionNode <- withParaProp (pCustomStyle "ImageCaption")
- $ blockToOpenXML opts (Para alt)
- return $ mknode "w:p" [] (paraProps ++ contents) : captionNode
--- fixDisplayMath sometimes produces a Para [] as artifact
-blockToOpenXML' _ (Para []) = return []
-blockToOpenXML' opts (Para lst) = do
- isFirstPara <- gets stFirstPara
- paraProps <- getParaProps $ case lst of
- [Math DisplayMath _] -> True
- _ -> False
- bodyTextStyle <- pStyleM "Body Text"
- let paraProps' = case paraProps of
- [] | isFirstPara -> [mknode "w:pPr" [] [pCustomStyle "FirstParagraph"]]
- [] -> [mknode "w:pPr" [] [bodyTextStyle]]
- ps -> ps
- modify $ \s -> s { stFirstPara = False }
- contents <- inlinesToOpenXML opts lst
- return [mknode "w:p" [] (paraProps' ++ contents)]
-blockToOpenXML' opts (LineBlock lns) = blockToOpenXML opts $ linesToPara lns
-blockToOpenXML' _ b@(RawBlock format str)
- | format == Format "openxml" = return [ x | Elem x <- parseXML str ]
- | otherwise = do
- report $ BlockNotRendered b
- return []
-blockToOpenXML' opts (BlockQuote blocks) = do
- p <- withParaPropM (pStyleM "Block Text") $ blocksToOpenXML opts blocks
- setFirstPara
- return p
-blockToOpenXML' opts (CodeBlock attrs str) = do
- p <- withParaProp (pCustomStyle "SourceCode") (blockToOpenXML opts $ Para [Code attrs str])
- setFirstPara
- return p
-blockToOpenXML' _ HorizontalRule = do
- setFirstPara
- return [
- mknode "w:p" [] $ mknode "w:r" [] $ mknode "w:pict" []
- $ mknode "v:rect" [("style","width:0;height:1.5pt"),
- ("o:hralign","center"),
- ("o:hrstd","t"),("o:hr","t")] () ]
-blockToOpenXML' opts (Table caption aligns widths headers rows) = do
- setFirstPara
- let captionStr = stringify caption
- caption' <- if null caption
- then return []
- else withParaProp (pCustomStyle "TableCaption")
- $ blockToOpenXML opts (Para caption)
- let alignmentFor al = mknode "w:jc" [("w:val",alignmentToString al)] ()
- let cellToOpenXML (al, cell) = withParaProp (alignmentFor al)
- $ blocksToOpenXML opts cell
- headers' <- mapM cellToOpenXML $ zip aligns headers
- rows' <- mapM (mapM cellToOpenXML . zip aligns) rows
- let borderProps = mknode "w:tcPr" []
- [ mknode "w:tcBorders" []
- $ mknode "w:bottom" [("w:val","single")] ()
- , mknode "w:vAlign" [("w:val","bottom")] () ]
- let emptyCell = [mknode "w:p" [] [mknode "w:pPr" [] [pCustomStyle "Compact"]]]
- let mkcell border contents = mknode "w:tc" []
- $ [ borderProps | border ] ++
- if null contents
- then emptyCell
- else contents
- let mkrow border cells = mknode "w:tr" [] $
- [mknode "w:trPr" [] [
- mknode "w:cnfStyle" [("w:firstRow","1")] ()] | border]
- ++ map (mkcell border) cells
- let textwidth = 7920 -- 5.5 in in twips, 1/20 pt
- let fullrow = 5000 -- 100% specified in pct
- let rowwidth = fullrow * sum widths
- let mkgridcol w = mknode "w:gridCol"
- [("w:w", show (floor (textwidth * w) :: Integer))] ()
- let hasHeader = not (all null headers)
- return $
- caption' ++
- [mknode "w:tbl" []
- ( mknode "w:tblPr" []
- ( mknode "w:tblStyle" [("w:val","TableNormal")] () :
- mknode "w:tblW" [("w:type", "pct"), ("w:w", show rowwidth)] () :
- mknode "w:tblLook" [("w:firstRow","1") | hasHeader ] () :
- [ mknode "w:tblCaption" [("w:val", captionStr)] ()
- | not (null caption) ] )
- : mknode "w:tblGrid" []
- (if all (==0) widths
- then []
- else map mkgridcol widths)
- : [ mkrow True headers' | hasHeader ] ++
- map (mkrow False) rows'
- )]
-blockToOpenXML' opts (BulletList lst) = do
- let marker = BulletMarker
- addList marker
- numid <- getNumId
- l <- asList $ concat `fmap` mapM (listItemToOpenXML opts numid) lst
- setFirstPara
- return l
-blockToOpenXML' opts (OrderedList (start, numstyle, numdelim) lst) = do
- let marker = NumberMarker numstyle numdelim start
- addList marker
- numid <- getNumId
- l <- asList $ concat `fmap` mapM (listItemToOpenXML opts numid) lst
- setFirstPara
- return l
-blockToOpenXML' opts (DefinitionList items) = do
- l <- concat `fmap` mapM (definitionListItemToOpenXML opts) items
- setFirstPara
- return l
-
-definitionListItemToOpenXML :: (PandocMonad m) => WriterOptions -> ([Inline],[[Block]]) -> WS m [Element]
-definitionListItemToOpenXML opts (term,defs) = do
- term' <- withParaProp (pCustomStyle "DefinitionTerm")
- $ blockToOpenXML opts (Para term)
- defs' <- withParaProp (pCustomStyle "Definition")
- $ concat `fmap` mapM (blocksToOpenXML opts) defs
- return $ term' ++ defs'
-
-addList :: (PandocMonad m) => ListMarker -> WS m ()
-addList marker = do
- lists <- gets stLists
- modify $ \st -> st{ stLists = lists ++ [marker] }
-
-listItemToOpenXML :: (PandocMonad m) => WriterOptions -> Int -> [Block] -> WS m [Element]
-listItemToOpenXML _ _ [] = return []
-listItemToOpenXML opts numid (first:rest) = do
- first' <- withNumId numid $ blockToOpenXML opts first
- -- baseListId is the code for no list marker:
- rest' <- withNumId baseListId $ blocksToOpenXML opts rest
- return $ first' ++ rest'
-
-alignmentToString :: Alignment -> [Char]
-alignmentToString alignment = case alignment of
- AlignLeft -> "left"
- AlignRight -> "right"
- AlignCenter -> "center"
- AlignDefault -> "left"
-
--- | Convert a list of inline elements to OpenXML.
-inlinesToOpenXML :: (PandocMonad m) => WriterOptions -> [Inline] -> WS m [Element]
-inlinesToOpenXML opts lst = concat `fmap` mapM (inlineToOpenXML opts) lst
-
-withNumId :: (PandocMonad m) => Int -> WS m a -> WS m a
-withNumId numid = local $ \env -> env{ envListNumId = numid }
-
-asList :: (PandocMonad m) => WS m a -> WS m a
-asList = local $ \env -> env{ envListLevel = envListLevel env + 1 }
-
-getTextProps :: (PandocMonad m) => WS m [Element]
-getTextProps = do
- props <- asks envTextProperties
- return $ if null props
- then []
- else [mknode "w:rPr" [] props]
-
-withTextProp :: PandocMonad m => Element -> WS m a -> WS m a
-withTextProp d p =
- local (\env -> env {envTextProperties = d : envTextProperties env}) p
-
-withTextPropM :: PandocMonad m => WS m Element -> WS m a -> WS m a
-withTextPropM = (. flip withTextProp) . (>>=)
-
-getParaProps :: PandocMonad m => Bool -> WS m [Element]
-getParaProps displayMathPara = do
- props <- asks envParaProperties
- listLevel <- asks envListLevel
- numid <- asks envListNumId
- let listPr = if listLevel >= 0 && not displayMathPara
- then [ mknode "w:numPr" []
- [ mknode "w:numId" [("w:val",show numid)] ()
- , mknode "w:ilvl" [("w:val",show listLevel)] () ]
- ]
- else []
- return $ case props ++ listPr of
- [] -> []
- ps -> [mknode "w:pPr" [] ps]
-
-withParaProp :: PandocMonad m => Element -> WS m a -> WS m a
-withParaProp d p =
- local (\env -> env {envParaProperties = d : envParaProperties env}) p
-
-withParaPropM :: PandocMonad m => WS m Element -> WS m a -> WS m a
-withParaPropM = (. flip withParaProp) . (>>=)
-
-formattedString :: PandocMonad m => String -> WS m [Element]
-formattedString str = do
- props <- getTextProps
- inDel <- asks envInDel
- return [ mknode "w:r" [] $
- props ++
- [ mknode (if inDel then "w:delText" else "w:t")
- [("xml:space","preserve")] (stripInvalidChars str) ] ]
-
-setFirstPara :: PandocMonad m => WS m ()
-setFirstPara = modify $ \s -> s { stFirstPara = True }
-
--- | Convert an inline element to OpenXML.
-inlineToOpenXML :: PandocMonad m => WriterOptions -> Inline -> WS m [Element]
-inlineToOpenXML opts il = withDirection $ inlineToOpenXML' opts il
-
-inlineToOpenXML' :: PandocMonad m => WriterOptions -> Inline -> WS m [Element]
-inlineToOpenXML' _ (Str str) = formattedString str
-inlineToOpenXML' opts Space = inlineToOpenXML opts (Str " ")
-inlineToOpenXML' opts SoftBreak = inlineToOpenXML opts (Str " ")
-inlineToOpenXML' opts (Span (ident,classes,kvs) ils)
- | Just sty <- lookup dynamicStyleKey kvs = do
- let kvs' = filter ((dynamicStyleKey, sty)/=) kvs
- modify $ \s -> s{stDynamicTextProps = sty : (stDynamicTextProps s)}
- withTextProp (rCustomStyle sty) $
- inlineToOpenXML opts (Span (ident,classes,kvs') ils)
- | Just "rtl" <- lookup "dir" kvs = do
- let kvs' = filter (("dir", "rtl")/=) kvs
- local (\env -> env { envRTL = True }) $
- inlineToOpenXML opts (Span (ident,classes,kvs') ils)
- | Just "ltr" <- lookup "dir" kvs = do
- let kvs' = filter (("dir", "ltr")/=) kvs
- local (\env -> env { envRTL = False }) $
- inlineToOpenXML opts (Span (ident,classes,kvs') ils)
- | "insertion" `elem` classes = do
- defaultAuthor <- asks envChangesAuthor
- defaultDate <- asks envChangesDate
- let author = fromMaybe defaultAuthor (lookup "author" kvs)
- date = fromMaybe defaultDate (lookup "date" kvs)
- insId <- gets stInsId
- modify $ \s -> s{stInsId = (insId + 1)}
- x <- inlinesToOpenXML opts ils
- return [ mknode "w:ins" [("w:id", (show insId)),
- ("w:author", author),
- ("w:date", date)]
- x ]
- | "deletion" `elem` classes = do
- defaultAuthor <- asks envChangesAuthor
- defaultDate <- asks envChangesDate
- let author = fromMaybe defaultAuthor (lookup "author" kvs)
- date = fromMaybe defaultDate (lookup "date" kvs)
- delId <- gets stDelId
- modify $ \s -> s{stDelId = (delId + 1)}
- x <- local (\env -> env {envInDel = True}) (inlinesToOpenXML opts ils)
- return [ mknode "w:del" [("w:id", (show delId)),
- ("w:author", author),
- ("w:date", date)]
- x ]
- | otherwise = do
- let off x = withTextProp (mknode x [("w:val","0")] ())
- ((if "csl-no-emph" `elem` classes then off "w:i" else id) .
- (if "csl-no-strong" `elem` classes then off "w:b" else id) .
- (if "csl-no-smallcaps" `elem` classes then off "w:smallCaps" else id))
- $ inlinesToOpenXML opts ils
-inlineToOpenXML' opts (Strong lst) =
- withTextProp (mknode "w:b" [] ()) $ inlinesToOpenXML opts lst
-inlineToOpenXML' opts (Emph lst) =
- withTextProp (mknode "w:i" [] ()) $ inlinesToOpenXML opts lst
-inlineToOpenXML' opts (Subscript lst) =
- withTextProp (mknode "w:vertAlign" [("w:val","subscript")] ())
- $ inlinesToOpenXML opts lst
-inlineToOpenXML' opts (Superscript lst) =
- withTextProp (mknode "w:vertAlign" [("w:val","superscript")] ())
- $ inlinesToOpenXML opts lst
-inlineToOpenXML' opts (SmallCaps lst) =
- withTextProp (mknode "w:smallCaps" [] ())
- $ inlinesToOpenXML opts lst
-inlineToOpenXML' opts (Strikeout lst) =
- withTextProp (mknode "w:strike" [] ())
- $ inlinesToOpenXML opts lst
-inlineToOpenXML' _ LineBreak = return [br]
-inlineToOpenXML' _ il@(RawInline f str)
- | f == Format "openxml" = return [ x | Elem x <- parseXML str ]
- | otherwise = do
- report $ InlineNotRendered il
- return []
-inlineToOpenXML' opts (Quoted quoteType lst) =
- inlinesToOpenXML opts $ [Str open] ++ lst ++ [Str close]
- where (open, close) = case quoteType of
- SingleQuote -> ("\x2018", "\x2019")
- DoubleQuote -> ("\x201C", "\x201D")
-inlineToOpenXML' opts (Math mathType str) = do
- when (mathType == DisplayMath) setFirstPara
- res <- (lift . lift) (convertMath writeOMML mathType str)
- case res of
- Right r -> return [r]
- Left il -> inlineToOpenXML' opts il
-inlineToOpenXML' opts (Cite _ lst) = inlinesToOpenXML opts lst
-inlineToOpenXML' opts (Code attrs str) = do
- let unhighlighted = intercalate [br] `fmap`
- (mapM formattedString $ lines str)
- formatOpenXML _fmtOpts = intercalate [br] . map (map toHlTok)
- toHlTok (toktype,tok) = mknode "w:r" []
- [ mknode "w:rPr" []
- [ rCustomStyle (show toktype) ]
- , mknode "w:t" [("xml:space","preserve")] (T.unpack tok) ]
- withTextProp (rCustomStyle "VerbatimChar")
- $ case writerHighlightStyle opts >> highlight formatOpenXML attrs str of
- Just h -> return h
- Nothing -> unhighlighted
-inlineToOpenXML' opts (Note bs) = do
- notes <- gets stFootnotes
- notenum <- (lift . lift) getUniqueId
- footnoteStyle <- rStyleM "Footnote Reference"
- let notemarker = mknode "w:r" []
- [ mknode "w:rPr" [] footnoteStyle
- , mknode "w:footnoteRef" [] () ]
- let notemarkerXml = RawInline (Format "openxml") $ ppElement notemarker
- let insertNoteRef (Plain ils : xs) = Plain (notemarkerXml : Space : ils) : xs
- insertNoteRef (Para ils : xs) = Para (notemarkerXml : Space : ils) : xs
- insertNoteRef xs = Para [notemarkerXml] : xs
-
- contents <- local (\env -> env{ envListLevel = -1
- , envParaProperties = []
- , envTextProperties = [] })
- (withParaPropM (pStyleM "Footnote Text") $ blocksToOpenXML opts
- $ insertNoteRef bs)
- let newnote = mknode "w:footnote" [("w:id", notenum)] $ contents
- modify $ \s -> s{ stFootnotes = newnote : notes }
- return [ mknode "w:r" []
- [ mknode "w:rPr" [] footnoteStyle
- , mknode "w:footnoteReference" [("w:id", notenum)] () ] ]
--- internal link:
-inlineToOpenXML' opts (Link _ txt ('#':xs,_)) = do
- contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
- return [ mknode "w:hyperlink" [("w:anchor",xs)] contents ]
--- external link:
-inlineToOpenXML' opts (Link _ txt (src,_)) = do
- contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
- extlinks <- gets stExternalLinks
- id' <- case M.lookup src extlinks of
- Just i -> return i
- Nothing -> do
- i <- ("rId"++) `fmap` ((lift . lift) getUniqueId)
- modify $ \st -> st{ stExternalLinks =
- M.insert src i extlinks }
- return i
- return [ mknode "w:hyperlink" [("r:id",id')] contents ]
-inlineToOpenXML' opts (Image attr alt (src, title)) = do
- -- first, check to see if we've already done this image
- pageWidth <- asks envPrintWidth
- imgs <- gets stImages
- case M.lookup src imgs of
- Just (_,_,_,elt,_) -> return [elt]
- Nothing -> do
- res <- runExceptT $ lift (P.fetchItem (writerSourceURL opts) src)
- case res of
- Left (_ :: PandocError) -> do
- report $ CouldNotFetchResource src ""
- -- emit alt text
- inlinesToOpenXML opts alt
- Right (img, mt) -> do
- ident <- ("rId"++) `fmap` ((lift . lift) getUniqueId)
- let (xpt,ypt) = desiredSizeInPoints opts attr
- (either (const def) id (imageSize img))
- -- 12700 emu = 1 pt
- let (xemu,yemu) = fitToPage (xpt * 12700, ypt * 12700) (pageWidth * 12700)
- let cNvPicPr = mknode "pic:cNvPicPr" [] $
- mknode "a:picLocks" [("noChangeArrowheads","1"),("noChangeAspect","1")] ()
- let nvPicPr = mknode "pic:nvPicPr" []
- [ mknode "pic:cNvPr"
- [("descr",src),("id","0"),("name","Picture")] ()
- , cNvPicPr ]
- let blipFill = mknode "pic:blipFill" []
- [ mknode "a:blip" [("r:embed",ident)] ()
- , mknode "a:stretch" [] $ mknode "a:fillRect" [] () ]
- let xfrm = mknode "a:xfrm" []
- [ mknode "a:off" [("x","0"),("y","0")] ()
- , mknode "a:ext" [("cx",show xemu),("cy",show yemu)] () ]
- let prstGeom = mknode "a:prstGeom" [("prst","rect")] $
- mknode "a:avLst" [] ()
- let ln = mknode "a:ln" [("w","9525")]
- [ mknode "a:noFill" [] ()
- , mknode "a:headEnd" [] ()
- , mknode "a:tailEnd" [] () ]
- let spPr = mknode "pic:spPr" [("bwMode","auto")]
- [xfrm, prstGeom, mknode "a:noFill" [] (), ln]
- let graphic = mknode "a:graphic" [] $
- mknode "a:graphicData" [("uri","http://schemas.openxmlformats.org/drawingml/2006/picture")]
- [ mknode "pic:pic" []
- [ nvPicPr
- , blipFill
- , spPr ] ]
- let imgElt = mknode "w:r" [] $
- mknode "w:drawing" [] $
- mknode "wp:inline" []
- [ mknode "wp:extent" [("cx",show xemu),("cy",show yemu)] ()
- , mknode "wp:effectExtent" [("b","0"),("l","0"),("r","0"),("t","0")] ()
- , mknode "wp:docPr" [("descr",stringify alt), ("title", title), ("id","1"),("name","Picture")] ()
- , graphic ]
- let imgext = case mt >>= extensionFromMimeType of
- Just x -> '.':x
- Nothing -> case imageType img of
- Just Png -> ".png"
- Just Jpeg -> ".jpeg"
- Just Gif -> ".gif"
- Just Pdf -> ".pdf"
- Just Eps -> ".eps"
- Nothing -> ""
- if null imgext
- then -- without an extension there is no rule for content type
- inlinesToOpenXML opts alt -- return alt to avoid corrupted docx
- else do
- let imgpath = "media/" ++ ident ++ imgext
- let mbMimeType = mt <|> getMimeType imgpath
- -- insert mime type to use in constructing [Content_Types].xml
- modify $ \st -> st{ stImages =
- M.insert src (ident, imgpath, mbMimeType, imgElt, img)
- $ stImages st }
- return [imgElt]
-
-br :: Element
-br = breakElement "textWrapping"
-
-breakElement :: String -> Element
-breakElement kind = mknode "w:r" [] [mknode "w:br" [("w:type",kind)] () ]
-
--- Word will insert these footnotes into the settings.xml file
--- (whether or not they're visible in the document). If they're in the
--- file, but not in the footnotes.xml file, it will produce
--- problems. So we want to make sure we insert them into our document.
-defaultFootnotes :: [Element]
-defaultFootnotes = [ mknode "w:footnote"
- [("w:type", "separator"), ("w:id", "-1")] $
- [ mknode "w:p" [] $
- [mknode "w:r" [] $
- [ mknode "w:separator" [] ()]]]
- , mknode "w:footnote"
- [("w:type", "continuationSeparator"), ("w:id", "0")] $
- [ mknode "w:p" [] $
- [ mknode "w:r" [] $
- [ mknode "w:continuationSeparator" [] ()]]]]
-
-parseXml :: (PandocMonad m) => Archive -> Archive -> String -> m Element
-parseXml refArchive distArchive relpath =
- case findEntryByPath relpath refArchive `mplus`
- findEntryByPath relpath distArchive of
- Nothing -> fail $ relpath ++ " missing in reference docx"
- Just e -> case parseXMLDoc . UTF8.toStringLazy . fromEntry $ e of
- Nothing -> fail $ relpath ++ " corrupt in reference docx"
- Just d -> return d
-
--- | Scales the image to fit the page
--- sizes are passed in emu
-fitToPage :: (Double, Double) -> Integer -> (Integer, Integer)
-fitToPage (x, y) pageWidth
- -- Fixes width to the page width and scales the height
- | x > fromIntegral pageWidth =
- (pageWidth, floor $ ((fromIntegral pageWidth) / x) * y)
- | otherwise = (floor x, floor y)
-
-withDirection :: PandocMonad m => WS m a -> WS m a
-withDirection x = do
- isRTL <- asks envRTL
- paraProps <- asks envParaProperties
- textProps <- asks envTextProperties
- -- We want to clean all bidirection (bidi) and right-to-left (rtl)
- -- properties from the props first. This is because we don't want
- -- them to stack up.
- let paraProps' = filter (\e -> (qName . elName) e /= "bidi") paraProps
- textProps' = filter (\e -> (qName . elName) e /= "rtl") textProps
- if isRTL
- -- if we are going right-to-left, we (re?)add the properties.
- then flip local x $
- \env -> env { envParaProperties = (mknode "w:bidi" [] ()) : paraProps'
- , envTextProperties = (mknode "w:rtl" [] ()) : textProps'
- }
- else flip local x $ \env -> env { envParaProperties = paraProps'
- , envTextProperties = textProps'
- }