From b1eba3c65ca2981c78b66d1ee9218f3dfeda89c7 Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Sun, 29 Jun 2014 08:14:36 -0400 Subject: [PATCH 1/8] Docx Reader: Update state properly Previously, a fresh state was created for the purpose of updating. In the future, when there is more than one field in the state, this obviously won't work. --- src/Text/Pandoc/Readers/Docx.hs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs index 71baa5dde..2e10ae3a4 100644 --- a/src/Text/Pandoc/Readers/Docx.hs +++ b/src/Text/Pandoc/Readers/Docx.hs @@ -110,6 +110,11 @@ data DEnv = DEnv { docxOptions :: ReaderOptions type DocxContext = ReaderT DEnv (State DState) +updateDState :: (DState -> DState) -> DocxContext () +updateDState f = do + st <- get + put $ f st + evalDocxContext :: DocxContext a -> DEnv -> DState -> a evalDocxContext ctx env st = evalState (runReaderT ctx env) st @@ -289,7 +294,7 @@ parPartToInlines (BookMark _ anchor) = let newAnchor = case anchor `elem` (M.elems anchorMap) of True -> uniqueIdent [Str anchor] (M.elems anchorMap) False -> anchor - put DState{ docxAnchorMap = M.insert anchor newAnchor anchorMap} + updateDState $ \s -> s { docxAnchorMap = M.insert anchor newAnchor anchorMap} return [Span (anchor, ["anchor"], []) []] parPartToInlines (Drawing relid) = do (Docx _ _ _ rels _) <- asks docxDocument @@ -329,7 +334,7 @@ makeHeaderAnchor (Header n (_, classes, kvs) ils) do hdrIDMap <- gets docxAnchorMap let newIdent = uniqueIdent ils (M.elems hdrIDMap) - put DState{docxAnchorMap = M.insert ident newIdent hdrIDMap} + updateDState $ \s -> s {docxAnchorMap = M.insert ident newIdent hdrIDMap} return $ Header n (newIdent, classes, kvs) (ils \\ (x:xs)) -- Otherwise we just give it a name, and register that name (associate -- it with itself.) @@ -337,7 +342,7 @@ makeHeaderAnchor (Header n (_, classes, kvs) ils) = do hdrIDMap <- gets docxAnchorMap let newIdent = uniqueIdent ils (M.elems hdrIDMap) - put DState{docxAnchorMap = M.insert newIdent newIdent hdrIDMap} + updateDState $ \s -> s {docxAnchorMap = M.insert newIdent newIdent hdrIDMap} return $ Header n (newIdent, classes, kvs) ils makeHeaderAnchor blk = return blk From 0587334bc0c87ae62f37cf14543b7727d408aeca Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Sun, 29 Jun 2014 16:38:51 -0400 Subject: [PATCH 2/8] Docx writer: insert bookmark tags inside <w:p> tag. This makes the header anchors in pandoc-generated ooxml match those generated by word. --- src/Text/Pandoc/Writers/Docx.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs index 31e64f14e..4b787b023 100644 --- a/src/Text/Pandoc/Writers/Docx.hs +++ b/src/Text/Pandoc/Writers/Docx.hs @@ -514,8 +514,11 @@ blockToOpenXML :: WriterOptions -> Block -> WS [Element] blockToOpenXML _ Null = return [] blockToOpenXML opts (Div _ bs) = blocksToOpenXML opts bs blockToOpenXML opts (Header lev (ident,_,_) lst) = do - contents <- withParaProp (pStyle $ "Heading" ++ show lev) $ - blockToOpenXML opts (Para lst) + + paraProps <- withParaProp (pStyle $ "Heading" ++ show lev) $ + getParaProps False + contents <- inlinesToOpenXML opts lst + usedIdents <- gets stSectionIds let bookmarkName = if null ident then uniqueIdent lst usedIdents @@ -525,7 +528,7 @@ blockToOpenXML opts (Header lev (ident,_,_) lst) = do let bookmarkStart = mknode "w:bookmarkStart" [("w:id", id') ,("w:name",bookmarkName)] () let bookmarkEnd = mknode "w:bookmarkEnd" [("w:id", id')] () - return $ [bookmarkStart] ++ contents ++ [bookmarkEnd] + return [mknode "w:p" [] (paraProps ++ [bookmarkStart, bookmarkEnd] ++ contents)] blockToOpenXML opts (Plain lst) = withParaProp (pStyle "Compact") $ blockToOpenXML opts (Para lst) -- title beginning with fig: indicates that the image is a figure From c0fcc8a7891892357854cf498ce262b256fac1ca Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Sun, 29 Jun 2014 18:44:22 -0400 Subject: [PATCH 3/8] Docx reader: Add ParIndentation type to parser. This lets us keep more information about the indentation, and act accordingly in the reader. --- src/Text/Pandoc/Readers/Docx/Parse.hs | 31 ++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Text/Pandoc/Readers/Docx/Parse.hs b/src/Text/Pandoc/Readers/Docx/Parse.hs index 07f34450d..537c5c272 100644 --- a/src/Text/Pandoc/Readers/Docx/Parse.hs +++ b/src/Text/Pandoc/Readers/Docx/Parse.hs @@ -42,6 +42,7 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..) , Relationship , Media , RunStyle(..) + , ParIndentation(..) , ParagraphStyle(..) , Row(..) , Cell(..) @@ -341,16 +342,37 @@ testBitMask bitMaskS n = [] -> False ((n', _) : _) -> ((n' .|. n) /= 0) +data ParIndentation = ParIndentation { leftParIndent :: Maybe Integer + , rightParIndent :: Maybe Integer + , hangingParIndent :: Maybe Integer} + deriving Show + data ParagraphStyle = ParagraphStyle { pStyle :: [String] - , indent :: Maybe Integer + , indentation :: Maybe ParIndentation } deriving Show defaultParagraphStyle :: ParagraphStyle defaultParagraphStyle = ParagraphStyle { pStyle = [] - , indent = Nothing + , indentation = Nothing } +elemToParIndentation :: NameSpaces -> Element -> Maybe ParIndentation +elemToParIndentation ns element + | qName (elName element) == "ind" && + qURI (elName element) == (lookup "w" ns) = + Just $ ParIndentation { + leftParIndent = + findAttr (QName "left" (lookup "w" ns) (Just "w")) element >>= + stringToInteger + , rightParIndent = + findAttr (QName "right" (lookup "w" ns) (Just "w")) element >>= + stringToInteger + , hangingParIndent = + findAttr (QName "hanging" (lookup "w" ns) (Just "w")) element >>= + stringToInteger} +elemToParIndentation _ _ = Nothing + elemToParagraphStyle :: NameSpaces -> Element -> ParagraphStyle elemToParagraphStyle ns element = case findChild (QName "pPr" (lookup "w" ns) (Just "w")) element of @@ -360,10 +382,9 @@ elemToParagraphStyle ns element = mapMaybe (findAttr (QName "val" (lookup "w" ns) (Just "w"))) (findChildren (QName "pStyle" (lookup "w" ns) (Just "w")) pPr) - , indent = + , indentation = findChild (QName "ind" (lookup "w" ns) (Just "w")) pPr >>= - findAttr (QName "left" (lookup "w" ns) (Just "w")) >>= - stringToInteger + elemToParIndentation ns } Nothing -> defaultParagraphStyle From 0f59196e0ef2f0977d404699ae16a48009fa7632 Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Sun, 29 Jun 2014 22:50:29 -0400 Subject: [PATCH 4/8] Docx reader: Make use of new ParIndentation info. Here, when hanging indents are greater than or equal to left indents, we don't set it to block quote. Such indents are frequently used in academic bibliographies. (Thanks to Caleb McDaniel.) --- src/Text/Pandoc/Readers/Docx.hs | 35 +++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs index 71baa5dde..8357b4cca 100644 --- a/src/Text/Pandoc/Readers/Docx.hs +++ b/src/Text/Pandoc/Readers/Docx.hs @@ -94,6 +94,7 @@ import System.FilePath (combine) import qualified Data.Map as M import Control.Monad.Reader import Control.Monad.State +import Control.Applicative (liftA2) readDocx :: ReaderOptions -> B.ByteString @@ -148,7 +149,6 @@ runStyleToContainers rPr = in classContainers ++ formatters - divAttrToContainers :: [String] -> [(String, String)] -> [Container Block] divAttrToContainers (c:cs) _ | Just n <- isHeaderClass c = [Container $ \_ -> @@ -166,22 +166,37 @@ divAttrToContainers (c:cs) kvs | c `elem` listParagraphDivs = divAttrToContainers (c:cs) kvs | c `elem` blockQuoteDivs = (Container BlockQuote) : (divAttrToContainers (cs \\ blockQuoteDivs) kvs) divAttrToContainers (_:cs) kvs = divAttrToContainers cs kvs -divAttrToContainers [] kvs | Just numString <- lookup "indent" kvs = - let kvs' = filter (\(k,_) -> k /= "indent") kvs +divAttrToContainers [] kvs | Just _ <- lookup "indent" kvs + , Just flInd <- lookup "first-line-indent" kvs = + let + kvs' = filter (\(k,_) -> notElem k ["indent", "first-line-indent"]) kvs in - case numString of - "0" -> divAttrToContainers [] kvs' - ('-' : _) -> divAttrToContainers [] kvs' - _ -> (Container BlockQuote) : divAttrToContainers [] kvs' + case flInd of + "0" -> divAttrToContainers [] kvs' + ('-':_) -> divAttrToContainers [] kvs' + _ -> (Container BlockQuote) : divAttrToContainers [] kvs' +divAttrToContainers [] kvs | Just ind <- lookup "indent" kvs = + let + kvs' = filter (\(k,_) -> notElem k ["indent"]) kvs + in + case ind of + "0" -> divAttrToContainers [] kvs' + ('-':_) -> divAttrToContainers [] kvs' + _ -> (Container BlockQuote) : divAttrToContainers [] kvs' + divAttrToContainers _ _ = [] parStyleToContainers :: ParagraphStyle -> [Container Block] parStyleToContainers pPr = let classes = pStyle pPr - kvs = case indent pPr of - Just n -> [("indent", show n)] - Nothing -> [] + indent = indentation pPr >>= leftParIndent + hanging = indentation pPr >>= hangingParIndent + firstLineIndent = liftA2 (-) indent hanging + kvs = mapMaybe id + [ indent >>= (\n -> Just ("indent", show n)), + firstLineIndent >>= (\n -> Just ("first-line-indent", show n)) + ] in divAttrToContainers classes kvs From 1405e7b7091dedb50578c9e0cafd62f2c77ca689 Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Sun, 29 Jun 2014 23:37:00 -0400 Subject: [PATCH 5/8] Docx reader: Add tests for hanging indent handline. We want to treat it as a plain paragraph if the hanging amount is greater to or equal to the left indent---i.e., if the first line has zero indentation. But we still want it to be a block quote if it starts to the right of the margin. Someone might format verse with wrapping lines with a hanging indent, for example. --- tests/Tests/Readers/Docx.hs | 4 ++++ tests/docx.hanging_indent.docx | Bin 0 -> 29924 bytes tests/docx.hanging_indent.native | 3 +++ 3 files changed, 7 insertions(+) create mode 100644 tests/docx.hanging_indent.docx create mode 100644 tests/docx.hanging_indent.native diff --git a/tests/Tests/Readers/Docx.hs b/tests/Tests/Readers/Docx.hs index 8c51217cf..a379bbf23 100644 --- a/tests/Tests/Readers/Docx.hs +++ b/tests/Tests/Readers/Docx.hs @@ -112,6 +112,10 @@ tests = [ testGroup "inlines" "blockquotes (parsing indent as blockquote)" "docx.block_quotes.docx" "docx.block_quotes_parse_indent.native" + , testCompare + "hanging indents" + "docx.hanging_indent.docx" + "docx.hanging_indent.native" , testCompare "tables" "docx.tables.docx" diff --git a/tests/docx.hanging_indent.docx b/tests/docx.hanging_indent.docx new file mode 100644 index 0000000000000000000000000000000000000000..6f62dc7315e7befbc4ab8ccacd4c4cb61d8891f1 GIT binary patch literal 29924 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 QH30rmgJSyn=E2?n0f&Po;s5{u literal 0 HcmV?d00001 diff --git a/tests/docx.hanging_indent.native b/tests/docx.hanging_indent.native new file mode 100644 index 000000000..138a6967f --- /dev/null +++ b/tests/docx.hanging_indent.native @@ -0,0 +1,3 @@ +[Para [Str "This",Space,Str "is",Space,Str "a",Space,Str "hanging",Space,Str "indent,",Space,Str "with",Space,Str "the",Space,Str "left",Space,Str "side",Space,Str "set",Space,Str "to",Space,Str "the",Space,Str "left",Space,Str "margin,",Space,Str "and",Space,Str "it",Space,Str "wraps",Space,Str "around",Space,Str "the",Space,Str "line."] +,BlockQuote + [Para [Str "Five",Space,Str "years",Space,Str "have",Space,Str "passed,",Space,Str "five",Space,Str "summers",Space,Str "with",Space,Str "the",Space,Str "length"]]] From 3fbbafd391334429df49255160ace17245409e41 Mon Sep 17 00:00:00 2001 From: John MacFarlane <jgm@berkeley.edu> Date: Sun, 29 Jun 2014 23:03:12 -0700 Subject: [PATCH 6/8] Rewrote normalize for efficiency. (Closes #1385.) * Added normalizeInlines, normalizeBlocks. * Type signature is now more narrow, `Pandoc -> Pandoc` instead of `Data a :: a -> a`. Some users may need to change their uses of `normalize` to the newly exported `normalizeInlines` or `normalizeBlocks`. --- man/make-pandoc-man-pages.hs | 6 +- src/Text/Pandoc/Shared.hs | 187 ++++++++++++++++++++++++----------- tests/Tests/Shared.hs | 8 +- 3 files changed, 137 insertions(+), 64 deletions(-) diff --git a/man/make-pandoc-man-pages.hs b/man/make-pandoc-man-pages.hs index 008294433..afba9135a 100644 --- a/man/make-pandoc-man-pages.hs +++ b/man/make-pandoc-man-pages.hs @@ -27,7 +27,7 @@ main = do unless (null ds1 && null ds2) $ do rmContents <- UTF8.readFile "README" - let (Pandoc meta blocks) = readMarkdown def rmContents + let (Pandoc meta blocks) = normalize $ readMarkdown def rmContents let manBlocks = removeSect [Str "Wrappers"] $ removeSect [Str "Pandoc's",Space,Str "markdown"] blocks let syntaxBlocks = extractSect [Str "Pandoc's",Space,Str "markdown"] blocks @@ -67,13 +67,13 @@ capitalize (Str xs) = Str $ map toUpper xs capitalize x = x removeSect :: [Inline] -> [Block] -> [Block] -removeSect ils (Header 1 _ x:xs) | normalize x == normalize ils = +removeSect ils (Header 1 _ x:xs) | x == ils = dropWhile (not . isHeader1) xs removeSect ils (x:xs) = x : removeSect ils xs removeSect _ [] = [] extractSect :: [Inline] -> [Block] -> [Block] -extractSect ils (Header 1 _ z:xs) | normalize z == normalize ils = +extractSect ils (Header 1 _ z:xs) | z == ils = bottomUp promoteHeader $ takeWhile (not . isHeader1) xs where promoteHeader (Header n attr x) = Header (n-1) attr x promoteHeader x = x diff --git a/src/Text/Pandoc/Shared.hs b/src/Text/Pandoc/Shared.hs index 5b0d9b6b4..4a536330d 100644 --- a/src/Text/Pandoc/Shared.hs +++ b/src/Text/Pandoc/Shared.hs @@ -55,6 +55,8 @@ module Text.Pandoc.Shared ( normalizeSpaces, extractSpaces, normalize, + normalizeInlines, + normalizeBlocks, stringify, compactify, compactify', @@ -84,7 +86,6 @@ module Text.Pandoc.Shared ( import Text.Pandoc.Definition import Text.Pandoc.Walk -import Text.Pandoc.Generic import Text.Pandoc.Builder (Inlines, Blocks, ToMetaValue(..)) import qualified Text.Pandoc.Builder as B import qualified Text.Pandoc.UTF8 as UTF8 @@ -350,72 +351,142 @@ extractSpaces f is = -- | Normalize @Pandoc@ document, consolidating doubled 'Space's, -- combining adjacent 'Str's and 'Emph's, remove 'Null's and -- empty elements, etc. -normalize :: (Eq a, Data a) => a -> a -normalize = topDown removeEmptyBlocks . - topDown consolidateInlines . - bottomUp (removeEmptyInlines . removeTrailingInlineSpaces) +normalize :: Pandoc -> Pandoc +normalize (Pandoc (Meta meta) blocks) = + Pandoc (Meta $ M.map go meta) (normalizeBlocks blocks) + where go (MetaInlines xs) = MetaInlines $ normalizeInlines xs + go (MetaBlocks xs) = MetaBlocks $ normalizeBlocks xs + go (MetaList ms) = MetaList $ map go ms + go (MetaMap m) = MetaMap $ M.map go m + go x = x -removeEmptyBlocks :: [Block] -> [Block] -removeEmptyBlocks (Null : xs) = removeEmptyBlocks xs -removeEmptyBlocks (BulletList [] : xs) = removeEmptyBlocks xs -removeEmptyBlocks (OrderedList _ [] : xs) = removeEmptyBlocks xs -removeEmptyBlocks (DefinitionList [] : xs) = removeEmptyBlocks xs -removeEmptyBlocks (RawBlock _ [] : xs) = removeEmptyBlocks xs -removeEmptyBlocks (x:xs) = x : removeEmptyBlocks xs -removeEmptyBlocks [] = [] +normalizeBlocks :: [Block] -> [Block] +normalizeBlocks (Null : xs) = normalizeBlocks xs +normalizeBlocks (Div attr bs : xs) = + Div attr (normalizeBlocks bs) : normalizeBlocks xs +normalizeBlocks (BlockQuote bs : xs) = + case normalizeBlocks bs of + [] -> normalizeBlocks xs + bs' -> BlockQuote bs' : normalizeBlocks xs +normalizeBlocks (BulletList [] : xs) = normalizeBlocks xs +normalizeBlocks (BulletList items : xs) = + BulletList (map normalizeBlocks items) : normalizeBlocks xs +normalizeBlocks (OrderedList _ [] : xs) = normalizeBlocks xs +normalizeBlocks (OrderedList attr items : xs) = + OrderedList attr (map normalizeBlocks items) : normalizeBlocks xs +normalizeBlocks (DefinitionList [] : xs) = normalizeBlocks xs +normalizeBlocks (DefinitionList items : xs) = + DefinitionList (map go items) : normalizeBlocks xs + where go (ils, bs) = (normalizeInlines ils, map normalizeBlocks bs) +normalizeBlocks (RawBlock _ "" : xs) = normalizeBlocks xs +normalizeBlocks (Para ils : xs) = + case normalizeInlines ils of + [] -> normalizeBlocks xs + ils' -> Para ils' : normalizeBlocks xs +normalizeBlocks (Plain ils : xs) = + case normalizeInlines ils of + [] -> normalizeBlocks xs + ils' -> Plain ils' : normalizeBlocks xs +normalizeBlocks (Header lev attr ils : xs) = + Header lev attr (normalizeInlines ils) : normalizeBlocks xs +normalizeBlocks (Table capt aligns widths hdrs rows : xs) = + Table (normalizeInlines capt) aligns widths + (map normalizeBlocks hdrs) (map (map normalizeBlocks) rows) + : normalizeBlocks xs +normalizeBlocks (x:xs) = x : normalizeBlocks xs +normalizeBlocks [] = [] -removeEmptyInlines :: [Inline] -> [Inline] -removeEmptyInlines (Emph [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Strong [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Subscript [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Superscript [] : zs) = removeEmptyInlines zs -removeEmptyInlines (SmallCaps [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Strikeout [] : zs) = removeEmptyInlines zs -removeEmptyInlines (RawInline _ [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Code _ [] : zs) = removeEmptyInlines zs -removeEmptyInlines (Str "" : zs) = removeEmptyInlines zs -removeEmptyInlines (x : xs) = x : removeEmptyInlines xs -removeEmptyInlines [] = [] - -removeTrailingInlineSpaces :: [Inline] -> [Inline] -removeTrailingInlineSpaces = reverse . removeLeadingInlineSpaces . reverse - -removeLeadingInlineSpaces :: [Inline] -> [Inline] -removeLeadingInlineSpaces = dropWhile isSpaceOrEmpty - -consolidateInlines :: [Inline] -> [Inline] -consolidateInlines (Str x : ys) = +normalizeInlines :: [Inline] -> [Inline] +normalizeInlines (Str x : ys) = case concat (x : map fromStr strs) of - "" -> consolidateInlines rest - n -> Str n : consolidateInlines rest + "" -> rest + n -> Str n : rest where - (strs, rest) = span isStr ys + (strs, rest) = span isStr $ normalizeInlines ys isStr (Str _) = True isStr _ = False fromStr (Str z) = z - fromStr _ = error "consolidateInlines - fromStr - not a Str" -consolidateInlines (Space : ys) = Space : rest + fromStr _ = error "normalizeInlines - fromStr - not a Str" +normalizeInlines (Space : ys) = + if null rest + then [] + else Space : rest where isSp Space = True isSp _ = False - rest = consolidateInlines $ dropWhile isSp ys -consolidateInlines (Emph xs : Emph ys : zs) = consolidateInlines $ - Emph (xs ++ ys) : zs -consolidateInlines (Strong xs : Strong ys : zs) = consolidateInlines $ - Strong (xs ++ ys) : zs -consolidateInlines (Subscript xs : Subscript ys : zs) = consolidateInlines $ - Subscript (xs ++ ys) : zs -consolidateInlines (Superscript xs : Superscript ys : zs) = consolidateInlines $ - Superscript (xs ++ ys) : zs -consolidateInlines (SmallCaps xs : SmallCaps ys : zs) = consolidateInlines $ - SmallCaps (xs ++ ys) : zs -consolidateInlines (Strikeout xs : Strikeout ys : zs) = consolidateInlines $ - Strikeout (xs ++ ys) : zs -consolidateInlines (RawInline f x : RawInline f' y : zs) | f == f' = - consolidateInlines $ RawInline f (x ++ y) : zs -consolidateInlines (Code a1 x : Code a2 y : zs) | a1 == a2 = - consolidateInlines $ Code a1 (x ++ y) : zs -consolidateInlines (x : xs) = x : consolidateInlines xs -consolidateInlines [] = [] + rest = dropWhile isSp $ normalizeInlines ys +normalizeInlines (Emph xs : zs) = + case normalizeInlines zs of + (Emph ys : rest) -> normalizeInlines $ + Emph (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> Emph xs' : rest +normalizeInlines (Strong xs : zs) = + case normalizeInlines zs of + (Strong ys : rest) -> normalizeInlines $ + Strong (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> Strong xs' : rest +normalizeInlines (Subscript xs : zs) = + case normalizeInlines zs of + (Subscript ys : rest) -> normalizeInlines $ + Subscript (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> Subscript xs' : rest +normalizeInlines (Superscript xs : zs) = + case normalizeInlines zs of + (Superscript ys : rest) -> normalizeInlines $ + Superscript (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> Superscript xs' : rest +normalizeInlines (SmallCaps xs : zs) = + case normalizeInlines zs of + (SmallCaps ys : rest) -> normalizeInlines $ + SmallCaps (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> SmallCaps xs' : rest +normalizeInlines (Strikeout xs : zs) = + case normalizeInlines zs of + (Strikeout ys : rest) -> normalizeInlines $ + Strikeout (normalizeInlines $ xs ++ ys) : rest + rest -> case normalizeInlines xs of + [] -> rest + xs' -> Strikeout xs' : rest +normalizeInlines (RawInline _ [] : ys) = normalizeInlines ys +normalizeInlines (RawInline f xs : zs) = + case normalizeInlines zs of + (RawInline f' ys : rest) | f == f' -> normalizeInlines $ + RawInline f (xs ++ ys) : rest + rest -> RawInline f xs : rest +normalizeInlines (Code _ "" : ys) = normalizeInlines ys +normalizeInlines (Code attr xs : zs) = + case normalizeInlines zs of + (Code attr' ys : rest) | attr == attr' -> normalizeInlines $ + Code attr (xs ++ ys) : rest + rest -> Code attr xs : rest +-- allow empty spans, they may carry identifiers etc. +-- normalizeInlines (Span _ [] : ys) = normalizeInlines ys +normalizeInlines (Span attr xs : zs) = + case normalizeInlines zs of + (Span attr' ys : rest) | attr == attr' -> normalizeInlines $ + Span attr (normalizeInlines $ xs ++ ys) : rest + rest -> Span attr (normalizeInlines xs) : rest +normalizeInlines (Note bs : ys) = Note (normalizeBlocks bs) : + normalizeInlines ys +normalizeInlines (Quoted qt ils : ys) = + Quoted qt (normalizeInlines ils) : normalizeInlines ys +normalizeInlines (Link ils t : ys) = + Link (normalizeInlines ils) t : normalizeInlines ys +normalizeInlines (Image ils t : ys) = + Image (normalizeInlines ils) t : normalizeInlines ys +normalizeInlines (Cite cs ils : ys) = + Cite cs (normalizeInlines ils) : normalizeInlines ys +normalizeInlines (x : xs) = x : normalizeInlines xs +normalizeInlines [] = [] -- | Convert pandoc structure to a string with formatting removed. -- Footnotes are skipped (since we don't want their contents in link diff --git a/tests/Tests/Shared.hs b/tests/Tests/Shared.hs index f4bf13da4..8c7c31674 100644 --- a/tests/Tests/Shared.hs +++ b/tests/Tests/Shared.hs @@ -16,11 +16,13 @@ tests = [ testGroup "normalize" ] p_normalize_blocks_rt :: [Block] -> Bool -p_normalize_blocks_rt bs = normalize bs == normalize (normalize bs) +p_normalize_blocks_rt bs = + normalizeBlocks bs == normalizeBlocks (normalizeBlocks bs) p_normalize_inlines_rt :: [Inline] -> Bool -p_normalize_inlines_rt ils = normalize ils == normalize (normalize ils) +p_normalize_inlines_rt ils = + normalizeInlines ils == normalizeInlines (normalizeInlines ils) p_normalize_no_trailing_spaces :: [Inline] -> Bool p_normalize_no_trailing_spaces ils = null ils' || last ils' /= Space - where ils' = normalize $ ils ++ [Space] + where ils' = normalizeInlines $ ils ++ [Space] From 0abfd386a4697bab78ca098fe439623aad5f4069 Mon Sep 17 00:00:00 2001 From: Jesse Rosenthal <jrosenthal@jhu.edu> Date: Mon, 30 Jun 2014 11:19:06 -0400 Subject: [PATCH 7/8] Docx reader: clean up parStyle processing. This gets rid of `divAttrToContainers`: an internal convenience function which had become pretty inconvenient. Rather than converting classes and indentations to string lists and back, we deal with the `pPr` attribute directly. --- src/Text/Pandoc/Readers/Docx.hs | 81 +++++++++++++++------------------ 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs index 6e9cf44b5..61c17156e 100644 --- a/src/Text/Pandoc/Readers/Docx.hs +++ b/src/Text/Pandoc/Readers/Docx.hs @@ -94,7 +94,6 @@ import System.FilePath (combine) import qualified Data.Map as M import Control.Monad.Reader import Control.Monad.State -import Control.Applicative (liftA2) readDocx :: ReaderOptions -> B.ByteString @@ -154,56 +153,48 @@ runStyleToContainers rPr = in classContainers ++ formatters -divAttrToContainers :: [String] -> [(String, String)] -> [Container Block] -divAttrToContainers (c:cs) _ | Just n <- isHeaderClass c = - [Container $ \_ -> - Header n ("", delete ("Heading" ++ show n) cs, []) []] -divAttrToContainers (c:cs) kvs | c `elem` divsToKeep = - (Container $ Div ("", [c], [])) : (divAttrToContainers cs kvs) -divAttrToContainers (c:cs) kvs | c `elem` codeDivs = +parStyleToContainers :: ParagraphStyle -> [Container Block] +parStyleToContainers pPr | (c:cs) <- pStyle pPr, Just n <- isHeaderClass c = + [Container $ \_ -> Header n ("", delete ("Heading" ++ show n) cs, []) []] +parStyleToContainers pPr | (c:cs) <- pStyle pPr, c `elem` divsToKeep = + let pPr' = pPr { pStyle = cs } + in + (Container $ Div ("", [c], [])) : (parStyleToContainers pPr') +parStyleToContainers pPr | (c:cs) <- pStyle pPr, c `elem` codeDivs = -- This is a bit of a cludge. We make the codeblock from the raw -- parparts in bodyPartToBlocks. But we need something to match against. - (Container $ \_ -> CodeBlock ("", [], []) "") : (divAttrToContainers cs kvs) -divAttrToContainers (c:cs) kvs | c `elem` listParagraphDivs = - let kvs' = filter (\(k,_) -> k /= "indent") kvs + let pPr' = pPr { pStyle = cs } in - (Container $ Div ("", [c], [])) : (divAttrToContainers cs kvs') -divAttrToContainers (c:cs) kvs | c `elem` blockQuoteDivs = - (Container BlockQuote) : (divAttrToContainers (cs \\ blockQuoteDivs) kvs) -divAttrToContainers (_:cs) kvs = divAttrToContainers cs kvs -divAttrToContainers [] kvs | Just _ <- lookup "indent" kvs - , Just flInd <- lookup "first-line-indent" kvs = - let - kvs' = filter (\(k,_) -> notElem k ["indent", "first-line-indent"]) kvs + (Container $ \_ -> CodeBlock ("", [], []) "") : (parStyleToContainers pPr') +parStyleToContainers pPr | (c:cs) <- pStyle pPr, c `elem` listParagraphDivs = + let pPr' = pPr { pStyle = cs, indentation = Nothing} in - case flInd of - "0" -> divAttrToContainers [] kvs' - ('-':_) -> divAttrToContainers [] kvs' - _ -> (Container BlockQuote) : divAttrToContainers [] kvs' -divAttrToContainers [] kvs | Just ind <- lookup "indent" kvs = - let - kvs' = filter (\(k,_) -> notElem k ["indent"]) kvs - in - case ind of - "0" -> divAttrToContainers [] kvs' - ('-':_) -> divAttrToContainers [] kvs' - _ -> (Container BlockQuote) : divAttrToContainers [] kvs' + (Container $ Div ("", [c], [])) : (parStyleToContainers pPr') -divAttrToContainers _ _ = [] - - -parStyleToContainers :: ParagraphStyle -> [Container Block] -parStyleToContainers pPr = - let classes = pStyle pPr - indent = indentation pPr >>= leftParIndent - hanging = indentation pPr >>= hangingParIndent - firstLineIndent = liftA2 (-) indent hanging - kvs = mapMaybe id - [ indent >>= (\n -> Just ("indent", show n)), - firstLineIndent >>= (\n -> Just ("first-line-indent", show n)) - ] +parStyleToContainers pPr | (c:cs) <- pStyle pPr, c `elem` blockQuoteDivs = + let pPr' = pPr { pStyle = cs \\ blockQuoteDivs } in - divAttrToContainers classes kvs + (Container BlockQuote) : (parStyleToContainers pPr') +parStyleToContainers pPr | (_:cs) <- pStyle pPr = + let pPr' = pPr { pStyle = cs} + in + parStyleToContainers pPr' +parStyleToContainers pPr | null (pStyle pPr), + Just left <- indentation pPr >>= leftParIndent, + Just hang <- indentation pPr >>= hangingParIndent = + let pPr' = pPr { indentation = Nothing } + in + case (left - hang) > 0 of + True -> (Container BlockQuote) : (parStyleToContainers pPr') + False -> parStyleToContainers pPr' +parStyleToContainers pPr | null (pStyle pPr), + Just left <- indentation pPr >>= leftParIndent = + let pPr' = pPr { indentation = Nothing } + in + case left > 0 of + True -> (Container BlockQuote) : (parStyleToContainers pPr') + False -> parStyleToContainers pPr' +parStyleToContainers _ = [] strToInlines :: String -> [Inline] From 264e366f1a973efa56fc32079927fc51cc1936ca Mon Sep 17 00:00:00 2001 From: John MacFarlane <jgm@berkeley.edu> Date: Mon, 30 Jun 2014 14:03:47 -0700 Subject: [PATCH 8/8] Filters: respect shebang if filter is executable. Closes #1389. --- pandoc.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pandoc.hs b/pandoc.hs index 6281113cb..bf5f387dd 100644 --- a/pandoc.hs +++ b/pandoc.hs @@ -49,7 +49,7 @@ import System.Console.GetOpt import Data.Char ( toLower ) import Data.List ( intercalate, isPrefixOf, isSuffixOf, sort ) import System.Directory ( getAppUserDataDirectory, findExecutable, - doesFileExist ) + doesFileExist, Permissions(..), getPermissions ) import System.IO ( stdout, stderr ) import System.IO.Error ( isDoesNotExistError ) import qualified Control.Exception as E @@ -104,8 +104,12 @@ externalFilter f args' d = do Nothing -> do exists <- doesFileExist f if exists - then return $ + then do + isExecutable <- executable `fmap` + getPermissions f + return $ case map toLower $ takeExtension f of + _ | isExecutable -> (f, args') ".py" -> ("python", f:args') ".hs" -> ("runhaskell", f:args') ".pl" -> ("perl", f:args')