OpenDocument/ODT writer: use predefined styles. (#11672)

Previously the OpenDocument writer emitted a fresh automatic style
(L1..Ln, P1..Pn, T1..Tn) for nearly every list, list-item paragraph,
block quote, preformatted block, and inline text style.  This produced
large ODT files, made `--reference-doc` customization ineffective (the
user's predefined styles were never referenced), and gave each list its
own indentation independent of any containing block quote.

This commit teaches the writer to reference the predefined styles that
LibreOffice ships and that pandoc's reference.odt now exports:

- Bullet lists use `List_20_1`; ordered lists with default start and
  decimal format use `Numbering_20_1`.  Non-default ordered lists
  generate a single named override style (`Pandoc_Numbering_N`)
  memoised by (ListNumberStyle, ListNumberDelim); a non-default start
  value with the default format is expressed via `text:start-value`
  on the `text:list` element instead of a new style.
- List-item paragraphs use `List_20_Bullet[_Tight]` and
  `List_20_Number[_Tight]`.  The Tight variants are pandoc-specific
  (zero top/bottom margin) and are injected into the user's
  reference.odt if missing, just like the Skylighting token styles.
- Block quotes use the predefined `Quotations` paragraph style
  directly.  Nested block quotes use a single automatic style that
  inherits from Quotations and only adds extra margin-left, so a list
  inside a block quote now inherits its container's indent (#2747).
- Preformatted blocks use `Preformatted_20_Text` directly.
- Emphasis, Strong, Strikeout, Subscript, Superscript and Code spans
  use the predefined `Emphasis`, `Strong_20_Emphasis`, `Strikeout`,
  `Subscript`, `Superscript` and `Source_20_Text` text styles.
- `paraStyle`/`paraStyleFromParent` no longer emit a wrapper automatic
  style when its only attribute would be `parent-style-name`; the
  parent name is returned directly.

Closes #9136.
Closes #5086.
Closes #2747.
Closes #3426.
Closes #7336.

Co-authored by: Claude Opus 4.7.
This commit is contained in:
John MacFarlane
2026-05-27 17:35:39 +02:00
committed by GitHub
parent 9c83002851
commit 1c7cfb2a2a
9 changed files with 760 additions and 1530 deletions
+18
View File
@@ -136,6 +136,24 @@ xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.3">
style:parent-style-name="Text_20_body" style:class="list">
<style:text-properties style:font-name-complex="Tahoma1" />
</style:style>
<style:style style:name="List_20_Bullet"
style:display-name="List Bullet" style:family="paragraph"
style:parent-style-name="List" style:class="list" />
<style:style style:name="List_20_Bullet_20_Tight"
style:display-name="List Bullet Tight" style:family="paragraph"
style:parent-style-name="List" style:class="list">
<style:paragraph-properties fo:margin-top="0in"
fo:margin-bottom="0in" style:contextual-spacing="false" />
</style:style>
<style:style style:name="List_20_Number"
style:display-name="List Number" style:family="paragraph"
style:parent-style-name="List" style:class="list" />
<style:style style:name="List_20_Number_20_Tight"
style:display-name="List Number Tight" style:family="paragraph"
style:parent-style-name="List" style:class="list">
<style:paragraph-properties fo:margin-top="0in"
fo:margin-bottom="0in" style:contextual-spacing="false" />
</style:style>
<style:style style:name="Caption" style:family="paragraph"
style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties fo:margin-top="0.0835in"
+53 -2
View File
@@ -212,9 +212,10 @@ updateStyle opts mbLang arch = do
. maybe id addLang mbLang
. transformElement (\qn -> qName qn == "styles" &&
qPrefix qn == Just "office" )
(case writerHighlightMethod opts of
(addListItemStyles .
(case writerHighlightMethod opts of
Skylighting style -> addHlStyles style
_ -> id)
_ -> id))
$ d )
| otherwise = pure e
entries <- mapM goEntry (zEntries arch)
@@ -228,6 +229,56 @@ addHlStyles sty el =
isHlStyle (Elem e) = "Tok" `T.isSuffixOf` (qName (elName e))
isHlStyle _ = False
-- | Ensure the office:styles element contains paragraph styles for
-- list items used by the OpenDocument writer. This injects only the
-- styles that are missing, so a user-supplied reference.odt may
-- override any of them.
addListItemStyles :: Element -> Element
addListItemStyles el =
el{ elContent = elContent el ++ map Elem missingStyles }
where
existing = [ T.concat [ attrVal a
| a <- elAttribs e
, qName (attrKey a) == "name"
, qPrefix (attrKey a) == Just "style" ]
| Elem e <- elContent el
, qName (elName e) == "style"
, qPrefix (elName e) == Just "style" ]
missingStyles =
[ s | s <- listItemStyleElements
, styleNameOf s `notElem` existing ]
styleNameOf e = T.concat [ attrVal a
| a <- elAttribs e
, qName (attrKey a) == "name"
, qPrefix (attrKey a) == Just "style" ]
listItemStyleElements :: [Element]
listItemStyleElements =
[ mkParaStyle "List_20_Bullet" "List Bullet" Nothing
, mkParaStyle "List_20_Bullet_20_Tight" "List Bullet Tight" (Just tightProps)
, mkParaStyle "List_20_Number" "List Number" Nothing
, mkParaStyle "List_20_Number_20_Tight" "List Number Tight" (Just tightProps)
]
where
styleQN n p = QName n Nothing (Just p)
mkAttr p n v = Attr (styleQN n p) v
tightProps =
Element (styleQN "paragraph-properties" "style")
[ mkAttr "fo" "margin-top" "0in"
, mkAttr "fo" "margin-bottom" "0in"
, mkAttr "style" "contextual-spacing" "false"
] [] Nothing
mkParaStyle name display mbProps =
Element (styleQN "style" "style")
[ mkAttr "style" "name" name
, mkAttr "style" "display-name" display
, mkAttr "style" "family" "paragraph"
, mkAttr "style" "parent-style-name" "List"
, mkAttr "style" "class" "list"
]
(maybe [] (\p -> [Elem p]) mbProps)
Nothing
-- top-down search
transformElement :: (QName -> Bool)
-> (Element -> Element)
+177 -157
View File
@@ -51,6 +51,29 @@ plainToPara :: Block -> Block
plainToPara (Plain x) = Para x
plainToPara x = x
-- Names of predefined styles in the reference.odt; see data/odt/styles.xml.
defaultBulletListStyleName, defaultNumberedListStyleName :: Text
defaultBulletListStyleName = "List_20_1"
defaultNumberedListStyleName = "Numbering_20_1"
bulletItemStyleName, bulletItemTightStyleName,
numberItemStyleName, numberItemTightStyleName :: Text
bulletItemStyleName = "List_20_Bullet"
bulletItemTightStyleName = "List_20_Bullet_20_Tight"
numberItemStyleName = "List_20_Number"
numberItemTightStyleName = "List_20_Number_20_Tight"
-- | Predefined inline style names for single-style spans.
wellKnownTextStyle :: Set.Set TextStyle -> Maybe Text
wellKnownTextStyle s = case Set.toList s of
[Italic] -> Just "Emphasis"
[Bold] -> Just "Strong_20_Emphasis"
[Strike] -> Just "Strikeout"
[Sub] -> Just "Subscript"
[Sup] -> Just "Superscript"
[Pre] -> Just "Source_20_Text"
_ -> Nothing
--
-- OpenDocument writer
--
@@ -66,7 +89,8 @@ data WriterState =
WriterState { stNotes :: [Doc Text]
, stTableStyles :: [Doc Text]
, stParaStyles :: [Doc Text]
, stListStyles :: [(Int, [Doc Text])]
, stListOverrides :: Map.Map (ListNumberStyle,ListNumberDelim)
(Text, Doc Text)
, stTextStyles :: Map.Map (Set.Set TextStyle)
(Text, Doc Text)
, stTextStyleAttr :: Set.Set TextStyle
@@ -85,7 +109,7 @@ defaultWriterState =
WriterState { stNotes = []
, stTableStyles = []
, stParaStyles = []
, stListStyles = []
, stListOverrides = Map.empty
, stTextStyles = Map.empty
, stTextStyleAttr = Set.empty
, stIndentPara = 0
@@ -121,10 +145,6 @@ increaseIndent = modify $ \s -> s { stIndentPara = 1 + stIndentPara s }
resetIndent :: PandocMonad m => OD m ()
resetIndent = modify $ \s -> s { stIndentPara = stIndentPara s - 1 }
inTightList :: PandocMonad m => OD m a -> OD m a
inTightList f = modify (\s -> s { stTight = True }) >> f >>= \r ->
modify (\s -> s { stTight = False }) >> return r
setInDefinitionList :: PandocMonad m => Bool -> OD m ()
setInDefinitionList b = modify $ \s -> s { stInDefinition = b }
@@ -163,22 +183,25 @@ inTextStyle d = do
at <- gets stTextStyleAttr
if Set.null at
then return d
else do
styles <- gets stTextStyles
case Map.lookup at styles of
Just (styleName, _) -> return $
inTags False "text:span" [("text:style-name",styleName)] d
Nothing -> do
let styleName = "T" <> tshow (Map.size styles + 1)
addTextStyle at (styleName,
inTags False "style:style"
[("style:name", styleName)
,("style:family", "text")]
$ selfClosingTag "style:text-properties"
(sortOn fst . Map.toList
$ L.foldl' textStyleAttr mempty (Set.toList at)))
return $ inTags False
"text:span" [("text:style-name",styleName)] d
else case wellKnownTextStyle at of
Just styleName -> return $
inTags False "text:span" [("text:style-name", styleName)] d
Nothing -> do
styles <- gets stTextStyles
case Map.lookup at styles of
Just (styleName, _) -> return $
inTags False "text:span" [("text:style-name",styleName)] d
Nothing -> do
let styleName = "T" <> tshow (Map.size styles + 1)
addTextStyle at (styleName,
inTags False "style:style"
[("style:name", styleName)
,("style:family", "text")]
$ selfClosingTag "style:text-properties"
(sortOn fst . Map.toList
$ L.foldl' textStyleAttr mempty (Set.toList at)))
return $ inTags False
"text:span" [("text:style-name",styleName)] d
formulaStyles :: [Doc Text]
formulaStyles = [formulaStyle InlineMath, formulaStyle DisplayMath]
@@ -263,10 +286,8 @@ writeOpenDocument opts (Pandoc meta blocks) = do
let styles = stTableStyles s ++ stParaStyles s ++ formulaStyles ++
map snd (sortBy (comparing (Down . fst)) (
Map.elems (stTextStyles s)))
listStyle (n,l) = inTags True "text:list-style"
[("style:name", "L" <> tshow n)] (vcat l)
let listStyles = map listStyle (stListStyles s)
let automaticStyles = vcat $ reverse $ styles ++ listStyles
let listStyles = map snd (Map.elems (stListOverrides s))
let automaticStyles = vcat $ reverse styles ++ listStyles
let context = defField "body" body
. defField "toc" (writerTableOfContents opts)
. defField "toc-depth" (tshow $ writerTOCDepth opts)
@@ -286,32 +307,86 @@ withParagraphStyle o s (b:bs)
withParagraphStyle _ _ [] = return empty
inPreformattedTags :: PandocMonad m => [Doc Text] -> OD m (Doc Text)
inPreformattedTags s = do
n <- paraStyle [("style:parent-style-name","Preformatted_20_Text")]
return $ inParagraphTagsWithStyle ("P" <> tshow n) $ hcat s
inPreformattedTags s =
return $ inParagraphTagsWithStyle "Preformatted_20_Text" $ hcat s
-- | Get the list-style name to use for an ordered list with the given
-- numbering style and delimiter, registering an override automatic style
-- if needed. Lists with default style/delimiter use the predefined
-- @Numbering_20_1@ style and no override is generated.
orderedListStyleName :: PandocMonad m
=> ListNumberStyle -> ListNumberDelim -> OD m Text
orderedListStyleName ns nd
| (ns == DefaultStyle || ns == Decimal) &&
(nd == DefaultDelim || nd == Period) =
return defaultNumberedListStyleName
| otherwise = do
overrides <- gets stListOverrides
case Map.lookup (ns, nd) overrides of
Just (name, _) -> return name
Nothing -> do
let name = "Pandoc_5f_Numbering_5f_" <>
tshow (Map.size overrides + 1)
doc = inTags True "text:list-style"
[("style:name", name)]
(vcat (map (orderedListLevel ns nd) [1..10]))
modify $ \s -> s { stListOverrides =
Map.insert (ns, nd) (name, doc)
(stListOverrides s) }
return name
orderedListLevel :: ListNumberStyle -> ListNumberDelim -> Int -> Doc Text
orderedListLevel ns nd lvl =
let suffix = case nd of
OneParen -> [("style:num-suffix", ")")]
TwoParens -> [("style:num-prefix", "(")
,("style:num-suffix", ")")]
_ -> [("style:num-suffix", ".")]
format = case ns of
UpperAlpha -> "A"
LowerAlpha -> "a"
UpperRoman -> "I"
LowerRoman -> "i"
_ -> "1"
in inTags True "text:list-level-style-number"
([ ("text:level" , tshow lvl)
, ("text:style-name" , "Numbering_20_Symbols")
, ("style:num-format", format)
] ++ suffix)
(selfClosingTag "style:list-level-properties"
[ ("text:space-before",
T.pack (printf "%.4fin" (0.1972 * fromIntegral (lvl - 1) :: Double)))
, ("text:min-label-width", "0.1965in")
, ("text:min-label-distance", "0.1in")
])
orderedListToOpenDocument :: PandocMonad m
=> WriterOptions -> Int -> [[Block]] -> OD m (Doc Text)
orderedListToOpenDocument o pn bs =
=> WriterOptions -> Text -> [[Block]]
-> OD m (Doc Text)
orderedListToOpenDocument o paraName bs =
vcat . map (inTagsIndented "text:list-item") <$>
mapM (orderedItemToOpenDocument o pn . map plainToPara) bs
mapM (orderedItemToOpenDocument o paraName . map plainToPara) bs
orderedItemToOpenDocument :: PandocMonad m
=> WriterOptions -> Int -> [Block] -> OD m (Doc Text)
orderedItemToOpenDocument o n bs = vcat <$> mapM go bs
where go (OrderedList a l) = newLevel a l
go (Para l) = inParagraphTagsWithStyle ("P" <> tshow n) <$>
=> WriterOptions -> Text -> [Block]
-> OD m (Doc Text)
orderedItemToOpenDocument o paraName bs = vcat <$> mapM go bs
where go (OrderedList a l) = orderedList a l
go (Para l) = inParagraphTagsWithStyle paraName <$>
inlinesToOpenDocument o l
go b = blockToOpenDocument o b
newLevel a l = do
nn <- length <$> gets stParaStyles
liststyles <- gets stListStyles
let ls = case liststyles of
[] -> (1,[]) -- should never happen
(s:_) -> s
modify $ \s -> s { stListStyles = orderedListLevelStyle a ls :
drop 1 (stListStyles s) }
inTagsIndented "text:list" <$> orderedListToOpenDocument o nn l
orderedList a@(_,ns,nd) l = do
lstName <- orderedListStyleName ns nd
let pn = if isTightList l then numberItemTightStyleName
else numberItemStyleName
let listAttrs = ("text:style-name", lstName) : startValueAttr a
inTags True "text:list" listAttrs <$>
orderedListToOpenDocument o pn l
-- | Generate a @text:start-value@ attribute when the start value is not 1.
startValueAttr :: ListAttributes -> [(Text, Text)]
startValueAttr (s,_,_) | s /= 1 = [("text:start-value", tshow s)]
startValueAttr _ = []
isTightList :: [[Block]] -> Bool
isTightList [] = False
@@ -319,23 +394,14 @@ isTightList (b:_)
| Plain {} : _ <- b = True
| otherwise = False
newOrderedListStyle :: PandocMonad m
=> Bool -> ListAttributes -> OD m (Int,Int)
newOrderedListStyle b a = do
ln <- (+) 1 . length <$> gets stListStyles
let nbs = orderedListLevelStyle a (ln, [])
pn <- if b then inTightList (paraListStyle ln) else paraListStyle ln
modify $ \s -> s { stListStyles = nbs : stListStyles s }
return (ln,pn)
bulletListToOpenDocument :: PandocMonad m
=> WriterOptions -> [[Block]] -> OD m (Doc Text)
bulletListToOpenDocument o b = do
ln <- (+) 1 . length <$> gets stListStyles
(pn,ns) <- if isTightList b then inTightList (bulletListStyle ln) else bulletListStyle ln
modify $ \s -> s { stListStyles = ns : stListStyles s }
is <- listItemsToOpenDocument ("P" <> tshow pn) o b
return $ inTags True "text:list" [("text:style-name", "L" <> tshow ln)] is
let pn = if isTightList b then bulletItemTightStyleName
else bulletItemStyleName
is <- listItemsToOpenDocument pn o b
return $ inTags True "text:list"
[("text:style-name", defaultBulletListStyleName)] is
listItemsToOpenDocument :: PandocMonad m
=> Text -> WriterOptions -> [[Block]] -> OD m (Doc Text)
@@ -354,16 +420,18 @@ deflistItemToOpenDocument o (t,d) = do
return $ t' $$ d'
inBlockQuote :: PandocMonad m
=> WriterOptions -> Int -> [Block] -> OD m (Doc Text)
inBlockQuote o i (b:bs)
=> WriterOptions -> Text -> [Block] -> OD m (Doc Text)
inBlockQuote o sty (b:bs)
| BlockQuote l <- b = do increaseIndent
ni <- paraStyle
[("style:parent-style-name","Quotations")]
go =<< inBlockQuote o ni (map plainToPara l)
| Para l <- b = go =<< inParagraphTagsWithStyle ("P" <> tshow i) <$> inlinesToOpenDocument o l
inner <- inBlockQuote o ni (map plainToPara l)
resetIndent
go inner
| Para l <- b = go =<< inParagraphTagsWithStyle sty <$> inlinesToOpenDocument o l
| otherwise = go =<< blockToOpenDocument o b
where go block = ($$) block <$> inBlockQuote o i bs
inBlockQuote _ _ [] = resetIndent >> return empty
where go block = ($$) block <$> inBlockQuote o sty bs
inBlockQuote _ _ [] = return empty
-- | Convert a list of Pandoc blocks to OpenDocument.
blocksToOpenDocument :: PandocMonad m => WriterOptions -> [Block] -> OD m (Doc Text)
@@ -425,13 +493,16 @@ blockToOpenDocument o = \case
if T.null ident
then i
else fmap mkBookmarkedDiv i
mkBlockQuote b = do increaseIndent
i <- paraStyle
[("style:parent-style-name","Quotations")]
inBlockQuote o i (map plainToPara b)
orderedList a b = do (ln,pn) <- newOrderedListStyle (isTightList b) a
inTags True "text:list" [ ("text:style-name", "L" <> tshow ln)]
<$> orderedListToOpenDocument o pn b
mkBlockQuote b = do sty <- paraStyle
[("style:parent-style-name","Quotations")]
inBlockQuote o sty (map plainToPara b)
orderedList a@(_,ns,nd) b = do
lstName <- orderedListStyleName ns nd
let pn = if isTightList b then numberItemTightStyleName
else numberItemStyleName
let listAttrs = ("text:style-name", lstName) : startValueAttr a
inTags True "text:list" listAttrs <$>
orderedListToOpenDocument o pn b
table :: PandocMonad m => WriterOptions -> Ann.Table -> OD m (Doc Text)
table opts
(Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies tfoot) = do
@@ -591,14 +662,9 @@ tableItemToOpenDocument o s (n,c) = do
aa = alignAttrib align
a = [ ("table:style-name" , s )
, ("office:value-type", "string" ) ] ++ csa ++ rsa
itemParaStyle <- case aa of
[] -> return 0
_ -> paraStyleFromParent n aa
let itemParaStyle' = case itemParaStyle of
0 -> n
x -> "P" <> tshow x
itemParaStyle <- paraStyleFromParent n aa
inTags True "table:table-cell" a <$>
withParagraphStyle o itemParaStyle' (map plainToPara i)
withParagraphStyle o itemParaStyle (map plainToPara i)
-- | Convert a list of inline elements to OpenDocument.
inlinesToOpenDocument :: PandocMonad m => WriterOptions -> [Inline] -> OD m (Doc Text)
@@ -761,55 +827,6 @@ mkLink o identTypes s t d =
then linkOrReference
else link
bulletListStyle :: PandocMonad m => Int -> OD m (Int,(Int,[Doc Text]))
bulletListStyle l = do
let doStyles i = inTags True "text:list-level-style-bullet"
[ ("text:level" , tshow (i + 1))
, ("text:style-name" , "Bullet_20_Symbols" )
, ("style:num-suffix", "." )
, ("text:bullet-char", T.singleton (bulletList !! i))
] (listLevelStyle (1 + i))
bulletList = map chr $ cycle [8226,9702,9642]
listElStyle = map doStyles [0..9]
pn <- paraListStyle l
return (pn, (l, listElStyle))
orderedListLevelStyle :: ListAttributes -> (Int, [Doc Text]) -> (Int,[Doc Text])
orderedListLevelStyle (s,n, d) (l,ls) =
let suffix = case d of
OneParen -> [("style:num-suffix", ")")]
TwoParens -> [("style:num-prefix", "(")
,("style:num-suffix", ")")]
_ -> [("style:num-suffix", ".")]
format = case n of
UpperAlpha -> "A"
LowerAlpha -> "a"
UpperRoman -> "I"
LowerRoman -> "i"
_ -> "1"
listStyle = inTags True "text:list-level-style-number"
([ ("text:level" , tshow $ 1 + length ls )
, ("text:style-name" , "Numbering_20_Symbols")
, ("style:num-format", format )
, ("text:start-value", tshow s )
] ++ suffix) (listLevelStyle (1 + length ls))
in (l, ls ++ [listStyle])
listLevelStyle :: Int -> Doc Text
listLevelStyle i =
let indent = tshow (0.25 + (0.25 * fromIntegral i :: Double)) in
inTags True "style:list-level-properties"
[ ("text:list-level-position-and-space-mode",
"label-alignment")
, ("fo:text-align", "right")
] $
selfClosingTag "style:list-level-label-alignment"
[ ("text:label-followed-by", "listtab")
, ("text:list-tab-stop-position", indent <> "in")
, ("fo:text-indent", "-0.25in")
, ("fo:margin-left", indent <> "in")
]
tableStyle :: Int -> Double -> [(Char,Double)] -> Doc Text
tableStyle num textWidth wcs =
let tableId = "Table" <> tshow (num + 1)
@@ -847,15 +864,17 @@ tableStyle num textWidth wcs =
columnStyles = map colStyle wcs
in cellStyles $$ table $$ vcat columnStyles
paraStyle :: PandocMonad m => [(Text,Text)] -> OD m Int
-- | Generate (or reuse) a paragraph style with the given attributes.
-- When the only attribute is @style:parent-style-name@ and no
-- indent/tight overrides are active, the parent name is returned
-- directly (no @Pn@ automatic style is created), so that the
-- predefined style passes through unchanged.
paraStyle :: PandocMonad m => [(Text,Text)] -> OD m Text
paraStyle attrs = do
pn <- (+) 1 . length <$> gets stParaStyles
i <- (*) (0.5 :: Double) . fromIntegral <$> gets stIndentPara
b <- gets stInDefinition
t <- gets stTight
let styleAttr = [ ("style:name" , "P" <> tshow pn)
, ("style:family" , "paragraph" )]
indentVal = flip (<>) "in" . tshow $ if b then max 0.5 i else i
let indentVal = flip (<>) "in" . tshow $ if b then max 0.5 i else i
tight = if t then [ ("fo:margin-top" , "0in" )
, ("fo:margin-bottom" , "0in" )]
else []
@@ -866,31 +885,32 @@ paraStyle attrs = do
, ("style:auto-text-indent" , "false" )]
else []
attributes = indent <> tight
paraProps = if null attributes
then mempty
else selfClosingTag
"style:paragraph-properties" attributes
addParaStyle $ inTags True "style:style" (styleAttr <> attrs) paraProps
return pn
case (attributes, attrs) of
([], [("style:parent-style-name", parent)]) -> return parent
_ -> do
pn <- (+) 1 . length <$> gets stParaStyles
let name = "P" <> tshow pn
styleAttr = [ ("style:name" , name)
, ("style:family", "paragraph") ]
paraProps = if null attributes
then mempty
else selfClosingTag
"style:paragraph-properties" attributes
addParaStyle $ inTags True "style:style" (styleAttr <> attrs) paraProps
return name
paraStyleFromParent :: PandocMonad m => Text -> [(Text,Text)] -> OD m Int
paraStyleFromParent parent attrs = do
pn <- (+) 1 . length <$> gets stParaStyles
let styleAttr = [ ("style:name" , "P" <> tshow pn)
, ("style:family" , "paragraph")
, ("style:parent-style-name", parent)]
paraProps = if null attrs
then mempty
else selfClosingTag
"style:paragraph-properties" attrs
addParaStyle $ inTags True "style:style" styleAttr paraProps
return pn
paraListStyle :: PandocMonad m => Int -> OD m Int
paraListStyle l = paraStyle
[("style:parent-style-name","Text_20_body")
,("style:list-style-name", "L" <> tshow l)]
paraStyleFromParent :: PandocMonad m => Text -> [(Text,Text)] -> OD m Text
paraStyleFromParent parent attrs
| null attrs = return parent
| otherwise = do
pn <- (+) 1 . length <$> gets stParaStyles
let name = "P" <> tshow pn
styleAttr = [ ("style:name" , name)
, ("style:family" , "paragraph")
, ("style:parent-style-name", parent)]
paraProps = selfClosingTag "style:paragraph-properties" attrs
addParaStyle $ inTags True "style:style" styleAttr paraProps
return name
paraTableStyles :: Text -> Int -> [Alignment] -> [(Text, Doc Text)]
paraTableStyles _ _ [] = []
+4 -3
View File
@@ -5,10 +5,11 @@ Aboard **the luxury cruise ship Heart of the Ocean[^1] in the Atlantic Ocean**..
[^1]: **Heart of the Ocean** (海洋之心) The Heart of the Ocean
^D
<text:p text:style-name="Text_20_body">Aboard
<text:span text:style-name="T1">the luxury cruise ship Heart of the
Ocean</text:span><text:note text:id="ftn0" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body><text:p text:style-name="Footnote"><text:span text:style-name="T1">Heart
<text:span text:style-name="Strong_20_Emphasis">the luxury cruise ship
Heart of the
Ocean</text:span><text:note text:id="ftn0" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body><text:p text:style-name="Footnote"><text:span text:style-name="Strong_20_Emphasis">Heart
of the Ocean</text:span> (海洋之心) The Heart of the
Ocean</text:p></text:note-body></text:note><text:span text:style-name="T1">
Ocean</text:p></text:note-body></text:note><text:span text:style-name="Strong_20_Emphasis">
in the Atlantic Ocean</text:span>…</text:p>
```
+19 -16
View File
@@ -6,23 +6,23 @@
2. beta
* gamma
^D
<text:list text:style-name="L1">
<text:list text:style-name="Numbering_20_1">
<text:list-item>
<text:p text:style-name="P1">a</text:p>
<text:p text:style-name="List_20_Number_20_Tight">a</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="P1">b</text:p>
<text:list>
<text:p text:style-name="List_20_Number_20_Tight">b</text:p>
<text:list text:style-name="Numbering_20_1">
<text:list-item>
<text:p text:style-name="P1">alpha</text:p>
<text:p text:style-name="List_20_Number_20_Tight">alpha</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="P1">beta</text:p>
<text:p text:style-name="List_20_Number_20_Tight">beta</text:p>
</text:list-item>
</text:list>
<text:list text:style-name="L2">
<text:list text:style-name="List_20_1">
<text:list-item>
<text:p text:style-name="P2">gamma</text:p>
<text:p text:style-name="List_20_Bullet_20_Tight">gamma</text:p>
</text:list-item>
</text:list>
</text:list-item>
@@ -40,20 +40,23 @@
more text -- this line is missing in the odt output
^D
<text:list text:style-name="L1">
<text:list text:style-name="Pandoc_5f_Numbering_5f_1">
<text:list-item>
<text:p text:style-name="P1">text</text:p>
<text:p text:style-name="P1">some text</text:p>
<text:list>
<text:p text:style-name="List_20_Number">text</text:p>
<text:p text:style-name="List_20_Number">some text</text:p>
<text:list text:style-name="Pandoc_5f_Numbering_5f_2">
<text:list-item>
<text:p text:style-name="P1">sub item 1</text:p>
<text:p text:style-name="List_20_Number_20_Tight">sub item
1</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="P1">sub item 2</text:p>
<text:p text:style-name="List_20_Number_20_Tight">sub item
2</text:p>
</text:list-item>
</text:list>
<text:p text:style-name="P1">more text this line is missing in the
odt output</text:p>
<text:p text:style-name="List_20_Number">more text this line is
missing in the odt output</text:p>
</text:list-item>
</text:list>
```
+1 -2
View File
@@ -16,14 +16,13 @@ Testing.
<style:font-face style:name="Courier New" style:font-family-generic="modern" style:font-pitch="fixed" svg:font-family="'Courier New'" />
</office:font-face-decls>
<office:automatic-styles>
<style:style style:name="T1" style:family="text"><style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic" /></style:style>
<style:style style:name="fr2" style:family="graphic" style:parent-style-name="Formula"><style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" style:wrap="none" /></style:style>
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Formula"><style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" /></style:style>
</office:automatic-styles>
<office:body>
<office:text>
<text:p text:style-name="Title">This
<text:span text:style-name="T1">is</text:span><text:line-break />a
<text:span text:style-name="Emphasis">is</text:span><text:line-break />a
test</text:p>
<text:p text:style-name="Author">Someone</text:p>
<text:p text:style-name="Text_20_body">Testing.</text:p>
+89
View File
@@ -0,0 +1,89 @@
Verify that the OpenDocument writer uses predefined list, paragraph, and
text styles instead of generating per-instance automatic styles.
```
% pandoc -t opendocument
- one
- two
1. ordered one
2. ordered two
Some *italic*, **bold**, ~~strike~~, and `code`.
> A block quote.
Final paragraph.
^D
<text:list text:style-name="List_20_1">
<text:list-item>
<text:p text:style-name="List_20_Bullet_20_Tight">one</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="List_20_Bullet_20_Tight">two</text:p>
</text:list-item>
</text:list>
<text:list text:style-name="Numbering_20_1">
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">ordered
one</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">ordered
two</text:p>
</text:list-item>
</text:list>
<text:p text:style-name="First_20_paragraph">Some
<text:span text:style-name="Emphasis">italic</text:span>,
<text:span text:style-name="Strong_20_Emphasis">bold</text:span>,
<text:span text:style-name="Strikeout">strike</text:span>, and
<text:span text:style-name="Source_20_Text">code</text:span>.</text:p>
<text:p text:style-name="Quotations">A block quote.</text:p>
<text:p text:style-name="First_20_paragraph">Final paragraph.</text:p>
```
A non-default ordered list (start value other than 1, or non-decimal
format) generates a single named override style, not one per instance.
```
% pandoc -t opendocument
A) upper-alpha one
B) upper-alpha two
5. start at five
6. and continue
^D
<text:list text:style-name="Pandoc_5f_Numbering_5f_1">
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">upper-alpha
one</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">upper-alpha
two</text:p>
</text:list-item>
</text:list>
<text:list text:style-name="Numbering_20_1" text:start-value="5">
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">start at
five</text:p>
</text:list-item>
<text:list-item>
<text:p text:style-name="List_20_Number_20_Tight">and
continue</text:p>
</text:list-item>
</text:list>
```
A nested block quote uses the predefined `Quotations` style for the
outer paragraph, and inherits indent from it for the inner paragraph.
```
% pandoc -t opendocument
> First.
>
> > Nested.
^D
<text:p text:style-name="Quotations">First.</text:p>
<text:p text:style-name="P1">Nested.</text:p>
```
+2 -2
View File
@@ -315,11 +315,11 @@
<table:table-column table:style-name="Table1.P" />
<table:table-row>
<table:table-cell table:style-name="TableRowCell" office:value-type="string" table:number-columns-spanned="8">
<text:p text:style-name="Table_20_Contents"><text:span text:style-name="T1">Octet
<text:p text:style-name="Table_20_Contents"><text:span text:style-name="Strong_20_Emphasis">Octet
no. 1</text:span></text:p>
</table:table-cell>
<table:table-cell table:style-name="TableRowCell" office:value-type="string" table:number-columns-spanned="8">
<text:p text:style-name="Table_20_Contents"><text:span text:style-name="T1">Octet
<text:p text:style-name="Table_20_Contents"><text:span text:style-name="Strong_20_Emphasis">Octet
no. 2</text:span></text:p>
</table:table-cell>
</table:table-row>
+397 -1348
View File
File diff suppressed because it is too large Load Diff