-
Notifications
You must be signed in to change notification settings - Fork 9
/
citeproc-style.el
354 lines (321 loc) · 13.6 KB
/
citeproc-style.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
;; citeproc-style.el --- CSL style structure and related functions -*- lexical-binding: t; -*-
;; Copyright (C) 2017 András Simonyi
;; Author: András Simonyi <andras.simonyi@gmail.com>
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;; This file is not part of GNU Emacs.
;;; Commentary:
;; Structure type and functions for constructing and accessing CSL style
;; objects.
;;; Code:
(require 'subr-x)
(require 'let-alist)
(require 'dash)
(require 'cl-lib)
(require 's)
(require 'citeproc-lib)
(require 'citeproc-locale)
(cl-defstruct (citeproc-style (:constructor citeproc-style--create))
"A struct representing a parsed and localized CSL style.
CATEGORY is the style's category as a string,
INFO is the style's general info (currently simply the
corresponding fragment of the parsed xml),
OPTS, BIB-OPTS, CITE-OPTS and LOCALE-OPTS are alists of general
and bibliography-, cite- and locale-specific CSL options,
BIB-SORT, BIB-LAYOUT, CITE-SORT and CITE-LAYOUT are anonymous
functions for calculating sort-keys and rendering,
BIB-SORT-ORDERS and CITE-SORT-ORDERS are the lists of sort orders
for bibliography and cite sort (the value is a list containg t
or nil as its n-th element depending on whether the sort for on
the n-th key should be in ascending or desending order,
CITE-LAYOUT-ATTRS contains the attributes of the citation layout
as an alist,
DATE-TEXT and DATE-NUMERIC are the style's date formats,
LOCALE contains the locale to be used or nil if not set,
MACROS is an alist with macro names as keys and corresponding
anonymous rendering functions,
TERMS is the style's parsed term-list,
USES-YS-VAR is non-nil iff the style uses the YEAR-SUFFIX
CSL-variable."
category info opts bib-opts bib-sort bib-sort-orders
bib-layout cite-opts cite-sort cite-sort-orders
cite-layout cite-layout-attrs locale-opts macros terms
uses-ys-var date-text date-numeric locale)
(defun citeproc-style-parse (style)
"Return the parsed representation of csl STYLE.
STYLE is either a path to a style file or a style as a string.
Returns a (YEAR-SUFF-P . PARSED-STYLE) cons cell. YEAR-SUFF-P is
non-nil if the style uses the `year-suffix' csl var; PARSED-STYLE
is the parsed form of the xml STYLE-FILE."
(let ((xml-input (s-matches-p " ?<" style)))
(with-temp-buffer
(let ((case-fold-search t))
(if xml-input (insert style)
(insert-file-contents style))
(goto-char 1)
(cons (re-search-forward "variable=\"year-suffix\"" nil t)
(citeproc-lib-remove-xml-comments
(libxml-parse-xml-region (point-min) (point-max))))))))
;; TODO: Parse and store info in a more structured and sensible form. Also,
;; currently the first in-style locale is loaded that is compatible with the
;; locale to be used. In theory, there may be more than one compatible in-style
;; locales that should be merged in an order reflecting their closeness to the
;; requested locale.
(defun citeproc-create-style-from-locale (parsed-style year-suffix locale)
"Create a citation style from parsed xml style PARSED-STYLE.
YEAR-SUFFIX specifies whether the style explicitly uses the
`year-suffix' csl variable. LOCALE is the locale for which
in-style locale information will be loaded (if available)."
(let* ((style (citeproc-style--create))
(style-opts (cadr parsed-style))
locale-loaded)
(setf (citeproc-style-opts style) style-opts
(citeproc-style-uses-ys-var style) year-suffix
(citeproc-style-locale style)
(or locale (alist-get 'default-locale style-opts)))
(--each (cddr parsed-style)
(pcase (car it)
('info
(let* ((info-lst (cddr it))
(category-info (cl-find-if
(lambda (x) (and (eq 'category (car x))
(eq 'citation-format (caaadr x))))
info-lst))
(category (cdaadr category-info)))
(setf (citeproc-style-category style) category)))
('locale
(let ((lang (alist-get 'lang (cadr it))))
(when (and (citeproc-locale--compatible-p lang locale)
(not locale-loaded))
(citeproc-style--update-locale style it)
(setq locale-loaded t))))
('citation
(citeproc-style--update-cite-info style it))
('bibliography
(citeproc-style--update-bib-info style it))
('macro
(citeproc-style--update-macros style it))))
style))
(defun citeproc-style--parse-layout-and-sort-frag (frag)
"Parse a citation or bibliography style xml FRAG.
Return an alist with keys `layout', `opts', `layout-attrs', `sort'
and `sort-orders'."
(let* ((opts (cadr frag))
(sort-p (eq (cl-caaddr frag) 'sort))
(layout (citeproc-style--transform-xmltree
(elt frag (if sort-p 3 2))))
(layout-attrs (cl-cadadr (cl-caddr layout)))
sort sort-orders)
(when sort-p
(let* ((sort-frag (cl-caddr frag)))
(setq sort (citeproc-style--transform-xmltree sort-frag)
sort-orders (--map (not (string= "descending" (alist-get 'sort (cadr it))))
(cddr sort-frag)))))
`((opts . ,opts) (layout . ,layout) (layout-attrs . ,layout-attrs)
(sort . ,sort) (sort-orders . ,sort-orders))))
(defun citeproc-style--update-cite-info (style frag)
"Update the cite info of STYLE on the basis of its parsed FRAG."
(let-alist (citeproc-style--parse-layout-and-sort-frag frag)
(setf (citeproc-style-cite-opts style) .opts
(citeproc-style-cite-layout style) .layout
(citeproc-style-cite-layout-attrs style) .layout-attrs
(citeproc-style-cite-sort style) .sort
(citeproc-style-cite-sort-orders style) .sort-orders)))
(defun citeproc-style--update-bib-info (style frag)
"Update the bib info of STYLE on the basis of its parsed FRAG."
(let-alist (citeproc-style--parse-layout-and-sort-frag frag)
(setf (citeproc-style-bib-opts style) .opts
(citeproc-style-bib-layout style) .layout
(citeproc-style-bib-sort style) .sort
(citeproc-style-bib-sort-orders style) .sort-orders)))
(defun citeproc-style--update-macros (style frag)
"Update the macro info of STYLE on the basis of its parsed FRAG."
(let ((name (cl-cdaadr frag)))
(setf (car frag) 'macro)
(setf (cadr frag) nil)
(push (cons name (citeproc-style--transform-xmltree frag))
(citeproc-style-macros style))))
(defun citeproc-style--update-locale (style frag)
"Update locale info in STYLE using xml fragment FRAG.
FRAG should be a parsed locale element from a style or a locale."
(--each (cddr frag)
(pcase (car it)
('style-options (setf (citeproc-style-locale-opts style)
(-concat (citeproc-style-locale-opts style)
(cadr it))))
('date
(citeproc-style--update-locale-date style it))
('terms
(let ((parsed-terms (citeproc-locale-termlist-from-xml-frag (cddr it))))
(setf (citeproc-style-terms style)
(if (citeproc-style-terms style)
(citeproc-term-list-update parsed-terms (citeproc-style-terms style))
parsed-terms)))))))
(defun citeproc-style--update-locale-date (style frag)
"Update date info in STYLE using xml fragment FRAG.
FRAG should be a parsed locale element from a style or a locale."
(let* ((date-attrs (cadr frag))
(form (alist-get 'form date-attrs))
(date-format (cons date-attrs
(citeproc-lib-named-parts-to-alist frag))))
(if (string= form "text")
(unless (citeproc-style-date-text style)
(setf (citeproc-style-date-text style) date-format))
(unless (citeproc-style-date-numeric style)
(setf (citeproc-style-date-numeric style) date-format)))))
(defconst citeproc-style--opt-defaults
'((cite-opts near-note-distance "5")
(locale-opts punctuation-in-quote "false")
(locale-opts limit-day-ordinals-to-day-1 "false")
(bib-opts hanging-indent "false")
(bib-opts line-spacing "1")
(bib-opts entry-spacing "1")
(opts initialize-with-hyphen "true")
(opts demote-non-dropping-particle "display-and-sort"))
"Global style options.
Specified as a list of (STYLE-SLOT OPTION-NAME OPTION-DEFAULT)
lists.
Note: Collapse-related options are not specified here since their
default settings are interdependent.")
(defun citeproc-style--set-opt (style opt-slot opt value)
"Set OPT in STYLE's OPT-SLOT to VALUE."
(setf (cl-struct-slot-value 'citeproc-style opt-slot style)
(cons (cons opt value)
(cl-struct-slot-value 'citeproc-style opt-slot style))))
(defun citeproc-style--set-opt-defaults (style)
"Set missing options of STYLE to their default values."
(cl-loop
for (slot option value) in citeproc-style--opt-defaults do
(let ((slot-value (cl-struct-slot-value 'citeproc-style slot style)))
(unless (alist-get option slot-value)
(setf (cl-struct-slot-value 'citeproc-style slot style)
(cons (cons option value) slot-value)))))
(let* ((cite-opts (citeproc-style-cite-opts style))
(collapse (alist-get 'collapse cite-opts)))
(when (and collapse (not (string= collapse "citation-number")))
(let ((cite-layout-dl
(alist-get 'delimiter (citeproc-style-cite-layout-attrs style)))
(cite-group-dl
(alist-get 'cite-group-delimiter cite-opts)))
(unless cite-group-dl
(citeproc-style--set-opt style 'cite-opts 'cite-group-delimiter ", "))
(unless (alist-get 'after-collapse-delimiter cite-opts)
(citeproc-style--set-opt
style 'cite-opts 'after-collapse-delimiter cite-layout-dl))
(when (and (member collapse '("year-suffix" "year-suffix-ranged"))
(null (alist-get 'year-suffix-delimiter cite-opts)))
(citeproc-style--set-opt
style 'cite-opts 'year-suffix-delimiter cite-layout-dl))))))
(defun citeproc-style--transform-xmltree (tree)
"Transform parsed csl xml fragment TREE into a lambda."
`(lambda (context) ,(citeproc-style--transform-xmltree-1 tree)))
(defun citeproc-style--transform-xmltree-1 (tree)
"Transform parsed xml fragment TREE into an eval-able form.
Symbols in car position are prefixed with `citeproc--' and the
symbol `context' is inserted everywhere after the second (attrs)
position and before the (possibly empty) body."
(pcase tree
((pred atom) tree)
(`(names . ,_) (citeproc-style--transform-names tree))
(_
`(,(intern (concat "citeproc--" (symbol-name (car tree))))
,(list 'quote (cadr tree))
context
,@(mapcar #'citeproc-style--transform-xmltree-1 (cddr tree))))))
(defun citeproc-style--transform-names (frag)
"Transform the content of a cs:names CSL element xml FRAG."
(let* ((names-attrs (cadr frag))
(body (-remove #'stringp (cddr frag)))
(vars (alist-get 'variable names-attrs))
substs name-attrs name-parts et-al-attrs
is-label label-attrs label-before-names)
(--each body
(pcase (car it)
('name
(setq name-attrs (cadr it)
name-parts (citeproc-lib-named-parts-to-alist it)
label-before-names t))
('et-al
(setq et-al-attrs (cadr it)))
('label
(setq is-label t
label-attrs (cadr it)
label-before-names nil))
('substitute
(setq substs
(mapcar
(lambda (x)
(if (eq (car x) 'names)
`(citeproc-name-render-vars
,(alist-get 'variable (cadr x))
names-attrs name-attrs name-parts et-al-attrs
is-label label-before-names label-attrs context)
(citeproc-style--transform-xmltree-1 x)))
(cddr it))))))
`(if (citeproc-var-value 'suppress-author context) (cons nil 'empty-vars)
(let* ((names-attrs ',names-attrs)
(name-attrs ',name-attrs)
(count (string= (alist-get 'form name-attrs) "count"))
(et-al-attrs ',et-al-attrs)
(name-parts ',name-parts)
(label-attrs ',label-attrs)
(is-label ,is-label)
(label-before-names ,label-before-names)
(val (citeproc-name-render-vars
,vars names-attrs name-attrs name-parts et-al-attrs
is-label label-before-names label-attrs context))
(result (if (car val)
val
(-if-let ((cont . type) (--first (car it)
(list ,@substs)))
(cons (cons (list '(subst . t)) (list cont)) type)
(cons nil 'empty-vars))))
(final (if count
(let* ((number (citeproc-rt-count-names (car result)))
(str (if (= 0 number) "" (number-to-string number))))
(cons str (cdr result)))
result)))
;; Handle `author' citation mode by stopping if needed
(citeproc-context-maybe-stop-rendering 'names context final)))))
(defun citeproc-style-cite-note (style)
"Return whether csl STYLE is a note style."
(string= (citeproc-style-category style) "note"))
(defun citeproc-style-cite-superscript-p (style)
"Return whether csl STYLE has a superscript citaton layout."
(string= (alist-get 'vertical-align (citeproc-style-cite-layout-attrs style))
"sup"))
(defun citeproc-style-global-opts (style layout)
"Return the global opts in STYLE for LAYOUT.
LAYOUT is either `bib' or `cite'."
(-concat (cl-ecase layout
(bib (citeproc-style-bib-opts style))
(cite (citeproc-style-cite-opts style)))
(citeproc-style-opts style)))
(defun citeproc-style-bib-opts-to-formatting-params (bib-opts)
"Convert BIB-OPTS to a formatting parameters alist."
(let ((result
(cl-loop
for (opt . val) in bib-opts
if (memq opt
'(hanging-indent line-spacing entry-spacing second-field-align))
collect (cons opt
(pcase val
("true" t)
("false" nil)
("flush" 'flush)
("margin" 'margin)
(_ (string-to-number val)))))))
(if (alist-get 'second-field-align result)
result
(cons (cons 'second-field-align nil)
result))))
(provide 'citeproc-style)
;;; citeproc-style.el ends here