git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #5606 from blmage/odt-frames

Improve the parsing of frames in ODT documents

commita16311c225e9b9788ee6499e474f9a9170510b11 authorJohn MacFarlane <jgm@berkeley.edu> dateSat 13 Jul 2019, 15:53 (7 years ago) parent1784161449c133
6 files changed+129−75
src/Text/Pandoc/Readers/Odt.hs+2−3 src/Text/Pandoc/Readers/Odt/ContentReader.hs+74−48 src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs+46−19 src/Text/Pandoc/Readers/Odt/StyleReader.hs+5−5 test/Tests/Readers/Odt.hs+1 test/odt/markdown/formula.md+1
src/Text/Pandoc/Readers/Odt.hs+2−3
@@ -86,9 +86,8 @@ archiveToOdt archive
8686 where
8787 filePathIsOdtMedia :: FilePath -> Bool
8888 filePathIsOdtMedia fp =
89− let (dir, _) = splitFileName fp
90− in
91− (dir == "Pictures/")
89+ let (dir, name) = splitFileName fp
90+ in (dir == "Pictures/") || (dir /= "./" && name == "content.xml")
9291
9392
9493--
src/Text/Pandoc/Readers/Odt/ContentReader.hs+74−48
@@ -1,5 +1,7 @@
11{-# LANGUAGE NoImplicitPrelude #-}
22{-# LANGUAGE Arrows #-}
3+{-# LANGUAGE DeriveFoldable #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
35{-# LANGUAGE PatternGuards #-}
46{-# LANGUAGE RecordWildCards #-}
57{-# LANGUAGE TupleSections #-}
@@ -26,21 +28,26 @@ import Control.Applicative hiding (liftA, liftA2, liftA3)
2628import Control.Arrow
2729
2830import qualified Data.ByteString.Lazy as B
29−import Data.List (find, intercalate)
31+import Data.Foldable (fold)
32+import Data.List (find, intercalate, stripPrefix)
3033import qualified Data.Map as M
3134import Data.Maybe
35+import Data.Semigroup (First(..), Option(..))
3236
37+import Text.TeXMath (readMathML, writeTeX)
3338import qualified Text.XML.Light as XML
3439
3540import Text.Pandoc.Builder
3641import Text.Pandoc.MediaBag (MediaBag, insertMedia)
3742import Text.Pandoc.Shared
3843import Text.Pandoc.Extensions (extensionsFromList, Extension(..))
44+import qualified Text.Pandoc.UTF8 as UTF8
3945
4046import Text.Pandoc.Readers.Odt.Base
4147import Text.Pandoc.Readers.Odt.Namespaces
4248import Text.Pandoc.Readers.Odt.StyleReader
4349
50+import Text.Pandoc.Readers.Odt.Arrows.State (foldS)
4451import Text.Pandoc.Readers.Odt.Arrows.Utils
4552import Text.Pandoc.Readers.Odt.Generic.Fallible
4653import Text.Pandoc.Readers.Odt.Generic.Utils
@@ -498,6 +505,13 @@ type InlineMatcher = ElementMatcher Inlines
498505type BlockMatcher = ElementMatcher Blocks
499506
500507
508+newtype FirstMatch a = FirstMatch (Option (First a))
509+ deriving (Foldable, Monoid, Semigroup)
510+
511+firstMatch :: a -> FirstMatch a
512+firstMatch = FirstMatch . Option . Just . First
513+
514+
501515--
502516matchingElement :: (Monoid e)
503517 => Namespace -> ElementName
@@ -598,7 +612,7 @@ read_paragraph = matchingElement NsText "p"
598612 , read_reference_start
599613 , read_bookmark_ref
600614 , read_reference_ref
601− , read_maybe_nested_img_frame
615+ , read_frame
602616 , read_text_seq
603617 ] read_plain_text
604618
@@ -624,7 +638,7 @@ read_header = matchingElement NsText "h"
624638 , read_reference_start
625639 , read_bookmark_ref
626640 , read_reference_ref
627− , read_maybe_nested_img_frame
641+ , read_frame
628642 ] read_plain_text
629643 ) -< blocks
630644 anchor <- getHeaderAnchor -< children
@@ -737,32 +751,43 @@ read_table_cell = matchingElement NsTable "table-cell"
737751 ]
738752
739753----------------------
740−-- Images
754+-- Frames
741755----------------------
742756
743757--
744−read_maybe_nested_img_frame :: InlineMatcher
745−read_maybe_nested_img_frame = matchingElement NsDraw "frame"
746− $ proc blocks -> do
747− img <- (findChild' NsDraw "image") -< ()
748− case img of
749− Just _ -> read_frame -< blocks
750− Nothing -> matchChildContent' [ read_frame_text_box ] -< blocks
751−
752−read_frame :: OdtReaderSafe Inlines Inlines
753−read_frame =
754− proc blocks -> do
755− let exts = extensionsFromList [Ext_auto_identifiers]
756− w <- ( findAttr' NsSVG "width" ) -< ()
757− h <- ( findAttr' NsSVG "height" ) -< ()
758− titleNodes <- ( matchChildContent' [ read_frame_title ] ) -< blocks
759− src <- matchChildContent' [ read_image_src ] -< blocks
760− resource <- lookupResource -< src
761− _ <- updateMediaWithResource -< resource
762− alt <- (matchChildContent [] read_plain_text) -< blocks
763− arr (uncurry4 imageWith ) -<
764− (image_attributes w h, src,
765− inlineListToIdentifier exts (toList titleNodes), alt)
758+read_frame :: InlineMatcher
759+read_frame = matchingElement NsDraw "frame"
760+ $ filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])
761+ >>> foldS read_frame_child
762+ >>> arr fold
763+
764+read_frame_child :: OdtReaderSafe XML.Element (FirstMatch Inlines)
765+read_frame_child =
766+ proc child -> case elName child of
767+ "image" -> read_frame_img -< child
768+ "object" -> read_frame_mathml -< child
769+ "text-box" -> read_frame_text_box -< child
770+ _ -> returnV mempty -< ()
771+
772+read_frame_img :: OdtReaderSafe XML.Element (FirstMatch Inlines)
773+read_frame_img =
774+ proc img -> do
775+ src <- executeIn (findAttr' NsXLink "href") -< img
776+ case fold src of
777+ "" -> returnV mempty -< ()
778+ src' -> do
779+ let exts = extensionsFromList [Ext_auto_identifiers]
780+ resource <- lookupResource -< src'
781+ _ <- updateMediaWithResource -< resource
782+ w <- findAttr' NsSVG "width" -< ()
783+ h <- findAttr' NsSVG "height" -< ()
784+ titleNodes <- matchChildContent' [ read_frame_title ] -< ()
785+ alt <- matchChildContent [] read_plain_text -< ()
786+ arr (firstMatch . uncurry4 imageWith) -<
787+ (image_attributes w h, src', inlineListToIdentifier exts (toList titleNodes), alt)
788+
789+read_frame_title :: InlineMatcher
790+read_frame_title = matchingElement NsSVG "title" (matchChildContent [] read_plain_text)
766791
767792image_attributes :: Maybe String -> Maybe String -> Attr
768793image_attributes x y =
@@ -772,28 +797,29 @@ image_attributes x y =
772797 dim name (Just v) = [(name, v)]
773798 dim _ Nothing = []
774799
775−read_image_src :: (Namespace, ElementName, OdtReader Anchor Anchor)
776−read_image_src = matchingElement NsDraw "image"
777− $ proc _ -> do
778− imgSrc <- findAttr NsXLink "href" -< ()
779− case imgSrc of
780− Right src -> returnV src -<< ()
781− Left _ -> returnV "" -< ()
782−
783−read_frame_title :: InlineMatcher
784−read_frame_title = matchingElement NsSVG "title" (matchChildContent [] read_plain_text)
785−
786−read_frame_text_box :: InlineMatcher
787−read_frame_text_box = matchingElement NsDraw "text-box"
788− $ proc blocks -> do
789− paragraphs <- (matchChildContent' [ read_paragraph ]) -< blocks
790− arr read_img_with_caption -< toList paragraphs
791−
792−read_img_with_caption :: [Block] -> Inlines
800+read_frame_mathml :: OdtReaderSafe XML.Element (FirstMatch Inlines)
801+read_frame_mathml =
802+ proc obj -> do
803+ src <- executeIn (findAttr' NsXLink "href") -< obj
804+ case fold src of
805+ "" -> returnV mempty -< ()
806+ src' -> do
807+ let path = fromMaybe src' (stripPrefix "./" src') ++ "/content.xml"
808+ (_, mathml) <- lookupResource -< path
809+ case readMathML (UTF8.toString $ B.toStrict mathml) of
810+ Left _ -> returnV mempty -< ()
811+ Right exps -> arr (firstMatch . displayMath . writeTeX) -< exps
812+
813+read_frame_text_box :: OdtReaderSafe XML.Element (FirstMatch Inlines)
814+read_frame_text_box = proc box -> do
815+ paragraphs <- executeIn (matchChildContent' [ read_paragraph ]) -< box
816+ arr read_img_with_caption -< toList paragraphs
817+
818+read_img_with_caption :: [Block] -> FirstMatch Inlines
793819read_img_with_caption (Para [Image attr alt (src,title)] : _) =
794− singleton (Image attr alt (src, 'f':'i':'g':':':title)) -- no text, default caption
820+ firstMatch $ singleton (Image attr alt (src, 'f':'i':'g':':':title)) -- no text, default caption
795821read_img_with_caption (Para (Image attr _ (src,title) : txt) : _) =
796− singleton (Image attr txt (src, 'f':'i':'g':':':title) ) -- override caption with the text that follows
822+ firstMatch $ singleton (Image attr txt (src, 'f':'i':'g':':':title) ) -- override caption with the text that follows
797823read_img_with_caption ( Para (_ : xs) : ys) =
798824 read_img_with_caption (Para xs : ys)
799825read_img_with_caption _ =
@@ -901,8 +927,8 @@ post_process' (Table _ a w h r : Div ("", ["caption"], _) [Para inlines] : xs) =
901927post_process' bs = bs
902928
903929read_body :: OdtReader _x (Pandoc, MediaBag)
904−read_body = executeIn NsOffice "body"
905− $ executeIn NsOffice "text"
930+read_body = executeInSub NsOffice "body"
931+ $ executeInSub NsOffice "text"
906932 $ liftAsSuccess
907933 $ proc inlines -> do
908934 txt <- read_text -< inlines
src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs+46−19
@@ -29,8 +29,10 @@ module Text.Pandoc.Readers.Odt.Generic.XMLConverter
2929, modifyExtraState
3030, producingExtraState
3131, findChild'
32+, filterChildrenName'
3233, isSet'
3334, isSetWithDefault
35+, elName
3436, searchAttr
3537, lookupAttr
3638, lookupAttr'
@@ -43,6 +45,7 @@ module Text.Pandoc.Readers.Odt.Generic.XMLConverter
4345, readAttrWithDefault
4446, getAttr
4547, executeIn
48+, executeInSub
4649, withEveryL
4750, tryAll
4851, matchContent'
@@ -309,34 +312,44 @@ readNSattributes = fromState $ \state -> maybe (state, failEmpty )
309312
310313-- | Given a namespace id and an element name, creates a 'XML.QName' for
311314-- internal use
312−elemName :: (NameSpaceID nsID)
315+qualifyName :: (NameSpaceID nsID)
313316 => nsID -> ElementName
314317 -> XMLConverter nsID extraState x XML.QName
315−elemName nsID name = lookupNSiri nsID
318+qualifyName nsID name = lookupNSiri nsID
316319 &&& lookupNSprefix nsID
317320 >>% XML.QName name
318321
322+-- | Checks if a given element matches both a specified namespace id
323+-- and a predicate
324+elemNameMatches :: (NameSpaceID nsID)
325+ => nsID -> (ElementName -> Bool)
326+ -> XMLConverter nsID extraState XML.Element Bool
327+elemNameMatches nsID f = keepingTheValue (lookupNSiri nsID) >>% hasMatchingName
328+ where hasMatchingName e iri = let name = XML.elName e
329+ in f (XML.qName name)
330+ && XML.qURI name == iri
331+
319332-- | Checks if a given element matches both a specified namespace id
320333-- and a specified element name
321334elemNameIs :: (NameSpaceID nsID)
322335 => nsID -> ElementName
323336 -> XMLConverter nsID extraState XML.Element Bool
324−elemNameIs nsID name = keepingTheValue (lookupNSiri nsID) >>% hasThatName
325− where hasThatName e iri = let elName = XML.elName e
326− in XML.qName elName == name
327− && XML.qURI elName == iri
337+elemNameIs nsID name = elemNameMatches nsID (== name)
328338
329339--------------------------------------------------------------------------------
330340-- General content
331341--------------------------------------------------------------------------------
332342
343+elName :: XML.Element -> ElementName
344+elName = XML.qName . XML.elName
345+
333346--
334347elContent :: XMLConverter nsID extraState x [XML.Content]
335348elContent = getCurrentElement
336349 >>^ XML.elContent
337350
338351--------------------------------------------------------------------------------
339−-- Chilren
352+-- Children
340353--------------------------------------------------------------------------------
341354
342355--
@@ -344,7 +357,7 @@ elContent = getCurrentElement
344357findChildren :: (NameSpaceID nsID)
345358 => nsID -> ElementName
346359 -> XMLConverter nsID extraState x [XML.Element]
347−findChildren nsID name = elemName nsID name
360+findChildren nsID name = qualifyName nsID name
348361 &&& getCurrentElement
349362 >>% XML.findChildren
350363
@@ -353,7 +366,7 @@ findChild' :: (NameSpaceID nsID)
353366 => nsID
354367 -> ElementName
355368 -> XMLConverter nsID extraState x (Maybe XML.Element)
356−findChild' nsID name = elemName nsID name
369+findChild' nsID name = qualifyName nsID name
357370 &&& getCurrentElement
358371 >>% XML.findChild
359372
@@ -364,6 +377,14 @@ findChild :: (NameSpaceID nsID)
364377findChild nsID name = findChild' nsID name
365378 >>> maybeToChoice
366379
380+filterChildrenName' :: (NameSpaceID nsID)
381+ => nsID
382+ -> (ElementName -> Bool)
383+ -> XMLConverter nsID extraState x [XML.Element]
384+filterChildrenName' nsID f = getCurrentElement
385+ >>> arr XML.elChildren
386+ >>> iterateS (keepingTheValue (elemNameMatches nsID f))
387+ >>> arr (catMaybes . fmap (uncurry $ bool Nothing . Just))
367388
368389--------------------------------------------------------------------------------
369390-- Attributes
@@ -441,7 +462,7 @@ lookupDefaultingAttr nsID attrName
441462findAttr' :: (NameSpaceID nsID)
442463 => nsID -> AttributeName
443464 -> XMLConverter nsID extraState x (Maybe AttributeValue)
444−findAttr' nsID attrName = elemName nsID attrName
465+findAttr' nsID attrName = qualifyName nsID attrName
445466 &&& getCurrentElement
446467 >>% XML.findAttr
447468
@@ -537,15 +558,21 @@ executeThere a = second jumpThere
537558 >>> jumpBack -- >>? jumpBack would not ensure the jump.
538559 >>^ collapseEither
539560
540−-- | Do something in a sub-element, tnen come back
541−executeIn :: (NameSpaceID nsID)
542− => nsID -> ElementName
543− -> FallibleXMLConverter nsID extraState f s
544− -> FallibleXMLConverter nsID extraState f s
545−executeIn nsID name a = keepingTheValue
546− (findChild nsID name)
547− >>> ignoringState liftFailure
548− >>? switchingTheStack a
561+
562+-- | Do something in a specific element, then come back
563+executeIn :: XMLConverter nsID extraState XML.Element s
564+ -> XMLConverter nsID extraState XML.Element s
565+executeIn a = duplicate >>> switchingTheStack a
566+
567+-- | Do something in a sub-element, then come back
568+executeInSub :: (NameSpaceID nsID)
569+ => nsID -> ElementName
570+ -> FallibleXMLConverter nsID extraState f s
571+ -> FallibleXMLConverter nsID extraState f s
572+executeInSub nsID name a = keepingTheValue
573+ (findChild nsID name)
574+ >>> ignoringState liftFailure
575+ >>? switchingTheStack a
549576 where liftFailure (_, (Left f)) = Left f
550577 liftFailure (x, (Right e)) = Right (x, e)
551578
src/Text/Pandoc/Readers/Odt/StyleReader.hs+5−5
@@ -113,7 +113,7 @@ type StyleReaderSafe a b = XMLReaderSafe FontPitches a b
113113
114114-- | A reader for font pitches
115115fontPitchReader :: XMLReader _s _x FontPitches
116−fontPitchReader = executeIn NsOffice "font-face-decls" (
116+fontPitchReader = executeInSub NsOffice "font-face-decls" (
117117 withEveryL NsStyle "font-face" (liftAsSuccess (
118118 findAttr' NsStyle "name"
119119 &&&
@@ -423,7 +423,7 @@ readAllStyles = ( readFontPitches
423423
424424--
425425readStyles :: StyleReader _x Styles
426−readStyles = executeIn NsOffice "styles" $ liftAsSuccess
426+readStyles = executeInSub NsOffice "styles" $ liftAsSuccess
427427 $ liftA3 Styles
428428 ( tryAll NsStyle "style" readStyle >>^ M.fromList )
429429 ( tryAll NsText "list-style" readListStyle >>^ M.fromList )
@@ -431,7 +431,7 @@ readStyles = executeIn NsOffice "styles" $ liftAsSuccess
431431
432432--
433433readAutomaticStyles :: StyleReader _x Styles
434−readAutomaticStyles = executeIn NsOffice "automatic-styles" $ liftAsSuccess
434+readAutomaticStyles = executeInSub NsOffice "automatic-styles" $ liftAsSuccess
435435 $ liftA3 Styles
436436 ( tryAll NsStyle "style" readStyle >>^ M.fromList )
437437 ( tryAll NsText "list-style" readListStyle >>^ M.fromList )
@@ -462,7 +462,7 @@ readStyleProperties = liftA2 SProps
462462--
463463readTextProperties :: StyleReader _x TextProperties
464464readTextProperties =
465− executeIn NsStyle "text-properties" $ liftAsSuccess
465+ executeInSub NsStyle "text-properties" $ liftAsSuccess
466466 ( liftA6 PropT
467467 ( searchAttr NsXSL_FO "font-style" False isFontEmphasised )
468468 ( searchAttr NsXSL_FO "font-weight" False isFontBold )
@@ -501,7 +501,7 @@ readLineMode modeAttr styleAttr = proc x -> do
501501--
502502readParaProperties :: StyleReader _x ParaProperties
503503readParaProperties =
504− executeIn NsStyle "paragraph-properties" $ liftAsSuccess
504+ executeInSub NsStyle "paragraph-properties" $ liftAsSuccess
505505 ( liftA3 PropP
506506 ( liftA2 readNumbering
507507 ( isSet' NsText "number-lines" )
test/Tests/Readers/Odt.hs+1
@@ -152,6 +152,7 @@ namesOfTestsComparingToMarkdown = [ "bold"
152152 , "endnote"
153153 , "externalLink"
154154 , "footnote"
155+ , "formula"
155156 , "headers"
156157-- , "horizontalRule"
157158 , "italic"
test/odt/markdown/formula.mdnew file+1
@@ -0,0 +1 @@
1+$$E = {m \cdot c^{2}}$$