@@ -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