git.iko.soy/typeable/pandoc
on master help sign in
Issue warning for duplicate header identifiers.

As noted in the previous commit, an autogenerated identifier may still coincide with an explicit identifier that is given for a header later in the document, or with an identifier on a div, span, link, or image. This commit adds a warning in this case, so users can supply an explicit identifier. * Added `DuplicateIdentifier` to LogMessage. * Modified HTML, Org, MediaWiki readers so their custom state type is an instance of HasLogMessages. This is necessary for `registerHeader` to issue warnings. See #1745.

commit2f8f8f0da64388fa01d83fccf3cf1f2899c64269 authorJohn MacFarlane <jgm@berkeley.edu> dateSun 12 Mar 2017, 22:03 (10 years ago) parentc8b9062
6 files changed+46−8
src/Text/Pandoc/Logging.hs+10−1 src/Text/Pandoc/Parsing.hs+8−2 src/Text/Pandoc/Readers/HTML.hs+11−5 src/Text/Pandoc/Readers/MediaWiki.hs+7 src/Text/Pandoc/Readers/Org.hs+2 src/Text/Pandoc/Readers/Org/ParserState.hs+8
src/Text/Pandoc/Logging.hs+10−1
@@ -62,6 +62,7 @@ data LogMessage =
6262 | CouldNotParseYamlMetadata String SourcePos
6363 | DuplicateLinkReference String SourcePos
6464 | DuplicateNoteReference String SourcePos
65+ | DuplicateIdentifier String SourcePos
6566 | ReferenceNotFound String SourcePos
6667 | CircularReference String SourcePos
6768 | ParsingUnescaped String SourcePos
@@ -106,6 +107,11 @@ instance ToJSON LogMessage where
106107 "source" .= Text.pack (sourceName pos),
107108 "line" .= toJSON (sourceLine pos),
108109 "column" .= toJSON (sourceColumn pos)]
110+ DuplicateIdentifier s pos ->
111+ ["contents" .= Text.pack s,
112+ "source" .= Text.pack (sourceName pos),
113+ "line" .= toJSON (sourceLine pos),
114+ "column" .= toJSON (sourceColumn pos)]
109115 ReferenceNotFound s pos ->
110116 ["contents" .= Text.pack s,
111117 "source" .= Text.pack (sourceName pos),
@@ -184,6 +190,8 @@ showLogMessage msg =
184190 "Duplicate link reference '" ++ s ++ "' at " ++ showPos pos
185191 DuplicateNoteReference s pos ->
186192 "Duplicate note reference '" ++ s ++ "' at " ++ showPos pos
193+ DuplicateIdentifier s pos ->
194+ "Duplicate identifier '" ++ s ++ "' at " ++ showPos pos
187195 ReferenceNotFound s pos ->
188196 "Reference not found for '" ++ s ++ "' at " ++ showPos pos
189197 CircularReference s pos ->
@@ -233,6 +241,7 @@ messageVerbosity msg =
233241 CouldNotParseYamlMetadata{} -> WARNING
234242 DuplicateLinkReference{} -> WARNING
235243 DuplicateNoteReference{} -> WARNING
244+ DuplicateIdentifier{} -> WARNING
236245 ReferenceNotFound{} -> WARNING
237246 CircularReference{} -> WARNING
238247 CouldNotLoadIncludeFile{} -> WARNING
@@ -249,4 +258,4 @@ messageVerbosity msg =
249258 CouldNotParseCSS{} -> WARNING
250259 Fetching{} -> INFO
251260 NoTitleElement{} -> WARNING
252− NoLangSpecified -> INFO
261+ NoLangSpecified -> INFO
src/Text/Pandoc/Parsing.hs+8−2
@@ -1112,8 +1112,11 @@ type SubstTable = M.Map Key Inlines
11121112-- with its associated identifier. If the identifier is null
11131113-- and the auto_identifers extension is set, generate a new
11141114-- unique identifier, and update the list of identifiers
1115−-- in state.
1116−registerHeader :: (Stream s m a, HasReaderOptions st, HasHeaderMap st, HasIdentifierList st)
1115+-- in state. Issue a warning if an explicit identifier
1116+-- is encountered that duplicates an earlier identifier
1117+-- (explict or automatically generated).
1118+registerHeader :: (Stream s m a, HasReaderOptions st,
1119+ HasHeaderMap st, HasLogMessages st, HasIdentifierList st)
11171120 => Attr -> Inlines -> ParserT s st m Attr
11181121registerHeader (ident,classes,kvs) header' = do
11191122 ids <- extractIdentifierList <$> getState
@@ -1131,6 +1134,9 @@ registerHeader (ident,classes,kvs) header' = do
11311134 return (id'',classes,kvs)
11321135 else do
11331136 unless (null ident) $ do
1137+ when (ident `Set.member` ids) $ do
1138+ pos <- getPosition
1139+ logMessage $ DuplicateIdentifier ident pos
11341140 updateState $ updateIdentifierList $ Set.insert ident
11351141 updateState $ updateHeaderMap $ insert' header' ident
11361142 return (ident,classes,kvs)
src/Text/Pandoc/Readers/HTML.hs+11−5
@@ -83,14 +83,15 @@ readHtml opts inp = do
8383 blocks <- (fixPlains False) . mconcat <$> manyTill block eof
8484 meta <- stateMeta . parserState <$> getState
8585 bs' <- replaceNotes (B.toList blocks)
86+ reportLogMessages
8687 return $ Pandoc meta bs'
8788 getError (errorMessages -> ms) = case ms of
8889 [] -> ""
8990 (m:_) -> messageString m
9091 result <- flip runReaderT def $
91− runParserT parseDoc
92− (HTMLState def{ stateOptions = opts } [] Nothing Set.empty M.empty)
93− "source" tags
92+ runParserT parseDoc
93+ (HTMLState def{ stateOptions = opts } [] Nothing Set.empty M.empty [])
94+ "source" tags
9495 case result of
9596 Right doc -> return doc
9697 Left err -> throwError $ PandocParseError $ getError err
@@ -110,7 +111,8 @@ data HTMLState =
110111 noteTable :: [(String, Blocks)],
111112 baseHref :: Maybe URI,
112113 identifiers :: Set.Set String,
113− headerMap :: M.Map Inlines String
114+ headerMap :: M.Map Inlines String,
115+ logMessages :: [LogMessage]
114116 }
115117
116118data HTMLLocal = HTMLLocal { quoteContext :: QuoteContext
@@ -376,7 +378,7 @@ ignore raw = do
376378 -- raw can be null for tags like <!DOCTYPE>; see paRawTag
377379 -- in this case we don't want a warning:
378380 unless (null raw) $
379− report $ SkippedContent raw pos
381+ logMessage $ SkippedContent raw pos
380382 return mempty
381383
382384pHtmlBlock :: PandocMonad m => String -> TagParser m String
@@ -1092,6 +1094,10 @@ instance HasHeaderMap HTMLState where
10921094 extractHeaderMap = headerMap
10931095 updateHeaderMap f s = s{ headerMap = f (headerMap s) }
10941096
1097+instance HasLogMessages HTMLState where
1098+ addLogMessage m s = s{ logMessages = m : logMessages s }
1099+ getLogMessages = reverse . logMessages
1100+
10951101-- This signature should be more general
10961102-- MonadReader HTMLLocal m => HasQuoteContext st m
10971103instance PandocMonad m => HasQuoteContext HTMLState (ReaderT HTMLLocal m) where
src/Text/Pandoc/Readers/MediaWiki.hs+7
@@ -73,6 +73,7 @@ readMediaWiki opts s = do
7373 , mwCategoryLinks = []
7474 , mwHeaderMap = M.empty
7575 , mwIdentifierList = Set.empty
76+ , mwLogMessages = []
7677 }
7778 (s ++ "\n")
7879 case parsed of
@@ -85,6 +86,7 @@ data MWState = MWState { mwOptions :: ReaderOptions
8586 , mwCategoryLinks :: [Inlines]
8687 , mwHeaderMap :: M.Map Inlines String
8788 , mwIdentifierList :: Set.Set String
89+ , mwLogMessages :: [LogMessage]
8890 }
8991
9092type MWParser m = ParserT [Char] MWState m
@@ -100,6 +102,10 @@ instance HasIdentifierList MWState where
100102 extractIdentifierList = mwIdentifierList
101103 updateIdentifierList f st = st{ mwIdentifierList = f $ mwIdentifierList st }
102104
105+instance HasLogMessages MWState where
106+ addLogMessage m s = s{ mwLogMessages = m : mwLogMessages s }
107+ getLogMessages = reverse . mwLogMessages
108+
103109--
104110-- auxiliary functions
105111--
@@ -187,6 +193,7 @@ parseMediaWiki = do
187193 let categories = if null categoryLinks
188194 then mempty
189195 else B.para $ mconcat $ intersperse B.space categoryLinks
196+ reportLogMessages
190197 return $ B.doc $ bs <> categories
191198
192199--
src/Text/Pandoc/Readers/Org.hs+2
@@ -35,6 +35,7 @@ import Text.Pandoc.Class (PandocMonad)
3535import Text.Pandoc.Definition
3636import Text.Pandoc.Error
3737import Text.Pandoc.Options
38+import Text.Pandoc.Parsing (reportLogMessages)
3839
3940import Control.Monad.Except (throwError)
4041import Control.Monad.Reader (runReaderT)
@@ -59,4 +60,5 @@ parseOrg :: PandocMonad m => OrgParser m Pandoc
5960parseOrg = do
6061 blocks' <- blockList
6162 meta' <- meta
63+ reportLogMessages
6264 return $ Pandoc meta' blocks'
src/Text/Pandoc/Readers/Org/ParserState.hs+8
@@ -60,7 +60,9 @@ import qualified Data.Set as Set
6060import Text.Pandoc.Builder (Blocks, Inlines, trimInlines)
6161import Text.Pandoc.Definition (Meta (..), nullMeta)
6262import Text.Pandoc.Options (ReaderOptions (..))
63+import Text.Pandoc.Logging
6364import Text.Pandoc.Parsing (HasHeaderMap (..), HasIdentifierList (..),
65+ HasLogMessages (..),
6466 HasLastStrPosition (..), HasQuoteContext (..),
6567 HasReaderOptions (..), ParserContext (..),
6668 QuoteContext (..), SourcePos)
@@ -104,6 +106,7 @@ data OrgParserState = OrgParserState
104106 , orgStateOptions :: ReaderOptions
105107 , orgStateParserContext :: ParserContext
106108 , orgStateTodoSequences :: [TodoSequence]
109+ , orgLogMessages :: [LogMessage]
107110 }
108111
109112data OrgParserLocal = OrgParserLocal { orgLocalQuoteContext :: QuoteContext }
@@ -130,6 +133,10 @@ instance HasHeaderMap OrgParserState where
130133 extractHeaderMap = orgStateHeaderMap
131134 updateHeaderMap f s = s{ orgStateHeaderMap = f (orgStateHeaderMap s) }
132135
136+instance HasLogMessages OrgParserState where
137+ addLogMessage msg st = st{ orgLogMessages = msg : orgLogMessages st }
138+ getLogMessages st = reverse $ orgLogMessages st
139+
133140instance Default OrgParserState where
134141 def = defaultOrgParserState
135142
@@ -150,6 +157,7 @@ defaultOrgParserState = OrgParserState
150157 , orgStateOptions = def
151158 , orgStateParserContext = NullState
152159 , orgStateTodoSequences = []
160+ , orgLogMessages = []
153161 }
154162
155163optionsToParserState :: ReaderOptions -> OrgParserState