oc-csl.el (31411B)
1 ;;; oc-csl.el --- csl citation processor for Org -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2021-2023 Free Software Foundation, Inc. 4 5 ;; Author: Nicolas Goaziou <mail@nicolasgoaziou.fr> 6 ;; Maintainer: András Simonyi <andras.simonyi@gmail.com> 7 8 ;; This file is part of GNU Emacs. 9 10 ;; GNU Emacs is free software: you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 15 ;; GNU Emacs is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 19 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; This library registers the `csl' citation processor, which provides 26 ;; the "export" capability for citations. 27 28 ;; The processor relies on the external Citeproc Emacs library, which must be 29 ;; available prior to loading this library. 30 31 ;; By default, citations are rendered in Chicago author-date CSL style. You can 32 ;; use another style file by specifying it in `org-cite-export-processors' or 33 ;; from within the document by adding the file name to "cite_export" keyword 34 ;; 35 ;; #+cite_export: csl /path/to/style-file.csl 36 ;; #+cite_export: csl "/path/to/style-file.csl" 37 ;; 38 ;; With the variable `org-cite-csl-styles-dir' set appropriately, the 39 ;; above can even be shortened to 40 ;; 41 ;; #+cite_export: csl style-file.csl 42 ;; 43 ;; Styles can be downloaded, for instance, from the Zotero Style Repository 44 ;; (<https://www.zotero.org/styles>). Dependent styles (which are not "unique" 45 ;; in the Zotero Style Repository terminology) are not supported. 46 47 ;; The processor uses the "en-US" CSL locale file shipped with Org for rendering 48 ;; localized dates and terms in the references, independently of the language 49 ;; settings of the Org document. Additional CSL locales can be made available 50 ;; by setting `org-cite-csl-locales-dir' to a directory containing the locale 51 ;; files in question (see <https://github.com/citation-style-language/locales> 52 ;; for such files). 53 54 ;; Bibliography is defined with the "bibliography" keyword. It supports files 55 ;; with ".bib", ".bibtex", and ".json" extensions. References are exported using 56 ;; the "print_bibliography" keyword. 57 58 ;; The library supports the following citation styles: 59 ;; 60 ;; - author (a), including bare (b), caps (c), bare-caps (bc), full (f), 61 ;; caps-full (cf), and bare-caps-full (bcf) variants, 62 ;; - noauthor (na), including bare (b), caps (c) and bare-caps (bc) variants, 63 ;; - nocite (n), 64 ;; - year (y), including a bare (b) variant, 65 ;; - text (t), including caps (c), full (f), and caps-full (cf) variants, 66 ;; - title (ti), including a bare (b) variant, 67 ;; - locators (l), including a bare (b) variant, 68 ;; - bibentry (b), including a bare (b) variant, 69 ;; - default style, including bare (b), caps (c) and bare-caps (bc) variants. 70 ;; 71 ;; Using "*" as a key in a nocite citation includes all available 72 ;; items in the printed bibliography. The "bibentry" citation style, 73 ;; similarly to biblatex's \fullcite, creates a citation which is 74 ;; similar to the bibliography entry. 75 76 ;; CSL styles recognize "locator" in citation references' suffix. For example, 77 ;; in the citation 78 ;; 79 ;; [cite:see @Tarski-1965 chapter 1, for an example] 80 ;; 81 ;; "chapter 1" is the locator. The whole citation is rendered as 82 ;; 83 ;; (see Tarski 1965, chap. 1 for an example) 84 ;; 85 ;; in the default CSL style. 86 ;; 87 ;; The locator starts with a locator term, among "bk.", "bks.", "book", "chap.", 88 ;; "chaps.", "chapter", "col.", "cols.", "column", "figure", "fig.", "figs.", 89 ;; "folio", "fol.", "fols.", "number", "no.", "nos.", "line", "l.", "ll.", 90 ;; "note", "n.", "nn.", "opus", "op.", "opp.", "page", "p.", "pp.", "paragraph", 91 ;; "para.", "paras.", "¶", "¶¶", "§", "§§", "part", "pt.", "pts.", "section", 92 ;; "sec.", "secs.", "sub verbo", "s.v.", "s.vv.", "verse", "v.", "vv.", 93 ;; "volume", "vol.", and "vols.". It ends with the last comma or digit in the 94 ;; suffix, whichever comes last, or runs till the end of the suffix. 95 ;; 96 ;; The part of the suffix before the locator is appended to reference's prefix. 97 ;; If no locator term is used, but a number is present, then "page" is assumed. 98 99 ;; Filtered sub-bibliographies can be printed by passing filtering 100 ;; options to the "print_bibliography" keywords. E.g., 101 ;; 102 ;; #+print_bibliography: :type book keyword: emacs 103 ;; 104 ;; If you need to use a key multiple times, you can separate its 105 ;; values with commas, but without any space in-between: 106 ;; 107 ;; #+print_bibliography: :keyword abc,xyz :type article 108 109 ;; This library was heavily inspired by and borrows from András Simonyi's 110 ;; Citeproc Org (<https://github.com/andras-simonyi/citeproc-org>) library. 111 ;; Many thanks to him! 112 113 ;;; Code: 114 115 (require 'org-macs) 116 (org-assert-version) 117 118 (require 'cl-lib) 119 (require 'map) 120 (require 'bibtex) 121 (require 'json) 122 (require 'oc) 123 124 (require 'citeproc nil t) 125 (declare-function citeproc-style-cite-note "ext:citeproc") 126 (declare-function citeproc-proc-style "ext:citeproc") 127 (declare-function citeproc-bt-entry-to-csl "ext:citeproc") 128 (declare-function citeproc-locale-getter-from-dir "ext:citeproc") 129 (declare-function citeproc-create "ext:citeproc") 130 (declare-function citeproc-citation-create "ext:citeproc") 131 (declare-function citeproc-append-citations "ext:citeproc") 132 (declare-function citeproc-add-uncited "ext:citeproc") 133 (declare-function citeproc-render-citations "ext:citeproc") 134 (declare-function citeproc-render-bib "ext:citeproc") 135 (declare-function citeproc-hash-itemgetter-from-any "ext:citeproc") 136 (declare-function citeproc-add-subbib-filters "ext:citeproc") 137 138 (declare-function org-element-interpret-data "org-element" (data)) 139 (declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated)) 140 (declare-function org-element-property "org-element" (property element)) 141 (declare-function org-element-put-property "org-element" (element property value)) 142 143 (declare-function org-export-data "org-export" (data info)) 144 (declare-function org-export-derived-backend-p "org-export" (backend &rest backends)) 145 (declare-function org-export-get-footnote-number "org-export" (footnote info &optional data body-first)) 146 147 148 ;;; Customization 149 150 ;;;; Location of CSL directories 151 (defcustom org-cite-csl-locales-dir nil 152 "Directory of CSL locale files. 153 If nil then only the fallback en-US locale will be available." 154 :group 'org-cite 155 :package-version '(Org . "9.5") 156 :type '(choice 157 (directory :tag "Locales directory") 158 (const :tag "Use en-US locale only" nil)) 159 ;; It's not obvious to me that arbitrary locations are safe. 160 ;;; :safe #'string-or-null-p 161 ) 162 163 (defcustom org-cite-csl-styles-dir nil 164 "Directory of CSL style files. 165 166 Relative style file names are expanded according to document's 167 default directory. If it fails and the variable is non-nil, Org 168 looks for style files in this directory, too." 169 :group 'org-cite 170 :package-version '(Org . "9.5") 171 :type '(choice 172 (directory :tag "Styles directory") 173 (const :tag "No central directory for style files" nil)) 174 ;; It's not obvious to me that arbitrary locations are safe. 175 ;;; :safe #'string-or-null-p 176 ) 177 178 ;;;; Citelinks 179 (defcustom org-cite-csl-link-cites t 180 "When non-nil, link cites to references." 181 :group 'org-cite 182 :package-version '(Org . "9.5") 183 :type 'boolean 184 :safe #'booleanp) 185 186 (defcustom org-cite-csl-no-citelinks-backends '(ascii) 187 "List of export back-ends for which cite linking is disabled. 188 Cite linking for export back-ends derived from any of the back-ends listed here, 189 is also disabled." 190 :group 'org-cite 191 :package-version '(Org . "9.5") 192 :type '(repeat symbol)) 193 194 ;;;; Output-specific variables 195 (defcustom org-cite-csl-html-hanging-indent "1.5em" 196 "Size of hanging-indent for HTML output in valid CSS units." 197 :group 'org-cite 198 :package-version '(Org . "9.5") 199 :type 'string 200 :safe #'stringp) 201 202 (defcustom org-cite-csl-html-label-width-per-char "0.6em" 203 "Character width in CSS units for calculating entry label widths. 204 Used only when `second-field-align' is activated by the used CSL style." 205 :group 'org-cite 206 :package-version '(Org . "9.5") 207 :type 'string 208 :safe #'stringp) 209 210 (defcustom org-cite-csl-latex-hanging-indent "1.5em" 211 "Size of hanging-indent for LaTeX output in valid LaTeX units." 212 :group 'org-cite 213 :package-version '(Org . "9.5") 214 :type 'string 215 :safe #'stringp) 216 217 218 ;;; Internal variables 219 (defconst org-cite-csl--etc-dir 220 (let ((oc-root (file-name-directory (locate-library "oc")))) 221 (cond 222 ;; First check whether it looks like we're running from the main 223 ;; Org repository. 224 ((let ((csl-org (expand-file-name "../etc/csl/" oc-root))) 225 (and (file-directory-p csl-org) csl-org))) 226 ;; Next look for the directory alongside oc.el because package.el 227 ;; and straight will put all of org-mode/lisp/ in org-mode/. 228 ((let ((csl-pkg (expand-file-name "etc/csl/" oc-root))) 229 (and (file-directory-p csl-pkg) csl-pkg))) 230 ;; Finally fall back the location used by shared system installs 231 ;; and when running directly from Emacs repository. 232 (t 233 (expand-file-name "org/csl/" data-directory)))) 234 "Directory containing CSL-related data files.") 235 236 (defconst org-cite-csl--fallback-locales-dir org-cite-csl--etc-dir 237 "Fallback CSL locale files directory.") 238 239 (defconst org-cite-csl--fallback-style-file 240 (expand-file-name "chicago-author-date.csl" 241 org-cite-csl--etc-dir) 242 "Default CSL style file, or nil. 243 If nil then the Chicago author-date style is used as a fallback.") 244 245 (defconst org-cite-csl--label-alist 246 '(("bk." . "book") 247 ("bks." . "book") 248 ("book" . "book") 249 ("chap." . "chapter") 250 ("chaps." . "chapter") 251 ("chapter" . "chapter") 252 ("col." . "column") 253 ("cols." . "column") 254 ("column" . "column") 255 ("figure" . "figure") 256 ("fig." . "figure") 257 ("figs." . "figure") 258 ("folio" . "folio") 259 ("fol." . "folio") 260 ("fols." . "folio") 261 ("number" . "number") 262 ("no." . "number") 263 ("nos." . "number") 264 ("line" . "line") 265 ("l." . "line") 266 ("ll." . "line") 267 ("note" . "note") 268 ("n." . "note") 269 ("nn." . "note") 270 ("opus" . "opus") 271 ("op." . "opus") 272 ("opp." . "opus") 273 ("page" . "page") 274 ("p" . "page") 275 ("p." . "page") 276 ("pp." . "page") 277 ("paragraph" . "paragraph") 278 ("para." . "paragraph") 279 ("paras." . "paragraph") 280 ("¶" . "paragraph") 281 ("¶¶" . "paragraph") 282 ("part" . "part") 283 ("pt." . "part") 284 ("pts." . "part") 285 ("§" . "section") 286 ("§§" . "section") 287 ("section" . "section") 288 ("sec." . "section") 289 ("secs." . "section") 290 ("sub verbo" . "sub verbo") 291 ("s.v." . "sub verbo") 292 ("s.vv." . "sub verbo") 293 ("verse" . "verse") 294 ("v." . "verse") 295 ("vv." . "verse") 296 ("volume" . "volume") 297 ("vol." . "volume") 298 ("vols." . "volume")) 299 "Alist mapping locator names to locators.") 300 301 (defconst org-cite-csl--label-regexp 302 ;; Prior to Emacs-27.1 argument of `regexp' form must be a string literal. 303 ;; It is the reason why `rx' is avoided here. 304 (rx-to-string 305 `(seq (or line-start space) 306 (regexp ,(regexp-opt (mapcar #'car org-cite-csl--label-alist) t)) 307 (0+ digit) 308 (or word-end line-end space " ")) 309 t) 310 "Regexp matching a label in a citation reference suffix. 311 Label is in match group 1.") 312 313 314 ;;; Internal functions 315 (defun org-cite-csl--barf-without-citeproc () 316 "Raise an error if Citeproc library is not loaded." 317 (unless (featurep 'citeproc) 318 (error "Citeproc library is not loaded"))) 319 320 (defun org-cite-csl--note-style-p (info) 321 "Non-nil when bibliography style implies wrapping citations in footnotes. 322 INFO is the export state, as a property list." 323 (citeproc-style-cite-note 324 (citeproc-proc-style 325 (org-cite-csl--processor info)))) 326 327 (defun org-cite-csl--nocite-p (citation info) 328 "Non-nil when CITATION object's style is nocite. 329 INFO is the export state, as a property list." 330 (member (car (org-cite-citation-style citation info)) 331 '("nocite" "n"))) 332 333 (defun org-cite-csl--create-structure-params (citation info) 334 "Return citeproc structure creation params for CITATION object. 335 STYLE is the citation style, as a string or nil. INFO is the export state, as 336 a property list." 337 (let ((style (org-cite-citation-style citation info))) 338 (pcase style 339 ;; "author" style. 340 (`(,(or "author" "a") . ,variant) 341 (pcase variant 342 ((or "bare" "b") '(:mode author-only :suppress-affixes t)) 343 ((or "caps" "c") '(:mode author-only :capitalize-first t)) 344 ((or "full" "f") '(:mode author-only :ignore-et-al t)) 345 ((or "bare-caps" "bc") '(:mode author-only :suppress-affixes t :capitalize-first t)) 346 ((or "bare-full" "bf") '(:mode author-only :suppress-affixes t :ignore-et-al t)) 347 ((or "caps-full" "cf") '(:mode author-only :capitalize-first t :ignore-et-al t)) 348 ((or "bare-caps-full" "bcf") '(:mode author-only :suppress-affixes t :capitalize-first t :ignore-et-al t)) 349 (_ '(:mode author-only)))) 350 ;; "noauthor" style. 351 (`(,(or "noauthor" "na") . ,variant) 352 (pcase variant 353 ((or "bare" "b") '(:mode suppress-author :suppress-affixes t)) 354 ((or "caps" "c") '(:mode suppress-author :capitalize-first t)) 355 ((or "bare-caps" "bc") 356 '(:mode suppress-author :suppress-affixes t :capitalize-first t)) 357 (_ '(:mode suppress-author)))) 358 ;; "year" style. 359 (`(,(or "year" "y") . ,variant) 360 (pcase variant 361 ((or "bare" "b") '(:mode year-only :suppress-affixes t)) 362 (_ '(:mode year-only)))) 363 ;; "bibentry" style. 364 (`(,(or "bibentry" "b") . ,variant) 365 (pcase variant 366 ((or "bare" "b") '(:mode bib-entry :suppress-affixes t)) 367 (_ '(:mode bib-entry)))) 368 ;; "locators" style. 369 (`(,(or "locators" "l") . ,variant) 370 (pcase variant 371 ((or "bare" "b") '(:mode locator-only :suppress-affixes t)) 372 (_ '(:mode locator-only)))) 373 ;; "title" style. 374 (`(,(or "title" "ti") . ,variant) 375 (pcase variant 376 ((or "bare" "b") '(:mode title-only :suppress-affixes t)) 377 (_ '(:mode title-only)))) 378 ;; "text" style. 379 (`(,(or "text" "t") . ,variant) 380 (pcase variant 381 ((or "caps" "c") '(:mode textual :capitalize-first t)) 382 ((or "full" "f") '(:mode textual :ignore-et-al t)) 383 ((or "caps-full" "cf") '(:mode textual :ignore-et-al t :capitalize-first t)) 384 (_ '(:mode textual)))) 385 ;; Default "nil" style. 386 (`(,_ . ,variant) 387 (pcase variant 388 ((or "caps" "c") '(:capitalize-first t)) 389 ((or "bare" "b") '(:suppress-affixes t)) 390 ((or "bare-caps" "bc") '(:suppress-affixes t :capitalize-first t)) 391 (_ nil))) 392 ;; This should not happen. 393 (_ (error "Invalid style: %S" style))))) 394 395 (defun org-cite-csl--no-citelinks-p (info) 396 "Non-nil when export BACKEND should not create cite-reference links." 397 (or (not org-cite-csl-link-cites) 398 (and org-cite-csl-no-citelinks-backends 399 (apply #'org-export-derived-backend-p 400 (plist-get info :back-end) 401 org-cite-csl-no-citelinks-backends)) 402 ;; No references are being exported anyway. 403 (not (org-element-map (plist-get info :parse-tree) 'keyword 404 (lambda (k) 405 (equal "PRINT_BIBLIOGRAPHY" (org-element-property :key k))) 406 info t)))) 407 408 (defun org-cite-csl--output-format (info) 409 "Return expected Citeproc's output format. 410 INFO is the export state, as a property list. The return value is a symbol 411 corresponding to one of the output formats supported by Citeproc: `html', 412 `latex', or `org'." 413 (let ((backend (plist-get info :back-end))) 414 (cond 415 ((org-export-derived-backend-p backend 'html) 'html) 416 ((org-export-derived-backend-p backend 'latex) 'latex) 417 (t 'org)))) 418 419 (defun org-cite-csl--style-file (info) 420 "Return style file associated to current export process. 421 422 INFO is the export state, as a property list. 423 424 When file name is relative, look for it in buffer's default 425 directory, failing that in `org-cite-csl-styles-dir' if non-nil. 426 Raise an error if no style file can be found." 427 (pcase (org-cite-bibliography-style info) 428 ('nil org-cite-csl--fallback-style-file) 429 ((and (pred file-name-absolute-p) file) file) 430 ((and (pred file-exists-p) file) (expand-file-name file)) 431 ((and (guard org-cite-csl-styles-dir) 432 (pred (lambda (f) 433 (file-exists-p 434 (expand-file-name f org-cite-csl-styles-dir)))) 435 file) 436 (expand-file-name file org-cite-csl-styles-dir)) 437 (other 438 (user-error "CSL style file not found: %S" other)))) 439 440 (defun org-cite-csl--locale-getter () 441 "Return a locale getter. 442 The getter looks for locales in `org-cite-csl-locales-dir' directory. If it 443 cannot find them, it retrieves the default \"en_US\" from 444 `org-cite-csl--fallback-locales-dir'." 445 (lambda (loc) 446 (or (and org-cite-csl-locales-dir 447 (ignore-errors 448 (funcall (citeproc-locale-getter-from-dir org-cite-csl-locales-dir) 449 loc))) 450 (funcall (citeproc-locale-getter-from-dir 451 org-cite-csl--fallback-locales-dir) 452 loc)))) 453 454 (defun org-cite-csl--processor (info) 455 "Return Citeproc processor reading items from current bibliography. 456 457 INFO is the export state, as a property list. 458 459 Newly created processor is stored as the value of the `:cite-citeproc-processor' 460 property in INFO." 461 (or (plist-get info :cite-citeproc-processor) 462 (let* ((bibliography (plist-get info :bibliography)) 463 (locale (or (plist-get info :language) "en_US")) 464 (processor 465 (citeproc-create 466 (org-cite-csl--style-file info) 467 (citeproc-hash-itemgetter-from-any bibliography) 468 (org-cite-csl--locale-getter) 469 locale))) 470 (plist-put info :cite-citeproc-processor processor) 471 processor))) 472 473 (defun org-cite-csl--parse-reference (reference info) 474 "Return Citeproc's structure associated to citation REFERENCE. 475 476 INFO is the export state, as a property list. 477 478 The result is a association list. Keys are: `id', `prefix',`suffix', 479 `location', `locator' and `label'." 480 (let (label location-start locator-start location locator prefix suffix) 481 ;; Parse suffix. Insert it in a temporary buffer to find 482 ;; different parts: pre-label, label, locator, location (label + 483 ;; locator), and suffix. 484 (with-temp-buffer 485 (save-excursion 486 (insert (org-element-interpret-data 487 (org-element-property :suffix reference)))) 488 (cond 489 ((re-search-forward org-cite-csl--label-regexp nil t) 490 (setq location-start (match-beginning 0)) 491 (setq label (cdr (assoc (match-string 1) org-cite-csl--label-alist))) 492 (goto-char (match-end 1)) 493 (skip-chars-forward "[:space:] ") 494 (setq locator-start (point))) 495 ((re-search-forward (rx digit) nil t) 496 (setq location-start (match-beginning 0)) 497 (setq label "page") 498 (setq locator-start location-start)) 499 (t 500 (setq suffix (org-element-property :suffix reference)))) 501 ;; Find locator's end, and suffix, if any. To that effect, look 502 ;; for the last comma or digit after label, whichever comes 503 ;; last. 504 (unless suffix 505 (goto-char (point-max)) 506 (let ((re (rx (or "," (group digit))))) 507 (when (re-search-backward re location-start t) 508 (goto-char (or (match-end 1) (match-beginning 0))) 509 (setq location (buffer-substring location-start (point))) 510 (setq locator (org-trim (buffer-substring locator-start (point)))) 511 ;; Skip comma in suffix. 512 (setq suffix 513 (org-cite-parse-objects 514 (buffer-substring (match-end 0) (point-max)) 515 t))))) 516 (setq prefix 517 (org-cite-concat 518 (org-element-property :prefix reference) 519 (and location-start 520 (org-cite-parse-objects 521 (buffer-substring 1 location-start) 522 t))))) 523 ;; Return value. 524 (let ((export 525 (lambda (data) 526 (org-string-nw-p 527 (org-trim 528 ;; When Citeproc exports to Org syntax, avoid mix and 529 ;; matching output formats by also generating Org 530 ;; syntax for prefix and suffix. 531 (if (eq 'org (org-cite-csl--output-format info)) 532 (org-element-interpret-data data) 533 (org-export-data data info))))))) 534 `((id . ,(org-element-property :key reference)) 535 (prefix . ,(funcall export prefix)) 536 (suffix . ,(funcall export suffix)) 537 (locator . ,locator) 538 (label . ,label) 539 (location . ,location))))) 540 541 (defun org-cite-csl--create-structure (citation info) 542 "Create Citeproc structure for CITATION object. 543 INFO is the export state, as a property list." 544 (let* ((cites (mapcar (lambda (r) 545 (org-cite-csl--parse-reference r info)) 546 (org-cite-get-references citation))) 547 (footnote (org-cite-inside-footnote-p citation))) 548 ;; Global prefix is inserted in front of the prefix of the first 549 ;; reference. 550 (let ((global-prefix (org-element-property :prefix citation))) 551 (when global-prefix 552 (let* ((first (car cites)) 553 (prefix-item (assq 'prefix first))) 554 (setcdr prefix-item 555 (concat (org-element-interpret-data global-prefix) 556 " " 557 (cdr prefix-item)))))) 558 ;; Global suffix is appended to the suffix of the last reference. 559 (let ((global-suffix (org-element-property :suffix citation))) 560 (when global-suffix 561 (let* ((last (org-last cites)) 562 (suffix-item (assq 'suffix last))) 563 (setcdr suffix-item 564 (concat (cdr suffix-item) 565 " " 566 (org-element-interpret-data global-suffix)))))) 567 ;; Check if CITATION needs wrapping, i.e., it should be wrapped in 568 ;; a footnote, but isn't yet. 569 (when (and (not footnote) (org-cite-csl--note-style-p info)) 570 (org-cite-adjust-note citation info) 571 (setq footnote (org-cite-wrap-citation citation info))) 572 ;; Return structure. 573 (apply #'citeproc-citation-create 574 `(:note-index 575 ,(and footnote (org-export-get-footnote-number footnote info)) 576 :cites ,cites 577 ,@(org-cite-csl--create-structure-params citation info))))) 578 579 (defun org-cite-csl--rendered-citations (info) 580 "Return the rendered citations as an association list. 581 582 INFO is the export state, as a property list. 583 584 Return an alist (CITATION . OUTPUT) where CITATION object has been rendered as 585 OUTPUT using Citeproc." 586 (or (plist-get info :cite-citeproc-rendered-citations) 587 (let ((citations (org-cite-list-citations info)) 588 (processor (org-cite-csl--processor info)) 589 normal-citations nocite-ids) 590 (dolist (citation citations) 591 (if (org-cite-csl--nocite-p citation info) 592 (setq nocite-ids (append (org-cite-get-references citation t) nocite-ids)) 593 (push citation normal-citations))) 594 (let ((structures 595 (mapcar (lambda (c) (org-cite-csl--create-structure c info)) 596 (nreverse normal-citations)))) 597 (citeproc-append-citations structures processor)) 598 (when nocite-ids 599 (citeproc-add-uncited nocite-ids processor)) 600 ;; All bibliographies have to be rendered in order to have 601 ;; correct citation numbers even if there are several 602 ;; sub-bibliograhies. 603 (org-cite-csl--rendered-bibliographies info) 604 (let (result 605 (rendered (citeproc-render-citations 606 processor 607 (org-cite-csl--output-format info) 608 (org-cite-csl--no-citelinks-p info)))) 609 (dolist (citation citations) 610 (push (cons citation 611 (if (org-cite-csl--nocite-p citation info) "" (pop rendered))) 612 result)) 613 (setq result (nreverse result)) 614 (plist-put info :cite-citeproc-rendered-citations result) 615 result)))) 616 617 (defun org-cite-csl--bibliography-filter (bib-props) 618 "Return the sub-bibliography filter corresponding to bibliography properties. 619 620 BIB-PROPS should be a plist representing the properties 621 associated with a \"print_bibliography\" keyword, as returned by 622 `org-cite-bibliography-properties'." 623 (let (result 624 (remove-keyword-colon (lambda (x) (intern (substring (symbol-name x) 1))))) 625 (map-do 626 (lambda (key value) 627 (pcase key 628 ((or :keyword :notkeyword :nottype :notcsltype :filter) 629 (dolist (v (split-string value ",")) 630 (push (cons (funcall remove-keyword-colon key) v) result))) 631 ((or :type :csltype) 632 (if (string-match-p "," value) 633 (user-error "The \"%s\" print_bibliography option does not support comma-separated values" key) 634 (push (cons (funcall remove-keyword-colon key) value) result))))) 635 bib-props) 636 result)) 637 638 (defun org-cite-csl--rendered-bibliographies (info) 639 "Return the rendered bibliographies. 640 641 INFO is the export state, as a property list. 642 643 Return an (OUTPUTS PARAMETERS) list where OUTPUTS is an alist 644 of (BIB-PROPS . OUTPUT) pairs where each key is a property list 645 of a \"print_bibliography\" keyword and the corresponding OUTPUT 646 value is the bibliography as rendered by Citeproc." 647 (or (plist-get info :cite-citeproc-rendered-bibliographies) 648 (let (bib-plists bib-filters) 649 ;; Collect bibliography property lists and the corresponding 650 ;; Citeproc sub-bib filters. 651 (org-element-map (plist-get info :parse-tree) 'keyword 652 (lambda (keyword) 653 (when (equal "PRINT_BIBLIOGRAPHY" (org-element-property :key keyword)) 654 (let ((bib-plist (org-cite-bibliography-properties keyword))) 655 (push bib-plist bib-plists) 656 (push (org-cite-csl--bibliography-filter bib-plist) bib-filters))))) 657 (setq bib-filters (nreverse bib-filters) 658 bib-plists (nreverse bib-plists)) 659 ;; Render and return all bibliographies. 660 (let ((processor (org-cite-csl--processor info))) 661 (citeproc-add-subbib-filters bib-filters processor) 662 (pcase-let* ((format (org-cite-csl--output-format info)) 663 (`(,rendered-bibs . ,parameters) 664 (citeproc-render-bib 665 (org-cite-csl--processor info) 666 format 667 (org-cite-csl--no-citelinks-p info))) 668 (outputs (cl-mapcar #'cons bib-plists rendered-bibs)) 669 (result (list outputs parameters))) 670 (plist-put info :cite-citeproc-rendered-bibliographies result) 671 result))))) 672 673 674 ;;; Export capability 675 (defun org-cite-csl-render-citation (citation _style _backend info) 676 "Export CITATION object. 677 INFO is the export state, as a property list." 678 (org-cite-csl--barf-without-citeproc) 679 (let ((output (cdr (assq citation (org-cite-csl--rendered-citations info))))) 680 (if (not (eq 'org (org-cite-csl--output-format info))) 681 output 682 ;; Parse Org output to re-export it during the regular export 683 ;; process. 684 (org-cite-parse-objects output)))) 685 686 (defun org-cite-csl-render-bibliography (_keys _files _style props _backend info) 687 "Export bibliography. 688 INFO is the export state, as a property list." 689 (org-cite-csl--barf-without-citeproc) 690 (pcase-let* ((format (org-cite-csl--output-format info)) 691 (`(,outputs ,parameters) (org-cite-csl--rendered-bibliographies info)) 692 (output (cdr (assoc props outputs)))) 693 (pcase format 694 ('html 695 (concat 696 (and (cdr (assq 'second-field-align parameters)) 697 (let* ((max-offset (cdr (assq 'max-offset parameters))) 698 (char-width 699 (string-to-number org-cite-csl-html-label-width-per-char)) 700 (char-width-unit 701 (progn 702 (string-match (number-to-string char-width) 703 org-cite-csl-html-label-width-per-char) 704 (substring org-cite-csl-html-label-width-per-char 705 (match-end 0))))) 706 (format 707 "<style>.csl-left-margin{float: left; padding-right: 0em;} 708 .csl-right-inline{margin: 0 0 0 %d%s;}</style>" 709 (* max-offset char-width) 710 char-width-unit))) 711 (and (cdr (assq 'hanging-indent parameters)) 712 (format 713 "<style>.csl-entry{text-indent: -%s; margin-left: %s;}</style>" 714 org-cite-csl-html-hanging-indent 715 org-cite-csl-html-hanging-indent)) 716 output)) 717 ('latex 718 (if (cdr (assq 'hanging-indent parameters)) 719 (format "\\begin{hangparas}{%s}{1}\n%s\n\\end{hangparas}" 720 org-cite-csl-latex-hanging-indent 721 output) 722 output)) 723 (_ 724 ;; Parse Org output to re-export it during the regular export 725 ;; process. 726 (org-cite-parse-elements output))))) 727 728 (defun org-cite-csl-finalizer (output _keys _files _style _backend info) 729 "Add \"hanging\" package if missing from LaTeX output. 730 OUTPUT is the export document, as a string. INFO is the export state, as a 731 property list." 732 (org-cite-csl--barf-without-citeproc) 733 (if (not (eq 'latex (org-cite-csl--output-format info))) 734 output 735 (with-temp-buffer 736 (save-excursion (insert output)) 737 (when (search-forward "\\begin{document}" nil t) 738 (goto-char (match-beginning 0)) 739 ;; Ensure that \citeprocitem is defined for citeproc-el. 740 (insert "\\makeatletter\n\\newcommand{\\citeprocitem}[2]{\\hyper@linkstart{cite}{citeproc_bib_item_#1}#2\\hyper@linkend}\n\\makeatother\n\n") 741 ;; Ensure there is a \usepackage{hanging} somewhere or add one. 742 (let ((re (rx "\\usepackage" (opt "[" (*? nonl) "]") "{hanging}"))) 743 (unless (re-search-backward re nil t) 744 (insert "\\usepackage[notquote]{hanging}\n")))) 745 (buffer-string)))) 746 747 748 ;;; Register `csl' processor 749 (org-cite-register-processor 'csl 750 :export-citation #'org-cite-csl-render-citation 751 :export-bibliography #'org-cite-csl-render-bibliography 752 :export-finalizer #'org-cite-csl-finalizer 753 :cite-styles 754 '((("author" "a") ("bare" "b") ("caps" "c") ("full" "f") ("bare-caps" "bc") ("caps-full" "cf") ("bare-caps-full" "bcf")) 755 (("noauthor" "na") ("bare" "b") ("caps" "c") ("bare-caps" "bc")) 756 (("year" "y") ("bare" "b")) 757 (("text" "t") ("caps" "c") ("full" "f") ("caps-full" "cf")) 758 (("nil") ("bare" "b") ("caps" "c") ("bare-caps" "bc")) 759 (("nocite" "n")) 760 (("title" "ti") ("bare" "b")) 761 (("bibentry" "b") ("bare" "b")) 762 (("locators" "l") ("bare" "b")))) 763 764 (provide 'oc-csl) 765 ;;; oc-csl.el ends here