Support lang attribute in OpenDocument and ODT writers.

This adds the required attributes to the temporary styles,
and also replaces existing language attributes in styles.xml.

Support for lang attributes on Div and Span has also been
added.

Closes #1667.
This commit is contained in:
John MacFarlane 2017-06-25 10:38:11 +02:00
parent a02f08c9fc
commit 083a224d1e
3 changed files with 72 additions and 18 deletions

View file

@ -67,7 +67,7 @@ import Text.Pandoc.Shared hiding (Element)
import qualified Text.Pandoc.UTF8 as UTF8 import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Walk import Text.Pandoc.Walk
import Text.Pandoc.Writers.Math import Text.Pandoc.Writers.Math
import Text.Pandoc.Writers.Shared (fixDisplayMath) import Text.Pandoc.Writers.Shared (fixDisplayMath, getLang)
import Text.Printf (printf) import Text.Printf (printf)
import Text.TeXMath import Text.TeXMath
import Text.XML.Light as XML import Text.XML.Light as XML
@ -257,10 +257,7 @@ writeDocx opts doc@(Pandoc meta _) = do
) )
-- styles -- styles
let lang = case lookupMeta "lang" meta of let lang = getLang opts meta
Just (MetaInlines [Str s]) -> Just s
Just (MetaString s) -> Just s
_ -> Nothing
let addLang :: Element -> Element let addLang :: Element -> Element
addLang e = case lang >>= \l -> (return . XMLC.toTree . go l . XMLC.fromElement) e of addLang e = case lang >>= \l -> (return . XMLC.toTree . go l . XMLC.fromElement) e of
Just (Elem e') -> e' Just (Elem e') -> e'

View file

@ -33,6 +33,7 @@ import Codec.Archive.Zip
import Control.Monad.Except (catchError) import Control.Monad.Except (catchError)
import Control.Monad.State.Strict import Control.Monad.State.Strict
import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy as B
import Data.Generics (everywhere', mkT)
import Data.List (isPrefixOf) import Data.List (isPrefixOf)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
@ -46,13 +47,13 @@ import Text.Pandoc.MIME (extensionFromMimeType, getMimeType)
import Text.Pandoc.Options (WrapOption (..), WriterOptions (..)) import Text.Pandoc.Options (WrapOption (..), WriterOptions (..))
import Text.Pandoc.Pretty import Text.Pandoc.Pretty
import Text.Pandoc.Shared (stringify) import Text.Pandoc.Shared (stringify)
import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy) import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy, toStringLazy)
import Text.Pandoc.Walk import Text.Pandoc.Walk
import Text.Pandoc.Writers.OpenDocument (writeOpenDocument) import Text.Pandoc.Writers.OpenDocument (writeOpenDocument)
import Text.Pandoc.Writers.Shared (fixDisplayMath) import Text.Pandoc.Writers.Shared (fixDisplayMath, getLang, splitLang)
import Text.Pandoc.XML import Text.Pandoc.XML
import Text.TeXMath import Text.TeXMath
import Text.XML.Light.Output import Text.XML.Light
data ODTState = ODTState { stEntries :: [Entry] data ODTState = ODTState { stEntries :: [Entry]
} }
@ -78,6 +79,7 @@ pandocToODT :: PandocMonad m
pandocToODT opts doc@(Pandoc meta _) = do pandocToODT opts doc@(Pandoc meta _) = do
let datadir = writerUserDataDir opts let datadir = writerUserDataDir opts
let title = docTitle meta let title = docTitle meta
let lang = getLang opts meta
refArchive <- refArchive <-
case writerReferenceDoc opts of case writerReferenceDoc opts of
Just f -> liftM toArchive $ lift $ P.readFileLazy f Just f -> liftM toArchive $ lift $ P.readFileLazy f
@ -132,18 +134,50 @@ pandocToODT opts doc@(Pandoc meta _) = do
,("xmlns:ooo","http://openoffice.org/2004/office") ,("xmlns:ooo","http://openoffice.org/2004/office")
,("xmlns:grddl","http://www.w3.org/2003/g/data-view#") ,("xmlns:grddl","http://www.w3.org/2003/g/data-view#")
,("office:version","1.2")] ,("office:version","1.2")]
$ ( inTagsSimple "office:meta" $ ( inTagsSimple "office:meta" $
$ ( inTagsSimple "dc:title" (text $ escapeStringForXML (stringify title)) ( inTagsSimple "dc:title"
) (text $ escapeStringForXML (stringify title))
$$
case lang of
Just l -> inTagsSimple "dc:language"
(text (escapeStringForXML l))
Nothing -> empty
)
) )
) )
-- make sure mimetype is first -- make sure mimetype is first
let mimetypeEntry = toEntry "mimetype" epochtime let mimetypeEntry = toEntry "mimetype" epochtime
$ fromStringLazy "application/vnd.oasis.opendocument.text" $ fromStringLazy "application/vnd.oasis.opendocument.text"
let archive'' = addEntryToArchive mimetypeEntry archive'' <- updateStyleWithLang lang
$ addEntryToArchive mimetypeEntry
$ addEntryToArchive metaEntry archive' $ addEntryToArchive metaEntry archive'
return $ fromArchive archive'' return $ fromArchive archive''
updateStyleWithLang :: PandocMonad m => Maybe String -> Archive -> O m Archive
updateStyleWithLang Nothing arch = return arch
updateStyleWithLang (Just l) arch = do
(mblang, mbcountry) <- splitLang l
epochtime <- floor `fmap` (lift P.getPOSIXTime)
return arch{ zEntries = [if eRelativePath e == "styles.xml"
then case parseXMLDoc
(toStringLazy (fromEntry e)) of
Nothing -> e
Just d ->
toEntry "styles.xml" epochtime
( fromStringLazy
. ppTopElement
. addLang mblang mbcountry $ d )
else e
| e <- zEntries arch] }
addLang :: Maybe String -> Maybe String -> Element -> Element
addLang mblang mbcountry = everywhere' (mkT updateLangAttr)
where updateLangAttr (Attr n@(QName "language" _ (Just "fo")) l)
= Attr n (maybe l id mblang)
updateLangAttr (Attr n@(QName "country" _ (Just "fo")) c)
= Attr n (maybe c id mbcountry)
updateLangAttr x = x
-- | transform both Image and Math elements -- | transform both Image and Math elements
transformPicMath :: PandocMonad m => WriterOptions ->Inline -> O m Inline transformPicMath :: PandocMonad m => WriterOptions ->Inline -> O m Inline
transformPicMath opts (Image attr@(id', cls, _) lab (src,t)) = catchError transformPicMath opts (Image attr@(id', cls, _) lab (src,t)) = catchError

