git.iko.soy/typeable/pandoc
on master help sign in
OpenDocument writer: Allow references for internal links (#6774)

This commit adds two extensions to the OpenDocument writer, `xrefs_name` and `xrefs_number`. Links to headings, figures and tables inside the document are substituted with cross-references that will use the name or caption of the referenced item for `xrefs_name` or the number for `xrefs_number`. For the `xrefs_number` to be useful heading numbers must be enabled in the generated document and table and figure captions must be enabled using for example the `native_numbering` extension. In order for numbers and reference text to be updated the generated document must be refreshed. Co-authored-by: Nils Carlson <nils.carlson@ludd.ltu.se>

commitc161893f442a3e001b64af1421e9f62376d71c92 authorNils Carlson <nils@nilscarlson.se> dateSat 5 Dec 2020, 18:00 (6 years ago) parentddb76cb
4 files changed+177−18
MANUAL.txt+37 src/Text/Pandoc/Extensions.hs+4 src/Text/Pandoc/Writers/OpenDocument.hs+73−18 test/command/6774.md+63
MANUAL.txt+37
@@ -3027,6 +3027,43 @@ This extension can be enabled/disabled for the following formats:
30273027output formats
30283028: `odt`, `opendocument`
30293029
3030+#### Extension: `xrefs_name` ####
3031+
3032+Links to headings, figures and tables inside the document are
3033+substituted with cross-references that will use the name or caption
3034+of the referenced item. The original link text is replaced once
3035+the generated document is refreshed. This extension can be combined
3036+with `xrefs_number` in which case numbers will appear before the
3037+name.
3038+
3039+Text in cross-references is only made consistent with the referenced
3040+item once the document has been refreshed.
3041+
3042+This extension can be enabled/disabled for the following formats:
3043+
3044+output formats
3045+: `odt`, `opendocument`
3046+
3047+#### Extension: `xrefs_number` ####
3048+
3049+Links to headings, figures and tables inside the document are
3050+substituted with cross-references that will use the number
3051+of the referenced item. The original link text is discarded.
3052+This extension can be combined with `xrefs_name` in which case
3053+the name or caption numbers will appear after the number.
3054+
3055+For the `xrefs_number` to be useful heading numbers must be enabled
3056+in the generated document, also table and figure captions must be enabled
3057+using for example the `native_numbering` extension.
3058+
3059+Numbers in cross-references are only visible in the final document once
3060+it has been refreshed.
3061+
3062+This extension can be enabled/disabled for the following formats:
3063+
3064+output formats
3065+: `odt`, `opendocument`
3066+
30303067#### Extension: `styles` #### {#ext-styles}
30313068
30323069When converting from docx, read all docx styles as divs (for
src/Text/Pandoc/Extensions.hs+4
@@ -149,6 +149,8 @@ data Extension =
149149 | Ext_tex_math_dollars -- ^ TeX math between $..$ or $$..$$
150150 | Ext_tex_math_double_backslash -- ^ TeX math btw \\(..\\) \\[..\\]
151151 | Ext_tex_math_single_backslash -- ^ TeX math btw \(..\) \[..\]
152+ | Ext_xrefs_name -- ^ Use xrefs with names
153+ | Ext_xrefs_number -- ^ Use xrefs with numbers
152154 | Ext_yaml_metadata_block -- ^ YAML metadata block
153155 | Ext_gutenberg -- ^ Use Project Gutenberg conventions for plain
154156 | Ext_attributes -- ^ Generic attribute syntax
@@ -465,6 +467,8 @@ getAllExtensions f = universalExtensions <> getAll f
465467 getAll "opendocument" = extensionsFromList
466468 [ Ext_empty_paragraphs
467469 , Ext_native_numbering
470+ , Ext_xrefs_name
471+ , Ext_xrefs_number
468472 ]
469473 getAll "odt" = getAll "opendocument" <> autoIdExtensions
470474 getAll "muse" = autoIdExtensions <>
src/Text/Pandoc/Writers/OpenDocument.hs+73−18
@@ -17,6 +17,7 @@ module Text.Pandoc.Writers.OpenDocument ( writeOpenDocument ) where
1717import Control.Arrow ((***), (>>>))
1818import Control.Monad.State.Strict hiding (when)
1919import Data.Char (chr)
20+import Data.Foldable (find)
2021import Data.List (sortOn, sortBy, foldl')
2122import qualified Data.Map as Map
2223import Data.Maybe (fromMaybe, isNothing)
@@ -35,6 +36,7 @@ import Text.DocLayout
3536import Text.Pandoc.Shared (linesToPara, tshow, blocksToInlines)
3637import Text.Pandoc.Templates (renderTemplate)
3738import qualified Text.Pandoc.Translations as Term (Term(Figure, Table))
39+import Text.Pandoc.Walk
3840import Text.Pandoc.Writers.Math
3941import Text.Pandoc.Writers.Shared
4042import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
@@ -54,6 +56,11 @@ plainToPara x = x
5456
5557type OD m = StateT WriterState m
5658
59+data ReferenceType
60+ = HeaderRef
61+ | TableRef
62+ | ImageRef
63+
5764data WriterState =
5865 WriterState { stNotes :: [Doc Text]
5966 , stTableStyles :: [Doc Text]
@@ -69,6 +76,7 @@ data WriterState =
6976 , stImageId :: Int
7077 , stTableCaptionId :: Int
7178 , stImageCaptionId :: Int
79+ , stIdentTypes :: [(Text,ReferenceType)]
7280 }
7381
7482defaultWriterState :: WriterState
@@ -86,6 +94,7 @@ defaultWriterState =
8694 , stImageId = 1
8795 , stTableCaptionId = 1
8896 , stImageCaptionId = 1
97+ , stIdentTypes = []
8998 }
9099
91100when :: Bool -> Doc Text -> Doc Text
@@ -243,6 +252,12 @@ writeOpenDocument opts (Pandoc meta blocks) = do
243252 meta
244253 ((body, metadata),s) <- flip runStateT
245254 defaultWriterState $ do
255+ let collectInlineIdent (Image (ident,_,_) _ _) = [(ident,ImageRef)]
256+ collectInlineIdent _ = []
257+ let collectBlockIdent (Header _ (ident,_,_) _) = [(ident,HeaderRef)]
258+ collectBlockIdent (Table (ident,_,_) _ _ _ _ _) = [(ident,TableRef)]
259+ collectBlockIdent _ = []
260+ modify $ \s -> s{ stIdentTypes = query collectBlockIdent blocks ++ query collectInlineIdent blocks }
246261 m <- metaToContext opts
247262 (blocksToOpenDocument opts)
248263 (fmap chomp . inlinesToOpenDocument opts)
@@ -411,7 +426,7 @@ blockToOpenDocument o bs
411426 inTags True "text:list" [ ("text:style-name", "L" <> tshow ln)]
412427 <$> orderedListToOpenDocument o pn b
413428 table :: PandocMonad m => Ann.Table -> OD m (Doc Text)
414− table (Ann.Table _ (Caption _ c) colspecs thead tbodies _) = do
429+ table (Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies _) = do
415430 tn <- length <$> gets stTableStyles
416431 pn <- length <$> gets stParaStyles
417432 let genIds = map chr [65..]
@@ -433,7 +448,7 @@ blockToOpenDocument o bs
433448 then return empty
434449 else inlinesToOpenDocument o (blocksToInlines c) >>=
435450 if isEnabled Ext_native_numbering o
436− then numberedTableCaption
451+ then numberedTableCaption ident
437452 else unNumberedCaption "TableCaption"
438453 th <- colHeadsToOpenDocument o (map fst paraHStyles) thead
439454 tr <- mapM (tableBodyToOpenDocument o (map fst paraStyles)) tbodies
@@ -442,36 +457,39 @@ blockToOpenDocument o bs
442457 , ("table:style-name", name)
443458 ] (vcat columns $$ th $$ vcat tr)
444459 return $ captionDoc $$ tableDoc
445− figure attr caption source title | null caption =
460+ figure attr@(ident, _, _) caption source title | null caption =
446461 withParagraphStyle o "Figure" [Para [Image attr caption (source,title)]]
447462 | otherwise = do
448463 imageDoc <- withParagraphStyle o "FigureWithCaption" [Para [Image attr caption (source,title)]]
449464 captionDoc <- inlinesToOpenDocument o caption >>=
450465 if isEnabled Ext_native_numbering o
451− then numberedFigureCaption
466+ then numberedFigureCaption ident
452467 else unNumberedCaption "FigureCaption"
453468 return $ imageDoc $$ captionDoc
454469
455470
456−numberedTableCaption :: PandocMonad m => Doc Text -> OD m (Doc Text)
457−numberedTableCaption caption = do
471+numberedTableCaption :: PandocMonad m => Text -> Doc Text -> OD m (Doc Text)
472+numberedTableCaption ident caption = do
458473 id' <- gets stTableCaptionId
459474 modify (\st -> st{ stTableCaptionId = id' + 1 })
460475 capterm <- translateTerm Term.Table
461− return $ numberedCaption "TableCaption" capterm "Table" id' caption
476+ return $ numberedCaption "TableCaption" capterm "Table" id' ident caption
462477
463−numberedFigureCaption :: PandocMonad m => Doc Text -> OD m (Doc Text)
464−numberedFigureCaption caption = do
478+numberedFigureCaption :: PandocMonad m => Text -> Doc Text -> OD m (Doc Text)
479+numberedFigureCaption ident caption = do
465480 id' <- gets stImageCaptionId
466481 modify (\st -> st{ stImageCaptionId = id' + 1 })
467482 capterm <- translateTerm Term.Figure
468− return $ numberedCaption "FigureCaption" capterm "Illustration" id' caption
483+ return $ numberedCaption "FigureCaption" capterm "Illustration" id' ident caption
469484
470−numberedCaption :: Text -> Text -> Text -> Int -> Doc Text -> Doc Text
471−numberedCaption style term name num caption =
485+numberedCaption :: Text -> Text -> Text -> Int -> Text -> Doc Text -> Doc Text
486+numberedCaption style term name num ident caption =
472487 let t = text $ T.unpack term
473488 r = num - 1
474− s = inTags False "text:sequence" [ ("text:ref-name", "ref" <> name <> tshow r),
489+ ident' = case ident of
490+ "" -> "ref" <> name <> tshow r
491+ _ -> ident
492+ s = inTags False "text:sequence" [ ("text:ref-name", ident'),
475493 ("text:name", name),
476494 ("text:formula", "ooow:" <> name <> "+1"),
477495 ("style:num-format", "1") ] $ text $ show num
@@ -607,7 +625,9 @@ inlineToOpenDocument o ils
607625 else do
608626 report $ InlineNotRendered ils
609627 return empty
610− Link _ l (s,t) -> mkLink s t <$> inlinesToOpenDocument o l
628+ Link _ l (s,t) -> do
629+ identTypes <- gets stIdentTypes
630+ mkLink o identTypes s t <$> inlinesToOpenDocument o l
611631 Image attr _ (s,t) -> mkImg attr s t
612632 Note l -> mkNote l
613633 where
@@ -619,10 +639,6 @@ inlineToOpenDocument o ils
619639 unhighlighted s = inlinedCode $ preformatted s
620640 preformatted s = handleSpaces $ escapeStringForXML s
621641 inlinedCode s = return $ inTags False "text:span" [("text:style-name", "Source_Text")] s
622− mkLink s t = inTags False "text:a" [ ("xlink:type" , "simple")
623− , ("xlink:href" , s )
624− , ("office:name", t )
625− ] . inSpanTags "Definition"
626642 mkImg (_, _, kvs) s _ = do
627643 id' <- gets stImageId
628644 modify (\st -> st{ stImageId = id' + 1 })
@@ -659,6 +675,45 @@ inlineToOpenDocument o ils
659675 addNote nn
660676 return nn
661677
678+mkLink :: WriterOptions -> [(Text,ReferenceType)] -> Text -> Text -> Doc Text -> Doc Text
679+mkLink o identTypes s t d =
680+ let maybeIdentAndType = case T.uncons s of
681+ Just ('#', ident) -> find ((ident ==) . fst) identTypes
682+ _ -> Nothing
683+ d' = inSpanTags "Definition" d
684+ ref refType format ident = inTags False refType
685+ [ ("text:reference-format", format ),
686+ ("text:ref-name", ident) ]
687+ inlineSpace = selfClosingTag "text:s" []
688+ bookmarkRef = ref "text:bookmark-ref"
689+ bookmarkRefNumber ident = bookmarkRef "number" ident mempty
690+ bookmarkRefName ident = bookmarkRef "text" ident d
691+ bookmarkRefNameNumber ident = bookmarkRefNumber ident <> inlineSpace <> bookmarkRefName ident
692+ bookmarkRef'
693+ | isEnabled Ext_xrefs_number o && isEnabled Ext_xrefs_name o = bookmarkRefNameNumber
694+ | isEnabled Ext_xrefs_name o = bookmarkRefName
695+ | otherwise = bookmarkRefNumber
696+ sequenceRef = ref "text:sequence-ref"
697+ sequenceRefNumber ident = sequenceRef "value" ident mempty
698+ sequenceRefName ident = sequenceRef "caption" ident d
699+ sequenceRefNameNumber ident = sequenceRefNumber ident <> inlineSpace <> sequenceRefName ident
700+ sequenceRef'
701+ | isEnabled Ext_xrefs_number o && isEnabled Ext_xrefs_name o = sequenceRefNameNumber
702+ | isEnabled Ext_xrefs_name o = sequenceRefName
703+ | otherwise = sequenceRefNumber
704+ link = inTags False "text:a" [ ("xlink:type" , "simple")
705+ , ("xlink:href" , s )
706+ , ("office:name", t )
707+ ] d'
708+ linkOrReference = case maybeIdentAndType of
709+ Just (ident, HeaderRef) -> bookmarkRef' ident
710+ Just (ident, TableRef) -> sequenceRef' ident
711+ Just (ident, ImageRef) -> sequenceRef' ident
712+ _ -> link
713+ in if isEnabled Ext_xrefs_name o || isEnabled Ext_xrefs_number o
714+ then linkOrReference
715+ else link
716+
662717bulletListStyle :: PandocMonad m => Int -> OD m (Int,(Int,[Doc Text]))
663718bulletListStyle l = do
664719 let doStyles i = inTags True "text:list-level-style-bullet"
test/command/6774.mdnew file+63
@@ -0,0 +1,63 @@
1+```
2+% pandoc -f native -t opendocument --quiet
3+[Header 1 ("chapter1",[],[]) [Str "The",Space,Str "Chapter"]
4+,Para [Str "Chapter",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "The",Space,Str "Chapter"] ("#chapter1","")]]
5+^D
6+<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="chapter1" />The
7+Chapter<text:bookmark-end text:name="chapter1" /></text:h>
8+<text:p text:style-name="First_20_paragraph">Chapter 1 references
9+<text:a xlink:type="simple" xlink:href="#chapter1" office:name=""><text:span text:style-name="Definition">The
10+Chapter</text:span></text:a></text:p>
11+```
12+```
13+% pandoc -f native -t opendocument+xrefs_name --quiet
14+[Header 1 ("chapter1",[],[]) [Str "The",Space,Str "Chapter"]
15+,Para [Str "Chapter",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "The",Space,Str "Chapter"] ("#chapter1","")]
16+,Para [Image ("lalune",[],[]) [Str "lalune"] ("lalune.jpg","fig:Voyage dans la Lune")]
17+,Para [Str "Image",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "La",Space,Str "Lune"] ("#lalune","")]]
18+^D
19+<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="chapter1" />The
20+Chapter<text:bookmark-end text:name="chapter1" /></text:h>
21+<text:p text:style-name="First_20_paragraph">Chapter 1 references
22+<text:bookmark-ref text:reference-format="text" text:ref-name="chapter1">The
23+Chapter</text:bookmark-ref></text:p>
24+<text:p text:style-name="FigureWithCaption"><draw:frame draw:name="img1"><draw:image xlink:href="lalune.jpg" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" /></draw:frame></text:p>
25+<text:p text:style-name="FigureCaption">lalune</text:p>
26+<text:p text:style-name="Text_20_body">Image 1 references
27+<text:sequence-ref text:reference-format="caption" text:ref-name="lalune">La
28+Lune</text:sequence-ref></text:p>
29+```
30+```
31+% pandoc -f native -t opendocument+xrefs_number --quiet
32+[Header 1 ("chapter1",[],[]) [Str "The",Space,Str "Chapter"]
33+,Para [Str "Chapter",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "The",Space,Str "Chapter"] ("#chapter1","")]
34+,Para [Image ("lalune",[],[]) [Str "lalune"] ("lalune.jpg","fig:Voyage dans la Lune")]
35+,Para [Str "Image",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "La",Space,Str "Lune"] ("#lalune","")]]
36+^D
37+<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="chapter1" />The
38+Chapter<text:bookmark-end text:name="chapter1" /></text:h>
39+<text:p text:style-name="First_20_paragraph">Chapter 1 references
40+<text:bookmark-ref text:reference-format="number" text:ref-name="chapter1"></text:bookmark-ref></text:p>
41+<text:p text:style-name="FigureWithCaption"><draw:frame draw:name="img1"><draw:image xlink:href="lalune.jpg" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" /></draw:frame></text:p>
42+<text:p text:style-name="FigureCaption">lalune</text:p>
43+<text:p text:style-name="Text_20_body">Image 1 references
44+<text:sequence-ref text:reference-format="value" text:ref-name="lalune"></text:sequence-ref></text:p>
45+```
46+```
47+% pandoc -f native -t opendocument+xrefs_number+xrefs_name --quiet
48+[Header 1 ("chapter1",[],[]) [Str "The",Space,Str "Chapter"]
49+,Para [Str "Chapter",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "The",Space,Str "Chapter"] ("#chapter1","")]
50+,Para [Image ("lalune",[],[]) [Str "lalune"] ("lalune.jpg","fig:Voyage dans la Lune")]
51+,Para [Str "Image",Space,Str "1",Space,Str "references",Space,Link ("",[],[]) [Str "La",Space,Str "Lune"] ("#lalune","")]]
52+^D
53+<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="chapter1" />The
54+Chapter<text:bookmark-end text:name="chapter1" /></text:h>
55+<text:p text:style-name="First_20_paragraph">Chapter 1 references
56+<text:bookmark-ref text:reference-format="number" text:ref-name="chapter1"></text:bookmark-ref><text:s /><text:bookmark-ref text:reference-format="text" text:ref-name="chapter1">The
57+Chapter</text:bookmark-ref></text:p>
58+<text:p text:style-name="FigureWithCaption"><draw:frame draw:name="img1"><draw:image xlink:href="lalune.jpg" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" /></draw:frame></text:p>
59+<text:p text:style-name="FigureCaption">lalune</text:p>
60+<text:p text:style-name="Text_20_body">Image 1 references
61+<text:sequence-ref text:reference-format="value" text:ref-name="lalune"></text:sequence-ref><text:s /><text:sequence-ref text:reference-format="caption" text:ref-name="lalune">La
62+Lune</text:sequence-ref></text:p>
63+```