Blame src/Text/Pandoc/Writers/ODT.hs

Packit Service d2f85f
{-# LANGUAGE ScopedTypeVariables #-}
Packit Service d2f85f
{-
Packit Service d2f85f
Copyright (C) 2008-2017 John MacFarlane <jgm@berkeley.edu>
Packit Service d2f85f
Packit Service d2f85f
This program is free software; you can redistribute it and/or modify
Packit Service d2f85f
it under the terms of the GNU General Public License as published by
Packit Service d2f85f
the Free Software Foundation; either version 2 of the License, or
Packit Service d2f85f
(at your option) any later version.
Packit Service d2f85f
Packit Service d2f85f
This program is distributed in the hope that it will be useful,
Packit Service d2f85f
but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service d2f85f
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service d2f85f
GNU General Public License for more details.
Packit Service d2f85f
Packit Service d2f85f
You should have received a copy of the GNU General Public License
Packit Service d2f85f
along with this program; if not, write to the Free Software
Packit Service d2f85f
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit Service d2f85f
-}
Packit Service d2f85f
Packit Service d2f85f
{- |
Packit Service d2f85f
   Module      : Text.Pandoc.Writers.ODT
Packit Service d2f85f
   Copyright   : Copyright (C) 2008-2017 John MacFarlane
Packit Service d2f85f
   License     : GNU GPL, version 2 or above
Packit Service d2f85f
Packit Service d2f85f
   Maintainer  : John MacFarlane <jgm@berkeley.edu>
Packit Service d2f85f
   Stability   : alpha
Packit Service d2f85f
   Portability : portable
Packit Service d2f85f
Packit Service d2f85f
Conversion of 'Pandoc' documents to ODT.
Packit Service d2f85f
-}
Packit Service d2f85f
module Text.Pandoc.Writers.ODT ( writeODT ) where
Packit Service d2f85f
import Codec.Archive.Zip
Packit Service d2f85f
import Control.Monad.Except (catchError)
Packit Service d2f85f
import Control.Monad.State.Strict
Packit Service d2f85f
import qualified Data.ByteString.Lazy as B
Packit Service d2f85f
import Data.Generics (everywhere', mkT)
Packit Service d2f85f
import Data.List (isPrefixOf)
Packit Service d2f85f
import Data.Maybe (fromMaybe)
Packit Service d2f85f
import qualified Data.Text.Lazy as TL
Packit Service d2f85f
import System.FilePath (takeDirectory, takeExtension, (<.>))
Packit Service d2f85f
import Text.Pandoc.BCP47 (Lang (..), getLang, renderLang)
Packit Service d2f85f
import Text.Pandoc.Class (PandocMonad, report, toLang)
Packit Service d2f85f
import qualified Text.Pandoc.Class as P
Packit Service d2f85f
import Text.Pandoc.Definition
Packit Service d2f85f
import Text.Pandoc.ImageSize
Packit Service d2f85f
import Text.Pandoc.Logging
Packit Service d2f85f
import Text.Pandoc.MIME (extensionFromMimeType, getMimeType)
Packit Service d2f85f
import Text.Pandoc.Options (WrapOption (..), WriterOptions (..))
Packit Service d2f85f
import Text.Pandoc.Pretty
Packit Service d2f85f
import Text.Pandoc.Shared (stringify)
Packit Service d2f85f
import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy, toStringLazy)
Packit Service d2f85f
import Text.Pandoc.Walk
Packit Service d2f85f
import Text.Pandoc.Writers.OpenDocument (writeOpenDocument)
Packit Service d2f85f
import Text.Pandoc.Writers.Shared (fixDisplayMath)
Packit Service d2f85f
import Text.Pandoc.XML
Packit Service d2f85f
import Text.TeXMath
Packit Service d2f85f
import Text.XML.Light
Packit Service d2f85f
Packit Service d2f85f
data ODTState = ODTState { stEntries :: [Entry]
Packit Service d2f85f
                         }
Packit Service d2f85f
Packit Service d2f85f
type O m = StateT ODTState m
Packit Service d2f85f
Packit Service d2f85f
-- | Produce an ODT file from a Pandoc document.
Packit Service d2f85f
writeODT :: PandocMonad m
Packit Service d2f85f
         => WriterOptions  -- ^ Writer options
Packit Service d2f85f
         -> Pandoc         -- ^ Document to convert
Packit Service d2f85f
         -> m B.ByteString
Packit Service d2f85f
writeODT  opts doc =
Packit Service d2f85f
  let initState = ODTState{ stEntries = []
Packit Service d2f85f
                          }
Packit Service d2f85f
  in
Packit Service d2f85f
    evalStateT (pandocToODT opts doc) initState
Packit Service d2f85f
Packit Service d2f85f
-- | Produce an ODT file from a Pandoc document.
Packit Service d2f85f
pandocToODT :: PandocMonad m
Packit Service d2f85f
            => WriterOptions  -- ^ Writer options
Packit Service d2f85f
            -> Pandoc         -- ^ Document to convert
Packit Service d2f85f
            -> O m B.ByteString
Packit Service d2f85f
pandocToODT opts doc@(Pandoc meta _) = do
Packit Service d2f85f
  let title = docTitle meta
Packit Service d2f85f
  lang <- toLang (getLang opts meta)
Packit Service d2f85f
  refArchive <-
Packit Service d2f85f
       case writerReferenceDoc opts of
Packit Service d2f85f
             Just f -> liftM toArchive $ lift $ P.readFileLazy f
Packit Service d2f85f
             Nothing -> lift $ (toArchive . B.fromStrict) <$>
Packit Service d2f85f
                                P.readDataFile "reference.odt"
Packit Service d2f85f
  -- handle formulas and pictures
Packit Service d2f85f
  -- picEntriesRef <- P.newIORef ([] :: [Entry])
Packit Service d2f85f
  doc' <- walkM (transformPicMath opts) $ walk fixDisplayMath doc
Packit Service d2f85f
  newContents <- lift $ writeOpenDocument opts{writerWrapText = WrapNone} doc'
Packit Service d2f85f
  epochtime <- floor `fmap` lift P.getPOSIXTime
Packit Service d2f85f
  let contentEntry = toEntry "content.xml" epochtime
Packit Service d2f85f
                     $ fromTextLazy $ TL.fromStrict newContents
Packit Service d2f85f
  picEntries <- gets stEntries
Packit Service d2f85f
  let archive = foldr addEntryToArchive refArchive
Packit Service d2f85f
                $ contentEntry : picEntries
Packit Service d2f85f
  -- construct META-INF/manifest.xml based on archive
Packit Service d2f85f
  let toFileEntry fp = case getMimeType fp of
Packit Service d2f85f
                        Nothing  -> empty
Packit Service d2f85f
                        Just m   -> selfClosingTag "manifest:file-entry"
Packit Service d2f85f
                                     [("manifest:media-type", m)
Packit Service d2f85f
                                     ,("manifest:full-path", fp)
Packit Service d2f85f
                                     ,("manifest:version", "1.2")
Packit Service d2f85f
                                     ]
Packit Service d2f85f
  let files = [ ent | ent <- filesInArchive archive,
Packit Service d2f85f
                             not ("META-INF" `isPrefixOf` ent) ]
Packit Service d2f85f
  let formulas = [ takeDirectory ent ++ "/" | ent <- filesInArchive archive,
Packit Service d2f85f
                      "Formula-" `isPrefixOf` ent, takeExtension ent == ".xml" ]
Packit Service d2f85f
  let manifestEntry = toEntry "META-INF/manifest.xml" epochtime
Packit Service d2f85f
        $ fromStringLazy $ render Nothing
Packit Service d2f85f
        $ text ""
Packit Service d2f85f
        $$
Packit Service d2f85f
         (inTags True "manifest:manifest"
Packit Service d2f85f
            [("xmlns:manifest","urn:oasis:names:tc:opendocument:xmlns:manifest:1.0")
Packit Service d2f85f
            ,("manifest:version","1.2")] ( selfClosingTag "manifest:file-entry"
Packit Service d2f85f
                 [("manifest:media-type","application/vnd.oasis.opendocument.text")
Packit Service d2f85f
                 ,("manifest:full-path","/")]
Packit Service d2f85f
                $$ vcat ( map toFileEntry files )
Packit Service d2f85f
                $$ vcat ( map toFileEntry formulas )
Packit Service d2f85f
              )
Packit Service d2f85f
         )
Packit Service d2f85f
  let archive' = addEntryToArchive manifestEntry archive
Packit Service d2f85f
  let metaEntry = toEntry "meta.xml" epochtime
Packit Service d2f85f
       $ fromStringLazy $ render Nothing
Packit Service d2f85f
       $ text ""
Packit Service d2f85f
       $$
Packit Service d2f85f
        (inTags True "office:document-meta"
Packit Service d2f85f
           [("xmlns:office","urn:oasis:names:tc:opendocument:xmlns:office:1.0")
Packit Service d2f85f
           ,("xmlns:xlink","http://www.w3.org/1999/xlink")
Packit Service d2f85f
           ,("xmlns:dc","http://purl.org/dc/elements/1.1/")
Packit Service d2f85f
           ,("xmlns:meta","urn:oasis:names:tc:opendocument:xmlns:meta:1.0")
Packit Service d2f85f
           ,("xmlns:ooo","http://openoffice.org/2004/office")
Packit Service d2f85f
           ,("xmlns:grddl","http://www.w3.org/2003/g/data-view#")
Packit Service d2f85f
           ,("office:version","1.2")] ( inTagsSimple "office:meta" $
Packit Service d2f85f
                 ( inTagsSimple "dc:title"
Packit Service d2f85f
                      (text $ escapeStringForXML (stringify title))
Packit Service d2f85f
                   $$
Packit Service d2f85f
                   case lang of
Packit Service d2f85f
                        Just l -> inTagsSimple "dc:language"
Packit Service d2f85f
                                    (text (escapeStringForXML (renderLang l)))
Packit Service d2f85f
                        Nothing -> empty
Packit Service d2f85f
                 )
Packit Service d2f85f
             )
Packit Service d2f85f
        )
Packit Service d2f85f
  -- make sure mimetype is first
Packit Service d2f85f
  let mimetypeEntry = toEntry "mimetype" epochtime
Packit Service d2f85f
                      $ fromStringLazy "application/vnd.oasis.opendocument.text"
Packit Service d2f85f
  archive'' <- updateStyleWithLang lang
Packit Service d2f85f
                  $ addEntryToArchive mimetypeEntry
Packit Service d2f85f
                  $ addEntryToArchive metaEntry archive'
Packit Service d2f85f
  return $ fromArchive archive''
Packit Service d2f85f
Packit Service d2f85f
updateStyleWithLang :: PandocMonad m => Maybe Lang -> Archive -> O m Archive
Packit Service d2f85f
updateStyleWithLang Nothing arch = return arch
Packit Service d2f85f
updateStyleWithLang (Just lang) arch = do
Packit Service d2f85f
  epochtime <- floor `fmap` lift P.getPOSIXTime
Packit Service d2f85f
  return arch{ zEntries = [if eRelativePath e == "styles.xml"
Packit Service d2f85f
                              then case parseXMLDoc
Packit Service d2f85f
                                      (toStringLazy (fromEntry e)) of
Packit Service d2f85f
                                      Nothing -> e
Packit Service d2f85f
                                      Just d ->
Packit Service d2f85f
                                        toEntry "styles.xml" epochtime
Packit Service d2f85f
                                        ( fromStringLazy
Packit Service d2f85f
                                        . ppTopElement
Packit Service d2f85f
                                        . addLang lang $ d )
Packit Service d2f85f
                              else e
Packit Service d2f85f
                            | e <- zEntries arch] }
Packit Service d2f85f
Packit Service d2f85f
addLang :: Lang -> Element -> Element
Packit Service d2f85f
addLang lang = everywhere' (mkT updateLangAttr)
Packit Service d2f85f
    where updateLangAttr (Attr n@(QName "language" _ (Just "fo")) _)
Packit Service d2f85f
                           = Attr n (langLanguage lang)
Packit Service d2f85f
          updateLangAttr (Attr n@(QName "country" _ (Just "fo")) _)
Packit Service d2f85f
                           = Attr n (langRegion lang)
Packit Service d2f85f
          updateLangAttr x = x
Packit Service d2f85f
Packit Service d2f85f
-- | transform both Image and Math elements
Packit Service d2f85f
transformPicMath :: PandocMonad m => WriterOptions ->Inline -> O m Inline
Packit Service d2f85f
transformPicMath opts (Image attr@(id', cls, _) lab (src,t)) = catchError
Packit Service d2f85f
   (do (img, mbMimeType) <- P.fetchItem src
Packit Service d2f85f
       (ptX, ptY) <- case imageSize opts img of
Packit Service d2f85f
                       Right s  -> return $ sizeInPoints s
Packit Service d2f85f
                       Left msg -> do
Packit Service d2f85f
                         report $ CouldNotDetermineImageSize src msg
Packit Service d2f85f
                         return (100, 100)
Packit Service d2f85f
       let dims =
Packit Service d2f85f
             case (getDim Width, getDim Height) of
Packit Service d2f85f
               (Just w, Just h)              -> [("width", show w), ("height", show h)]
Packit Service d2f85f
               (Just w@(Percent p), Nothing) -> [("width", show w), ("height", show (p / ratio) ++ "%")]
Packit Service d2f85f
               (Nothing, Just h@(Percent p)) -> [("width", show (p * ratio) ++ "%"), ("height", show h)]
Packit Service d2f85f
               (Just w@(Inch i), Nothing)    -> [("width", show w), ("height", show (i / ratio) ++ "in")]
Packit Service d2f85f
               (Nothing, Just h@(Inch i))    -> [("width", show (i * ratio) ++ "in"), ("height", show h)]
Packit Service d2f85f
               _                             -> [("width", show ptX ++ "pt"), ("height", show ptY ++ "pt")]
Packit Service d2f85f
             where
Packit Service d2f85f
               ratio = ptX / ptY
Packit Service d2f85f
               getDim dir = case dimension dir attr of
Packit Service d2f85f
                              Just (Percent i) -> Just $ Percent i
Packit Service d2f85f
                              Just dim         -> Just $ Inch $ inInch opts dim
Packit Service d2f85f
                              Nothing          -> Nothing
Packit Service d2f85f
       let  newattr = (id', cls, dims)
Packit Service d2f85f
       entries <- gets stEntries
Packit Service d2f85f
       let extension = fromMaybe (takeExtension $ takeWhile (/='?') src)
Packit Service d2f85f
                           (mbMimeType >>= extensionFromMimeType)
Packit Service d2f85f
       let newsrc = "Pictures/" ++ show (length entries) <.> extension
Packit Service d2f85f
       let toLazy = B.fromChunks . (:[])
Packit Service d2f85f
       epochtime <- floor `fmap` lift P.getPOSIXTime
Packit Service d2f85f
       let entry = toEntry newsrc epochtime $ toLazy img
Packit Service d2f85f
       modify $ \st -> st{ stEntries = entry : entries }
Packit Service d2f85f
       return $ Image newattr lab (newsrc, t))
Packit Service d2f85f
   (\e -> do
Packit Service d2f85f
       report $ CouldNotFetchResource src (show e)
Packit Service d2f85f
       return $ Emph lab)
Packit Service d2f85f
Packit Service d2f85f
transformPicMath _ (Math t math) = do
Packit Service d2f85f
  entries <- gets stEntries
Packit Service d2f85f
  let dt = if t == InlineMath then DisplayInline else DisplayBlock
Packit Service d2f85f
  case writeMathML dt <$> readTeX math of
Packit Service d2f85f
       Left  _ -> return $ Math t math
Packit Service d2f85f
       Right r -> do
Packit Service d2f85f
         let conf = useShortEmptyTags (const False) defaultConfigPP
Packit Service d2f85f
         let mathml = ppcTopElement conf r
Packit Service d2f85f
         epochtime <- floor `fmap` (lift P.getPOSIXTime)
Packit Service d2f85f
         let dirname = "Formula-" ++ show (length entries) ++ "/"
Packit Service d2f85f
         let fname = dirname ++ "content.xml"
Packit Service d2f85f
         let entry = toEntry fname epochtime (fromStringLazy mathml)
Packit Service d2f85f
         modify $ \st -> st{ stEntries = entry : entries }
Packit Service d2f85f
         return $ RawInline (Format "opendocument") $ render Nothing $
Packit Service d2f85f
           inTags False "draw:frame" [("text:anchor-type",
Packit Service d2f85f
                                       if t == DisplayMath
Packit Service d2f85f
                                          then "paragraph"
Packit Service d2f85f
                                          else "as-char")
Packit Service d2f85f
                                     ,("style:vertical-pos", "middle")
Packit Service d2f85f
                                     ,("style:vertical-rel", "text")] $
Packit Service d2f85f
             selfClosingTag "draw:object" [("xlink:href", dirname)
Packit Service d2f85f
                                        , ("xlink:type", "simple")
Packit Service d2f85f
                                        , ("xlink:show", "embed")
Packit Service d2f85f
                                        , ("xlink:actuate", "onLoad")]
Packit Service d2f85f
Packit Service d2f85f
transformPicMath _ x = return x