View file

@ -45,7 +45,7 @@ import Text.Pandoc.Definition
import Text.Pandoc.Logging import Text.Pandoc.Logging
import Text.Pandoc.Options import Text.Pandoc.Options
import Text.Pandoc.Pretty import Text.Pandoc.Pretty
import Text.Pandoc.Shared (linesToPara) import Text.Pandoc.Shared (linesToPara, splitBy)
import Text.Pandoc.Templates (renderTemplate') import Text.Pandoc.Templates (renderTemplate')
import Text.Pandoc.Writers.Math import Text.Pandoc.Writers.Math
import Text.Pandoc.Writers.Shared import Text.Pandoc.Writers.Shared
@ -75,6 +75,8 @@ data WriterState =
, stTight :: Bool , stTight :: Bool
, stFirstPara :: Bool , stFirstPara :: Bool
, stImageId :: Int , stImageId :: Int
, stLang :: Maybe String
, stCountry :: Maybe String
} }
defaultWriterState :: WriterState defaultWriterState :: WriterState
@ -90,6 +92,8 @@ defaultWriterState =
, stTight = False , stTight = False
, stFirstPara = False , stFirstPara = False
, stImageId = 1 , stImageId = 1
, stLang = Nothing
, stCountry = Nothing
} }
when :: Bool -> Doc -> Doc when :: Bool -> Doc -> Doc
@ -155,6 +159,10 @@ withTextStyle s f = do
inTextStyle :: PandocMonad m => Doc -> OD m Doc inTextStyle :: PandocMonad m => Doc -> OD m Doc
inTextStyle d = do inTextStyle d = do
at <- gets stTextStyleAttr at <- gets stTextStyleAttr
mblang <- gets stLang
mbcountry <- gets stCountry
let langat = maybe [] (\la -> [("fo:language", la)]) mblang
let countryat = maybe [] (\co -> [("fo:country", co)]) mbcountry
if Set.null at if Set.null at
then return d then return d
else do else do
@ -168,8 +176,9 @@ inTextStyle d = do
inTags False "style:style" inTags False "style:style"
[("style:name", styleName) [("style:name", styleName)
,("style:family", "text")] ,("style:family", "text")]
$ selfClosingTag "style:text-properties" $ selfClosingTag "style:text-properties"
(concatMap textStyleAttr (Set.toList at))) (langat ++ countryat ++
concatMap textStyleAttr (Set.toList at)))
return $ inTags False return $ inTags False
"text:span" [("text:style-name",styleName)] d "text:span" [("text:style-name",styleName)] d
@ -203,8 +212,10 @@ writeOpenDocument opts (Pandoc meta blocks) = do
else Nothing else Nothing
let render' :: Doc -> Text let render' :: Doc -> Text
render' = render colwidth render' = render colwidth
let lang = getLang opts meta
(mblang, mbcountry) <- maybe (return (Nothing, Nothing)) splitLang lang
((body, metadata),s) <- flip runStateT ((body, metadata),s) <- flip runStateT
defaultWriterState $ do defaultWriterState{ stLang = mblang, stCountry = mbcountry } $ do
m <- metaToJSON opts m <- metaToJSON opts
(fmap render' . blocksToOpenDocument opts) (fmap render' . blocksToOpenDocument opts)
(fmap render' . inlinesToOpenDocument opts) (fmap render' . inlinesToOpenDocument opts)
@ -326,7 +337,8 @@ blockToOpenDocument o bs
then return empty then return empty
else inParagraphTags =<< inlinesToOpenDocument o b else inParagraphTags =<< inlinesToOpenDocument o b
| LineBlock b <- bs = blockToOpenDocument o $ linesToPara b | LineBlock b <- bs = blockToOpenDocument o $ linesToPara b
| Div _ xs <- bs = blocksToOpenDocument o xs | Div attr xs <- bs = withLangFromAttr attr
(blocksToOpenDocument o xs)
| Header i _ b <- bs = setFirstPara >> | Header i _ b <- bs = setFirstPara >>
(inHeaderTags i =<< inlinesToOpenDocument o b) (inHeaderTags i =<< inlinesToOpenDocument o b)
| BlockQuote b <- bs = setFirstPara >> mkBlockQuote b | BlockQuote b <- bs = setFirstPara >> mkBlockQuote b
@ -444,7 +456,7 @@ inlineToOpenDocument o ils
| writerWrapText o == WrapPreserve | writerWrapText o == WrapPreserve
-> return $ preformatted "\n" -> return $ preformatted "\n"
| otherwise -> return $ space | otherwise -> return $ space
Span _ xs -> inlinesToOpenDocument o xs Span attr xs -> withLangFromAttr attr (inlinesToOpenDocument o xs)
LineBreak -> return $ selfClosingTag "text:line-break" [] LineBreak -> return $ selfClosingTag "text:line-break" []
Str s -> return $ handleSpaces $ escapeStringForXML s Str s -> return $ handleSpaces $ escapeStringForXML s
Emph l -> withTextStyle Italic $ inlinesToOpenDocument o l Emph l -> withTextStyle Italic $ inlinesToOpenDocument o l
@ -625,3 +637,14 @@ textStyleAttr s
,("style:font-name-asian" ,"Courier New") ,("style:font-name-asian" ,"Courier New")
,("style:font-name-complex" ,"Courier New")] ,("style:font-name-complex" ,"Courier New")]
| otherwise = [] | otherwise = []
withLangFromAttr :: PandocMonad m => Attr -> OD m a -> OD m a
withLangFromAttr (_,_,kvs) action = do
oldlang <- gets stLang
case lookup "lang" kvs of
Nothing -> action
Just l -> do
modify (\st -> st{ stLang = Just l})
result <- action
modify (\st -> st{ stLang = oldlang})
return result