@@ -1,7 +1,11 @@
11{-# LANGUAGE NoImplicitPrelude #-}
22{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE FlexibleContexts #-}
34{-# LANGUAGE PatternGuards #-}
45{-# LANGUAGE ViewPatterns #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
59{- |
610 Module : Text.Pandoc.Readers.Docx.Parse
711 Copyright : Copyright (C) 2014-2019 Jesse Rosenthal
@@ -31,6 +35,8 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..)
3135 , VertAlign(..)
3236 , ParIndentation(..)
3337 , ParagraphStyle(..)
38+ , ParStyle
39+ , CharStyle(cStyleData)
3440 , Row(..)
3541 , Cell(..)
3642 , TrackedChange(..)
@@ -38,8 +44,17 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..)
3844 , ChangeInfo(..)
3945 , FieldInfo(..)
4046 , Level(..)
47+ , ParaStyleName
48+ , CharStyleName
49+ , FromStyleName(..)
50+ , HasStyleName(..)
51+ , HasParentStyle(..)
4152 , archiveToDocx
4253 , archiveToDocxWithWarnings
54+ , getStyleNames
55+ , pHeading
56+ , constructBogusParStyleData
57+ , leftBiasedMergeRunStyle
4358 ) where
4459import Prelude
4560import Codec.Archive.Zip
@@ -49,10 +64,13 @@ import Control.Monad.Reader
4964import Control.Monad.State.Strict
5065import Data.Bits ((.|.))
5166import qualified Data.ByteString.Lazy as B
52−import Data.Char (chr, ord, readLitChar)
67+import Data.Char (chr, ord, readLitChar, toLower)
5368import Data.List
69+import Data.Function (on)
70+import Data.String (IsString(..))
5471import qualified Data.Map as M
5572import Data.Maybe
73+import Data.Coerce
5674import System.FilePath
5775import Text.Pandoc.Readers.Docx.Util
5876import Text.Pandoc.Readers.Docx.Fields
@@ -160,13 +178,9 @@ newtype Body = Body [BodyPart]
160178
161179type Media = [(FilePath, B.ByteString)]
162180
163−type CharStyle = (String, RunStyle)
181+type CharStyleMap = M.Map CharStyleId CharStyle
164182
165−type ParStyle = (String, ParStyleData)
166−
167−type CharStyleMap = M.Map String RunStyle
168−
169−type ParStyleMap = M.Map String ParStyleData
183+type ParStyleMap = M.Map ParaStyleId ParStyle
170184
171185data Numbering = Numbering NameSpaces [Numb] [AbstractNumb]
172186 deriving Show
@@ -213,12 +227,9 @@ data ChangeInfo = ChangeInfo ChangeId Author ChangeDate
213227data TrackedChange = TrackedChange ChangeType ChangeInfo
214228 deriving Show
215229
216−data ParagraphStyle = ParagraphStyle { pStyle :: [String]
230+data ParagraphStyle = ParagraphStyle { pStyle :: [ParStyle]
217231 , indentation :: Maybe ParIndentation
218232 , dropCap :: Bool
219− , pHeading :: Maybe (String, Int)
220− , pNumInfo :: Maybe (String, String)
221− , pBlockQuote :: Maybe Bool
222233 , pChange :: Maybe TrackedChange
223234 }
224235 deriving Show
@@ -227,9 +238,6 @@ defaultParagraphStyle :: ParagraphStyle
227238defaultParagraphStyle = ParagraphStyle { pStyle = []
228239 , indentation = Nothing
229240 , dropCap = False
230− , pHeading = Nothing
231− , pNumInfo = Nothing
232− , pBlockQuote = Nothing
233241 , pChange = Nothing
234242 }
235243
@@ -254,6 +262,49 @@ newtype Row = Row [Cell]
254262newtype Cell = Cell [BodyPart]
255263 deriving Show
256264
265+newtype CharStyleId = CharStyleId { fromCharStyleId :: String }
266+ deriving (Show, Eq, Ord, FromStyleId)
267+newtype ParaStyleId = ParaStyleId { fromParaStyleId :: String }
268+ deriving (Show, Eq, Ord, FromStyleId)
269+
270+newtype CharStyleName = CharStyleName { fromCharStyleName :: CIString }
271+ deriving (Show, Eq, Ord, IsString, FromStyleName)
272+newtype ParaStyleName = ParaStyleName { fromParaStyleName :: CIString }
273+ deriving (Show, Eq, Ord, IsString, FromStyleName)
274+
275+-- Case-insensitive comparisons
276+newtype CIString = CIString String deriving (Show, IsString, FromStyleName)
277+
278+class FromStyleName a where
279+ fromStyleName :: a -> String
280+
281+instance FromStyleName String where
282+ fromStyleName = id
283+
284+class FromStyleId a where
285+ fromStyleId :: a -> String
286+
287+instance FromStyleId String where
288+ fromStyleId = id
289+
290+instance Eq CIString where
291+ (==) = (==) `on` map toLower . coerce
292+
293+instance Ord CIString where
294+ compare = compare `on` map toLower . coerce
295+
296+leftBiasedMergeRunStyle :: RunStyle -> RunStyle -> RunStyle
297+leftBiasedMergeRunStyle a b = RunStyle
298+ { isBold = isBold a <|> isBold b
299+ , isItalic = isItalic a <|> isItalic b
300+ , isSmallCaps = isSmallCaps a <|> isSmallCaps b
301+ , isStrike = isStrike a <|> isStrike b
302+ , isRTL = isRTL a <|> isRTL b
303+ , rVertAlign = rVertAlign a <|> rVertAlign b
304+ , rUnderline = rUnderline a <|> rUnderline b
305+ , rParentStyle = rParentStyle a
306+ }
307+
257308-- (width, height) in EMUs
258309type Extent = Maybe (Double, Double)
259310
@@ -285,21 +336,28 @@ data RunElem = TextRun String | LnBrk | Tab | SoftHyphen | NoBreakHyphen
285336data VertAlign = BaseLn | SupScrpt | SubScrpt
286337 deriving Show
287338
288−data RunStyle = RunStyle { isBold :: Maybe Bool
289− , isItalic :: Maybe Bool
290− , isSmallCaps :: Maybe Bool
291− , isStrike :: Maybe Bool
292− , isRTL :: Maybe Bool
293− , rVertAlign :: Maybe VertAlign
294− , rUnderline :: Maybe String
295− , rStyle :: Maybe CharStyle
339+data CharStyle = CharStyle { cStyleId :: CharStyleId
340+ , cStyleName :: CharStyleName
341+ , cStyleData :: RunStyle
342+ } deriving (Show)
343+
344+data RunStyle = RunStyle { isBold :: Maybe Bool
345+ , isItalic :: Maybe Bool
346+ , isSmallCaps :: Maybe Bool
347+ , isStrike :: Maybe Bool
348+ , isRTL :: Maybe Bool
349+ , rVertAlign :: Maybe VertAlign
350+ , rUnderline :: Maybe String
351+ , rParentStyle :: Maybe CharStyle
296352 }
297353 deriving Show
298354
299−data ParStyleData = ParStyleData { headingLev :: Maybe (String, Int)
300− , isBlockQuote :: Maybe Bool
301− , numInfo :: Maybe (String, String)
302− , psStyle :: Maybe ParStyle}
355+data ParStyle = ParStyle { headingLev :: Maybe (ParaStyleName, Int)
356+ , numInfo :: Maybe (String, String)
357+ , psParentStyle :: Maybe ParStyle
358+ , pStyleName :: ParaStyleName
359+ , pStyleId :: ParaStyleId
360+ }
303361 deriving Show
304362
305363defaultRunStyle :: RunStyle
@@ -310,7 +368,7 @@ defaultRunStyle = RunStyle { isBold = Nothing
310368 , isRTL = Nothing
311369 , rVertAlign = Nothing
312370 , rUnderline = Nothing
313− , rStyle = Nothing
371+ , rParentStyle = Nothing
314372 }
315373
316374type Target = String
@@ -390,7 +448,10 @@ elemToBody ns element | isElem ns "w" "body" element =
390448elemToBody _ _ = throwError WrongElem
391449
392450archiveToStyles :: Archive -> (CharStyleMap, ParStyleMap)
393−archiveToStyles zf =
451+archiveToStyles = archiveToStyles' getStyleId getStyleId
452+archiveToStyles' :: (Ord k1, Ord k2, ElemToStyle a1, ElemToStyle a2) =>
453+ (a1 -> k1) -> (a2 -> k2) -> Archive -> (M.Map k1 a1, M.Map k2 a2)
454+archiveToStyles' conv1 conv2 zf =
394455 let stylesElem = findEntryByPath "word/styles.xml" zf >>=
395456 (parseXMLDoc . UTF8.toStringLazy . fromEntry)
396457 in
@@ -399,19 +460,17 @@ archiveToStyles zf =
399460 Just styElem ->
400461 let namespaces = elemToNameSpaces styElem
401462 in
402− ( M.fromList $ buildBasedOnList namespaces styElem
403− (Nothing :: Maybe CharStyle),
404− M.fromList $ buildBasedOnList namespaces styElem
405− (Nothing :: Maybe ParStyle) )
463+ ( M.fromList $ map (\r -> (conv1 r, r)) $ buildBasedOnList namespaces styElem Nothing,
464+ M.fromList $ map (\p -> (conv2 p, p)) $ buildBasedOnList namespaces styElem Nothing)
406465
407−isBasedOnStyle :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> Bool
466+isBasedOnStyle :: (ElemToStyle a, FromStyleId (StyleId a)) => NameSpaces -> Element -> Maybe a -> Bool
408467isBasedOnStyle ns element parentStyle
409468 | isElem ns "w" "style" element
410469 , Just styleType <- findAttrByName ns "w" "type" element
411470 , styleType == cStyleType parentStyle
412471 , Just basedOnVal <- findChildByName ns "w" "basedOn" element >>=
413472 findAttrByName ns "w" "val"
414− , Just ps <- parentStyle = basedOnVal == getStyleId ps
473+ , Just ps <- parentStyle = basedOnVal == fromStyleId (getStyleId ps)
415474 | isElem ns "w" "style" element
416475 , Just styleType <- findAttrByName ns "w" "type" element
417476 , styleType == cStyleType parentStyle
@@ -419,30 +478,70 @@ isBasedOnStyle ns element parentStyle
419478 , Nothing <- parentStyle = True
420479 | otherwise = False
421480
422−class ElemToStyle a where
481+class HasStyleId a => ElemToStyle a where
423482 cStyleType :: Maybe a -> String
424483 elemToStyle :: NameSpaces -> Element -> Maybe a -> Maybe a
425− getStyleId :: a -> String
484+
485+class FromStyleId (StyleId a) => HasStyleId a where
486+ type StyleId a
487+ getStyleId :: a -> StyleId a
488+
489+class FromStyleName (StyleName a) => HasStyleName a where
490+ type StyleName a
491+ getStyleName :: a -> StyleName a
492+
493+class HasParentStyle a where
494+ getParentStyle :: a -> Maybe a
495+
496+instance HasParentStyle CharStyle where
497+ getParentStyle = rParentStyle . cStyleData
498+
499+instance HasParentStyle ParStyle where
500+ getParentStyle = psParentStyle
501+
502+getStyleNames :: (Functor t, HasStyleName a) => t a -> t (StyleName a)
503+getStyleNames = fmap getStyleName
504+
505+constructBogusParStyleData :: ParaStyleName -> ParStyle
506+constructBogusParStyleData stName = ParStyle
507+ { headingLev = Nothing
508+ , numInfo = Nothing
509+ , psParentStyle = Nothing
510+ , pStyleName = stName
511+ , pStyleId = ParaStyleId . filter (/=' ') . fromStyleName $ stName
512+ }
426513
427514instance ElemToStyle CharStyle where
428515 cStyleType _ = "character"
429516 elemToStyle ns element parentStyle
430517 | isElem ns "w" "style" element
431− , Just "character" <- findAttrByName ns "w" "type" element
432− , Just styleId <- findAttrByName ns "w" "styleId" element =
433− Just (styleId, elemToRunStyle ns element parentStyle)
518+ , Just "character" <- findAttrByName ns "w" "type" element =
519+ elemToCharStyle ns element parentStyle
434520 | otherwise = Nothing
435− getStyleId s = fst s
521+
522+instance HasStyleId CharStyle where
523+ type StyleId CharStyle = CharStyleId
524+ getStyleId = cStyleId
525+
526+instance HasStyleName CharStyle where
527+ type StyleName CharStyle = CharStyleName
528+ getStyleName = cStyleName
436529
437530instance ElemToStyle ParStyle where
438531 cStyleType _ = "paragraph"
439532 elemToStyle ns element parentStyle
440533 | isElem ns "w" "style" element
441534 , Just "paragraph" <- findAttrByName ns "w" "type" element
442− , Just styleId <- findAttrByName ns "w" "styleId" element =
443− Just (styleId, elemToParStyleData ns element parentStyle)
535+ = elemToParStyleData ns element parentStyle
444536 | otherwise = Nothing
445− getStyleId s = fst s
537+
538+instance HasStyleId ParStyle where
539+ type StyleId ParStyle = ParaStyleId
540+ getStyleId = pStyleId
541+
542+instance HasStyleName ParStyle where
543+ type StyleName ParStyle = ParaStyleName
544+ getStyleName = pStyleName
446545
447546getStyleChildren :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
448547getStyleChildren ns element parentStyle
@@ -693,6 +792,12 @@ testBitMask bitMaskS n =
693792stringToInteger :: String -> Maybe Integer
694793stringToInteger s = listToMaybe $ map fst (reads s :: [(Integer, String)])
695794
795+pHeading :: ParagraphStyle -> Maybe (ParaStyleName, Int)
796+pHeading = getParStyleField headingLev . pStyle
797+
798+pNumInfo :: ParagraphStyle -> Maybe (String, String)
799+pNumInfo = getParStyleField numInfo . pStyle
800+
696801elemToBodyPart :: NameSpaces -> Element -> D BodyPart
697802elemToBodyPart ns element
698803 | isElem ns "w" "p" element
@@ -1003,20 +1108,18 @@ elemToRun ns element
10031108 return $ Run runStyle runElems
10041109elemToRun _ _ = throwError WrongElem
10051110
1006−getParentStyleValue :: (ParStyleData -> Maybe a) -> ParStyleData -> Maybe a
1111+getParentStyleValue :: (ParStyle -> Maybe a) -> ParStyle -> Maybe a
10071112getParentStyleValue field style
10081113 | Just value <- field style = Just value
1009− | Just parentStyle <- psStyle style
1010− = getParentStyleValue field (snd parentStyle)
1114+ | Just parentStyle <- psParentStyle style
1115+ = getParentStyleValue field parentStyle
10111116getParentStyleValue _ _ = Nothing
10121117
1013−getParStyleField :: (ParStyleData -> Maybe a) -> ParStyleMap -> [String] ->
1014− Maybe a
1015−getParStyleField field stylemap styles
1016− | x <- mapMaybe (\x -> M.lookup x stylemap) styles
1017− , (y:_) <- mapMaybe (getParentStyleValue field) x
1118+getParStyleField :: (ParStyle -> Maybe a) -> [ParStyle] -> Maybe a
1119+getParStyleField field styles
1120+ | (y:_) <- mapMaybe (getParentStyleValue field) styles
10181121 = Just y
1019−getParStyleField _ _ _ = Nothing
1122+getParStyleField _ _ = Nothing
10201123
10211124getTrackedChange :: NameSpaces -> Element -> Maybe TrackedChange
10221125getTrackedChange ns element
@@ -1038,10 +1141,10 @@ elemToParagraphStyle ns element sty
10381141 | Just pPr <- findChildByName ns "w" "pPr" element =
10391142 let style =
10401143 mapMaybe
1041− (findAttrByName ns "w" "val")
1144+ (fmap ParaStyleId . findAttrByName ns "w" "val")
10421145 (findChildrenByName ns "w" "pStyle" pPr)
10431146 in ParagraphStyle
1044− {pStyle = style
1147+ {pStyle = mapMaybe (`M.lookup` sty) style
10451148 , indentation =
10461149 findChildByName ns "w" "ind" pPr >>=
10471150 elemToParIndentation ns
@@ -1053,9 +1156,6 @@ elemToParagraphStyle ns element sty
10531156 Just "none" -> False
10541157 Just _ -> True
10551158 Nothing -> False
1056− , pHeading = getParStyleField headingLev sty style
1057− , pNumInfo = getParStyleField numInfo sty style
1058− , pBlockQuote = getParStyleField isBlockQuote sty style
10591159 , pChange = findChildByName ns "w" "rPr" pPr >>=
10601160 filterChild (\e -> isElem ns "w" "ins" e ||
10611161 isElem ns "w" "moveTo" e ||
@@ -1085,16 +1185,20 @@ elemToRunStyleD :: NameSpaces -> Element -> D RunStyle
10851185elemToRunStyleD ns element
10861186 | Just rPr <- findChildByName ns "w" "rPr" element = do
10871187 charStyles <- asks envCharStyles
1088− let parentSty = case
1188+ let parentSty =
10891189 findChildByName ns "w" "rStyle" rPr >>=
1090− findAttrByName ns "w" "val"
1091− of
1092− Just styName | Just style <- M.lookup styName charStyles ->
1093− Just (styName, style)
1094− _ -> Nothing
1190+ findAttrByName ns "w" "val" >>=
1191+ flip M.lookup charStyles . CharStyleId
10951192 return $ elemToRunStyle ns element parentSty
10961193elemToRunStyleD _ _ = return defaultRunStyle
10971194
1195+elemToCharStyle :: NameSpaces
1196+ -> Element -> Maybe CharStyle -> Maybe CharStyle
1197+elemToCharStyle ns element parentStyle
1198+ = CharStyle <$> (CharStyleId <$> findAttrByName ns "w" "styleId" element)
1199+ <*> getElementStyleName ns element
1200+ <*> (Just $ elemToRunStyle ns element parentStyle)
1201+
10981202elemToRunStyle :: NameSpaces -> Element -> Maybe CharStyle -> RunStyle
10991203elemToRunStyle ns element parentStyle
11001204 | Just rPr <- findChildByName ns "w" "rPr" element =
@@ -1117,38 +1221,23 @@ elemToRunStyle ns element parentStyle
11171221 , rUnderline =
11181222 findChildByName ns "w" "u" rPr >>=
11191223 findAttrByName ns "w" "val"
1120− , rStyle = parentStyle
1224+ , rParentStyle = parentStyle
11211225 }
11221226elemToRunStyle _ _ _ = defaultRunStyle
11231227
1124−getHeaderLevel :: NameSpaces -> Element -> Maybe (String,Int)
1228+getHeaderLevel :: NameSpaces -> Element -> Maybe (ParaStyleName, Int)
11251229getHeaderLevel ns element
1126− | Just styleId <- findAttrByName ns "w" "styleId" element
1127− , Just index <- stripPrefix "Heading" styleId
1128− , Just n <- stringToInteger index
1129− , n > 0 = Just (styleId, fromInteger n)
1130− | Just styleId <- findAttrByName ns "w" "styleId" element
1131− , Just index <- findChildByName ns "w" "name" element >>=
1132− findAttrByName ns "w" "val" >>=
1133− stripPrefix "heading "
1134− , Just n <- stringToInteger index
1135− , n > 0 = Just (styleId, fromInteger n)
1230+ | Just styleName <- getElementStyleName ns element
1231+ , Just n <- stringToInteger =<<
1232+ (stripPrefix "heading " . map toLower $
1233+ fromStyleName styleName)
1234+ , n > 0 = Just (styleName, fromInteger n)
11361235getHeaderLevel _ _ = Nothing
11371236
1138−blockQuoteStyleIds :: [String]
1139−blockQuoteStyleIds = ["Quote", "BlockQuote", "BlockQuotation"]
1140−
1141−blockQuoteStyleNames :: [String]
1142−blockQuoteStyleNames = ["Quote", "Block Text"]
1143−
1144−getBlockQuote :: NameSpaces -> Element -> Maybe Bool
1145−getBlockQuote ns element
1146− | Just styleId <- findAttrByName ns "w" "styleId" element
1147− , styleId `elem` blockQuoteStyleIds = Just True
1148− | Just styleName <- findChildByName ns "w" "name" element >>=
1149− findAttrByName ns "w" "val"
1150− , styleName `elem` blockQuoteStyleNames = Just True
1151−getBlockQuote _ _ = Nothing
1237+getElementStyleName :: Coercible String a => NameSpaces -> Element -> Maybe a
1238+getElementStyleName ns el = coerce <$>
1239+ ((findChildByName ns "w" "name" el >>= findAttrByName ns "w" "val")
1240+ <|> findAttrByName ns "w" "styleId" el)
11521241
11531242getNumInfo :: NameSpaces -> Element -> Maybe (String, String)
11541243getNumInfo ns element = do
@@ -1163,15 +1252,19 @@ getNumInfo ns element = do
11631252 return (numId, lvl)
11641253
11651254
1166−elemToParStyleData :: NameSpaces -> Element -> Maybe ParStyle -> ParStyleData
1167−elemToParStyleData ns element parentStyle =
1168− ParStyleData
1255+elemToParStyleData :: NameSpaces -> Element -> Maybe ParStyle -> Maybe ParStyle
1256+elemToParStyleData ns element parentStyle
1257+ | Just styleId <- findAttrByName ns "w" "styleId" element
1258+ , Just styleName <- getElementStyleName ns element
1259+ = Just $ ParStyle
11691260 {
11701261 headingLev = getHeaderLevel ns element
1171− , isBlockQuote = getBlockQuote ns element
11721262 , numInfo = getNumInfo ns element
1173− , psStyle = parentStyle
1174− }
1263+ , psParentStyle = parentStyle
1264+ , pStyleName = styleName
1265+ , pStyleId = ParaStyleId styleId
1266+ }
1267+elemToParStyleData _ _ _ = Nothing
11751268
11761269elemToRunElem :: NameSpaces -> Element -> D RunElem
11771270elemToRunElem ns element