git.iko.soy/typeable/pandoc
on master help sign in
Add unexported Text.Pandoc.Readers.Metadata.

For YAML metadata parsing. A step in the direction of #5914. No API change.

commit659ee981764b85fb46845c086e3b10f1fc57a712 authorJohn MacFarlane <jgm@berkeley.edu> dateSun 24 Nov 2019, 11:50 (7 years ago) parentda5b6d5
4 files changed+163−105
pandoc.cabal+1 src/Text/Pandoc/App.hs+1−1 src/Text/Pandoc/Readers/Markdown.hs+21−104 src/Text/Pandoc/Readers/Metadata.hs+140
pandoc.cabal+1
@@ -590,6 +590,7 @@ library
590590 Text.Pandoc.Readers.Org.ParserState,
591591 Text.Pandoc.Readers.Org.Parsing,
592592 Text.Pandoc.Readers.Org.Shared,
593+ Text.Pandoc.Readers.Metadata,
593594 Text.Pandoc.Readers.Roff,
594595 Text.Pandoc.Writers.Docx.StyleMap,
595596 Text.Pandoc.Writers.Roff,
src/Text/Pandoc/App.hs+1−1
@@ -55,12 +55,12 @@ import Text.Pandoc.BCP47 (Lang (..), parseBCP47)
5555import Text.Pandoc.Builder (setMeta)
5656import Text.Pandoc.Filter (Filter (JSONFilter, LuaFilter), applyFilters)
5757import Text.Pandoc.PDF (makePDF)
58−import Text.Pandoc.Readers.Markdown (yamlToMeta)
5958import Text.Pandoc.SelfContained (makeDataURI, makeSelfContained)
6059import Text.Pandoc.Shared (eastAsianLineBreakFilter, stripEmptyParagraphs,
6160 headerShift, isURI, tabFilter, uriPathToPath, filterIpynbOutput,
6261 defaultUserDataDirs, tshow)
6362import Text.Pandoc.Writers.Shared (lookupMetaString)
63+import Text.Pandoc.Readers.Markdown (yamlToMeta)
6464import qualified Text.Pandoc.UTF8 as UTF8
6565#ifndef _WINDOWS
6666import System.Posix.IO (stdOutput)
src/Text/Pandoc/Readers/Markdown.hs+21−104
@@ -20,7 +20,6 @@ module Text.Pandoc.Readers.Markdown ( readMarkdown, yamlToMeta ) where
2020import Prelude
2121import Control.Monad
2222import Control.Monad.Except (throwError)
23−import qualified Data.ByteString.Lazy as BS
2423import Data.Char (isAlphaNum, isPunctuation, isSpace)
2524import Data.List (sortBy, transpose, elemIndex)
2625import qualified Data.Map as M
@@ -30,8 +29,7 @@ import qualified Data.Set as Set
3029import Data.Text (Text)
3130import qualified Data.Text as T
3231import qualified Data.Text.Lazy as TL
33−import qualified Data.YAML as YAML
34−import qualified Data.YAML.Event as YE
32+import qualified Data.ByteString.Lazy as BL
3533import System.FilePath (addExtension, takeExtension)
3634import Text.HTML.TagSoup
3735import Text.Pandoc.Builder (Blocks, Inlines)
@@ -49,6 +47,7 @@ import Text.Pandoc.Readers.LaTeX (applyMacros, rawLaTeXBlock, rawLaTeXInline)
4947import Text.Pandoc.Shared
5048import qualified Text.Pandoc.UTF8 as UTF8
5149import Text.Pandoc.XML (fromEntities)
50+import Text.Pandoc.Readers.Metadata (yamlBsToMeta)
5251
5352type MarkdownParser m = ParserT Text ParserState m
5453
@@ -64,6 +63,23 @@ readMarkdown opts s = do
6463 Right result -> return result
6564 Left e -> throwError e
6665
66+-- | Read a YAML string and convert it to pandoc metadata.
67+-- String scalars in the YAML are parsed as Markdown.
68+yamlToMeta :: PandocMonad m
69+ => ReaderOptions
70+ -> BL.ByteString
71+ -> m Meta
72+yamlToMeta opts bstr = do
73+ let parser = do
74+ meta <- yamlBsToMeta parseBlocks bstr
75+ return $ runF meta defaultParserState
76+ parsed <- readWithM parser def{ stateOptions = opts } ""
77+ case parsed of
78+ Right result -> return result
79+ Left e -> throwError e
80+
81+
82+
6783--
6884-- Constants and data structure definitions
6985--
@@ -228,111 +244,12 @@ yamlMetaBlock = try $ do
228244 -- by including --- and ..., we allow yaml blocks with just comments:
229245 let rawYaml = T.unlines ("---" : (rawYamlLines ++ ["..."]))
230246 optional blanklines
231− newMetaF <- yamlBsToMeta $ UTF8.fromTextLazy $ TL.fromStrict rawYaml
247+ newMetaF <- yamlBsToMeta parseBlocks
248+ $ UTF8.fromTextLazy $ TL.fromStrict rawYaml
232249 -- Since `<>` is left-biased, existing values are not touched:
233250 updateState $ \st -> st{ stateMeta' = (stateMeta' st) <> newMetaF }
234251 return mempty
235252
236−-- | Read a YAML string and convert it to pandoc metadata.
237−-- String scalars in the YAML are parsed as Markdown.
238−yamlToMeta :: PandocMonad m => ReaderOptions -> BS.ByteString -> m Meta
239−yamlToMeta opts bstr = do
240− let parser = do
241− meta <- yamlBsToMeta bstr
242− return $ runF meta defaultParserState
243− parsed <- readWithM parser def{ stateOptions = opts } ""
244− case parsed of
245− Right result -> return result
246− Left e -> throwError e
247−
248−yamlBsToMeta :: PandocMonad m => BS.ByteString -> MarkdownParser m (F Meta)
249−yamlBsToMeta bstr = do
250− pos <- getPosition
251− case YAML.decodeNode' YAML.failsafeSchemaResolver False False bstr of
252− Right ((YAML.Doc (YAML.Mapping _ _ o)):_) -> (fmap Meta) <$> yamlMap o
253− Right [] -> return . return $ mempty
254− Right [YAML.Doc (YAML.Scalar _ YAML.SNull)] -> return . return $ mempty
255− Right _ -> do
256− logMessage $
257− CouldNotParseYamlMetadata "not an object"
258− pos
259− return . return $ mempty
260− Left (_pos, err') -> do
261− logMessage $ CouldNotParseYamlMetadata
262− (T.pack err') pos
263− return . return $ mempty
264−
265−nodeToKey :: PandocMonad m => YAML.Node YE.Pos -> m Text
266−nodeToKey (YAML.Scalar _ (YAML.SStr t)) = return t
267−nodeToKey (YAML.Scalar _ (YAML.SUnknown _ t)) = return t
268−nodeToKey _ = throwError $ PandocParseError
269− "Non-string key in YAML mapping"
270−
271−toMetaValue :: PandocMonad m
272− => Text -> MarkdownParser m (F MetaValue)
273−toMetaValue x =
274− -- Note: a standard quoted or unquoted YAML value will
275− -- not end in a newline, but a "block" set off with
276− -- `|` or `>` will.
277− if "\n" `T.isSuffixOf` x
278− then parseFromString' (asBlocks <$> parseBlocks) (x <> "\n")
279− else parseFromString'
280− ((asInlines <$> try pInlines) <|> (asBlocks <$> parseBlocks))
281− x
282− where pInlines = trimInlinesF . mconcat <$> manyTill inline eof
283− asBlocks p = do
284− p' <- p
285− return $ MetaBlocks (B.toList p')
286− asInlines p = do
287− p' <- p
288− return $ MetaInlines (B.toList p')
289−
290−checkBoolean :: Text -> Maybe Bool
291−checkBoolean t =
292− if t == T.pack "true" || t == T.pack "True" || t == T.pack "TRUE"
293− then Just True
294− else if t == T.pack "false" || t == T.pack "False" || t == T.pack "FALSE"
295− then Just False
296− else Nothing
297−
298−yamlToMetaValue :: PandocMonad m
299− => YAML.Node YE.Pos-> MarkdownParser m (F MetaValue)
300−yamlToMetaValue (YAML.Scalar _ x) =
301− case x of
302− YAML.SStr t -> toMetaValue t
303− YAML.SBool b -> return $ return $ MetaBool b
304− YAML.SFloat d -> return $ return $ MetaString $ tshow d
305− YAML.SInt i -> return $ return $ MetaString $ tshow i
306− YAML.SUnknown _ t ->
307− case checkBoolean t of
308− Just b -> return $ return $ MetaBool b
309− Nothing -> toMetaValue t
310− YAML.SNull -> return $ return $ MetaString ""
311−yamlToMetaValue (YAML.Sequence _ _ xs) = do
312− xs' <- mapM yamlToMetaValue xs
313− return $ do
314− xs'' <- sequence xs'
315− return $ B.toMetaValue xs''
316−yamlToMetaValue (YAML.Mapping _ _ o) = fmap B.toMetaValue <$> yamlMap o
317−yamlToMetaValue _ = return $ return $ MetaString ""
318−
319−yamlMap :: PandocMonad m
320− => M.Map (YAML.Node YE.Pos) (YAML.Node YE.Pos)
321− -> MarkdownParser m (F (M.Map Text MetaValue))
322−yamlMap o = do
323− kvs <- forM (M.toList o) $ \(key, v) -> do
324− k <- nodeToKey key
325− return (k, v)
326− let kvs' = filter (not . ignorable . fst) kvs
327− (fmap M.fromList . sequence) <$> mapM toMeta kvs'
328− where
329− ignorable t = "_" `T.isSuffixOf` t
330− toMeta (k, v) = do
331− fv <- yamlToMetaValue v
332− return $ do
333− v' <- fv
334− return (k, v')
335−
336253stopLine :: PandocMonad m => MarkdownParser m ()
337254stopLine = try $ (string "---" <|> string "...") >> blankline >> return ()
338255
src/Text/Pandoc/Readers/Metadata.hsnew file+140
@@ -0,0 +1,140 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE RelaxedPolyRec #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE TupleSections #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE ViewPatterns #-}
7+{- |
8+ Module : Text.Pandoc.Readers.Metadata
9+ Copyright : Copyright (C) 2006-2019 John MacFarlane
10+ License : GNU GPL, version 2 or above
11+
12+ Maintainer : John MacFarlane <jgm@berkeley.edu>
13+ Stability : alpha
14+ Portability : portable
15+
16+Parse YAML/JSON metadata to 'Pandoc' 'Meta'.
17+-}
18+module Text.Pandoc.Readers.Metadata ( yamlBsToMeta ) where
19+
20+import Prelude
21+import Control.Monad
22+import Control.Monad.Except (throwError)
23+import qualified Data.ByteString.Lazy as BL
24+import qualified Data.Map as M
25+import Data.Text (Text)
26+import qualified Data.Text as T
27+import qualified Data.YAML as YAML
28+import qualified Data.YAML.Event as YE
29+import qualified Text.Pandoc.Builder as B
30+import Text.Pandoc.Builder (Blocks)
31+import Text.Pandoc.Class (PandocMonad (..))
32+import Text.Pandoc.Definition
33+import Text.Pandoc.Error
34+import Text.Pandoc.Logging
35+import Text.Pandoc.Parsing hiding (tableWith)
36+import Text.Pandoc.Shared
37+
38+yamlBsToMeta :: PandocMonad m
39+ => ParserT Text ParserState m (F Blocks)
40+ -> BL.ByteString
41+ -> ParserT Text ParserState m (F Meta)
42+yamlBsToMeta pBlocks bstr = do
43+ pos <- getPosition
44+ case YAML.decodeNode' YAML.failsafeSchemaResolver False False bstr of
45+ Right ((YAML.Doc (YAML.Mapping _ _ o)):_)
46+ -> (fmap Meta) <$> yamlMap pBlocks o
47+ Right [] -> return . return $ mempty
48+ Right [YAML.Doc (YAML.Scalar _ YAML.SNull)]
49+ -> return . return $ mempty
50+ Right _ -> do logMessage $ CouldNotParseYamlMetadata "not an object"
51+ pos
52+ return . return $ mempty
53+ Left (_pos, err')
54+ -> do logMessage $ CouldNotParseYamlMetadata
55+ (T.pack err') pos
56+ return . return $ mempty
57+
58+nodeToKey :: PandocMonad m
59+ => YAML.Node YE.Pos
60+ -> m Text
61+nodeToKey (YAML.Scalar _ (YAML.SStr t)) = return t
62+nodeToKey (YAML.Scalar _ (YAML.SUnknown _ t)) = return t
63+nodeToKey _ = throwError $ PandocParseError
64+ "Non-string key in YAML mapping"
65+
66+toMetaValue :: PandocMonad m
67+ => ParserT Text ParserState m (F Blocks)
68+ -> Text
69+ -> ParserT Text ParserState m (F MetaValue)
70+toMetaValue pBlocks x =
71+ -- Note: a standard quoted or unquoted YAML value will
72+ -- not end in a newline, but a "block" set off with
73+ -- `|` or `>` will.
74+ if "\n" `T.isSuffixOf` x
75+ then parseFromString' (asBlocks <$> pBlocks) (x <> "\n")
76+ else parseFromString' pInlines x
77+ where pInlines = do
78+ bs <- pBlocks
79+ return $ do
80+ bs' <- bs
81+ return $
82+ case B.toList bs' of
83+ [Plain ils] -> MetaInlines ils
84+ [Para ils] -> MetaInlines ils
85+ xs -> MetaBlocks xs
86+ asBlocks p = do
87+ p' <- p
88+ return $ MetaBlocks (B.toList p')
89+
90+checkBoolean :: Text -> Maybe Bool
91+checkBoolean t =
92+ if t == T.pack "true" || t == T.pack "True" || t == T.pack "TRUE"
93+ then Just True
94+ else if t == T.pack "false" || t == T.pack "False" || t == T.pack "FALSE"
95+ then Just False
96+ else Nothing
97+
98+yamlToMetaValue :: PandocMonad m
99+ => ParserT Text ParserState m (F Blocks)
100+ -> YAML.Node YE.Pos
101+ -> ParserT Text ParserState m (F MetaValue)
102+yamlToMetaValue pBlocks (YAML.Scalar _ x) =
103+ case x of
104+ YAML.SStr t -> toMetaValue pBlocks t
105+ YAML.SBool b -> return $ return $ MetaBool b
106+ YAML.SFloat d -> return $ return $ MetaString $ tshow d
107+ YAML.SInt i -> return $ return $ MetaString $ tshow i
108+ YAML.SUnknown _ t ->
109+ case checkBoolean t of
110+ Just b -> return $ return $ MetaBool b
111+ Nothing -> toMetaValue pBlocks t
112+ YAML.SNull -> return $ return $ MetaString ""
113+
114+yamlToMetaValue pBlocks (YAML.Sequence _ _ xs) = do
115+ xs' <- mapM (yamlToMetaValue pBlocks) xs
116+ return $ do
117+ xs'' <- sequence xs'
118+ return $ B.toMetaValue xs''
119+yamlToMetaValue pBlocks (YAML.Mapping _ _ o) =
120+ fmap B.toMetaValue <$> yamlMap pBlocks o
121+yamlToMetaValue _ _ = return $ return $ MetaString ""
122+
123+yamlMap :: PandocMonad m
124+ => ParserT Text ParserState m (F Blocks)
125+ -> M.Map (YAML.Node YE.Pos) (YAML.Node YE.Pos)
126+ -> ParserT Text ParserState m (F (M.Map Text MetaValue))
127+yamlMap pBlocks o = do
128+ kvs <- forM (M.toList o) $ \(key, v) -> do
129+ k <- nodeToKey key
130+ return (k, v)
131+ let kvs' = filter (not . ignorable . fst) kvs
132+ (fmap M.fromList . sequence) <$> mapM toMeta kvs'
133+ where
134+ ignorable t = "_" `T.isSuffixOf` t
135+ toMeta (k, v) = do
136+ fv <- yamlToMetaValue pBlocks v
137+ return $ do
138+ v' <- fv
139+ return (k, v')
140+