git.iko.soy/typeable/pandoc
on master help sign in
Add biblatex, bibtex as output formats (closes #7040).

* `biblatex` and `bibtex` are now supported as output as well as input formats. * New module Text.Pandoc.Writers.BibTeX, exporting writeBibTeX and writeBibLaTeX. [API change] * New unexported function `writeBibtexString` in Text.Pandoc.Citeproc.BibTeX.

commitfa952c8dbea9ad47ea684729f862a3c6bdd0fecc authorJohn MacFarlane <jgm@berkeley.edu> dateWed 20 Jan 2021, 21:08 (6 years ago) parent5f98ac6
6 files changed+312−6
MANUAL.txt+5−3 pandoc.cabal+1 src/Text/Pandoc/Citeproc/BibTeX.hs+251−3 src/Text/Pandoc/Templates.hs+2 src/Text/Pandoc/Writers.hs+5 src/Text/Pandoc/Writers/BibTeX.hs+48
MANUAL.txt+5−3
@@ -281,6 +281,8 @@ header when requesting a document from a URL:
281281 ::: {#output-formats}
282282 - `asciidoc` ([AsciiDoc]) or `asciidoctor` ([AsciiDoctor])
283283 - `beamer` ([LaTeX beamer][`beamer`] slide show)
284+ - `bibtex` ([BibTeX] bibliography)
285+ - `biblatex` ([BibLaTeX] bibliography)
284286 - `commonmark` ([CommonMark] Markdown)
285287 - `commonmark_x` ([CommonMark] Markdown with extensions)
286288 - `context` ([ConTeXt])
@@ -5258,11 +5260,11 @@ section from a BibTeX, BibLaTeX, or CSL JSON bibliography:
52585260 pandoc chem.bib -s -f biblatex -t markdown
52595261 pandoc chem.json -s -f csljson -t markdown
52605262
5261−`pandoc` can also be used to produce CSL JSON bibliography
5262−from BibTeX, BibLaTeX, or markdown YAML:
5263+Indeed, `pandoc` can convert between any of these
5264+citation formats:
52635265
52645266 pandoc chem.bib -s -f biblatex -t csljson
5265− pandoc chem.yaml -s -f markdown -t csljson
5267+ pandoc chem.yaml -s -f markdown -t biblatex
52665268
52675269Running pandoc on a bibliography file with the `--citeproc`
52685270option will create a formatted bibliography in the format
pandoc.cabal+1
@@ -573,6 +573,7 @@ library
573573 Text.Pandoc.Writers.Shared,
574574 Text.Pandoc.Writers.OOXML,
575575 Text.Pandoc.Writers.AnnotatedTable,
576+ Text.Pandoc.Writers.BibTeX,
576577 Text.Pandoc.Lua,
577578 Text.Pandoc.PDF,
578579 Text.Pandoc.UTF8,
src/Text/Pandoc/Citeproc/BibTeX.hs+251−3
@@ -1,4 +1,5 @@
11{-# LANGUAGE ViewPatterns #-}
2+{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE ScopedTypeVariables #-}
45{-# LANGUAGE FlexibleContexts #-}
@@ -17,6 +18,7 @@
1718module Text.Pandoc.Citeproc.BibTeX
1819 ( Variant(..)
1920 , readBibtexString
21+ , writeBibtexString
2022 )
2123 where
2224
@@ -24,10 +26,11 @@ import Text.Pandoc.Definition
2426import Text.Pandoc.Builder as B
2527import Text.Pandoc.Readers.LaTeX (readLaTeX)
2628import Text.Pandoc.Extensions (Extension(..), extensionsFromList)
27−import Text.Pandoc.Options (ReaderOptions(..))
28−import Text.Pandoc.Class (runPure)
29+import Text.Pandoc.Options (ReaderOptions(..), WriterOptions)
2930import Text.Pandoc.Error (PandocError)
3031import Text.Pandoc.Shared (stringify)
32+import Text.Pandoc.Writers.LaTeX (writeLaTeX)
33+import Text.Pandoc.Class (runPure)
3134import qualified Text.Pandoc.Walk as Walk
3235import Citeproc.Types
3336import Citeproc.Pandoc ()
@@ -46,8 +49,9 @@ import qualified Data.Sequence as Seq
4649import Data.Char (isAlphaNum, isDigit, isLetter,
4750 isUpper, toLower, toUpper,
4851 isLower, isPunctuation)
49−import Data.List (foldl', intercalate)
52+import Data.List (foldl', intercalate, intersperse)
5053import Safe (readMay)
54+import Text.Printf (printf)
5155
5256data Variant = Bibtex | Biblatex
5357 deriving (Show, Eq, Ord)
@@ -68,6 +72,250 @@ readBibtexString variant locale idpred contents = do
6872 Left err -> Left err
6973 Right xs -> return xs
7074
75+-- | Write BibTeX or BibLaTeX given given a 'Reference'.
76+writeBibtexString :: WriterOptions -- ^ options (for writing LaTex)
77+ -> Variant -- ^ bibtex or biblatex
78+ -> Maybe Lang -- ^ Language
79+ -> Reference Inlines -- ^ Reference to write
80+ -> Text
81+writeBibtexString opts variant mblang ref =
82+ "@" <> bibtexType <> "{" <> unItemId (referenceId ref) <> ",\n " <>
83+ renderFields fs <> "\n}\n"
84+
85+ where
86+ bibtexType =
87+ case referenceType ref of
88+ "article-magazine" -> "article"
89+ "article-newspaper" -> "article"
90+ "article-journal" -> "article"
91+ "book" -> "book"
92+ "pamphlet" -> "booklet"
93+ "dataset" | variant == Biblatex -> "dataset"
94+ "webpage" | variant == Biblatex -> "online"
95+ "chapter" -> case getVariable "editor" of
96+ Just _ -> "incollection"
97+ Nothing -> "inbook"
98+ "entry-encyclopedia" | variant == Biblatex -> "inreference"
99+ | otherwise -> "inbook"
100+ "paper-conference" -> "inproceedings"
101+ "thesis" -> case getVariableAsText "genre" of
102+ Just "mathesis" -> "mastersthesis"
103+ _ -> "phdthesis"
104+ "patent" | variant == Biblatex -> "patent"
105+ "report" | variant == Biblatex -> "report"
106+ | otherwise -> "techreport"
107+ "speech" -> "unpublished"
108+ "manuscript" -> "unpublished"
109+ "graphic" | variant == Biblatex -> "artwork"
110+ "song" | variant == Biblatex -> "music"
111+ "legal_case" | variant == Biblatex -> "jurisdictionN"
112+ "legislation" | variant == Biblatex -> "legislation"
113+ "treaty" | variant == Biblatex -> "legal"
114+ "personal_communication" | variant == Biblatex -> "letter"
115+ "motion_picture" | variant == Biblatex -> "movie"
116+ "review" | variant == Biblatex -> "review"
117+ _ -> "misc"
118+
119+ mbSubtype =
120+ case referenceType ref of
121+ "article-magazine" -> Just "magazine"
122+ "article-newspaper" -> Just "newspaper"
123+ _ -> Nothing
124+
125+ fs =
126+ case variant of
127+ Biblatex ->
128+ [ "author"
129+ , "editor"
130+ , "translator"
131+ , "publisher"
132+ , "title"
133+ , "booktitle"
134+ , "journal"
135+ , "series"
136+ , "edition"
137+ , "volume"
138+ , "volumes"
139+ , "number"
140+ , "pages"
141+ , "date"
142+ , "eventdate"
143+ , "urldate"
144+ , "address"
145+ , "url"
146+ , "doi"
147+ , "isbn"
148+ , "issn"
149+ , "type"
150+ , "entrysubtype"
151+ , "note"
152+ , "language"
153+ , "abstract"
154+ , "keywords"
155+ ]
156+ Bibtex ->
157+ [ "author"
158+ , "editor"
159+ , "translator"
160+ , "publisher"
161+ , "title"
162+ , "booktitle"
163+ , "journal"
164+ , "series"
165+ , "edition"
166+ , "volume"
167+ , "number"
168+ , "pages"
169+ , "year"
170+ , "month"
171+ , "address"
172+ , "type"
173+ , "note"
174+ ]
175+
176+ valToInlines (TextVal t) = B.text t
177+ valToInlines (FancyVal ils) = ils
178+ valToInlines (NumVal n) = B.text (T.pack $ show n)
179+ valToInlines (NamesVal names) =
180+ mconcat $ intersperse (B.space <> B.text "and" <> B.space)
181+ $ map renderName names
182+ valToInlines (DateVal date) = B.text $
183+ case dateLiteral date of
184+ Just t -> t
185+ Nothing -> T.intercalate "/" (map renderDatePart (dateParts date)) <>
186+ (if dateCirca date then "~" else mempty)
187+
188+ renderDatePart (DateParts xs) = T.intercalate "-" $
189+ map (T.pack . printf "%02d") xs
190+
191+ renderName name =
192+ case nameLiteral name of
193+ Just t -> B.text t
194+ Nothing -> spacedMaybes
195+ [ nameNonDroppingParticle name
196+ , nameFamily name
197+ , if nameCommaSuffix name
198+ then (", " <>) <$> nameSuffix name
199+ else nameSuffix name ]
200+ <>
201+ spacedMaybes
202+ [ (", " <>) <$> nameGiven name,
203+ nameDroppingParticle name ]
204+
205+ titlecase = case mblang of
206+ Just (Lang "en" _) -> titlecase'
207+ Nothing -> titlecase'
208+ _ -> id
209+
210+ titlecase' = addTextCase mblang TitleCase .
211+ (\ils -> B.fromList
212+ (case B.toList ils of
213+ Str t : xs -> Str t : Walk.walk spanAroundCapitalizedWords xs
214+ xs -> Walk.walk spanAroundCapitalizedWords xs))
215+
216+ -- protect capitalized words when we titlecase
217+ spanAroundCapitalizedWords (Str t)
218+ | not (T.all (\c -> isLower c || not (isLetter c)) t) =
219+ Span ("",["nocase"],[]) [Str t]
220+ spanAroundCapitalizedWords x = x
221+
222+ spacedMaybes = mconcat . intersperse B.space . mapMaybe (fmap B.text)
223+
224+ toLaTeX x =
225+ case runPure (writeLaTeX opts $ doc (B.plain x)) of
226+ Left _ -> Nothing
227+ Right t -> Just t
228+
229+ renderField name = (\contents -> name <> " = {" <> contents <> "}")
230+ <$> getContentsFor name
231+
232+ getVariable v = lookupVariable (toVariable v) ref
233+
234+ getVariableAsText v = (stringify . valToInlines) <$> getVariable v
235+
236+ getYear val =
237+ case val of
238+ DateVal date ->
239+ case dateLiteral date of
240+ Just t -> toLaTeX (B.text t)
241+ Nothing ->
242+ case dateParts date of
243+ [DateParts (y1:_), DateParts (y2:_)] ->
244+ Just (T.pack (printf "%04d" y1) <> "--" <>
245+ T.pack (printf "%04d" y2))
246+ [DateParts (y1:_)] ->
247+ Just (T.pack (printf "%04d" y1))
248+ _ -> Nothing
249+ _ -> Nothing
250+
251+ toMonth 1 = "jan"
252+ toMonth 2 = "feb"
253+ toMonth 3 = "mar"
254+ toMonth 4 = "apr"
255+ toMonth 5 = "may"
256+ toMonth 6 = "jun"
257+ toMonth 7 = "jul"
258+ toMonth 8 = "aug"
259+ toMonth 9 = "sep"
260+ toMonth 10 = "oct"
261+ toMonth 11 = "nov"
262+ toMonth 12 = "dec"
263+ toMonth x = T.pack $ show x
264+
265+ getMonth val =
266+ case val of
267+ DateVal date ->
268+ case dateParts date of
269+ [DateParts (_:m1:_), DateParts (_:m2:_)] ->
270+ Just (toMonth m1 <> "--" <> toMonth m2)
271+ [DateParts (_:m1:_)] -> Just (toMonth m1)
272+ _ -> Nothing
273+ _ -> Nothing
274+
275+ getContentsFor :: Text -> Maybe Text
276+ getContentsFor "type" =
277+ getVariableAsText "genre" >>=
278+ \case
279+ "mathesis" -> Just "mastersthesis"
280+ "phdthesis" -> Just "phdthesis"
281+ _ -> Nothing
282+ getContentsFor "entrysubtype" = mbSubtype
283+ getContentsFor "journal"
284+ | bibtexType `elem` ["article", "periodical", "suppperiodical", "review"]
285+ = getVariable "container-title" >>= toLaTeX . valToInlines
286+ | otherwise = Nothing
287+ getContentsFor "booktitle"
288+ | bibtexType `elem`
289+ ["inbook","incollection","inproceedings","inreference","bookinbook"]
290+ = (getVariable "volume-title" <|> getVariable "container-title")
291+ >>= toLaTeX . valToInlines
292+ | otherwise = Nothing
293+ getContentsFor "series" = getVariable "collection-title"
294+ >>= toLaTeX . valToInlines
295+ getContentsFor "address" = getVariable "publisher-place"
296+ >>= toLaTeX . valToInlines
297+ getContentsFor "date" = getVariable "issued" >>= toLaTeX . valToInlines
298+ getContentsFor "eventdate" = getVariable "event-date" >>= toLaTeX . valToInlines
299+ getContentsFor "urldate" = getVariable "accessed" >>= toLaTeX . valToInlines
300+ getContentsFor "year" = getVariable "issued" >>= getYear
301+ getContentsFor "month" = getVariable "issued" >>= getMonth
302+ getContentsFor "number" = (getVariable "number"
303+ <|> getVariable "collection-number"
304+ <|> getVariable "issue") >>= toLaTeX . valToInlines
305+
306+ getContentsFor x = getVariable x >>=
307+ if isURL x
308+ then Just . stringify . valToInlines
309+ else toLaTeX .
310+ (if x == "title"
311+ then titlecase
312+ else id) .
313+ valToInlines
314+
315+ isURL x = x `elem` ["url","doi","issn","isbn"]
316+
317+ renderFields = T.intercalate ",\n " . mapMaybe renderField
318+
71319defaultLang :: Lang
72320defaultLang = Lang "en" (Just "US")
73321
src/Text/Pandoc/Templates.hs+2
@@ -81,6 +81,8 @@ getDefaultTemplate writer = do
8181 case format of
8282 "native" -> return ""
8383 "csljson" -> return ""
84+ "bibtex" -> return ""
85+ "biblatex" -> return ""
8486 "json" -> return ""
8587 "docx" -> return ""
8688 "fb2" -> return ""
src/Text/Pandoc/Writers.hs+5
@@ -21,6 +21,8 @@ module Text.Pandoc.Writers
2121 , writeAsciiDoc
2222 , writeAsciiDoctor
2323 , writeBeamer
24+ , writeBibTeX
25+ , writeBibLaTeX
2426 , writeCommonMark
2527 , writeConTeXt
2628 , writeCustom
@@ -85,6 +87,7 @@ import Text.Pandoc.Options
8587import qualified Text.Pandoc.UTF8 as UTF8
8688import Text.Pandoc.Error
8789import Text.Pandoc.Writers.AsciiDoc
90+import Text.Pandoc.Writers.BibTeX
8891import Text.Pandoc.Writers.CommonMark
8992import Text.Pandoc.Writers.ConTeXt
9093import Text.Pandoc.Writers.CslJson
@@ -185,6 +188,8 @@ writers = [
185188 ,("tei" , TextWriter writeTEI)
186189 ,("muse" , TextWriter writeMuse)
187190 ,("csljson" , TextWriter writeCslJson)
191+ ,("bibtex" , TextWriter writeBibTeX)
192+ ,("biblatex" , TextWriter writeBibLaTeX)
188193 ]
189194
190195-- | Retrieve writer, extensions based on formatSpec (format+extensions).
src/Text/Pandoc/Writers/BibTeX.hsnew file+48
@@ -0,0 +1,48 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{- |
3+ Module : Text.Pandoc.Writers.BibTeX
4+ Copyright : Copyright (C) 2021 John MacFarlane
5+ License : GNU GPL, version 2 or above
6+
7+ Maintainer : John MacFarlane <jgm@berkeley.edu>
8+ Stability : alpha
9+ Portability : portable
10+
11+Writes a BibTeX or BibLaTeX bibliographies based on the
12+'references' metadata in a Pandoc document.
13+-}
14+module Text.Pandoc.Writers.BibTeX
15+ ( writeBibTeX
16+ , writeBibLaTeX
17+ )
18+where
19+
20+import Text.Pandoc.Options
21+import Text.Pandoc.Definition
22+import Data.Text (Text)
23+import Data.Maybe (mapMaybe)
24+import Citeproc (parseLang)
25+import Text.Pandoc.Class (PandocMonad)
26+import Text.Pandoc.Citeproc.BibTeX as BibTeX
27+import Text.Pandoc.Citeproc.MetaValue (metaValueToReference)
28+import Text.Pandoc.Writers.Shared (lookupMetaString)
29+
30+-- | Write BibTeX based on the references metadata from a Pandoc document.
31+writeBibTeX :: PandocMonad m => WriterOptions -> Pandoc -> m Text
32+writeBibTeX = writeBibTeX' BibTeX.Bibtex
33+
34+-- | Write BibLaTeX based on the references metadata from a Pandoc document.
35+writeBibLaTeX :: PandocMonad m => WriterOptions -> Pandoc -> m Text
36+writeBibLaTeX = writeBibTeX' BibTeX.Biblatex
37+
38+writeBibTeX' :: PandocMonad m => Variant -> WriterOptions -> Pandoc -> m Text
39+writeBibTeX' variant opts (Pandoc meta _) = do
40+ let mblang = case lookupMetaString "lang" meta of
41+ "" -> Nothing
42+ t -> Just $ parseLang t
43+ let refs = case lookupMeta "references" meta of
44+ Just (MetaList xs) -> mapMaybe metaValueToReference xs
45+ _ -> []
46+ return $ mconcat $
47+ map (BibTeX.writeBibtexString opts variant mblang) refs
48+