dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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