git.iko.soy/typeable/pandoc
on master help sign in
RST reader: flatten nested inlines, closes #4368 (#4554)

nested inlines are not valid RST syntax, so we flatten them following some readability criteria discussed in #4368.

commiteef1c211f58f0a2ffc6c500bd2158569b83fca1f authorFrancesco Occhipinti <focchi.pinti@gmail.com> dateThu 26 Apr 2018, 21:17 (8 years ago) parentcfa4eee
3 files changed+106−10
src/Text/Pandoc/Writers/RST.hs+75−3 test/Tests/Writers/RST.hs+24 test/writer.rst+7−7
src/Text/Pandoc/Writers/RST.hs+75−3
@@ -31,7 +31,7 @@ Conversion of 'Pandoc' documents to reStructuredText.
3131
3232reStructuredText: <http://docutils.sourceforge.net/rst.html>
3333-}
34−module Text.Pandoc.Writers.RST ( writeRST ) where
34+module Text.Pandoc.Writers.RST ( writeRST, flatten ) where
3535import Prelude
3636import Control.Monad.State.Strict
3737import Data.Char (isSpace, toLower)
@@ -377,8 +377,10 @@ blockListToRST :: PandocMonad m
377377blockListToRST = blockListToRST' False
378378
379379transformInlines :: [Inline] -> [Inline]
380−transformInlines = stripLeadingTrailingSpace . insertBS
381− . filter hasContents . removeSpaceAfterDisplayMath
380+transformInlines = insertBS .
381+ filter hasContents .
382+ removeSpaceAfterDisplayMath .
383+ concatMap (transformNested . flatten)
382384 where -- empty inlines are not valid RST syntax
383385 hasContents :: Inline -> Bool
384386 hasContents (Str "") = False
@@ -412,6 +414,8 @@ transformInlines = stripLeadingTrailingSpace . insertBS
412414 x : insertBS (y : zs)
413415 insertBS (x:ys) = x : insertBS ys
414416 insertBS [] = []
417+ transformNested :: [Inline] -> [Inline]
418+ transformNested = map (mapNested stripLeadingTrailingSpace)
415419 surroundComplex :: Inline -> Inline -> Bool
416420 surroundComplex (Str s@(_:_)) (Str s'@(_:_)) =
417421 case (last s, head s') of
@@ -449,6 +453,74 @@ transformInlines = stripLeadingTrailingSpace . insertBS
449453 isComplex (Span _ (x:_)) = isComplex x
450454 isComplex _ = False
451455
456+-- | Flattens nested inlines. Extracts nested inlines and goes through
457+-- them either collapsing them in the outer inline container or
458+-- pulling them out of it
459+flatten :: Inline -> [Inline]
460+flatten outer = combineAll $ dropInlineParent outer
461+ where combineAll = foldl combine []
462+
463+ combine :: [Inline] -> Inline -> [Inline]
464+ combine f i =
465+ case (outer, i) of
466+ -- quotes are not rendered using RST inlines, so we can keep
467+ -- them and they will be readable and parsable
468+ (Quoted _ _, _) -> keep f i
469+ (_, Quoted _ _) -> keep f i
470+ -- parent inlines would prevent links from being correctly
471+ -- parsed, in this case we prioritise the content over the
472+ -- style
473+ (_, Link _ _ _) -> emerge f i
474+ -- always give priority to strong text over emphasis
475+ (Emph _, Strong _) -> emerge f i
476+ -- drop all other nested styles
477+ (_, _) -> collapse f i
478+
479+ emerge f i = f <> [i]
480+ keep f i = appendToLast f [i]
481+ collapse f i = appendToLast f $ dropInlineParent i
482+
483+ appendToLast :: [Inline] -> [Inline] -> [Inline]
484+ appendToLast [] toAppend = [setInlineChildren outer toAppend]
485+ appendToLast flattened toAppend
486+ | isOuter lastFlat = init flattened <> [appendTo lastFlat toAppend]
487+ | otherwise = flattened <> [setInlineChildren outer toAppend]
488+ where lastFlat = last flattened
489+ appendTo o i = mapNested (<> i) o
490+ isOuter i = emptyParent i == emptyParent outer
491+ emptyParent i = setInlineChildren i []
492+
493+mapNested :: ([Inline] -> [Inline]) -> Inline -> Inline
494+mapNested f i = setInlineChildren i (f (dropInlineParent i))
495+
496+dropInlineParent :: Inline -> [Inline]
497+dropInlineParent (Link _ i _) = i
498+dropInlineParent (Emph i) = i
499+dropInlineParent (Strong i) = i
500+dropInlineParent (Strikeout i) = i
501+dropInlineParent (Superscript i) = i
502+dropInlineParent (Subscript i) = i
503+dropInlineParent (SmallCaps i) = i
504+dropInlineParent (Cite _ i) = i
505+dropInlineParent (Image _ i _) = i
506+dropInlineParent (Span _ i) = i
507+dropInlineParent (Quoted _ i) = i
508+dropInlineParent i = [i] -- not a parent, like Str or Space
509+
510+setInlineChildren :: Inline -> [Inline] -> Inline
511+setInlineChildren (Link a _ t) i = Link a i t
512+setInlineChildren (Emph _) i = Emph i
513+setInlineChildren (Strong _) i = Strong i
514+setInlineChildren (Strikeout _) i = Strikeout i
515+setInlineChildren (Superscript _) i = Superscript i
516+setInlineChildren (Subscript _) i = Subscript i
517+setInlineChildren (SmallCaps _) i = SmallCaps i
518+setInlineChildren (Quoted q _) i = Quoted q i
519+setInlineChildren (Cite c _) i = Cite c i
520+setInlineChildren (Image a _ t) i = Image a i t
521+setInlineChildren (Span a _) i = Span a i
522+setInlineChildren leaf _ = leaf
523+
452524inlineListToRST :: PandocMonad m => [Inline] -> RST m Doc
453525inlineListToRST = writeInlines . walk transformInlines
454526
test/Tests/Writers/RST.hs+24
@@ -4,10 +4,12 @@ module Tests.Writers.RST (tests) where
44
55import Prelude
66import Test.Tasty
7+import Test.Tasty.HUnit
78import Tests.Helpers
89import Text.Pandoc
910import Text.Pandoc.Arbitrary ()
1011import Text.Pandoc.Builder
12+import Text.Pandoc.Writers.RST
1113
1214infix 4 =:
1315(=:) :: (ToString a, ToPandoc a)
@@ -52,6 +54,17 @@ tests = [ testGroup "rubrics"
5254 , ""
5355 , " quoted"]
5456 ]
57+ , testGroup "flatten"
58+ [ testCase "emerges nested styles as expected" $
59+ flatten (Emph [Str "1", Strong [Str "2"], Str "3"]) @?=
60+ [Emph [Str "1"], Strong [Str "2"], Emph [Str "3"]]
61+ , testCase "could introduce trailing spaces" $
62+ flatten (Emph [Str "f", Space, Strong [Str "2"]]) @?=
63+ [Emph [Str "f", Space], Strong [Str "2"]]
64+ -- the test above is the reason why we call
65+ -- stripLeadingTrailingSpace through transformNested after
66+ -- flatten
67+ ]
5568 , testGroup "inlines"
5669 [ "are removed when empty" =: -- #4434
5770 plain (strong (str "")) =?> ""
@@ -64,6 +77,17 @@ tests = [ testGroup "rubrics"
6477 strong (space <> str "text" <> space <> space) =?> "**text**"
6578 , "single space stripped" =:
6679 strong space =?> ""
80+ , "give priority to strong style over emphasis" =:
81+ strong (emph (strong (str "s"))) =?> "**s**"
82+ , "links are not elided by outer style" =:
83+ strong (emph (link "loc" "" (str "text"))) =?>
84+ "`text <loc>`__"
85+ , "RST inlines cannot start nor end with spaces" =:
86+ emph (str "f" <> space <> strong (str "d") <> space <> str "l") =?>
87+ "*f*\\ **d**\\ *l*"
88+ , "keeps quotes" =:
89+ strong (str "f" <> doubleQuoted (str "d") <> str "l") =?>
90+ "**f“d”l**"
6791 ]
6892 , testGroup "headings"
6993 [ "normal heading" =:
test/writer.rst+7−7
@@ -615,21 +615,21 @@ This is *emphasized*, and so *is this*.
615615
616616This is **strong**, and so **is this**.
617617
618−An *`emphasized link </url>`__*.
618+An `emphasized link </url>`__.
619619
620−***This is strong and em.***
620+**This is strong and em.**
621621
622−So is ***this*** word.
622+So is **this** word.
623623
624−***This is strong and em.***
624+**This is strong and em.**
625625
626−So is ***this*** word.
626+So is **this** word.
627627
628628This is code: ``>``, ``$``, ``\``, ``\$``, ``<html>``.
629629
630−[STRIKEOUT:This is *strikeout*.]
630+[STRIKEOUT:This is strikeout.]
631631
632−Superscripts: a\ :sup:`bc`\ d a\ :sup:`*hello*` a\ :sup:`hello there`.
632+Superscripts: a\ :sup:`bc`\ d a\ :sup:`hello` a\ :sup:`hello there`.
633633
634634Subscripts: H\ :sub:`2`\ O, H\ :sub:`23`\ O, H\ :sub:`many of them`\ O.
635635