git.iko.soy/typeable/pandoc
on master help sign in
Merge branch 'lierdakil-docx-reader-styles'
commit780079aaec6b82d3d235e20afda06cdfc8b486d5 authorJohn MacFarlane <jgm@berkeley.edu> dateSat 21 Sep 2019, 11:39 (7 years ago) parente3a66489dbfd23
17 files changed+407−312
.gitignore+1 MANUAL.txt+3−3 src/Text/Pandoc/Readers/Docx.hs+99−113 src/Text/Pandoc/Readers/Docx/Lists.hs+15−10 src/Text/Pandoc/Readers/Docx/Parse.hs+202−113 src/Text/Pandoc/Writers/Docx.hs+61−66 test/Tests/Readers/Docx.hs+9 test/docx/0_level_headers.native+2−2 test/docx/adjacent_codeblocks.docx test/docx/compact-style-removal.docx test/docx/compact-style-removal.native+5 test/docx/custom-style-with-styles.native+3−3 test/docx/lists-compact.docx test/docx/lists-compact.native+5 test/docx/lists.docx test/docx/lists.native+1−1 test/docx/nested_anchors_in_header.native+1−1
.gitignore+1
@@ -23,3 +23,4 @@ windows/*.wixobj
2323data/reference.docx
2424data/reference.odt
2525.stack-work
26+cabal.project.local
MANUAL.txt+3−3
@@ -5321,17 +5321,17 @@ And with the extension:
53215321
53225322 $ pandoc test/docx/custom-style-reference.docx -f docx+styles -t markdown
53235323
5324− ::: {custom-style="FirstParagraph"}
5324+ ::: {custom-style="First Paragraph"}
53255325 This is some text.
53265326 :::
53275327
5328− ::: {custom-style="BodyText"}
5328+ ::: {custom-style="Body Text"}
53295329 This is text with an [emphasized]{custom-style="Emphatic"} text style.
53305330 And this is text with a [strengthened]{custom-style="Strengthened"}
53315331 text style.
53325332 :::
53335333
5334− ::: {custom-style="MyBlockStyle"}
5334+ ::: {custom-style="My Block Style"}
53355335 > Here is a styled paragraph that inherits from Block Text.
53365336 :::
53375337
src/Text/Pandoc/Readers/Docx.hs+99−113
@@ -2,6 +2,8 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE PatternGuards #-}
5+{-# LANGUAGE MultiWayIf #-}
6+{-# LANGUAGE FlexibleContexts #-}
57{- |
68 Module : Text.Pandoc.Readers.Docx
79 Copyright : Copyright (C) 2014-2019 Jesse Rosenthal
@@ -64,6 +66,7 @@ import Control.Monad.State.Strict
6466import qualified Data.ByteString.Lazy as B
6567import Data.Default (Default)
6668import Data.List (delete, intersect)
69+import Data.Char (isSpace)
6770import qualified Data.Map as M
6871import Data.Maybe (isJust, fromMaybe)
6972import Data.Sequence (ViewL (..), viewl)
@@ -129,16 +132,16 @@ instance Default DEnv where
129132type DocxContext m = ReaderT DEnv (StateT DState m)
130133
131134evalDocxContext :: PandocMonad m => DocxContext m a -> DEnv -> DState -> m a
132−evalDocxContext ctx env st = flip evalStateT st $flip runReaderT env ctx
135+evalDocxContext ctx env st = flip evalStateT st $ runReaderT ctx env
133136
134137-- This is empty, but we put it in for future-proofing.
135−spansToKeep :: [String]
138+spansToKeep :: [CharStyleName]
136139spansToKeep = []
137140
138−divsToKeep :: [String]
139−divsToKeep = ["list-item", "Definition", "DefinitionTerm"]
141+divsToKeep :: [ParaStyleName]
142+divsToKeep = ["Definition", "Definition Term"]
140143
141−metaStyles :: M.Map String String
144+metaStyles :: M.Map ParaStyleName String
142145metaStyles = M.fromList [ ("Title", "title")
143146 , ("Subtitle", "subtitle")
144147 , ("Author", "author")
@@ -150,7 +153,7 @@ sepBodyParts = span (\bp -> isMetaPar bp || isEmptyPar bp)
150153
151154isMetaPar :: BodyPart -> Bool
152155isMetaPar (Paragraph pPr _) =
153− not $ null $ intersect (pStyle pPr) (M.keys metaStyles)
156+ not $ null $ intersect (getStyleNames $ pStyle pPr) (M.keys metaStyles)
154157isMetaPar _ = False
155158
156159isEmptyPar :: BodyPart -> Bool
@@ -167,7 +170,7 @@ bodyPartsToMeta' :: PandocMonad m => [BodyPart] -> DocxContext m (M.Map String M
167170bodyPartsToMeta' [] = return M.empty
168171bodyPartsToMeta' (bp : bps)
169172 | (Paragraph pPr parParts) <- bp
170− , (c : _)<- (pStyle pPr) `intersect` (M.keys metaStyles)
173+ , (c : _)<- getStyleNames (pStyle pPr) `intersect` M.keys metaStyles
171174 , (Just metaField) <- M.lookup c metaStyles = do
172175 inlines <- smushInlines <$> mapM parPartToInlines parParts
173176 remaining <- bodyPartsToMeta' bps
@@ -197,11 +200,29 @@ fixAuthors (MetaBlocks blks) =
197200 g _ = MetaInlines []
198201fixAuthors mv = mv
199202
200−codeStyles :: [String]
201−codeStyles = ["VerbatimChar"]
203+isInheritedFromStyles :: (Eq (StyleName s), HasStyleName s, HasParentStyle s) => [StyleName s] -> s -> Bool
204+isInheritedFromStyles names sty
205+ | getStyleName sty `elem` names = True
206+ | Just psty <- getParentStyle sty = isInheritedFromStyles names psty
207+ | otherwise = False
202208
203−codeDivs :: [String]
204−codeDivs = ["SourceCode"]
209+hasStylesInheritedFrom :: [ParaStyleName] -> ParagraphStyle -> Bool
210+hasStylesInheritedFrom ns s = any (isInheritedFromStyles ns) $ pStyle s
211+
212+removeStyleNamed :: ParaStyleName -> ParagraphStyle -> ParagraphStyle
213+removeStyleNamed sn ps = ps{pStyle = filter (\psd -> getStyleName psd /= sn) $ pStyle ps}
214+
215+isCodeCharStyle :: CharStyle -> Bool
216+isCodeCharStyle = isInheritedFromStyles ["Verbatim Char"]
217+
218+isCodeDiv :: ParagraphStyle -> Bool
219+isCodeDiv = hasStylesInheritedFrom ["Source Code"]
220+
221+isBlockQuote :: ParStyle -> Bool
222+isBlockQuote =
223+ isInheritedFromStyles [
224+ "Quote", "Block Text", "Block Quote", "Block Quotation"
225+ ]
205226
206227runElemToInlines :: RunElem -> Inlines
207228runElemToInlines (TextRun s) = text s
@@ -227,57 +248,31 @@ parPartToString (InternalHyperLink _ runs) = concatMap runToString runs
227248parPartToString (ExternalHyperLink _ runs) = concatMap runToString runs
228249parPartToString _ = ""
229250
230−blacklistedCharStyles :: [String]
251+blacklistedCharStyles :: [CharStyleName]
231252blacklistedCharStyles = ["Hyperlink"]
232253
233254resolveDependentRunStyle :: PandocMonad m => RunStyle -> DocxContext m RunStyle
234255resolveDependentRunStyle rPr
235− | Just (s, _) <- rStyle rPr, s `elem` blacklistedCharStyles =
256+ | Just s <- rParentStyle rPr
257+ , getStyleName s `elem` blacklistedCharStyles =
236258 return rPr
237− | Just (_, cs) <- rStyle rPr = do
259+ | Just s <- rParentStyle rPr = do
238260 opts <- asks docxOptions
239261 if isEnabled Ext_styles opts
240262 then return rPr
241− else do rPr' <- resolveDependentRunStyle cs
242− return $
243− RunStyle { isBold = case isBold rPr of
244− Just bool -> Just bool
245− Nothing -> isBold rPr'
246− , isItalic = case isItalic rPr of
247− Just bool -> Just bool
248− Nothing -> isItalic rPr'
249− , isSmallCaps = case isSmallCaps rPr of
250− Just bool -> Just bool
251− Nothing -> isSmallCaps rPr'
252− , isStrike = case isStrike rPr of
253− Just bool -> Just bool
254− Nothing -> isStrike rPr'
255− , isRTL = case isRTL rPr of
256− Just bool -> Just bool
257− Nothing -> isRTL rPr'
258− , rVertAlign = case rVertAlign rPr of
259− Just valign -> Just valign
260− Nothing -> rVertAlign rPr'
261− , rUnderline = case rUnderline rPr of
262− Just ulstyle -> Just ulstyle
263− Nothing -> rUnderline rPr'
264− , rStyle = rStyle rPr
265− }
263+ else leftBiasedMergeRunStyle rPr <$> resolveDependentRunStyle (cStyleData s)
266264 | otherwise = return rPr
267265
268266runStyleToTransform :: PandocMonad m => RunStyle -> DocxContext m (Inlines -> Inlines)
269267runStyleToTransform rPr
270− | Just (s, _) <- rStyle rPr
271− , s `elem` spansToKeep = do
272− transform <- runStyleToTransform rPr{rStyle = Nothing}
273− return $ spanWith ("", [s], []) . transform
274− | Just (s, _) <- rStyle rPr = do
275− opts <- asks docxOptions
276− let extraInfo = if isEnabled Ext_styles opts
277− then spanWith ("", [], [("custom-style", s)])
278− else id
279− transform <- runStyleToTransform rPr{rStyle = Nothing}
280− return $ extraInfo . transform
268+ | Just sn <- getStyleName <$> rParentStyle rPr
269+ , sn `elem` spansToKeep = do
270+ transform <- runStyleToTransform rPr{rParentStyle = Nothing}
271+ return $ spanWith ("", [normalizeToClassName sn], []) . transform
272+ | Just s <- rParentStyle rPr = do
273+ ei <- extraInfo spanWith s
274+ transform <- runStyleToTransform rPr{rParentStyle = Nothing}
275+ return $ ei . transform
281276 | Just True <- isItalic rPr = do
282277 transform <- runStyleToTransform rPr{isItalic = Nothing}
283278 return $ emph . transform
@@ -309,8 +304,7 @@ runStyleToTransform rPr
309304
310305runToInlines :: PandocMonad m => Run -> DocxContext m Inlines
311306runToInlines (Run rs runElems)
312− | Just (s, _) <- rStyle rs
313− , s `elem` codeStyles = do
307+ | maybe False isCodeCharStyle $ rParentStyle rs = do
314308 rPr <- resolveDependentRunStyle rs
315309 let codeString = code $ concatMap runElemToString runElems
316310 return $ case rVertAlign rPr of
@@ -525,55 +519,49 @@ trimSps (Many ils) = Many $ Seq.dropWhileL isSp $Seq.dropWhileR isSp ils
525519 isSp LineBreak = True
526520 isSp _ = False
527521
522+extraInfo :: (Eq (StyleName a), PandocMonad m, HasStyleName a)
523+ => (Attr -> i -> i) -> a -> DocxContext m (i -> i)
524+extraInfo f s = do
525+ opts <- asks docxOptions
526+ return $ if | isEnabled Ext_styles opts
527+ -> f ("", [], [("custom-style", fromStyleName $ getStyleName s)])
528+ | otherwise -> id
529+
528530parStyleToTransform :: PandocMonad m => ParagraphStyle -> DocxContext m (Blocks -> Blocks)
529531parStyleToTransform pPr
530532 | (c:cs) <- pStyle pPr
531− , c `elem` divsToKeep = do
533+ , getStyleName c `elem` divsToKeep = do
532534 let pPr' = pPr { pStyle = cs }
533535 transform <- parStyleToTransform pPr'
534− return $ divWith ("", [c], []) . transform
536+ return $ divWith ("", [normalizeToClassName $ getStyleName c], []) . transform
535537 | (c:cs) <- pStyle pPr,
536− c `elem` listParagraphDivs = do
538+ getStyleName c `elem` listParagraphStyles = do
537539 let pPr' = pPr { pStyle = cs, indentation = Nothing}
538540 transform <- parStyleToTransform pPr'
539− return $ divWith ("", [c], []) . transform
540− | (c:cs) <- pStyle pPr
541− , Just True <- pBlockQuote pPr = do
542− opts <- asks docxOptions
543− let pPr' = pPr { pStyle = cs }
544− transform <- parStyleToTransform pPr'
545− let extraInfo = if isEnabled Ext_styles opts
546− then divWith ("", [], [("custom-style", c)])
547− else id
548− return $ extraInfo . blockQuote . transform
541+ return $ divWith ("", [normalizeToClassName $ getStyleName c], []) . transform
549542 | (c:cs) <- pStyle pPr = do
550− opts <- asks docxOptions
551− let pPr' = pPr { pStyle = cs}
543+ let pPr' = pPr { pStyle = cs }
552544 transform <- parStyleToTransform pPr'
553− let extraInfo = if isEnabled Ext_styles opts
554− then divWith ("", [], [("custom-style", c)])
555− else id
556− return $ extraInfo . transform
545+ ei <- extraInfo divWith c
546+ return $ ei . (if isBlockQuote c then blockQuote else id) . transform
557547 | null (pStyle pPr)
558− , Just left <- indentation pPr >>= leftParIndent
559− , Just hang <- indentation pPr >>= hangingParIndent = do
548+ , Just left <- indentation pPr >>= leftParIndent = do
560549 let pPr' = pPr { indentation = Nothing }
550+ hang = fromMaybe 0 $ indentation pPr >>= hangingParIndent
561551 transform <- parStyleToTransform pPr'
562− return $ case (left - hang) > 0 of
563− True -> blockQuote . transform
564− False -> transform
565− | null (pStyle pPr),
566− Just left <- indentation pPr >>= leftParIndent = do
567− let pPr' = pPr { indentation = Nothing }
568− transform <- parStyleToTransform pPr'
569− return $ case left > 0 of
570− True -> blockQuote . transform
571− False -> transform
552+ return $ if (left - hang) > 0
553+ then blockQuote . transform
554+ else transform
572555parStyleToTransform _ = return id
573556
557+normalizeToClassName :: (FromStyleName a) => a -> String
558+normalizeToClassName = map go . fromStyleName
559+ where go c | isSpace c = '-'
560+ | otherwise = c
561+
574562bodyPartToBlocks :: PandocMonad m => BodyPart -> DocxContext m Blocks
575563bodyPartToBlocks (Paragraph pPr parparts)
576− | not $ null $ codeDivs `intersect` (pStyle pPr) = do
564+ | isCodeDiv pPr = do
577565 transform <- parStyleToTransform pPr
578566 return $
579567 transform $
@@ -583,62 +571,60 @@ bodyPartToBlocks (Paragraph pPr parparts)
583571 ils <-local (\s-> s{docxInHeaderBlock=True})
584572 (smushInlines <$> mapM parPartToInlines parparts)
585573 makeHeaderAnchor $
586− headerWith ("", delete style (pStyle pPr), []) n ils
574+ headerWith ("", map normalizeToClassName . delete style $ getStyleNames (pStyle pPr), []) n ils
587575 | otherwise = do
588− ils <- (trimSps . smushInlines) <$> mapM parPartToInlines parparts
576+ ils <- trimSps . smushInlines <$> mapM parPartToInlines parparts
589577 prevParaIls <- gets docxPrevPara
590578 dropIls <- gets docxDropCap
591579 let ils' = dropIls <> ils
592− if dropCap pPr
580+ let (paraOrPlain, pPr')
581+ | hasStylesInheritedFrom ["Compact"] pPr = (plain, removeStyleNamed "Compact" pPr)
582+ | otherwise = (para, pPr)
583+ if dropCap pPr'
593584 then do modify $ \s -> s { docxDropCap = ils' }
594585 return mempty
595586 else do modify $ \s -> s { docxDropCap = mempty }
596587 let ils'' = prevParaIls <>
597588 (if isNull prevParaIls then mempty else space) <>
598589 ils'
590+ handleInsertion = do
591+ modify $ \s -> s {docxPrevPara = mempty}
592+ transform <- parStyleToTransform pPr'
593+ return $ transform $ paraOrPlain ils''
599594 opts <- asks docxOptions
600− case () of
601−
602− _ | isNull ils'' && not (isEnabled Ext_empty_paragraphs opts) ->
595+ if | isNull ils'' && not (isEnabled Ext_empty_paragraphs opts) ->
603596 return mempty
604− _ | Just (TrackedChange Insertion _) <- pChange pPr
605− , AcceptChanges <- readerTrackChanges opts -> do
606− modify $ \s -> s {docxPrevPara = mempty}
607− transform <- parStyleToTransform pPr
608− return $ transform $ para ils''
609− _ | Just (TrackedChange Insertion _) <- pChange pPr
597+ | Just (TrackedChange Insertion _) <- pChange pPr'
598+ , AcceptChanges <- readerTrackChanges opts ->
599+ handleInsertion
600+ | Just (TrackedChange Insertion _) <- pChange pPr'
610601 , RejectChanges <- readerTrackChanges opts -> do
611602 modify $ \s -> s {docxPrevPara = ils''}
612603 return mempty
613− _ | Just (TrackedChange Insertion cInfo) <- pChange pPr
604+ | Just (TrackedChange Insertion cInfo) <- pChange pPr'
614605 , AllChanges <- readerTrackChanges opts
615606 , ChangeInfo _ cAuthor cDate <- cInfo -> do
616607 let attr = ("", ["paragraph-insertion"], [("author", cAuthor), ("date", cDate)])
617608 insertMark = spanWith attr mempty
618− transform <- parStyleToTransform pPr
609+ transform <- parStyleToTransform pPr'
619610 return $ transform $
620− para $ ils'' <> insertMark
621− _ | Just (TrackedChange Deletion _) <- pChange pPr
611+ paraOrPlain $ ils'' <> insertMark
612+ | Just (TrackedChange Deletion _) <- pChange pPr'
622613 , AcceptChanges <- readerTrackChanges opts -> do
623614 modify $ \s -> s {docxPrevPara = ils''}
624615 return mempty
625− _ | Just (TrackedChange Deletion _) <- pChange pPr
626− , RejectChanges <- readerTrackChanges opts -> do
627− modify $ \s -> s {docxPrevPara = mempty}
628− transform <- parStyleToTransform pPr
629− return $ transform $ para ils''
630− _ | Just (TrackedChange Deletion cInfo) <- pChange pPr
616+ | Just (TrackedChange Deletion _) <- pChange pPr'
617+ , RejectChanges <- readerTrackChanges opts ->
618+ handleInsertion
619+ | Just (TrackedChange Deletion cInfo) <- pChange pPr'
631620 , AllChanges <- readerTrackChanges opts
632621 , ChangeInfo _ cAuthor cDate <- cInfo -> do
633622 let attr = ("", ["paragraph-deletion"], [("author", cAuthor), ("date", cDate)])
634623 insertMark = spanWith attr mempty
635− transform <- parStyleToTransform pPr
624+ transform <- parStyleToTransform pPr'
636625 return $ transform $
637− para $ ils'' <> insertMark
638− _ | otherwise -> do
639− modify $ \s -> s {docxPrevPara = mempty}
640− transform <- parStyleToTransform pPr
641− return $ transform $ para ils''
626+ paraOrPlain $ ils'' <> insertMark
627+ | otherwise -> handleInsertion
642628bodyPartToBlocks (ListItem pPr numId lvl (Just levelInfo) parparts) = do
643629 -- We check whether this current numId has previously been used,
644630 -- since Docx expects us to pick up where we left off.
@@ -658,7 +644,7 @@ bodyPartToBlocks (ListItem pPr numId lvl (Just levelInfo) parparts) = do
658644 blks <- bodyPartToBlocks (Paragraph pPr parparts)
659645 return $ divWith ("", ["list-item"], kvs) blks
660646bodyPartToBlocks (ListItem pPr _ _ _ parparts) =
661− let pPr' = pPr {pStyle = "ListParagraph": pStyle pPr}
647+ let pPr' = pPr {pStyle = constructBogusParStyleData "list-paragraph": pStyle pPr}
662648 in
663649 bodyPartToBlocks $ Paragraph pPr' parparts
664650bodyPartToBlocks (Tbl _ _ _ []) =
src/Text/Pandoc/Readers/Docx/Lists.hs+15−10
@@ -1,4 +1,5 @@
11{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE OverloadedStrings #-}
23{- |
34 Module : Text.Pandoc.Readers.Docx.Lists
45 Copyright : Copyright (C) 2014-2019 Jesse Rosenthal
@@ -14,13 +15,16 @@ Functions for converting flat docx paragraphs into nested lists.
1415module Text.Pandoc.Readers.Docx.Lists ( blocksToBullets
1516 , blocksToDefinitions
1617 , listParagraphDivs
18+ , listParagraphStyles
1719 ) where
1820
1921import Prelude
2022import Data.List
2123import Data.Maybe
24+import Data.String (fromString)
2225import Text.Pandoc.Generic (bottomUp)
2326import Text.Pandoc.JSON
27+import Text.Pandoc.Readers.Docx.Parse (ParaStyleName)
2428import Text.Pandoc.Shared (trim, safeRead)
2529
2630isListItem :: Block -> Bool
@@ -79,7 +83,10 @@ getListType b@(Div (_, _, kvs) _) | isListItem b =
7983getListType _ = Nothing
8084
8185listParagraphDivs :: [String]
82−listParagraphDivs = ["ListParagraph"]
86+listParagraphDivs = ["list-paragraph"]
87+
88+listParagraphStyles :: [ParaStyleName]
89+listParagraphStyles = map fromString listParagraphDivs
8390
8491-- This is a first stab at going through and attaching meaning to list
8592-- paragraphs, without an item marker, following a list item. We
@@ -160,7 +167,7 @@ blocksToDefinitions' defAcc acc [] =
160167 reverse $ DefinitionList (reverse defAcc) : acc
161168blocksToDefinitions' defAcc acc
162169 (Div (_, classes1, _) blks1 : Div (ident2, classes2, kvs2) blks2 : blks)
163− | "DefinitionTerm" `elem` classes1 && "Definition" `elem` classes2 =
170+ | "Definition-Term" `elem` classes1 && "Definition" `elem` classes2 =
164171 let remainingAttr2 = (ident2, delete "Definition" classes2, kvs2)
165172 pair = if remainingAttr2 == ("", [], []) then (concatMap plainParaInlines blks1, [blks2]) else (concatMap plainParaInlines blks1, [[Div remainingAttr2 blks2]])
166173 in
@@ -169,12 +176,12 @@ blocksToDefinitions' ((defTerm, defItems):defs) acc
169176 (Div (ident2, classes2, kvs2) blks2 : blks)
170177 | "Definition" `elem` classes2 =
171178 let remainingAttr2 = (ident2, delete "Definition" classes2, kvs2)
172− defItems2 = case remainingAttr2 == ("", [], []) of
173− True -> blks2
174− False -> [Div remainingAttr2 blks2]
175− defAcc' = case null defItems of
176− True -> (defTerm, [defItems2]) : defs
177− False -> (defTerm, init defItems ++ [last defItems ++ defItems2]) : defs
179+ defItems2 = if remainingAttr2 == ("", [], [])
180+ then blks2
181+ else [Div remainingAttr2 blks2]
182+ defAcc' = if null defItems
183+ then (defTerm, [defItems2]) : defs
184+ else (defTerm, init defItems ++ [last defItems ++ defItems2]) : defs
178185 in
179186 blocksToDefinitions' defAcc' acc blks
180187blocksToDefinitions' [] acc (b:blks) =
@@ -198,7 +205,5 @@ removeListDivs' blk = [blk]
198205removeListDivs :: [Block] -> [Block]
199206removeListDivs = concatMap removeListDivs'
200207
201−
202−
203208blocksToDefinitions :: [Block] -> [Block]
204209blocksToDefinitions = blocksToDefinitions' [] []
src/Text/Pandoc/Readers/Docx/Parse.hs+202−113
@@ -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
@@ -121,9 +139,9 @@ unwrap :: NameSpaces -> Content -> [Content]
121139unwrap ns (Elem element)
122140 | isElem ns "w" "sdt" element
123141 , Just sdtContent <- findChildByName ns "w" "sdtContent" element
124− = concatMap ((unwrap ns) . Elem) (elChildren sdtContent)
142+ = concatMap (unwrap ns . Elem) (elChildren sdtContent)
125143 | isElem ns "w" "smartTag" element
126− = concatMap ((unwrap ns) . Elem) (elChildren element)
144+ = concatMap (unwrap ns . Elem) (elChildren element)
127145unwrap _ content = [content]
128146
129147unwrapChild :: NameSpaces -> Content -> Content
@@ -149,24 +167,20 @@ walkDocument ns element =
149167 _ -> Nothing
150168
151169
152−data Docx = Docx Document
170+newtype Docx = Docx Document
153171 deriving Show
154172
155173data Document = Document NameSpaces Body
156174 deriving Show
157175
158−data Body = Body [BodyPart]
176+newtype Body = Body [BodyPart]
159177 deriving Show
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
@@ -242,18 +250,61 @@ data BodyPart = Paragraph ParagraphStyle [ParPart]
242250
243251type TblGrid = [Integer]
244252
245−data TblLook = TblLook {firstRowFormatting::Bool}
253+newtype TblLook = TblLook {firstRowFormatting::Bool}
246254 deriving Show
247255
248256defaultTblLook :: TblLook
249257defaultTblLook = TblLook{firstRowFormatting = False}
250258
251−data Row = Row [Cell]
259+newtype Row = Row [Cell]
252260 deriving Show
253261
254−data Cell = Cell [BodyPart]
262+newtype 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
@@ -495,7 +594,7 @@ filePathToRelType "word/_rels/endnotes.xml.rels" _ = Just InEndnote
495594-- -- to see if it's a documentPath, we have to check against the dynamic
496595-- -- docPath specified in "_rels/.rels"
497596filePathToRelType path docXmlPath =
498− if path == "word/_rels/" ++ (takeFileName docXmlPath) ++ ".rels"
597+ if path == "word/_rels/" ++ takeFileName docXmlPath ++ ".rels"
499598 then Just InDocument
500599 else Nothing
501600
@@ -537,7 +636,7 @@ lookupLevel numId ilvl (Numbering _ numbs absNumbs) = do
537636 case lvlOverride of
538637 Just (LevelOverride _ _ (Just lvl')) -> Just lvl'
539638 Just (LevelOverride _ (Just strt) _) ->
540− lookup ilvl $ map (\(Level i fmt s _) -> (i, (Level i fmt s (Just strt)))) lvls
639+ lookup ilvl $ map (\(Level i fmt s _) -> (i, Level i fmt s (Just strt))) lvls
541640 _ ->
542641 lookup ilvl $ map (\l@(Level i _ _ _) -> (i, l)) lvls
543642
@@ -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
@@ -703,23 +808,19 @@ elemToBodyPart ns element
703808elemToBodyPart ns element
704809 | isElem ns "w" "p" element
705810 , Just (numId, lvl) <- getNumInfo ns element = do
706− sty <- asks envParStyles
707− let parstyle = elemToParagraphStyle ns element sty
811+ parstyle <- elemToParagraphStyle ns element <$> asks envParStyles
708812 parparts <- mapD (elemToParPart ns) (elChildren element)
709− num <- asks envNumbering
710− let levelInfo = lookupLevel numId lvl num
813+ levelInfo <- lookupLevel numId lvl <$> asks envNumbering
711814 return $ ListItem parstyle numId lvl levelInfo parparts
712815elemToBodyPart ns element
713816 | isElem ns "w" "p" element = do
714− sty <- asks envParStyles
715− let parstyle = elemToParagraphStyle ns element sty
817+ parstyle <- elemToParagraphStyle ns element <$> asks envParStyles
716818 parparts <- mapD (elemToParPart ns) (elChildren element)
717819 -- Word uses list enumeration for numbered headings, so we only
718820 -- want to infer a list from the styles if it is NOT a heading.
719821 case pHeading parstyle of
720822 Nothing | Just (numId, lvl) <- pNumInfo parstyle -> do
721− num <- asks envNumbering
722− let levelInfo = lookupLevel numId lvl num
823+ levelInfo <- lookupLevel numId lvl <$> asks envNumbering
723824 return $ ListItem parstyle numId lvl levelInfo parparts
724825 _ -> return $ Paragraph parstyle parparts
725826elemToBodyPart ns element
@@ -727,7 +828,7 @@ elemToBodyPart ns element
727828 let caption' = findChildByName ns "w" "tblPr" element
728829 >>= findChildByName ns "w" "tblCaption"
729830 >>= findAttrByName ns "w" "val"
730− caption = (fromMaybe "" caption')
831+ caption = fromMaybe "" caption'
731832 grid' = case findChildByName ns "w" "tblGrid" element of
732833 Just g -> elemToTblGrid ns g
733834 Nothing -> return []
@@ -1007,20 +1108,18 @@ elemToRun ns element
10071108 return $ Run runStyle runElems
10081109elemToRun _ _ = throwError WrongElem
10091110
1010−getParentStyleValue :: (ParStyleData -> Maybe a) -> ParStyleData -> Maybe a
1111+getParentStyleValue :: (ParStyle -> Maybe a) -> ParStyle -> Maybe a
10111112getParentStyleValue field style
10121113 | Just value <- field style = Just value
1013− | Just parentStyle <- psStyle style
1014− = getParentStyleValue field (snd parentStyle)
1114+ | Just parentStyle <- psParentStyle style
1115+ = getParentStyleValue field parentStyle
10151116getParentStyleValue _ _ = Nothing
10161117
1017−getParStyleField :: (ParStyleData -> Maybe a) -> ParStyleMap -> [String] ->
1018− Maybe a
1019−getParStyleField field stylemap styles
1020− | x <- mapMaybe (\x -> M.lookup x stylemap) styles
1021− , (y:_) <- mapMaybe (getParentStyleValue field) x
1118+getParStyleField :: (ParStyle -> Maybe a) -> [ParStyle] -> Maybe a
1119+getParStyleField field styles
1120+ | (y:_) <- mapMaybe (getParentStyleValue field) styles
10221121 = Just y
1023−getParStyleField _ _ _ = Nothing
1122+getParStyleField _ _ = Nothing
10241123
10251124getTrackedChange :: NameSpaces -> Element -> Maybe TrackedChange
10261125getTrackedChange ns element
@@ -1042,10 +1141,10 @@ elemToParagraphStyle ns element sty
10421141 | Just pPr <- findChildByName ns "w" "pPr" element =
10431142 let style =
10441143 mapMaybe
1045− (findAttrByName ns "w" "val")
1144+ (fmap ParaStyleId . findAttrByName ns "w" "val")
10461145 (findChildrenByName ns "w" "pStyle" pPr)
10471146 in ParagraphStyle
1048− {pStyle = style
1147+ {pStyle = mapMaybe (`M.lookup` sty) style
10491148 , indentation =
10501149 findChildByName ns "w" "ind" pPr >>=
10511150 elemToParIndentation ns
@@ -1057,9 +1156,6 @@ elemToParagraphStyle ns element sty
10571156 Just "none" -> False
10581157 Just _ -> True
10591158 Nothing -> False
1060− , pHeading = getParStyleField headingLev sty style
1061− , pNumInfo = getParStyleField numInfo sty style
1062− , pBlockQuote = getParStyleField isBlockQuote sty style
10631159 , pChange = findChildByName ns "w" "rPr" pPr >>=
10641160 filterChild (\e -> isElem ns "w" "ins" e ||
10651161 isElem ns "w" "moveTo" e ||
@@ -1089,16 +1185,20 @@ elemToRunStyleD :: NameSpaces -> Element -> D RunStyle
10891185elemToRunStyleD ns element
10901186 | Just rPr <- findChildByName ns "w" "rPr" element = do
10911187 charStyles <- asks envCharStyles
1092− let parentSty = case
1188+ let parentSty =
10931189 findChildByName ns "w" "rStyle" rPr >>=
1094− findAttrByName ns "w" "val"
1095− of
1096− Just styName | Just style <- M.lookup styName charStyles ->
1097− Just (styName, style)
1098− _ -> Nothing
1190+ findAttrByName ns "w" "val" >>=
1191+ flip M.lookup charStyles . CharStyleId
10991192 return $ elemToRunStyle ns element parentSty
11001193elemToRunStyleD _ _ = return defaultRunStyle
11011194
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+
11021202elemToRunStyle :: NameSpaces -> Element -> Maybe CharStyle -> RunStyle
11031203elemToRunStyle ns element parentStyle
11041204 | Just rPr <- findChildByName ns "w" "rPr" element =
@@ -1121,38 +1221,23 @@ elemToRunStyle ns element parentStyle
11211221 , rUnderline =
11221222 findChildByName ns "w" "u" rPr >>=
11231223 findAttrByName ns "w" "val"
1124− , rStyle = parentStyle
1224+ , rParentStyle = parentStyle
11251225 }
11261226elemToRunStyle _ _ _ = defaultRunStyle
11271227
1128−getHeaderLevel :: NameSpaces -> Element -> Maybe (String,Int)
1228+getHeaderLevel :: NameSpaces -> Element -> Maybe (ParaStyleName, Int)
11291229getHeaderLevel ns element
1130− | Just styleId <- findAttrByName ns "w" "styleId" element
1131− , Just index <- stripPrefix "Heading" styleId
1132− , Just n <- stringToInteger index
1133− , n > 0 = Just (styleId, fromInteger n)
1134− | Just styleId <- findAttrByName ns "w" "styleId" element
1135− , Just index <- findChildByName ns "w" "name" element >>=
1136− findAttrByName ns "w" "val" >>=
1137− stripPrefix "heading "
1138− , Just n <- stringToInteger index
1139− , 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)
11401235getHeaderLevel _ _ = Nothing
11411236
1142−blockQuoteStyleIds :: [String]
1143−blockQuoteStyleIds = ["Quote", "BlockQuote", "BlockQuotation"]
1144−
1145−blockQuoteStyleNames :: [String]
1146−blockQuoteStyleNames = ["Quote", "Block Text"]
1147−
1148−getBlockQuote :: NameSpaces -> Element -> Maybe Bool
1149−getBlockQuote ns element
1150− | Just styleId <- findAttrByName ns "w" "styleId" element
1151− , styleId `elem` blockQuoteStyleIds = Just True
1152− | Just styleName <- findChildByName ns "w" "name" element >>=
1153− findAttrByName ns "w" "val"
1154− , styleName `elem` blockQuoteStyleNames = Just True
1155−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)
11561241
11571242getNumInfo :: NameSpaces -> Element -> Maybe (String, String)
11581243getNumInfo ns element = do
@@ -1167,15 +1252,19 @@ getNumInfo ns element = do
11671252 return (numId, lvl)
11681253
11691254
1170−elemToParStyleData :: NameSpaces -> Element -> Maybe ParStyle -> ParStyleData
1171−elemToParStyleData ns element parentStyle =
1172− 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
11731260 {
11741261 headingLev = getHeaderLevel ns element
1175− , isBlockQuote = getBlockQuote ns element
11761262 , numInfo = getNumInfo ns element
1177− , psStyle = parentStyle
1178− }
1263+ , psParentStyle = parentStyle
1264+ , pStyleName = styleName
1265+ , pStyleId = ParaStyleId styleId
1266+ }
1267+elemToParStyleData _ _ _ = Nothing
11791268
11801269elemToRunElem :: NameSpaces -> Element -> D RunElem
11811270elemToRunElem ns element
src/Text/Pandoc/Writers/Docx.hs+61−66
@@ -208,7 +208,7 @@ writeDocx opts doc@(Pandoc meta _) = do
208208 let doc' = walk fixDisplayMath doc
209209 username <- P.lookupEnv "USERNAME"
210210 utctime <- P.getCurrentTime
211− distArchive <- (toArchive . BL.fromStrict) <$> do
211+ distArchive <- toArchive . BL.fromStrict <$> do
212212 oldUserDataDir <- P.getUserDataDir
213213 P.setUserDataDir Nothing
214214 res <- P.readDefaultDataFile "reference.docx"
@@ -216,7 +216,7 @@ writeDocx opts doc@(Pandoc meta _) = do
216216 return res
217217 refArchive <- case writerReferenceDoc opts of
218218 Just f -> toArchive <$> P.readFileLazy f
219− Nothing -> (toArchive . BL.fromStrict) <$>
219+ Nothing -> toArchive . BL.fromStrict <$>
220220 P.readDataFile "reference.docx"
221221
222222 parsedDoc <- parseXml refArchive distArchive "word/document.xml"
@@ -237,7 +237,7 @@ writeDocx opts doc@(Pandoc meta _) = do
237237 >>= subtrct mbAttrMarRight
238238 >>= subtrct mbAttrMarLeft
239239 where
240− subtrct mbStr = \x -> mbStr >>= safeRead >>= (\y -> Just $ x - y)
240+ subtrct mbStr x = mbStr >>= safeRead >>= (\y -> Just $ x - y)
241241
242242 -- styles
243243 mblang <- toLang $ getLang opts meta
@@ -285,7 +285,7 @@ writeDocx opts doc@(Pandoc meta _) = do
285285 envRTL = isRTLmeta
286286 , envChangesAuthor = fromMaybe "unknown" username
287287 , envChangesDate = formatTime defaultTimeLocale "%FT%XZ" utctime
288− , envPrintWidth = maybe 420 (\x -> quot x 20) pgContentWidth
288+ , envPrintWidth = maybe 420 (`quot` 20) pgContentWidth
289289 }
290290
291291
@@ -366,7 +366,7 @@ writeDocx opts doc@(Pandoc meta _) = do
366366 map (\x -> (maybe "" ("/word/" ++) $ extractTarget x,
367367 "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml")) footers) ++
368368 map mkImageOverride imgs ++
369− map mkMediaOverride [ eRelativePath e | e <- zEntries refArchive
369+ [ mkMediaOverride (eRelativePath e) | e <- zEntries refArchive
370370 , "word/media/" `isPrefixOf` eRelativePath e ]
371371
372372 let defaultnodes = [mknode "Default"
@@ -589,8 +589,8 @@ writeDocx opts doc@(Pandoc meta _) = do
589589 mapMaybe (fmap ("word/" ++) . extractTarget)
590590 (headers ++ footers)
591591 let miscRelEntries = [ e | e <- zEntries refArchive
592− , "word/_rels/" `isPrefixOf` (eRelativePath e)
593− , ".xml.rels" `isSuffixOf` (eRelativePath e)
592+ , "word/_rels/" `isPrefixOf` eRelativePath e
593+ , ".xml.rels" `isSuffixOf` eRelativePath e
594594 , eRelativePath e /= "word/_rels/document.xml.rels"
595595 , eRelativePath e /= "word/_rels/footnotes.xml.rels" ]
596596 let otherMediaEntries = [ e | e <- zEntries refArchive
@@ -778,24 +778,24 @@ makeTOC opts = do
778778 tocTitle <- gets stTocTitle
779779 title <- withParaPropM (pStyleM "TOC Heading") (blocksToOpenXML opts [Para tocTitle])
780780 return
781− [mknode "w:sdt" [] ([
781+ [mknode "w:sdt" [] [
782782 mknode "w:sdtPr" [] (
783− mknode "w:docPartObj" [] (
783+ mknode "w:docPartObj" []
784784 [mknode "w:docPartGallery" [("w:val","Table of Contents")] (),
785785 mknode "w:docPartUnique" [] ()]
786− ) -- w:docPartObj
786+ -- w:docPartObj
787787 ), -- w:sdtPr
788788 mknode "w:sdtContent" [] (title++[
789789 mknode "w:p" [] (
790− mknode "w:r" [] ([
790+ mknode "w:r" [] [
791791 mknode "w:fldChar" [("w:fldCharType","begin"),("w:dirty","true")] (),
792792 mknode "w:instrText" [("xml:space","preserve")] tocCmd,
793793 mknode "w:fldChar" [("w:fldCharType","separate")] (),
794794 mknode "w:fldChar" [("w:fldCharType","end")] ()
795− ]) -- w:r
795+ ] -- w:r
796796 ) -- w:p
797797 ])
798− ])] -- w:sdt
798+ ]] -- w:sdt
799799
800800-- | Convert Pandoc document to two lists of
801801-- OpenXML elements (the main document and footnotes).
@@ -809,12 +809,12 @@ writeOpenXML opts (Pandoc meta blocks) = do
809809 let includeTOC = writerTableOfContents opts || lookupMetaBool "toc" meta
810810 title <- withParaPropM (pStyleM "Title") $ blocksToOpenXML opts [Para tit | not (null tit)]
811811 subtitle <- withParaPropM (pStyleM "Subtitle") $ blocksToOpenXML opts [Para subtitle' | not (null subtitle')]
812− authors <- withParaProp (pCustomStyle "Author") $ blocksToOpenXML opts $
812+ authors <- withParaPropM (pStyleM "Author") $ blocksToOpenXML opts $
813813 map Para auths
814814 date <- withParaPropM (pStyleM "Date") $ blocksToOpenXML opts [Para dat | not (null dat)]
815815 abstract <- if null abstract'
816816 then return []
817− else withParaProp (pCustomStyle "Abstract") $ blocksToOpenXML opts abstract'
817+ else withParaPropM (pStyleM "Abstract") $ blocksToOpenXML opts abstract'
818818 let convertSpace (Str x : Space : Str y : xs) = Str (x ++ " " ++ y) : xs
819819 convertSpace (Str x : Str y : xs) = Str (x ++ y) : xs
820820 convertSpace xs = xs
@@ -848,18 +848,12 @@ writeOpenXML opts (Pandoc meta blocks) = do
848848blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Element]
849849blocksToOpenXML opts bls = concat `fmap` mapM (blockToOpenXML opts) bls
850850
851−pCustomStyle :: String -> Element
852−pCustomStyle sty = mknode "w:pStyle" [("w:val",sty)] ()
853−
854851pStyleM :: (PandocMonad m) => String -> WS m XML.Element
855852pStyleM styleName = do
856853 styleMaps <- gets stStyleMaps
857854 let sty' = getStyleId styleName $ sParaStyleMap styleMaps
858855 return $ mknode "w:pStyle" [("w:val",sty')] ()
859856
860−rCustomStyle :: String -> Element
861−rCustomStyle sty = mknode "w:rStyle" [("w:val",sty)] ()
862−
863857rStyleM :: (PandocMonad m) => String -> WS m XML.Element
864858rStyleM styleName = do
865859 styleMaps <- gets stStyleMaps
@@ -921,19 +915,19 @@ blockToOpenXML' opts (Header lev (ident,_,_) lst) = do
921915blockToOpenXML' opts (Plain lst) = do
922916 isInTable <- gets stInTable
923917 let block = blockToOpenXML opts (Para lst)
924− para <- if isInTable then withParaProp (pCustomStyle "Compact") block else block
918+ prop <- pStyleM "Compact"
919+ para <- if isInTable then withParaProp prop block else block
925920 return $ para
926−
927921-- title beginning with fig: indicates that the image is a figure
928922blockToOpenXML' opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) = do
929923 setFirstPara
930− let prop = pCustomStyle $
924+ prop <- pStyleM $
931925 if null alt
932926 then "Figure"
933− else "CaptionedFigure"
927+ else "Captioned Figure"
934928 paraProps <- local (\env -> env { envParaProperties = EnvProps (Just prop) [] <> envParaProperties env }) (getParaProps False)
935929 contents <- inlinesToOpenXML opts [Image attr alt (src,tit)]
936− captionNode <- withParaProp (pCustomStyle "ImageCaption")
930+ captionNode <- withParaPropM (pStyleM "Image Caption")
937931 $ blockToOpenXML opts (Para alt)
938932 return $ mknode "w:p" [] (paraProps ++ contents) : captionNode
939933blockToOpenXML' opts (Para lst)
@@ -944,10 +938,10 @@ blockToOpenXML' opts (Para lst)
944938 [x] -> isDisplayMath x
945939 _ -> False
946940 paraProps <- getParaProps displayMathPara
947− bodyTextStyle <- pStyleM "Body Text"
941+ bodyTextStyle <- if isFirstPara
942+ then pStyleM "First Paragraph"
943+ else pStyleM "Body Text"
948944 let paraProps' = case paraProps of
949− [] | isFirstPara -> [mknode "w:pPr" []
950− [pCustomStyle "FirstParagraph"]]
951945 [] -> [mknode "w:pPr" [] [bodyTextStyle]]
952946 ps -> ps
953947 modify $ \s -> s { stFirstPara = False }
@@ -965,7 +959,7 @@ blockToOpenXML' opts (BlockQuote blocks) = do
965959 setFirstPara
966960 return p
967961blockToOpenXML' opts (CodeBlock attrs@(ident, _, _) str) = do
968− p <- withParaProp (pCustomStyle "SourceCode") (blockToOpenXML opts $ Para [Code attrs str])
962+ p <- withParaPropM (pStyleM "Source Code") (blockToOpenXML opts $ Para [Code attrs str])
969963 setFirstPara
970964 wrapBookmark ident p
971965blockToOpenXML' _ HorizontalRule = do
@@ -981,7 +975,7 @@ blockToOpenXML' opts (Table caption aligns widths headers rows) = do
981975 let captionStr = stringify caption
982976 caption' <- if null caption
983977 then return []
984− else withParaProp (pCustomStyle "TableCaption")
978+ else withParaPropM (pStyleM "Table Caption")
985979 $ blockToOpenXML opts (Para caption)
986980 let alignmentFor al = mknode "w:jc" [("w:val",alignmentToString al)] ()
987981 -- Table cells require a <w:p> element, even an empty one!
@@ -997,7 +991,8 @@ blockToOpenXML' opts (Table caption aligns widths headers rows) = do
997991 [ mknode "w:tcBorders" []
998992 $ mknode "w:bottom" [("w:val","single")] ()
999993 , mknode "w:vAlign" [("w:val","bottom")] () ]
1000− let emptyCell = [mknode "w:p" [] [mknode "w:pPr" [] [pCustomStyle "Compact"]]]
994+ compactStyle <- pStyleM "Compact"
995+ let emptyCell = [mknode "w:p" [] [mknode "w:pPr" [] [compactStyle]]]
1001996 let mkcell border contents = mknode "w:tc" []
1002997 $ [ borderProps | border ] ++
1003998 if null contents
@@ -1030,20 +1025,17 @@ blockToOpenXML' opts (Table caption aligns widths headers rows) = do
10301025 : [ mkrow True headers' | hasHeader ] ++
10311026 map (mkrow False) rows'
10321027 )]
1033−blockToOpenXML' opts (BulletList lst) = do
1034− let marker = BulletMarker
1035− addList marker
1036− numid <- getNumId
1037− l <- asList $ concat `fmap` mapM (listItemToOpenXML opts numid) lst
1038− setFirstPara
1039− return l
1040−blockToOpenXML' opts (OrderedList (start, numstyle, numdelim) lst) = do
1041− let marker = NumberMarker numstyle numdelim start
1042− addList marker
1043− numid <- getNumId
1044− l <- asList $ concat `fmap` mapM (listItemToOpenXML opts numid) lst
1045− setFirstPara
1046− return l
1028+blockToOpenXML' opts el
1029+ | BulletList lst <- el = addOpenXMLList BulletMarker lst
1030+ | OrderedList (start, numstyle, numdelim) lst <- el
1031+ = addOpenXMLList (NumberMarker numstyle numdelim start) lst
1032+ where
1033+ addOpenXMLList marker lst = do
1034+ addList marker
1035+ numid <- getNumId
1036+ l <- asList $ concat `fmap` mapM (listItemToOpenXML opts numid) lst
1037+ setFirstPara
1038+ return l
10471039blockToOpenXML' opts (DefinitionList items) = do
10481040 l <- concat `fmap` mapM (definitionListItemToOpenXML opts) items
10491041 setFirstPara
@@ -1051,9 +1043,9 @@ blockToOpenXML' opts (DefinitionList items) = do
10511043
10521044definitionListItemToOpenXML :: (PandocMonad m) => WriterOptions -> ([Inline],[[Block]]) -> WS m [Element]
10531045definitionListItemToOpenXML opts (term,defs) = do
1054− term' <- withParaProp (pCustomStyle "DefinitionTerm")
1046+ term' <- withParaPropM (pStyleM "Definition Term")
10551047 $ blockToOpenXML opts (Para term)
1056− defs' <- withParaProp (pCustomStyle "Definition")
1048+ defs' <- withParaPropM (pStyleM "Definition")
10571049 $ concat `fmap` mapM (blocksToOpenXML opts) defs
10581050 return $ term' ++ defs'
10591051
@@ -1159,7 +1151,7 @@ inlineToOpenXML' _ (Str str) =
11591151 formattedString str
11601152inlineToOpenXML' opts Space = inlineToOpenXML opts (Str " ")
11611153inlineToOpenXML' opts SoftBreak = inlineToOpenXML opts (Str " ")
1162−inlineToOpenXML' opts (Span (_,["underline"],_) ils) = do
1154+inlineToOpenXML' opts (Span (_,["underline"],_) ils) =
11631155 withTextProp (mknode "w:u" [("w:val","single")] ()) $
11641156 inlinesToOpenXML opts ils
11651157inlineToOpenXML' _ (Span (ident,["comment-start"],kvs) ils) = do
@@ -1192,18 +1184,21 @@ inlineToOpenXML' opts (Span (ident,classes,kvs) ils) = do
11921184 Just "rtl" -> local (\env -> env { envRTL = True })
11931185 Just "ltr" -> local (\env -> env { envRTL = False })
11941186 _ -> id
1195− let off x = withTextProp (mknode x [("w:val","0")] ())
1196− let pmod = (if "csl-no-emph" `elem` classes then off "w:i" else id) .
1187+ off x = withTextProp (mknode x [("w:val","0")] ())
1188+ pmod = (if "csl-no-emph" `elem` classes then off "w:i" else id) .
11971189 (if "csl-no-strong" `elem` classes then off "w:b" else id) .
11981190 (if "csl-no-smallcaps" `elem` classes
11991191 then off "w:smallCaps"
12001192 else id)
1193+ getChangeAuthorDate = do
1194+ defaultAuthor <- asks envChangesAuthor
1195+ defaultDate <- asks envChangesDate
1196+ let author = fromMaybe defaultAuthor (lookup "author" kvs)
1197+ date = fromMaybe defaultDate (lookup "date" kvs)
1198+ return (author, date)
12011199 insmod <- if "insertion" `elem` classes
12021200 then do
1203− defaultAuthor <- asks envChangesAuthor
1204− defaultDate <- asks envChangesDate
1205− let author = fromMaybe defaultAuthor (lookup "author" kvs)
1206− date = fromMaybe defaultDate (lookup "date" kvs)
1201+ (author, date) <- getChangeAuthorDate
12071202 insId <- gets stInsId
12081203 modify $ \s -> s{stInsId = insId + 1}
12091204 return $ \f -> do
@@ -1215,10 +1210,7 @@ inlineToOpenXML' opts (Span (ident,classes,kvs) ils) = do
12151210 else return id
12161211 delmod <- if "deletion" `elem` classes
12171212 then do
1218− defaultAuthor <- asks envChangesAuthor
1219− defaultDate <- asks envChangesDate
1220− let author = fromMaybe defaultAuthor (lookup "author" kvs)
1221− date = fromMaybe defaultDate (lookup "date" kvs)
1213+ (author, date) <- getChangeAuthorDate
12221214 delId <- gets stDelId
12231215 modify $ \s -> s{stDelId = delId + 1}
12241216 return $ \f -> local (\env->env{envInDel=True}) $ do
@@ -1266,14 +1258,17 @@ inlineToOpenXML' opts (Math mathType str) = do
12661258 Left il -> inlineToOpenXML' opts il
12671259inlineToOpenXML' opts (Cite _ lst) = inlinesToOpenXML opts lst
12681260inlineToOpenXML' opts (Code attrs str) = do
1261+ let alltoktypes = [KeywordTok ..]
1262+ tokTypesMap <- mapM (\tt -> (,) tt <$> rStyleM (show tt)) alltoktypes
12691263 let unhighlighted = intercalate [br] `fmap`
12701264 mapM formattedString (lines str)
12711265 formatOpenXML _fmtOpts = intercalate [br] . map (map toHlTok)
1272− toHlTok (toktype,tok) = mknode "w:r" []
1273− [ mknode "w:rPr" []
1274− [ rCustomStyle (show toktype) ]
1275− , mknode "w:t" [("xml:space","preserve")] (T.unpack tok) ]
1276− withTextProp (rCustomStyle "VerbatimChar")
1266+ toHlTok (toktype,tok) =
1267+ mknode "w:r" []
1268+ [ mknode "w:rPr" [] $
1269+ maybeToList (lookup toktype tokTypesMap)
1270+ , mknode "w:t" [("xml:space","preserve")] (T.unpack tok) ]
1271+ withTextPropM (rStyleM "Verbatim Char")
12771272 $ if isNothing (writerHighlightStyle opts)
12781273 then unhighlighted
12791274 else case highlight (writerSyntaxMap opts)
@@ -1431,12 +1426,12 @@ defaultFootnotes :: [Element]
14311426defaultFootnotes = [ mknode "w:footnote"
14321427 [("w:type", "separator"), ("w:id", "-1")]
14331428 [ mknode "w:p" []
1434− [mknode "w:r" [] $
1429+ [mknode "w:r" []
14351430 [ mknode "w:separator" [] ()]]]
14361431 , mknode "w:footnote"
14371432 [("w:type", "continuationSeparator"), ("w:id", "0")]
14381433 [ mknode "w:p" []
1439− [ mknode "w:r" [] $
1434+ [ mknode "w:r" []
14401435 [ mknode "w:continuationSeparator" [] ()]]]]
14411436
14421437
test/Tests/Readers/Docx.hs+9
@@ -255,6 +255,10 @@ tests = [ testGroup "document"
255255 "lists"
256256 "docx/lists.docx"
257257 "docx/lists.native"
258+ , testCompare
259+ "compact lists"
260+ "docx/lists-compact.docx"
261+ "docx/lists-compact.native"
258262 , testCompare
259263 "lists with level overrides"
260264 "docx/lists_level_override.docx"
@@ -425,6 +429,11 @@ tests = [ testGroup "document"
425429 "custom styles (`+styles`) enabled"
426430 "docx/custom-style-reference.docx"
427431 "docx/custom-style-with-styles.native"
432+ , testCompareWithOpts
433+ def{readerExtensions=extensionsFromList [Ext_styles]}
434+ "custom styles (`+styles`): Compact style is removed from output"
435+ "docx/compact-style-removal.docx"
436+ "docx/compact-style-removal.native"
428437 ]
429438 , testGroup "metadata"
430439 [ testCompareWithOpts def{readerStandalone=True}
test/docx/0_level_headers.native+2−2
@@ -15,10 +15,10 @@
1515,Para [Str "FIGURES",Space,Str "iv"]
1616,Para [Str "TABLES",Space,Str "v"]
1717,Para [Str "SECTION",Space,Str "1",Space,Str "Introduction",Space,Str "2"]
18−,Header 1 ("figures",["Heading0"],[]) [Str "FIGURES"]
18+,Header 1 ("figures",["Heading-0"],[]) [Str "FIGURES"]
1919,Para [Strong [Str "Figure",Space,Str "Page"]]
2020,Para [Strong [Str "No",Space,Str "table",Space,Str "of",Space,Str "figures",Space,Str "entries",Space,Str "found."]]
21−,Header 1 ("tables",["Heading0"],[]) [Str "TABLES"]
21+,Header 1 ("tables",["Heading-0"],[]) [Str "TABLES"]
2222,Para [Strong [Str "Table",Space,Str "Page"]]
2323,Para [Strong [Str "No",Space,Str "table",Space,Str "of",Space,Str "figures",Space,Str "entries",Space,Str "found."]]
2424,Header 1 ("introduction",[],[]) [Str "Introduction"]
test/docx/adjacent_codeblocks.docx

Binary file not shown.

test/docx/compact-style-removal.docxnew file

Binary file not shown.

test/docx/compact-style-removal.nativenew file+5
@@ -0,0 +1,5 @@
1+[OrderedList (1,Decimal,Period)
2+ [[Plain [Str "One"]]
3+ ,[Plain [Str "Two"]]
4+ ,[Plain [Str "Three"]]
5+ ,[Plain [Str "Four"]]]]
test/docx/custom-style-with-styles.native+3−3
@@ -1,7 +1,7 @@
1−[Div ("",[],[("custom-style","FirstParagraph")])
1+[Div ("",[],[("custom-style","First Paragraph")])
22 [Para [Str "This",Space,Str "is",Space,Str "some",Space,Str "text."]]
3−,Div ("",[],[("custom-style","BodyText")])
3+,Div ("",[],[("custom-style","Body Text")])
44 [Para [Str "This",Space,Str "is",Space,Str "text",Space,Str "with",Space,Str "an",Space,Span ("",[],[("custom-style","Emphatic")]) [Str "emphasized"],Space,Str "text",Space,Str "style.",Space,Str "And",Space,Str "this",Space,Str "is",Space,Str "text",Space,Str "with",Space,Str "a",Space,Span ("",[],[("custom-style","Strengthened")]) [Str "strengthened"],Space,Str "text",Space,Str "style."]]
5−,Div ("",[],[("custom-style","MyBlockStyle")])
5+,Div ("",[],[("custom-style","My Block Style")])
66 [BlockQuote
77 [Para [Str "Here",Space,Str "is",Space,Str "a",Space,Str "styled",Space,Str "paragraph",Space,Str "that",Space,Str "inherits",Space,Str "from",Space,Str "Block",Space,Str "Text."]]]]
test/docx/lists-compact.docxnew file

Binary file not shown.

test/docx/lists-compact.nativenew file+5
@@ -0,0 +1,5 @@
1+[OrderedList (1,Decimal,Period)
2+ [[Plain [Str "One"]]
3+ ,[Plain [Str "Two"]]
4+ ,[Plain [Str "Three"]]
5+ ,[Plain [Str "Four"]]]]
test/docx/lists.docx

Binary file not shown.

test/docx/lists.native+1−1
@@ -15,4 +15,4 @@
1515 ,Para [Str "Sub",Space,Str "paragraph"]]]]]]
1616 ,[Para [Str "Same",Space,Str "list"]]]
1717,BulletList
18− [[Para [Str "Different",Space,Str "list",Space,Str "adjacent",Space,Str "to",Space,Str "the",Space,Str "one",Space,Str "above."]]]]
18+ [[Plain [Str "Different",Space,Str "list",Space,Str "adjacent",Space,Str "to",Space,Str "the",Space,Str "one",Space,Str "above."]]]]
test/docx/nested_anchors_in_header.native+1−1
@@ -1,4 +1,4 @@
1−[Header 1 ("\1086\1075\1083\1072\1074\1083\1077\1085\1080\1077",["TOCHeading"],[]) [Str "\1054\1075\1083\1072\1074\1083\1077\1085\1080\1077"]
1+[Header 1 ("\1086\1075\1083\1072\1074\1083\1077\1085\1080\1077",["TOC-Heading"],[]) [Str "\1054\1075\1083\1072\1074\1083\1077\1085\1080\1077"]
22,Para [Link ("",[],[]) [Str "Short",Space,Str "instructions",Space,Str "1"] ("#short-instructions","")]
33,Para [Link ("",[],[]) [Str "Some",Space,Str "instructions",Space,Str "1"] ("#some-instructions","")]
44,Para [Link ("",[],[]) [Str "Remote",Space,Str "folder",Space,Str "or",Space,Str "longlonglonglonglong",Space,Str "file",Space,Str "with",Space,Str "manymanymanymany",Space,Str "letters",Space,Str "inside",Space,Str "opening",Space,Str "2"] ("#remote-folder-or-longlonglonglonglong-file-with-manymanymanymany-letters-inside-opening","")]