git.iko.soy/typeable/pandoc
on master help sign in
Org reader: add support for "Berkeley-style" cites

A specification for an official Org-mode citation syntax was drafted by Richard Lawrence and enhanced with the help of others on the orgmode mailing list. Basic support for this citation style is added to the reader. This closes #1978.

commit8a9f5915ab822b476c270f46e8a800982b018ba3 authorAlbert Krewinkel <albert@zeitkraut.de> dateTue 31 May 2016, 12:01 (10 years ago) parent06dfe32
3 files changed+176−7
src/Text/Pandoc/Readers/Org/Inlines.hs+126−7 src/Text/Pandoc/Readers/Org/Parsing.hs+1 tests/Tests/Readers/Org.hs+49
src/Text/Pandoc/Readers/Org/Inlines.hs+126−7
@@ -49,11 +49,13 @@ import Text.Pandoc.Readers.LaTeX ( inlineCommand, rawLaTeXInline )
4949import Text.TeXMath ( readTeX, writePandoc, DisplayType(..) )
5050import qualified Text.TeXMath.Readers.MathML.EntityMap as MathMLEntityMap
5151
52−import Control.Monad ( guard, mplus, mzero, when )
52+import Prelude hiding (sequence)
53+import Control.Monad ( guard, mplus, mzero, when, void )
5354import Data.Char ( isAlphaNum, isSpace )
54−import Data.List ( isPrefixOf )
55+import Data.List ( intersperse, isPrefixOf )
5556import Data.Maybe ( fromMaybe )
5657import qualified Data.Map as M
58+import Data.Traversable (sequence)
5759
5860--
5961-- Functions acting on the parser state
@@ -166,19 +168,42 @@ endline = try $ do
166168 updateLastPreCharPos
167169 return . return $ B.softbreak
168170
171+
172+--
173+-- Citations
174+--
175+
176+-- The state of citations is a bit confusing due to the lack of an official
177+-- syntax and multiple syntaxes coexisting. The pandocOrgCite syntax was the
178+-- first to be implemented here and is almost identical to Markdown's citation
179+-- syntax. The org-ref package is in wide use to handle citations, but the
180+-- syntax is a bit limiting and not quite as simple to write. The
181+-- semi-offical Org-mode citation syntax is based on John MacFarlane's Pandoc
182+-- sytax and Org-oriented enhancements contributed by Richard Lawrence and
183+-- others. It's dubbed Berkeley syntax due the place of activity of its main
184+-- contributors. All this should be consolidated once an official Org-mode
185+-- citation syntax has emerged.
186+
169187cite :: OrgParser (F Inlines)
170−cite = try $ do
188+cite = try $ berkeleyCite <|> do
171189 guardEnabled Ext_citations
172− (cs, raw) <- withRaw (pandocOrgCite <|> orgRefCite)
190+ (cs, raw) <- withRaw $ choice
191+ [ pandocOrgCite
192+ , orgRefCite
193+ , berkeleyTextualCite
194+ ]
173195 return $ (flip B.cite (B.text raw)) <$> cs
174196
175−-- | A citation in Pandoc Org-mode style (@[\@citekey]@).
197+-- | A citation in Pandoc Org-mode style (@[prefix \@citekey suffix]@).
176198pandocOrgCite :: OrgParser (F [Citation])
177199pandocOrgCite = try $
178200 char '[' *> skipSpaces *> citeList <* skipSpaces <* char ']'
179201
180202orgRefCite :: OrgParser (F [Citation])
181−orgRefCite = try $ normalOrgRefCite <|> (fmap (:[]) <$> linkLikeOrgRefCite)
203+orgRefCite = try $ choice
204+ [ normalOrgRefCite
205+ , fmap (:[]) <$> linkLikeOrgRefCite
206+ ]
182207
183208normalOrgRefCite :: OrgParser (F [Citation])
184209normalOrgRefCite = try $ do
@@ -199,6 +224,100 @@ normalOrgRefCite = try $ do
199224 , citationHash = 0
200225 }
201226
227+-- | Read an Berkeley-style Org-mode citation. Berkeley citation style was
228+-- develop and adjusted to Org-mode style by John MacFarlane and Richard
229+-- Lawrence, respectively, both philosophers at UC Berkeley.
230+berkeleyCite :: OrgParser (F Inlines)
231+berkeleyCite = try $ do
232+ bcl <- berkeleyCitationList
233+ return $ do
234+ parens <- berkeleyCiteParens <$> bcl
235+ prefix <- berkeleyCiteCommonPrefix <$> bcl
236+ suffix <- berkeleyCiteCommonSuffix <$> bcl
237+ citationList <- berkeleyCiteCitations <$> bcl
238+ if parens
239+ then return . toCite . addToFirstAndLast prefix suffix $ citationList
240+ else return $ maybe mempty (<> " ") prefix
241+ <> (toListOfCites $ map toInTextMode citationList)
242+ <> maybe mempty (", " <>) suffix
243+ where
244+ toCite :: [Citation] -> Inlines
245+ toCite cs = B.cite cs mempty
246+
247+ toListOfCites :: [Citation] -> Inlines
248+ toListOfCites = mconcat . intersperse ", " . map (\c -> B.cite [c] mempty)
249+
250+ toInTextMode :: Citation -> Citation
251+ toInTextMode c = c { citationMode = AuthorInText }
252+
253+ addToFirstAndLast :: Maybe Inlines -> Maybe Inlines -> [Citation] -> [Citation]
254+ addToFirstAndLast pre suf (c:cs) =
255+ let firstCite = maybe c
256+ (\p -> c { citationPrefix = B.toList p <> citationPrefix c })
257+ pre
258+ cites = firstCite:cs
259+ lc = last cites
260+ lastCite = maybe lc
261+ (\s -> lc { citationSuffix = B.toList s <> citationSuffix lc })
262+ suf
263+ in init cites ++ [lastCite]
264+ addToFirstAndLast _ _ _ = []
265+
266+data BerkeleyCitationList = BerkeleyCitationList
267+ { berkeleyCiteParens :: Bool
268+ , berkeleyCiteCommonPrefix :: Maybe Inlines
269+ , berkeleyCiteCommonSuffix :: Maybe Inlines
270+ , berkeleyCiteCitations :: [Citation]
271+ }
272+berkeleyCitationList :: OrgParser (F BerkeleyCitationList)
273+berkeleyCitationList = try $ do
274+ char '['
275+ parens <- choice [ False <$ berkeleyBareTag, True <$ berkeleyParensTag ]
276+ char ':'
277+ skipSpaces
278+ commonPrefix <- optionMaybe (try $ citationListPart <* char ';')
279+ citations <- citeList
280+ commonSuffix <- optionMaybe (try $ citationListPart)
281+ char ']'
282+ return (BerkeleyCitationList parens
283+ <$> sequence commonPrefix
284+ <*> sequence commonSuffix
285+ <*> citations)
286+ where
287+ citationListPart :: OrgParser (F Inlines)
288+ citationListPart = fmap (trimInlinesF . mconcat) . try . many1 $ do
289+ notFollowedBy' citeKey
290+ notFollowedBy (oneOf ";]")
291+ inline
292+
293+berkeleyBareTag :: OrgParser ()
294+berkeleyBareTag = try $ void berkeleyBareTag'
295+
296+berkeleyParensTag :: OrgParser ()
297+berkeleyParensTag = try . void $ enclosedByPair '(' ')' berkeleyBareTag'
298+
299+berkeleyBareTag' :: OrgParser ()
300+berkeleyBareTag' = try $ void (string "cite")
301+
302+berkeleyTextualCite :: OrgParser (F [Citation])
303+berkeleyTextualCite = try $ do
304+ (suppressAuthor, key) <- citeKey
305+ returnF . return $ Citation
306+ { citationId = key
307+ , citationPrefix = mempty
308+ , citationSuffix = mempty
309+ , citationMode = if suppressAuthor then SuppressAuthor else AuthorInText
310+ , citationNoteNum = 0
311+ , citationHash = 0
312+ }
313+
314+-- The following is what a Berkeley-style bracketed textual citation parser
315+-- would look like. However, as these citations are a subset of Pandoc's Org
316+-- citation style, this isn't used.
317+-- berkeleyBracketedTextualCite :: OrgParser (F [Citation])
318+-- berkeleyBracketedTextualCite = try . (fmap head) $
319+-- enclosedByPair '[' ']' berkeleyTextualCite
320+
202321-- | Read a link-like org-ref style citation. The citation includes pre and
203322-- post text. However, multiple citations are not possible due to limitations
204323-- in the syntax.
@@ -243,7 +362,7 @@ orgRefCiteMode =
243362 ]
244363
245364citeList :: OrgParser (F [Citation])
246−citeList = sequence <$> sepBy1 citation (try $ char ';' *> skipSpaces)
365+citeList = sequence <$> sepEndBy1 citation (try $ char ';' *> skipSpaces)
247366
248367citation :: OrgParser (F Citation)
249368citation = try $ do
src/Text/Pandoc/Readers/Org/Parsing.hs+1
@@ -97,6 +97,7 @@ module Text.Pandoc.Readers.Org.Parsing
9797 , try
9898 , sepBy
9999 , sepBy1
100+ , sepEndBy1
100101 , option
101102 , optional
102103 , optionMaybe
tests/Tests/Readers/Org.hs+49
@@ -336,6 +336,55 @@ tests =
336336 }
337337 in (para $ cite [citation] "[[citep:Dominik201408][See page 20::, for example]]")
338338
339+ , testGroup "Berkeley-style citations" $
340+ let pandocCite = Citation
341+ { citationId = "Pandoc"
342+ , citationPrefix = mempty
343+ , citationSuffix = mempty
344+ , citationMode = NormalCitation
345+ , citationNoteNum = 0
346+ , citationHash = 0
347+ }
348+ pandocInText = pandocCite { citationMode = AuthorInText }
349+ dominikCite = Citation
350+ { citationId = "Dominik201408"
351+ , citationPrefix = mempty
352+ , citationSuffix = mempty
353+ , citationMode = NormalCitation
354+ , citationNoteNum = 0
355+ , citationHash = 0
356+ }
357+ dominikInText = dominikCite { citationMode = AuthorInText }
358+ in [
359+ "Berkeley-style in-text citation" =:
360+ "See @Dominik201408." =?>
361+ (para $ "See "
362+ <> cite [dominikInText] "@Dominik201408"
363+ <> ".")
364+
365+ , "Berkeley-style parenthetical citation list" =:
366+ "[(cite): see; @Dominik201408;also @Pandoc; and others]" =?>
367+ let pandocCite' = pandocCite {
368+ citationPrefix = toList "also"
369+ , citationSuffix = toList "and others"
370+ }
371+ dominikCite' = dominikCite {
372+ citationPrefix = toList "see"
373+ }
374+ in (para $ cite [dominikCite', pandocCite'] "")
375+
376+ , "Berkeley-style plain citation list" =:
377+ "[cite: See; @Dominik201408; and @Pandoc; and others]" =?>
378+ let pandocCite' = pandocInText {
379+ citationPrefix = toList "and"
380+ }
381+ in (para $ "See "
382+ <> cite [dominikInText] ""
383+ <> "," <> space
384+ <> cite [pandocCite'] ""
385+ <> "," <> space <> "and others")
386+ ]
387+
339388 , "Inline LaTeX symbol" =:
340389 "\\dots" =?>
341390 para "…"