git.iko.soy/typeable/pandoc
on master help sign in
[Docx Writer] Re-use Readers.Docx.Parse for StyleMap (#5766)

* [Docx Parser] Move style-parsing-specific code to a new module * [Docx Writer] Re-use Readers.Docx.Parse.Styles for StyleMap * [Docx Writer] Move Readers.Docx.StyleMap to Writers.Docx.StyleMap It's never used outside of writer code, so it makes more sense to scope it under writers really.

commit9b6ee81c1916bb23d2cb24534adb88d65b4642df authorNikolay Yakimov <root@livid.pp.ru> dateSun 22 Sep 2019, 23:00 (7 years ago) parentd247e9f
6 files changed+387−412
pandoc.cabal+2−1 src/Text/Pandoc/Readers/Docx/Parse.hs+3−257 src/Text/Pandoc/Readers/Docx/Parse/Styles.hs+304 src/Text/Pandoc/Readers/Docx/StyleMap.hs−123 src/Text/Pandoc/Writers/Docx.hs+30−31 src/Text/Pandoc/Writers/Docx/StyleMap.hs+48
pandoc.cabal+2−1
@@ -566,8 +566,8 @@ library
566566 Text.Pandoc.Readers.Docx.Lists,
567567 Text.Pandoc.Readers.Docx.Combine,
568568 Text.Pandoc.Readers.Docx.Parse,
569+ Text.Pandoc.Readers.Docx.Parse.Styles,
569570 Text.Pandoc.Readers.Docx.Util,
570− Text.Pandoc.Readers.Docx.StyleMap,
571571 Text.Pandoc.Readers.Docx.Fields,
572572 Text.Pandoc.Readers.LaTeX.Parsing,
573573 Text.Pandoc.Readers.LaTeX.Lang,
@@ -592,6 +592,7 @@ library
592592 Text.Pandoc.Readers.Org.Parsing,
593593 Text.Pandoc.Readers.Org.Shared,
594594 Text.Pandoc.Readers.Roff,
595+ Text.Pandoc.Writers.Docx.StyleMap,
595596 Text.Pandoc.Writers.Roff,
596597 Text.Pandoc.Writers.Powerpoint.Presentation,
597598 Text.Pandoc.Writers.Powerpoint.Output,
src/Text/Pandoc/Readers/Docx/Parse.hs+3−257
@@ -1,14 +1,9 @@
11{-# LANGUAGE NoImplicitPrelude #-}
2−{-# LANGUAGE FlexibleInstances #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE PatternGuards #-}
52{-# LANGUAGE ViewPatterns #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE TypeFamilies #-}
8−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
93{- |
104 Module : Text.Pandoc.Readers.Docx.Parse
115 Copyright : Copyright (C) 2014-2019 Jesse Rosenthal
6+ 2019 Nikolay Yakimov <root@livid.pp.ru>
127 License : GNU GPL, version 2 or above
138
149 Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
@@ -57,6 +52,7 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..)
5752 , leftBiasedMergeRunStyle
5853 ) where
5954import Prelude
55+import Text.Pandoc.Readers.Docx.Parse.Styles
6056import Codec.Archive.Zip
6157import Control.Applicative ((<|>))
6258import Control.Monad.Except
@@ -64,13 +60,10 @@ import Control.Monad.Reader
6460import Control.Monad.State.Strict
6561import Data.Bits ((.|.))
6662import qualified Data.ByteString.Lazy as B
67−import Data.Char (chr, ord, readLitChar, toLower)
63+import Data.Char (chr, ord, readLitChar)
6864import Data.List
69−import Data.Function (on)
70−import Data.String (IsString(..))
7165import qualified Data.Map as M
7266import Data.Maybe
73−import Data.Coerce
7467import System.FilePath
7568import Text.Pandoc.Readers.Docx.Util
7669import Text.Pandoc.Readers.Docx.Fields
@@ -262,37 +255,6 @@ newtype Row = Row [Cell]
262255newtype Cell = Cell [BodyPart]
263256 deriving Show
264257
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−
296258leftBiasedMergeRunStyle :: RunStyle -> RunStyle -> RunStyle
297259leftBiasedMergeRunStyle a b = RunStyle
298260 { isBold = isBold a <|> isBold b
@@ -333,44 +295,6 @@ data Run = Run RunStyle [RunElem]
333295data RunElem = TextRun String | LnBrk | Tab | SoftHyphen | NoBreakHyphen
334296 deriving Show
335297
336−data VertAlign = BaseLn | SupScrpt | SubScrpt
337− deriving Show
338−
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
352− }
353− deriving Show
354−
355−data ParStyle = ParStyle { headingLev :: Maybe (ParaStyleName, Int)
356− , numInfo :: Maybe (String, String)
357− , psParentStyle :: Maybe ParStyle
358− , pStyleName :: ParaStyleName
359− , pStyleId :: ParaStyleId
360− }
361− deriving Show
362−
363−defaultRunStyle :: RunStyle
364−defaultRunStyle = RunStyle { isBold = Nothing
365− , isItalic = Nothing
366− , isSmallCaps = Nothing
367− , isStrike = Nothing
368− , isRTL = Nothing
369− , rVertAlign = Nothing
370− , rUnderline = Nothing
371− , rParentStyle = Nothing
372− }
373−
374298type Target = String
375299type Anchor = String
376300type URL = String
@@ -449,46 +373,6 @@ elemToBody _ _ = throwError WrongElem
449373
450374archiveToStyles :: Archive -> (CharStyleMap, ParStyleMap)
451375archiveToStyles = 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 =
455− let stylesElem = findEntryByPath "word/styles.xml" zf >>=
456− (parseXMLDoc . UTF8.toStringLazy . fromEntry)
457− in
458− case stylesElem of
459− Nothing -> (M.empty, M.empty)
460− Just styElem ->
461− let namespaces = elemToNameSpaces styElem
462− in
463− ( M.fromList $ map (\r -> (conv1 r, r)) $ buildBasedOnList namespaces styElem Nothing,
464− M.fromList $ map (\p -> (conv2 p, p)) $ buildBasedOnList namespaces styElem Nothing)
465−
466−isBasedOnStyle :: (ElemToStyle a, FromStyleId (StyleId a)) => NameSpaces -> Element -> Maybe a -> Bool
467−isBasedOnStyle ns element parentStyle
468− | isElem ns "w" "style" element
469− , Just styleType <- findAttrByName ns "w" "type" element
470− , styleType == cStyleType parentStyle
471− , Just basedOnVal <- findChildByName ns "w" "basedOn" element >>=
472− findAttrByName ns "w" "val"
473− , Just ps <- parentStyle = basedOnVal == fromStyleId (getStyleId ps)
474− | isElem ns "w" "style" element
475− , Just styleType <- findAttrByName ns "w" "type" element
476− , styleType == cStyleType parentStyle
477− , Nothing <- findChildByName ns "w" "basedOn" element
478− , Nothing <- parentStyle = True
479− | otherwise = False
480−
481−class HasStyleId a => ElemToStyle a where
482− cStyleType :: Maybe a -> String
483− elemToStyle :: NameSpaces -> Element -> Maybe a -> Maybe a
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
492376
493377class HasParentStyle a where
494378 getParentStyle :: a -> Maybe a
@@ -511,52 +395,6 @@ constructBogusParStyleData stName = ParStyle
511395 , pStyleId = ParaStyleId . filter (/=' ') . fromStyleName $ stName
512396 }
513397
514−instance ElemToStyle CharStyle where
515− cStyleType _ = "character"
516− elemToStyle ns element parentStyle
517− | isElem ns "w" "style" element
518− , Just "character" <- findAttrByName ns "w" "type" element =
519− elemToCharStyle ns element parentStyle
520− | otherwise = Nothing
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
529−
530−instance ElemToStyle ParStyle where
531− cStyleType _ = "paragraph"
532− elemToStyle ns element parentStyle
533− | isElem ns "w" "style" element
534− , Just "paragraph" <- findAttrByName ns "w" "type" element
535− = elemToParStyleData ns element parentStyle
536− | otherwise = Nothing
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
545−
546−getStyleChildren :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
547−getStyleChildren ns element parentStyle
548− | isElem ns "w" "styles" element =
549− mapMaybe (\e -> elemToStyle ns e parentStyle) $
550− filterChildren (\e' -> isBasedOnStyle ns e' parentStyle) element
551− | otherwise = []
552−
553−buildBasedOnList :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
554−buildBasedOnList ns element rootStyle =
555− case getStyleChildren ns element rootStyle of
556− [] -> []
557− stys -> stys ++
558− concatMap (buildBasedOnList ns element . Just) stys
559−
560398archiveToNotes :: Archive -> Notes
561399archiveToNotes zf =
562400 let fnElem = findEntryByPath "word/footnotes.xml" zf
@@ -789,9 +627,6 @@ testBitMask bitMaskS n =
789627 [] -> False
790628 ((n', _) : _) -> (n' .|. n) /= 0
791629
792−stringToInteger :: String -> Maybe Integer
793−stringToInteger s = listToMaybe $ map fst (reads s :: [(Integer, String)])
794−
795630pHeading :: ParagraphStyle -> Maybe (ParaStyleName, Int)
796631pHeading = getParStyleField headingLev . pStyle
797632
@@ -1166,21 +1001,6 @@ elemToParagraphStyle ns element sty
11661001 }
11671002elemToParagraphStyle _ _ _ = defaultParagraphStyle
11681003
1169−checkOnOff :: NameSpaces -> Element -> QName -> Maybe Bool
1170−checkOnOff ns rPr tag
1171− | Just t <- findChild tag rPr
1172− , Just val <- findAttrByName ns "w" "val" t =
1173− Just $ case val of
1174− "true" -> True
1175− "false" -> False
1176− "on" -> True
1177− "off" -> False
1178− "1" -> True
1179− "0" -> False
1180− _ -> False
1181− | Just _ <- findChild tag rPr = Just True
1182−checkOnOff _ _ _ = Nothing
1183−
11841004elemToRunStyleD :: NameSpaces -> Element -> D RunStyle
11851005elemToRunStyleD ns element
11861006 | Just rPr <- findChildByName ns "w" "rPr" element = do
@@ -1192,80 +1012,6 @@ elemToRunStyleD ns element
11921012 return $ elemToRunStyle ns element parentSty
11931013elemToRunStyleD _ _ = return defaultRunStyle
11941014
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−
1202−elemToRunStyle :: NameSpaces -> Element -> Maybe CharStyle -> RunStyle
1203−elemToRunStyle ns element parentStyle
1204− | Just rPr <- findChildByName ns "w" "rPr" element =
1205− RunStyle
1206− {
1207− isBold = checkOnOff ns rPr (elemName ns "w" "b") `mplus`
1208− checkOnOff ns rPr (elemName ns "w" "bCs")
1209− , isItalic = checkOnOff ns rPr (elemName ns "w" "i") `mplus`
1210− checkOnOff ns rPr (elemName ns "w" "iCs")
1211− , isSmallCaps = checkOnOff ns rPr (elemName ns "w" "smallCaps")
1212− , isStrike = checkOnOff ns rPr (elemName ns "w" "strike")
1213− , isRTL = checkOnOff ns rPr (elemName ns "w" "rtl")
1214− , rVertAlign =
1215− findChildByName ns "w" "vertAlign" rPr >>=
1216− findAttrByName ns "w" "val" >>=
1217− \v -> Just $ case v of
1218− "superscript" -> SupScrpt
1219− "subscript" -> SubScrpt
1220− _ -> BaseLn
1221− , rUnderline =
1222− findChildByName ns "w" "u" rPr >>=
1223− findAttrByName ns "w" "val"
1224− , rParentStyle = parentStyle
1225− }
1226−elemToRunStyle _ _ _ = defaultRunStyle
1227−
1228−getHeaderLevel :: NameSpaces -> Element -> Maybe (ParaStyleName, Int)
1229−getHeaderLevel ns element
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)
1235−getHeaderLevel _ _ = Nothing
1236−
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)
1241−
1242−getNumInfo :: NameSpaces -> Element -> Maybe (String, String)
1243−getNumInfo ns element = do
1244− let numPr = findChildByName ns "w" "pPr" element >>=
1245− findChildByName ns "w" "numPr"
1246− lvl = fromMaybe "0" (numPr >>=
1247− findChildByName ns "w" "ilvl" >>=
1248− findAttrByName ns "w" "val")
1249− numId <- numPr >>=
1250− findChildByName ns "w" "numId" >>=
1251− findAttrByName ns "w" "val"
1252− return (numId, lvl)
1253−
1254−
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
1260− {
1261− headingLev = getHeaderLevel ns element
1262− , numInfo = getNumInfo ns element
1263− , psParentStyle = parentStyle
1264− , pStyleName = styleName
1265− , pStyleId = ParaStyleId styleId
1266− }
1267−elemToParStyleData _ _ _ = Nothing
1268−
12691015elemToRunElem :: NameSpaces -> Element -> D RunElem
12701016elemToRunElem ns element
12711017 | isElem ns "w" "t" element
src/Text/Pandoc/Readers/Docx/Parse/Styles.hsnew file+304
@@ -0,0 +1,304 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+{- |
7+ Module : Text.Pandoc.Readers.Docx.Parse.Styles
8+ Copyright : Copyright (C) 2014-2019 Jesse Rosenthal
9+ 2019 Nikolay Yakimov <root@livid.pp.ru>
10+ License : GNU GPL, version 2 or above
11+
12+ Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
13+ Stability : alpha
14+ Portability : portable
15+
16+Type machinery and code for extraction and manipulation of docx styles
17+-}
18+
19+module Text.Pandoc.Readers.Docx.Parse.Styles (
20+ CharStyleId(..)
21+ , CharStyle
22+ , ParaStyleId(..)
23+ , ParStyle(..)
24+ , RunStyle(..)
25+ , HasStyleName
26+ , StyleName
27+ , ParaStyleName
28+ , CharStyleName
29+ , FromStyleName
30+ , VertAlign(..)
31+ , StyleId
32+ , HasStyleId
33+ , archiveToStyles'
34+ , getStyleId
35+ , getStyleName
36+ , cStyleData
37+ , fromStyleName
38+ , fromStyleId
39+ , stringToInteger
40+ , getNumInfo
41+ , elemToRunStyle
42+ , defaultRunStyle
43+ ) where
44+import Prelude
45+import Codec.Archive.Zip
46+import Control.Applicative ((<|>))
47+import Control.Monad.Except
48+import Data.Char (toLower)
49+import Data.List
50+import Data.Function (on)
51+import Data.String (IsString(..))
52+import qualified Data.Map as M
53+import Data.Maybe
54+import Data.Coerce
55+import Text.Pandoc.Readers.Docx.Util
56+import qualified Text.Pandoc.UTF8 as UTF8
57+import Text.XML.Light
58+
59+newtype CharStyleId = CharStyleId String
60+ deriving (Show, Eq, Ord, IsString, FromStyleId)
61+newtype ParaStyleId = ParaStyleId String
62+ deriving (Show, Eq, Ord, IsString, FromStyleId)
63+
64+newtype CharStyleName = CharStyleName CIString
65+ deriving (Show, Eq, Ord, IsString, FromStyleName)
66+newtype ParaStyleName = ParaStyleName CIString
67+ deriving (Show, Eq, Ord, IsString, FromStyleName)
68+
69+-- Case-insensitive comparisons
70+newtype CIString = CIString String deriving (Show, IsString, FromStyleName)
71+
72+class FromStyleName a where
73+ fromStyleName :: a -> String
74+
75+instance FromStyleName String where
76+ fromStyleName = id
77+
78+class FromStyleId a where
79+ fromStyleId :: a -> String
80+
81+instance FromStyleId String where
82+ fromStyleId = id
83+
84+instance Eq CIString where
85+ (==) = (==) `on` map toLower . coerce
86+
87+instance Ord CIString where
88+ compare = compare `on` map toLower . coerce
89+
90+data VertAlign = BaseLn | SupScrpt | SubScrpt
91+ deriving Show
92+
93+data CharStyle = CharStyle { cStyleId :: CharStyleId
94+ , cStyleName :: CharStyleName
95+ , cStyleData :: RunStyle
96+ } deriving (Show)
97+
98+data RunStyle = RunStyle { isBold :: Maybe Bool
99+ , isItalic :: Maybe Bool
100+ , isSmallCaps :: Maybe Bool
101+ , isStrike :: Maybe Bool
102+ , isRTL :: Maybe Bool
103+ , rVertAlign :: Maybe VertAlign
104+ , rUnderline :: Maybe String
105+ , rParentStyle :: Maybe CharStyle
106+ }
107+ deriving Show
108+
109+data ParStyle = ParStyle { headingLev :: Maybe (ParaStyleName, Int)
110+ , numInfo :: Maybe (String, String)
111+ , psParentStyle :: Maybe ParStyle
112+ , pStyleName :: ParaStyleName
113+ , pStyleId :: ParaStyleId
114+ }
115+ deriving Show
116+
117+defaultRunStyle :: RunStyle
118+defaultRunStyle = RunStyle { isBold = Nothing
119+ , isItalic = Nothing
120+ , isSmallCaps = Nothing
121+ , isStrike = Nothing
122+ , isRTL = Nothing
123+ , rVertAlign = Nothing
124+ , rUnderline = Nothing
125+ , rParentStyle = Nothing
126+ }
127+
128+archiveToStyles' :: (Ord k1, Ord k2, ElemToStyle a1, ElemToStyle a2) =>
129+ (a1 -> k1) -> (a2 -> k2) -> Archive -> (M.Map k1 a1, M.Map k2 a2)
130+archiveToStyles' conv1 conv2 zf =
131+ let stylesElem = findEntryByPath "word/styles.xml" zf >>=
132+ (parseXMLDoc . UTF8.toStringLazy . fromEntry)
133+ in
134+ case stylesElem of
135+ Nothing -> (M.empty, M.empty)
136+ Just styElem ->
137+ let namespaces = elemToNameSpaces styElem
138+ in
139+ ( M.fromList $ map (\r -> (conv1 r, r)) $ buildBasedOnList namespaces styElem Nothing,
140+ M.fromList $ map (\p -> (conv2 p, p)) $ buildBasedOnList namespaces styElem Nothing)
141+
142+isBasedOnStyle :: (ElemToStyle a, FromStyleId (StyleId a)) => NameSpaces -> Element -> Maybe a -> Bool
143+isBasedOnStyle ns element parentStyle
144+ | isElem ns "w" "style" element
145+ , Just styleType <- findAttrByName ns "w" "type" element
146+ , styleType == cStyleType parentStyle
147+ , Just basedOnVal <- findChildByName ns "w" "basedOn" element >>=
148+ findAttrByName ns "w" "val"
149+ , Just ps <- parentStyle = basedOnVal == fromStyleId (getStyleId ps)
150+ | isElem ns "w" "style" element
151+ , Just styleType <- findAttrByName ns "w" "type" element
152+ , styleType == cStyleType parentStyle
153+ , Nothing <- findChildByName ns "w" "basedOn" element
154+ , Nothing <- parentStyle = True
155+ | otherwise = False
156+
157+class HasStyleId a => ElemToStyle a where
158+ cStyleType :: Maybe a -> String
159+ elemToStyle :: NameSpaces -> Element -> Maybe a -> Maybe a
160+
161+class FromStyleId (StyleId a) => HasStyleId a where
162+ type StyleId a
163+ getStyleId :: a -> StyleId a
164+
165+class FromStyleName (StyleName a) => HasStyleName a where
166+ type StyleName a
167+ getStyleName :: a -> StyleName a
168+
169+instance ElemToStyle CharStyle where
170+ cStyleType _ = "character"
171+ elemToStyle ns element parentStyle
172+ | isElem ns "w" "style" element
173+ , Just "character" <- findAttrByName ns "w" "type" element =
174+ elemToCharStyle ns element parentStyle
175+ | otherwise = Nothing
176+
177+instance HasStyleId CharStyle where
178+ type StyleId CharStyle = CharStyleId
179+ getStyleId = cStyleId
180+
181+instance HasStyleName CharStyle where
182+ type StyleName CharStyle = CharStyleName
183+ getStyleName = cStyleName
184+
185+instance ElemToStyle ParStyle where
186+ cStyleType _ = "paragraph"
187+ elemToStyle ns element parentStyle
188+ | isElem ns "w" "style" element
189+ , Just "paragraph" <- findAttrByName ns "w" "type" element
190+ = elemToParStyleData ns element parentStyle
191+ | otherwise = Nothing
192+
193+instance HasStyleId ParStyle where
194+ type StyleId ParStyle = ParaStyleId
195+ getStyleId = pStyleId
196+
197+instance HasStyleName ParStyle where
198+ type StyleName ParStyle = ParaStyleName
199+ getStyleName = pStyleName
200+
201+getStyleChildren :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
202+getStyleChildren ns element parentStyle
203+ | isElem ns "w" "styles" element =
204+ mapMaybe (\e -> elemToStyle ns e parentStyle) $
205+ filterChildren (\e' -> isBasedOnStyle ns e' parentStyle) element
206+ | otherwise = []
207+
208+buildBasedOnList :: (ElemToStyle a) => NameSpaces -> Element -> Maybe a -> [a]
209+buildBasedOnList ns element rootStyle =
210+ case getStyleChildren ns element rootStyle of
211+ [] -> []
212+ stys -> stys ++
213+ concatMap (buildBasedOnList ns element . Just) stys
214+
215+stringToInteger :: String -> Maybe Integer
216+stringToInteger s = listToMaybe $ map fst (reads s :: [(Integer, String)])
217+
218+checkOnOff :: NameSpaces -> Element -> QName -> Maybe Bool
219+checkOnOff ns rPr tag
220+ | Just t <- findChild tag rPr
221+ , Just val <- findAttrByName ns "w" "val" t =
222+ Just $ case val of
223+ "true" -> True
224+ "false" -> False
225+ "on" -> True
226+ "off" -> False
227+ "1" -> True
228+ "0" -> False
229+ _ -> False
230+ | Just _ <- findChild tag rPr = Just True
231+checkOnOff _ _ _ = Nothing
232+
233+elemToCharStyle :: NameSpaces
234+ -> Element -> Maybe CharStyle -> Maybe CharStyle
235+elemToCharStyle ns element parentStyle
236+ = CharStyle <$> (CharStyleId <$> findAttrByName ns "w" "styleId" element)
237+ <*> getElementStyleName ns element
238+ <*> (Just $ elemToRunStyle ns element parentStyle)
239+
240+elemToRunStyle :: NameSpaces -> Element -> Maybe CharStyle -> RunStyle
241+elemToRunStyle ns element parentStyle
242+ | Just rPr <- findChildByName ns "w" "rPr" element =
243+ RunStyle
244+ {
245+ isBold = checkOnOff ns rPr (elemName ns "w" "b") `mplus`
246+ checkOnOff ns rPr (elemName ns "w" "bCs")
247+ , isItalic = checkOnOff ns rPr (elemName ns "w" "i") `mplus`
248+ checkOnOff ns rPr (elemName ns "w" "iCs")
249+ , isSmallCaps = checkOnOff ns rPr (elemName ns "w" "smallCaps")
250+ , isStrike = checkOnOff ns rPr (elemName ns "w" "strike")
251+ , isRTL = checkOnOff ns rPr (elemName ns "w" "rtl")
252+ , rVertAlign =
253+ findChildByName ns "w" "vertAlign" rPr >>=
254+ findAttrByName ns "w" "val" >>=
255+ \v -> Just $ case v of
256+ "superscript" -> SupScrpt
257+ "subscript" -> SubScrpt
258+ _ -> BaseLn
259+ , rUnderline =
260+ findChildByName ns "w" "u" rPr >>=
261+ findAttrByName ns "w" "val"
262+ , rParentStyle = parentStyle
263+ }
264+elemToRunStyle _ _ _ = defaultRunStyle
265+
266+getHeaderLevel :: NameSpaces -> Element -> Maybe (ParaStyleName, Int)
267+getHeaderLevel ns element
268+ | Just styleName <- getElementStyleName ns element
269+ , Just n <- stringToInteger =<<
270+ (stripPrefix "heading " . map toLower $
271+ fromStyleName styleName)
272+ , n > 0 = Just (styleName, fromInteger n)
273+getHeaderLevel _ _ = Nothing
274+
275+getElementStyleName :: Coercible String a => NameSpaces -> Element -> Maybe a
276+getElementStyleName ns el = coerce <$>
277+ ((findChildByName ns "w" "name" el >>= findAttrByName ns "w" "val")
278+ <|> findAttrByName ns "w" "styleId" el)
279+
280+getNumInfo :: NameSpaces -> Element -> Maybe (String, String)
281+getNumInfo ns element = do
282+ let numPr = findChildByName ns "w" "pPr" element >>=
283+ findChildByName ns "w" "numPr"
284+ lvl = fromMaybe "0" (numPr >>=
285+ findChildByName ns "w" "ilvl" >>=
286+ findAttrByName ns "w" "val")
287+ numId <- numPr >>=
288+ findChildByName ns "w" "numId" >>=
289+ findAttrByName ns "w" "val"
290+ return (numId, lvl)
291+
292+elemToParStyleData :: NameSpaces -> Element -> Maybe ParStyle -> Maybe ParStyle
293+elemToParStyleData ns element parentStyle
294+ | Just styleId <- findAttrByName ns "w" "styleId" element
295+ , Just styleName <- getElementStyleName ns element
296+ = Just $ ParStyle
297+ {
298+ headingLev = getHeaderLevel ns element
299+ , numInfo = getNumInfo ns element
300+ , psParentStyle = parentStyle
301+ , pStyleName = styleName
302+ , pStyleId = ParaStyleId styleId
303+ }
304+elemToParStyleData _ _ _ = Nothing
src/Text/Pandoc/Readers/Docx/StyleMap.hsdeleted−123
@@ -1,123 +0,0 @@
1−{-# LANGUAGE NoImplicitPrelude #-}
2−{- |
3− Module : Text.Pandoc.Readers.Docx.StyleMaps
4− Copyright : © 2014-2019 Jesse Rosenthal <jrosenthal@jhu.edu>,
5− 2014-2019 John MacFarlane <jgm@berkeley.edu>,
6− 2015 Nikolay Yakimov <root@livid.pp.ru>
7− License : GNU GPL, version 2 or above
8−
9− Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
10− Stability : alpha
11− Portability : portable
12−
13−Mappings of element styles (word to pandoc-internal).
14−-}
15−module Text.Pandoc.Readers.Docx.StyleMap ( StyleMaps(..)
16− , alterMap
17− , getMap
18− , defaultStyleMaps
19− , getStyleMaps
20− , getStyleId
21− , hasStyleName
22− ) where
23−
24−import Prelude
25−import Control.Monad.State.Strict
26−import Data.Char (toLower)
27−import qualified Data.Map as M
28−import Text.Pandoc.Readers.Docx.Util
29−import Text.XML.Light
30−
31−newtype ParaStyleMap = ParaStyleMap ( M.Map String String )
32−newtype CharStyleMap = CharStyleMap ( M.Map String String )
33−
34−class StyleMap a where
35− alterMap :: (M.Map String String -> M.Map String String) -> a -> a
36− getMap :: a -> M.Map String String
37−
38−instance StyleMap ParaStyleMap where
39− alterMap f (ParaStyleMap m) = ParaStyleMap $ f m
40− getMap (ParaStyleMap m) = m
41−
42−instance StyleMap CharStyleMap where
43− alterMap f (CharStyleMap m) = CharStyleMap $ f m
44− getMap (CharStyleMap m) = m
45−
46−insert :: (StyleMap a) => Maybe String -> Maybe String -> a -> a
47−insert (Just k) (Just v) m = alterMap (M.insert k v) m
48−insert _ _ m = m
49−
50−getStyleId :: (StyleMap a) => String -> a -> String
51−getStyleId s = M.findWithDefault (filter (/=' ') s) (map toLower s) . getMap
52−
53−hasStyleName :: (StyleMap a) => String -> a -> Bool
54−hasStyleName styleName = M.member (map toLower styleName) . getMap
55−
56−data StyleMaps = StyleMaps { sNameSpaces :: NameSpaces
57− , sParaStyleMap :: ParaStyleMap
58− , sCharStyleMap :: CharStyleMap
59− }
60−
61−data StyleType = ParaStyle | CharStyle
62−
63−defaultStyleMaps :: StyleMaps
64−defaultStyleMaps = StyleMaps { sNameSpaces = []
65− , sParaStyleMap = ParaStyleMap M.empty
66− , sCharStyleMap = CharStyleMap M.empty
67− }
68−
69−type StateM a = State StyleMaps a
70−
71−getStyleMaps :: Element -> StyleMaps
72−getStyleMaps docElem = execState genStyleMap state'
73− where
74− state' = defaultStyleMaps {sNameSpaces = elemToNameSpaces docElem}
75− genStyleItem e = do
76− styleType <- getStyleType e
77− styleId <- getAttrStyleId e
78− nameValLowercase <- fmap (map toLower) `fmap` getNameVal e
79− case styleType of
80− Just ParaStyle -> modParaStyleMap $ insert nameValLowercase styleId
81− Just CharStyle -> modCharStyleMap $ insert nameValLowercase styleId
82− _ -> return ()
83− genStyleMap = do
84− style <- elemName' "style"
85− let styles = findChildren style docElem
86− forM_ styles genStyleItem
87−
88−modParaStyleMap :: (ParaStyleMap -> ParaStyleMap) -> StateM ()
89−modParaStyleMap f = modify $ \s ->
90− s {sParaStyleMap = f $ sParaStyleMap s}
91−
92−modCharStyleMap :: (CharStyleMap -> CharStyleMap) -> StateM ()
93−modCharStyleMap f = modify $ \s ->
94− s {sCharStyleMap = f $ sCharStyleMap s}
95−
96−getStyleType :: Element -> StateM (Maybe StyleType)
97−getStyleType e = do
98− styleTypeStr <- getAttrType e
99− case styleTypeStr of
100− Just "paragraph" -> return $ Just ParaStyle
101− Just "character" -> return $ Just CharStyle
102− _ -> return Nothing
103−
104−getAttrType :: Element -> StateM (Maybe String)
105−getAttrType el = do
106− name <- elemName' "type"
107− return $ findAttr name el
108−
109−getAttrStyleId :: Element -> StateM (Maybe String)
110−getAttrStyleId el = do
111− name <- elemName' "styleId"
112− return $ findAttr name el
113−
114−getNameVal :: Element -> StateM (Maybe String)
115−getNameVal el = do
116− name <- elemName' "name"
117− val <- elemName' "val"
118− return $ findChild name el >>= findAttr val
119−
120−elemName' :: String -> StateM QName
121−elemName' name = do
122− namespaces <- gets sNameSpaces
123− return $ elemName namespaces "w" name
src/Text/Pandoc/Writers/Docx.hs+30−31
@@ -3,6 +3,7 @@
33{-# LANGUAGE RankNTypes #-}
44{-# LANGUAGE ScopedTypeVariables #-}
55{-# LANGUAGE ViewPatterns #-}
6+{-# LANGUAGE OverloadedStrings #-}
67{- |
78 Module : Text.Pandoc.Writers.Docx
89 Copyright : Copyright (C) 2012-2019 John MacFarlane
@@ -23,8 +24,9 @@ import Control.Monad.Reader
2324import Control.Monad.State.Strict
2425import qualified Data.ByteString as B
2526import qualified Data.ByteString.Lazy as BL
26−import Data.Char (isSpace, ord, toLower, isLetter)
27+import Data.Char (isSpace, ord, isLetter)
2728import Data.List (intercalate, isPrefixOf, isSuffixOf)
29+import Data.String (fromString)
2830import qualified Data.Map as M
2931import Data.Maybe (fromMaybe, isNothing, mapMaybe, maybeToList)
3032import qualified Data.Set as Set
@@ -46,7 +48,7 @@ import Text.Pandoc.Logging
4648import Text.Pandoc.MIME (MimeType, extensionFromMimeType, getMimeType,
4749 getMimeTypeDef)
4850import Text.Pandoc.Options
49−import Text.Pandoc.Readers.Docx.StyleMap
51+import Text.Pandoc.Writers.Docx.StyleMap
5052import Text.Pandoc.Shared
5153import Text.Pandoc.Walk
5254import Text.Pandoc.Writers.Math
@@ -132,8 +134,8 @@ data WriterState = WriterState{
132134 , stFirstPara :: Bool
133135 , stInTable :: Bool
134136 , stTocTitle :: [Inline]
135− , stDynamicParaProps :: Set.Set String
136− , stDynamicTextProps :: Set.Set String
137+ , stDynamicParaProps :: Set.Set ParaStyleName
138+ , stDynamicTextProps :: Set.Set CharStyleName
137139 , stCurId :: Int
138140 }
139141
@@ -147,7 +149,7 @@ defaultWriterState = WriterState{
147149 , stLists = [NoMarker]
148150 , stInsId = 1
149151 , stDelId = 1
150− , stStyleMaps = defaultStyleMaps
152+ , stStyleMaps = StyleMaps M.empty M.empty
151153 , stFirstPara = False
152154 , stInTable = False
153155 , stTocTitle = [Str "Table of Contents"]
@@ -265,7 +267,7 @@ writeDocx opts doc@(Pandoc meta _) = do
265267 styledoc <- addLang <$> parseXml refArchive distArchive stylepath
266268
267269 -- parse styledoc for heading styles
268− let styleMaps = getStyleMaps styledoc
270+ let styleMaps = getStyleMaps refArchive
269271
270272 let tocTitle = case lookupMetaInlines "toc-title" meta of
271273 [] -> stTocTitle defaultWriterState
@@ -462,11 +464,11 @@ writeDocx opts doc@(Pandoc meta _) = do
462464 -- are not already in the style map. Note that keys in the stylemap
463465 -- are normalized as lowercase.
464466 let newDynamicParaProps = filter
465− (\sty -> isNothing $ M.lookup (toLower <$> sty) $ getMap $ sParaStyleMap styleMaps)
467+ (\sty -> not $ hasStyleName sty $ smParaStyle styleMaps)
466468 (Set.toList $ stDynamicParaProps st)
467469
468470 newDynamicTextProps = filter
469− (\sty -> isNothing $ M.lookup (toLower <$> sty) $ getMap $ sCharStyleMap styleMaps)
471+ (\sty -> not $ hasStyleName sty $ smCharStyle styleMaps)
470472 (Set.toList $ stDynamicTextProps st)
471473
472474 let newstyles = map newParaPropToOpenXml newDynamicParaProps ++
@@ -609,8 +611,8 @@ writeDocx opts doc@(Pandoc meta _) = do
609611 return $ fromArchive archive
610612
611613
612−newParaPropToOpenXml :: String -> Element
613−newParaPropToOpenXml s =
614+newParaPropToOpenXml :: ParaStyleName -> Element
615+newParaPropToOpenXml (fromStyleName -> s) =
614616 let styleId = filter (not . isSpace) s
615617 in mknode "w:style" [ ("w:type", "paragraph")
616618 , ("w:customStyle", "1")
@@ -620,8 +622,8 @@ newParaPropToOpenXml s =
620622 , mknode "w:qFormat" [] ()
621623 ]
622624
623−newTextPropToOpenXml :: String -> Element
624−newTextPropToOpenXml s =
625+newTextPropToOpenXml :: CharStyleName -> Element
626+newTextPropToOpenXml (fromStyleName -> s) =
625627 let styleId = filter (not . isSpace) s
626628 in mknode "w:style" [ ("w:type", "character")
627629 , ("w:customStyle", "1")
@@ -634,7 +636,7 @@ styleToOpenXml :: StyleMaps -> Style -> [Element]
634636styleToOpenXml sm style =
635637 maybeToList parStyle ++ mapMaybe toStyle alltoktypes
636638 where alltoktypes = enumFromTo KeywordTok NormalTok
637− toStyle toktype | hasStyleName (show toktype) (sCharStyleMap sm) = Nothing
639+ toStyle toktype | hasStyleName (fromString $ show toktype) (smCharStyle sm) = Nothing
638640 | otherwise = Just $
639641 mknode "w:style" [("w:type","character"),
640642 ("w:customStyle","1"),("w:styleId",show toktype)]
@@ -657,7 +659,7 @@ styleToOpenXml sm style =
657659 tokBg toktype = maybe "auto" (drop 1 . fromColor)
658660 $ (tokenBackground =<< M.lookup toktype tokStyles)
659661 `mplus` backgroundColor style
660− parStyle | hasStyleName "Source Code" (sParaStyleMap sm) = Nothing
662+ parStyle | hasStyleName "Source Code" (smParaStyle sm) = Nothing
661663 | otherwise = Just $
662664 mknode "w:style" [("w:type","paragraph"),
663665 ("w:customStyle","1"),("w:styleId","SourceCode")]
@@ -848,17 +850,17 @@ writeOpenXML opts (Pandoc meta blocks) = do
848850blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Element]
849851blocksToOpenXML opts bls = concat `fmap` mapM (blockToOpenXML opts) bls
850852
851−pStyleM :: (PandocMonad m) => String -> WS m XML.Element
853+pStyleM :: (PandocMonad m) => ParaStyleName -> WS m XML.Element
852854pStyleM styleName = do
853− styleMaps <- gets stStyleMaps
854− let sty' = getStyleId styleName $ sParaStyleMap styleMaps
855− return $ mknode "w:pStyle" [("w:val",sty')] ()
855+ pStyleMap <- gets (smParaStyle . stStyleMaps)
856+ let sty' = getStyleIdFromName styleName pStyleMap
857+ return $ mknode "w:pStyle" [("w:val", fromStyleId sty')] ()
856858
857−rStyleM :: (PandocMonad m) => String -> WS m XML.Element
859+rStyleM :: (PandocMonad m) => CharStyleName -> WS m XML.Element
858860rStyleM styleName = do
859− styleMaps <- gets stStyleMaps
860− let sty' = getStyleId styleName $ sCharStyleMap styleMaps
861− return $ mknode "w:rStyle" [("w:val",sty')] ()
861+ cStyleMap <- gets (smCharStyle . stStyleMaps)
862+ let sty' = getStyleIdFromName styleName cStyleMap
863+ return $ mknode "w:rStyle" [("w:val", fromStyleId sty')] ()
862864
863865getUniqueId :: (PandocMonad m) => WS m String
864866-- the + 20 is to ensure that there are no clashes with the rIds
@@ -880,7 +882,7 @@ blockToOpenXML' :: (PandocMonad m) => WriterOptions -> Block -> WS m [Element]
880882blockToOpenXML' _ Null = return []
881883blockToOpenXML' opts (Div (ident,_classes,kvs) bs) = do
882884 stylemod <- case lookup dynamicStyleKey kvs of
883− Just sty -> do
885+ Just (fromString -> sty) -> do
884886 modify $ \s ->
885887 s{stDynamicParaProps = Set.insert sty
886888 (stDynamicParaProps s)}
@@ -901,7 +903,7 @@ blockToOpenXML' opts (Div (ident,_classes,kvs) bs) = do
901903 wrapBookmark ident $ header ++ contents
902904blockToOpenXML' opts (Header lev (ident,_,_) lst) = do
903905 setFirstPara
904− paraProps <- withParaPropM (pStyleM ("Heading "++show lev)) $
906+ paraProps <- withParaPropM (pStyleM (fromString $ "Heading "++show lev)) $
905907 getParaProps False
906908 contents <- inlinesToOpenXML opts lst
907909 if null ident
@@ -916,8 +918,7 @@ blockToOpenXML' opts (Plain lst) = do
916918 isInTable <- gets stInTable
917919 let block = blockToOpenXML opts (Para lst)
918920 prop <- pStyleM "Compact"
919− para <- if isInTable then withParaProp prop block else block
920− return $ para
921+ if isInTable then withParaProp prop block else block
921922-- title beginning with fig: indicates that the image is a figure
922923blockToOpenXML' opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) = do
923924 setFirstPara
@@ -1087,9 +1088,7 @@ getTextProps :: (PandocMonad m) => WS m [Element]
10871088getTextProps = do
10881089 props <- asks envTextProperties
10891090 let squashed = squashProps props
1090− return $ if null squashed
1091− then []
1092− else [mknode "w:rPr" [] squashed]
1091+ return [mknode "w:rPr" [] squashed | (not . null) squashed]
10931092
10941093withTextProp :: PandocMonad m => Element -> WS m a -> WS m a
10951094withTextProp d p =
@@ -1174,7 +1173,7 @@ inlineToOpenXML' _ (Span (ident,["comment-end"],kvs) _) =
11741173 ]
11751174inlineToOpenXML' opts (Span (ident,classes,kvs) ils) = do
11761175 stylemod <- case lookup dynamicStyleKey kvs of
1177− Just sty -> do
1176+ Just (fromString -> sty) -> do
11781177 modify $ \s ->
11791178 s{stDynamicTextProps = Set.insert sty
11801179 (stDynamicTextProps s)}
@@ -1259,7 +1258,7 @@ inlineToOpenXML' opts (Math mathType str) = do
12591258inlineToOpenXML' opts (Cite _ lst) = inlinesToOpenXML opts lst
12601259inlineToOpenXML' opts (Code attrs str) = do
12611260 let alltoktypes = [KeywordTok ..]
1262− tokTypesMap <- mapM (\tt -> (,) tt <$> rStyleM (show tt)) alltoktypes
1261+ tokTypesMap <- mapM (\tt -> (,) tt <$> rStyleM (fromString $ show tt)) alltoktypes
12631262 let unhighlighted = intercalate [br] `fmap`
12641263 mapM formattedString (lines str)
12651264 formatOpenXML _fmtOpts = intercalate [br] . map (map toHlTok)
src/Text/Pandoc/Writers/Docx/StyleMap.hsnew file+48
@@ -0,0 +1,48 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{- |
4+ Module : Text.Pandoc.Writers.Docx.StyleMap
5+ Copyright : © 2014-2019 Jesse Rosenthal <jrosenthal@jhu.edu>,
6+ 2014-2019 John MacFarlane <jgm@berkeley.edu>,
7+ 2015-2019 Nikolay Yakimov <root@livid.pp.ru>
8+ License : GNU GPL, version 2 or above
9+
10+ Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
11+ Stability : alpha
12+ Portability : portable
13+
14+Mappings of element styles (word to pandoc-internal).
15+-}
16+
17+module Text.Pandoc.Writers.Docx.StyleMap ( StyleMaps(..)
18+ , ParaStyleName
19+ , CharStyleName
20+ , getStyleMaps
21+ , getStyleIdFromName
22+ , hasStyleName
23+ , fromStyleId
24+ , fromStyleName
25+ ) where
26+
27+import Text.Pandoc.Readers.Docx.Parse.Styles
28+import Codec.Archive.Zip
29+import qualified Data.Map as M
30+import Data.String
31+import Data.Char (isSpace)
32+import Prelude
33+
34+data StyleMaps = StyleMaps { smCharStyle :: CharStyleNameMap, smParaStyle :: ParaStyleNameMap }
35+type ParaStyleNameMap = M.Map ParaStyleName ParStyle
36+type CharStyleNameMap = M.Map CharStyleName CharStyle
37+
38+getStyleIdFromName :: (Ord sn, FromStyleName sn, IsString (StyleId sty), HasStyleId sty)
39+ => sn -> M.Map sn sty -> StyleId sty
40+getStyleIdFromName s = maybe (fallback s) getStyleId . M.lookup s
41+ where fallback = fromString . filter (not . isSpace) . fromStyleName
42+
43+hasStyleName :: (Ord sn, HasStyleId sty)
44+ => sn -> M.Map sn sty -> Bool
45+hasStyleName styleName = M.member styleName
46+
47+getStyleMaps :: Archive -> StyleMaps
48+getStyleMaps = uncurry StyleMaps . archiveToStyles' getStyleName getStyleName