config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

oc-csl.el (36657B)


      1 ;;; oc-csl.el --- csl citation processor for Org -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2021-2024 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 (declare-function citeproc-style-cite-superscript-p "ext:citeproc")
    138 
    139 (declare-function org-element-interpret-data "org-element" (data))
    140 (declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated))
    141 (declare-function org-element-property "org-element-ast" (property node))
    142 (declare-function org-element-put-property "org-element-ast" (node property value))
    143 
    144 (declare-function org-export-data "org-export" (data info))
    145 (declare-function org-export-derived-backend-p "org-export" (backend &rest backends))
    146 (declare-function org-export-get-footnote-number "org-export" (footnote info &optional data body-first))
    147 
    148 
    149 ;;; Customization
    150 
    151 ;;;; Location of CSL directories
    152 (defcustom org-cite-csl-locales-dir nil
    153   "Directory of CSL locale files.
    154 If nil then only the fallback en-US locale will be available."
    155   :group 'org-cite
    156   :package-version '(Org . "9.5")
    157   :type '(choice
    158           (directory :tag "Locales directory")
    159           (const :tag "Use en-US locale only" nil))
    160   ;; It's not obvious to me that arbitrary locations are safe.
    161 ;;;  :safe #'string-or-null-p
    162   )
    163 
    164 (defcustom org-cite-csl-styles-dir nil
    165   "Directory of CSL style files.
    166 
    167 Relative style file names are expanded according to document's
    168 default directory.  If it fails and the variable is non-nil, Org
    169 looks for style files in this directory, too."
    170   :group 'org-cite
    171   :package-version '(Org . "9.5")
    172   :type '(choice
    173           (directory :tag "Styles directory")
    174           (const :tag "No central directory for style files" nil))
    175   ;; It's not obvious to me that arbitrary locations are safe.
    176 ;;;  :safe #'string-or-null-p
    177   )
    178 
    179 ;;;; Citelinks
    180 (defcustom org-cite-csl-link-cites t
    181   "When non-nil, link cites to references."
    182   :group 'org-cite
    183   :package-version '(Org . "9.5")
    184   :type 'boolean
    185   :safe #'booleanp)
    186 
    187 (defcustom org-cite-csl-no-citelinks-backends '(ascii)
    188   "List of export backends for which cite linking is disabled.
    189 Cite linking for export backends derived from any of the backends listed here,
    190 is also disabled."
    191   :group 'org-cite
    192   :package-version '(Org . "9.5")
    193   :type '(repeat symbol))
    194 
    195 ;;;; Output-specific variables
    196 (defcustom org-cite-csl-html-hanging-indent "1.5em"
    197   "Size of hanging-indent for HTML output in valid CSS units."
    198   :group 'org-cite
    199   :package-version '(Org . "9.5")
    200   :type 'string
    201   :safe #'stringp)
    202 
    203 (defcustom org-cite-csl-html-label-width-per-char "0.6em"
    204   "Character width in CSS units for calculating entry label widths.
    205 Used only when `second-field-align' is activated by the used CSL style."
    206   :group 'org-cite
    207   :package-version '(Org . "9.5")
    208   :type 'string
    209   :safe #'stringp)
    210 
    211 (defcustom org-cite-csl-latex-hanging-indent "1.5em"
    212   "Size of hanging-indent for LaTeX output in valid LaTeX units."
    213   :group 'org-cite
    214   :package-version '(Org . "9.5")
    215   :type 'string
    216   :safe #'stringp)
    217 
    218 (defcustom org-cite-csl-latex-label-separator "0.6em"
    219   "Distance between citation label and bibliography item for LaTeX output.
    220 The value is a string representing the distance in valid LaTeX units.
    221 Used only when `second-field-align' is activated by the used CSL
    222 style.
    223 
    224 The indentation length in these cases is computed as the sum of
    225 `org-cite-csl-latex-label-separator' and the maximal label width, for
    226 example,
    227 
    228     indentation length
    229 <------------------------->
    230 max.  label width  separator
    231 <---------------><-------->
    232 [Doe22]                    John Doe.  A title...
    233 [DoeSmithJones19]          John Doe, Jane Smith and...
    234 [SmithDoe02]               Jane Smith and John Doe...
    235 
    236 The maximal label width, in turn, is calculated as the product of
    237 `org-cite-csl-latex-label-width-per-char' and the maximal label
    238 length measured in characters."
    239   :group 'org-cite
    240   :package-version '(Org . "9.7")
    241   :type 'string
    242   :safe #'stringp)
    243 
    244 (defcustom org-cite-csl-latex-label-width-per-char "0.45em"
    245   "Character width in LaTeX units for calculating entry label widths.
    246 Used only when `second-field-align' is activated by the used CSL
    247 style.
    248 
    249 See the documentation of `org-cite-csl-latex-label-separator' for
    250 details."
    251   :group 'org-cite
    252   :package-version '(Org . "9.7")
    253   :type 'string
    254   :safe #'stringp)
    255 
    256 ;; The following was inspired by and in many details follows how
    257 ;; Pandoc's (<https://github.com/jgm/pandoc>) default LaTeX template
    258 ;; handles CSL output.  Many thanks to the author, John MacFarlane!
    259 (defcustom org-cite-csl-latex-preamble
    260   "\\usepackage{calc}
    261 \\newlength{\\cslhangindent}
    262 \\setlength{\\cslhangindent}{[CSL-HANGINDENT]}
    263 \\newlength{\\csllabelsep}
    264 \\setlength{\\csllabelsep}{[CSL-LABELSEP]}
    265 \\newlength{\\csllabelwidth}
    266 \\setlength{\\csllabelwidth}{[CSL-LABELWIDTH-PER-CHAR] * [CSL-MAXLABEL-CHARS]}
    267 \\newenvironment{cslbibliography}[2] % 1st arg. is hanging-indent, 2nd entry spacing.
    268  {% By default, paragraphs are not indented.
    269   \\setlength{\\parindent}{0pt}
    270   % Hanging indent is turned on when first argument is 1.
    271   \\ifodd #1
    272   \\let\\oldpar\\par
    273   \\def\\par{\\hangindent=\\cslhangindent\\oldpar}
    274   \\fi
    275   % Set entry spacing based on the second argument.
    276   \\setlength{\\parskip}{\\parskip +  #2\\baselineskip}
    277  }%
    278  {}
    279 \\newcommand{\\cslblock}[1]{#1\\hfill\\break}
    280 \\newcommand{\\cslleftmargin}[1]{\\parbox[t]{\\csllabelsep + \\csllabelwidth}{#1}}
    281 \\newcommand{\\cslrightinline}[1]
    282   {\\parbox[t]{\\linewidth - \\csllabelsep - \\csllabelwidth}{#1}\\break}
    283 \\newcommand{\\cslindent}[1]{\\hspace{\\cslhangindent}#1}
    284 \\newcommand{\\cslbibitem}[2]
    285   {\\leavevmode\\vadjust pre{\\hypertarget{citeproc_bib_item_#1}{}}#2}
    286 \\makeatletter
    287 \\newcommand{\\cslcitation}[2]
    288  {\\protect\\hyper@linkstart{cite}{citeproc_bib_item_#1}#2\\hyper@linkend}
    289 \\makeatother"
    290   "LaTeX preamble content inserted by the `csl' citation processor.
    291 
    292 This preamble can be anything as long as it provides definitions
    293 for the environment and commands that Citeproc's `org-latex'
    294 formatter uses for formatting citations and bibliographies.  In
    295 particular, it has to define
    296 - the commands \\cslblock{<text>}, \\cslleftmargin{<text>},
    297   \\cslrightinline{<text>} and \\cslindent{<text>} for formatting
    298   text that have, respectively, the CSL display attributes
    299   `block', `left-margin', `right-inline' and `indent';
    300 - the commands \\cslcitation{<item_no>}{<item_text>} and
    301   \\cslbibitem{<item_no>}{<item_text>}, which are used to
    302   format individual citations and bibliography items, including
    303   hyperlinking citations to the corresponding bibliography entry
    304   using their numerical id, which is passed as the first,
    305   <item_no> argument;
    306 - and the environment \\cslbibliography{<hanging-indent>}{<entry-spacing>},
    307   in which bibliographies are wrapped; the value of the
    308   <hanging-indent> argument is 1 if hanging indent should be
    309   applied and 0 if not, while the <entry-spacing> argument is an
    310   integer specifying the number of extra line-heights
    311   required between bibliography entries in addition to normal
    312   line spacing.
    313 
    314 When present, the placeholders [CSL-HANGINDENT], [CSL-LABELSEP],
    315 [CSL-LABELWIDTH-PER-CHAR] and [CSL-MAXLABEL-CHARS] are replaced,
    316 respectively, by the contents of the customizable variables
    317 `org-cite-csl-latex-hanging-indent', `org-cite-csl-latex-label-separator',
    318 `org-cite-csl-latex-label-width-per-char', and the maximal label length
    319 in the bibliography measured in characters."
    320   :group 'org-cite
    321   :type 'string
    322   :package-version '(Org . "9.7"))
    323 
    324 
    325 ;;; Internal variables
    326 (defconst org-cite-csl--etc-dir
    327   (let ((oc-root (file-name-directory (locate-library "oc"))))
    328     (cond
    329      ;; First check whether it looks like we're running from the main
    330      ;; Org repository.
    331      ((let ((csl-org (expand-file-name "../etc/csl/" oc-root)))
    332         (and (file-directory-p csl-org) csl-org)))
    333      ;; Next look for the directory alongside oc.el because package.el
    334      ;; and straight will put all of org-mode/lisp/ in org-mode/.
    335      ((let ((csl-pkg (expand-file-name "etc/csl/" oc-root)))
    336         (and (file-directory-p csl-pkg) csl-pkg)))
    337      ;; Finally fall back the location used by shared system installs
    338      ;; and when running directly from Emacs repository.
    339      (t
    340       (expand-file-name "org/csl/" data-directory))))
    341   "Directory containing CSL-related data files.")
    342 
    343 (defconst org-cite-csl--fallback-locales-dir org-cite-csl--etc-dir
    344   "Fallback CSL locale files directory.")
    345 
    346 (defconst org-cite-csl--fallback-style-file
    347   (expand-file-name "chicago-author-date.csl"
    348                     org-cite-csl--etc-dir)
    349   "Default CSL style file, or nil.
    350 If nil then the Chicago author-date style is used as a fallback.")
    351 
    352 (defconst org-cite-csl--label-alist
    353   '(("bk."       . "book")
    354     ("bks."      . "book")
    355     ("book"      . "book")
    356     ("chap."     . "chapter")
    357     ("chaps."    . "chapter")
    358     ("chapter"   . "chapter")
    359     ("col."      . "column")
    360     ("cols."     . "column")
    361     ("column"    . "column")
    362     ("figure"    . "figure")
    363     ("fig."      . "figure")
    364     ("figs."     . "figure")
    365     ("folio"     . "folio")
    366     ("fol."      . "folio")
    367     ("fols."     . "folio")
    368     ("number"    . "number")
    369     ("no."       . "number")
    370     ("nos."      . "number")
    371     ("line"      . "line")
    372     ("l."        . "line")
    373     ("ll."       . "line")
    374     ("note"      . "note")
    375     ("n."        . "note")
    376     ("nn."       . "note")
    377     ("opus"      . "opus")
    378     ("op."       . "opus")
    379     ("opp."      . "opus")
    380     ("page"      . "page")
    381     ("p"         . "page")
    382     ("p."        . "page")
    383     ("pp."       . "page")
    384     ("paragraph" . "paragraph")
    385     ("para."     . "paragraph")
    386     ("paras."    . "paragraph")
    387     ("\\P"       . "paragraph")
    388     ("¶"         . "paragraph")
    389     ("\\P\\P"    . "paragraph")
    390     ("¶¶"        . "paragraph")
    391     ("part"      . "part")
    392     ("pt."       . "part")
    393     ("pts."      . "part")
    394     ("§"         . "section")
    395     ("\\S"       . "section")
    396     ("§§"        . "section")
    397     ("\\S\\S"    . "section")
    398     ("section"   . "section")
    399     ("sec."      . "section")
    400     ("secs."     . "section")
    401     ("sub verbo" . "sub verbo")
    402     ("s.v."      . "sub verbo")
    403     ("s.vv."     . "sub verbo")
    404     ("verse"     . "verse")
    405     ("v."        . "verse")
    406     ("vv."       . "verse")
    407     ("volume"    . "volume")
    408     ("vol."      . "volume")
    409     ("vols."     . "volume"))
    410   "Alist mapping locator names to locators.")
    411 
    412 (defconst org-cite-csl--label-regexp
    413   ;; Prior to Emacs-27.1 argument of `regexp' form must be a string literal.
    414   ;; It is the reason why `rx' is avoided here.
    415   (rx-to-string
    416    `(seq (or line-start space)
    417          (regexp ,(regexp-opt (mapcar #'car org-cite-csl--label-alist) t))
    418          (0+ digit)
    419          (or word-end line-end space " "))
    420    t)
    421   "Regexp matching a label in a citation reference suffix.
    422 Label is in match group 1.")
    423 
    424 
    425 ;;; Internal functions
    426 
    427 (defun org-cite-csl--note-style-p (info)
    428   "Non-nil when bibliography style implies wrapping citations in footnotes.
    429 INFO is the export state, as a property list."
    430   (citeproc-style-cite-note
    431    (citeproc-proc-style
    432     (org-cite-csl--processor info))))
    433 
    434 (defun org-cite-csl--style-cite-superscript-p (info)
    435   "Non-nil when bibliography style produces citations in superscript.
    436 INFO is the export state, as a property list."
    437   (citeproc-style-cite-superscript-p
    438    (citeproc-proc-style
    439     (org-cite-csl--processor info))))
    440 
    441 (defun org-cite-csl--nocite-p (citation info)
    442   "Non-nil when CITATION object's style is nocite.
    443 INFO is the export state, as a property list."
    444   (member (car (org-cite-citation-style citation info))
    445           '("nocite" "n")))
    446 
    447 (defun org-cite-csl--create-structure-params (citation info)
    448   "Return citeproc structure creation params for CITATION object.
    449 STYLE is the citation style, as a string or nil.  INFO is the export
    450 state, as a property list."
    451   (let ((style (org-cite-citation-style citation info)))
    452     (pcase style
    453       ;; "author" style.
    454       (`(,(or "author" "a") . ,variant)
    455        (pcase variant
    456 	 ((or "bare" "b") '(:mode author-only :suppress-affixes t))
    457 	 ((or "caps" "c") '(:mode author-only :capitalize-first t))
    458 	 ((or "full" "f") '(:mode author-only :ignore-et-al t))
    459 	 ((or "bare-caps" "bc") '(:mode author-only :suppress-affixes t :capitalize-first t))
    460 	 ((or "bare-full" "bf") '(:mode author-only :suppress-affixes t :ignore-et-al t))
    461 	 ((or "caps-full" "cf") '(:mode author-only :capitalize-first t :ignore-et-al t))
    462 	 ((or "bare-caps-full" "bcf") '(:mode author-only :suppress-affixes t :capitalize-first t :ignore-et-al t))
    463 	 (_ '(:mode author-only))))
    464       ;; "noauthor" style.
    465       (`(,(or "noauthor" "na") . ,variant)
    466        (pcase variant
    467 	 ((or "bare" "b") '(:mode suppress-author :suppress-affixes t))
    468 	 ((or "caps" "c") '(:mode suppress-author :capitalize-first t))
    469 	 ((or "bare-caps" "bc")
    470           '(:mode suppress-author :suppress-affixes t :capitalize-first t))
    471 	 (_ '(:mode suppress-author))))
    472       ;; "year" style.
    473       (`(,(or "year" "y") . ,variant)
    474        (pcase variant
    475 	 ((or "bare" "b") '(:mode year-only :suppress-affixes t))
    476 	 (_ '(:mode year-only))))
    477       ;; "bibentry" style.
    478       (`(,(or "bibentry" "b") . ,variant)
    479        (pcase variant
    480 	 ((or "bare" "b") '(:mode bib-entry :suppress-affixes t))
    481 	 (_ '(:mode bib-entry))))
    482       ;; "locators" style.
    483       (`(,(or "locators" "l") . ,variant)
    484        (pcase variant
    485 	 ((or "bare" "b") '(:mode locator-only :suppress-affixes t))
    486 	 (_ '(:mode locator-only))))
    487       ;; "title" style.
    488       (`(,(or "title" "ti") . ,variant)
    489        (pcase variant
    490 	 ((or "bare" "b") '(:mode title-only :suppress-affixes t))
    491 	 (_ '(:mode title-only))))
    492       ;; "text" style.
    493       (`(,(or "text" "t") . ,variant)
    494        (pcase variant
    495          ((or "caps" "c") '(:mode textual :capitalize-first t))
    496          ((or "full" "f") '(:mode textual :ignore-et-al t))
    497          ((or "caps-full" "cf") '(:mode textual :ignore-et-al t :capitalize-first t))
    498          (_ '(:mode textual))))
    499       ;; Default "nil" style.
    500       (`(,_ . ,variant)
    501        (pcase variant
    502          ((or "caps" "c") '(:capitalize-first t))
    503          ((or "bare" "b") '(:suppress-affixes t))
    504          ((or "bare-caps" "bc") '(:suppress-affixes t :capitalize-first t))
    505          (_  nil)))
    506       ;; This should not happen.
    507       (_ (error "Invalid style: %S" style)))))
    508 
    509 (defun org-cite-csl--no-citelinks-p (info)
    510   "Non-nil when export backend should not create cite-reference links.
    511 INFO is the info channel plist."
    512   (or (not org-cite-csl-link-cites)
    513       (and org-cite-csl-no-citelinks-backends
    514            (apply #'org-export-derived-backend-p
    515                   (plist-get info :back-end)
    516                   org-cite-csl-no-citelinks-backends))
    517       ;; No references are being exported anyway.
    518       (not (org-element-map (plist-get info :parse-tree) 'keyword
    519              (lambda (k)
    520                (equal "PRINT_BIBLIOGRAPHY" (org-element-property :key k)))
    521              info t))))
    522 
    523 (defun org-cite-csl--output-format (info)
    524   "Return expected Citeproc's output format.
    525 INFO is the export state, as a property list.  The return value is a symbol
    526 corresponding to one of the output formats supported by Citeproc: `html',
    527 `latex', or `org'."
    528   (let ((backend (plist-get info :back-end)))
    529     (cond
    530      ((org-export-derived-backend-p backend 'html) 'html)
    531      ((org-export-derived-backend-p backend 'latex) 'org-latex)
    532      (t 'org))))
    533 
    534 (defun org-cite-csl--style-file (info)
    535   "Return style file associated to current export process.
    536 
    537 INFO is the export state, as a property list.
    538 
    539 When file name is relative, look for it in buffer's default
    540 directory, failing that in `org-cite-csl-styles-dir' if non-nil.
    541 Raise an error if no style file can be found."
    542   (pcase (org-cite-bibliography-style info)
    543     ('nil org-cite-csl--fallback-style-file)
    544     ((and (pred file-name-absolute-p) file) file)
    545     ((and (pred file-exists-p) file) (expand-file-name file))
    546     ((and (guard org-cite-csl-styles-dir)
    547           (pred (lambda (f)
    548                   (file-exists-p
    549                    (expand-file-name f org-cite-csl-styles-dir))))
    550           file)
    551      (expand-file-name file org-cite-csl-styles-dir))
    552     (other
    553      (user-error "CSL style file not found: %S" other))))
    554 
    555 (defun org-cite-csl--locale-getter ()
    556   "Return a locale getter.
    557 The getter looks for locales in `org-cite-csl-locales-dir' directory.  If it
    558 cannot find them, it retrieves the default \"en_US\" from
    559 `org-cite-csl--fallback-locales-dir'."
    560   (lambda (loc)
    561     (or (and org-cite-csl-locales-dir
    562              (ignore-errors
    563                (funcall (citeproc-locale-getter-from-dir org-cite-csl-locales-dir)
    564                         loc)))
    565         (funcall (citeproc-locale-getter-from-dir
    566                   org-cite-csl--fallback-locales-dir)
    567                  loc))))
    568 
    569 (defun org-cite-csl--processor (info)
    570   "Return Citeproc processor reading items from current bibliography.
    571 
    572 INFO is the export state, as a property list.
    573 
    574 Newly created processor is stored as the value of the `:cite-citeproc-processor'
    575 property in INFO."
    576   (or (plist-get info :cite-citeproc-processor)
    577       (let* ((bibliography (plist-get info :bibliography))
    578              (locale (or (plist-get info :language) "en_US"))
    579              (processor
    580               (citeproc-create
    581                (org-cite-csl--style-file info)
    582                (citeproc-hash-itemgetter-from-any bibliography)
    583                (org-cite-csl--locale-getter)
    584                locale)))
    585         (plist-put info :cite-citeproc-processor processor)
    586         processor)))
    587 
    588 (defun org-cite-csl--parse-reference (reference info)
    589   "Return Citeproc's structure associated to citation REFERENCE.
    590 
    591 INFO is the export state, as a property list.
    592 
    593 The result is a association list.  Keys are: `id', `prefix',`suffix',
    594 `location', `locator' and `label'."
    595   (let (label location-start locator-start location locator prefix suffix)
    596     ;; Parse suffix.  Insert it in a temporary buffer to find
    597     ;; different parts: pre-label, label, locator, location (label +
    598     ;; locator), and suffix.
    599     (with-temp-buffer
    600       (save-excursion
    601         (insert (org-element-interpret-data
    602                  (org-element-property :suffix reference))))
    603       (cond
    604        ((re-search-forward org-cite-csl--label-regexp nil t)
    605         (setq location-start (match-beginning 0))
    606         (setq label (cdr (assoc (match-string 1) org-cite-csl--label-alist)))
    607         (goto-char (match-end 1))
    608         (skip-chars-forward "[:space:] ")
    609         (setq locator-start (point)))
    610        ((re-search-forward (rx digit) nil t)
    611         (setq location-start (match-beginning 0))
    612         (setq label "page")
    613         (setq locator-start location-start))
    614        (t
    615         (setq suffix (org-element-property :suffix reference))))
    616       ;; Find locator's end, and suffix, if any. To that effect, look
    617       ;; for the last comma or digit after label, whichever comes
    618       ;; last.
    619       (unless suffix
    620         (goto-char (point-max))
    621         (let ((re (rx (or "," (group digit)))))
    622           (when (re-search-backward re location-start t)
    623             (goto-char (or (match-end 1) (match-beginning 0)))
    624             (setq location (buffer-substring location-start (point)))
    625             (setq locator (org-trim (buffer-substring locator-start (point))))
    626             ;; Skip comma in suffix.
    627             (setq suffix
    628                   (org-cite-parse-objects
    629                    (buffer-substring (match-end 0) (point-max))
    630                    t)))))
    631       (setq prefix
    632             (org-cite-concat
    633              (org-element-property :prefix reference)
    634              (and location-start
    635                   (org-cite-parse-objects
    636                    (buffer-substring 1 location-start)
    637                    t)))))
    638     ;; Return value.
    639     (let ((export
    640            (lambda (data)
    641              (org-string-nw-p
    642               (org-trim
    643                ;; When Citeproc exports to Org syntax, avoid mix and
    644                ;; matching output formats by also generating Org
    645                ;; syntax for prefix and suffix.
    646                (if (eq 'org (org-cite-csl--output-format info))
    647                    (org-element-interpret-data data)
    648                  (org-export-data data info)))))))
    649       `((id . ,(org-element-property :key reference))
    650         (prefix . ,(funcall export prefix))
    651         (suffix . ,(funcall export suffix))
    652         (locator . ,locator)
    653         (label . ,label)
    654         (location . ,location)))))
    655 
    656 (defun org-cite-csl--create-structure (citation info)
    657   "Create Citeproc structure for CITATION object.
    658 INFO is the export state, as a property list."
    659   (let* ((cites (mapcar (lambda (r)
    660                           (org-cite-csl--parse-reference r info))
    661                         (org-cite-get-references citation)))
    662          (footnote (org-cite-inside-footnote-p citation)))
    663     ;; Global prefix is inserted in front of the prefix of the first
    664     ;; reference.
    665     (let ((global-prefix (org-element-property :prefix citation)))
    666       (when global-prefix
    667         (let* ((first (car cites))
    668                (prefix-item (assq 'prefix first)))
    669           (setcdr prefix-item
    670                   (concat (org-element-interpret-data global-prefix)
    671                           " "
    672                           (cdr prefix-item))))))
    673     ;; Global suffix is appended to the suffix of the last reference.
    674     (let ((global-suffix (org-element-property :suffix citation)))
    675       (when global-suffix
    676         (let* ((last (org-last cites))
    677                (suffix-item (assq 'suffix last)))
    678           (setcdr suffix-item
    679                   (concat (cdr suffix-item)
    680                           " "
    681                           (org-element-interpret-data global-suffix))))))
    682     ;; Check if CITATION needs wrapping, i.e., it should be wrapped in
    683     ;; a footnote, but isn't yet.
    684     (when (and (not footnote) (org-cite-csl--note-style-p info))
    685       (org-cite-adjust-note citation info)
    686       (setq footnote (org-cite-wrap-citation citation info)))
    687     ;; Remove white space before CITATION when it is in superscript.
    688     (when (org-cite-csl--style-cite-superscript-p info)
    689       (org-cite--set-previous-post-blank citation 0 info))
    690     ;; Return structure.
    691     (apply #'citeproc-citation-create
    692            `(:note-index
    693              ,(and footnote (org-export-get-footnote-number footnote info))
    694              :cites ,cites
    695              ,@(org-cite-csl--create-structure-params citation info)))))
    696 
    697 (defun org-cite-csl--rendered-citations (info)
    698   "Return the rendered citations as an association list.
    699 
    700 INFO is the export state, as a property list.
    701 
    702 Return an alist (CITATION . OUTPUT) where CITATION object has been rendered as
    703 OUTPUT using Citeproc."
    704   (or (plist-get info :cite-citeproc-rendered-citations)
    705       (let ((citations (org-cite-list-citations info))
    706 	    (processor (org-cite-csl--processor info))
    707 	    normal-citations nocite-ids)
    708 	(dolist (citation citations)
    709 	  (if (org-cite-csl--nocite-p citation info)
    710 	      (setq nocite-ids (append (org-cite-get-references citation t) nocite-ids))
    711 	    (push citation normal-citations)))
    712 	(let ((structures
    713 	       (mapcar (lambda (c) (org-cite-csl--create-structure c info))
    714 		       (nreverse normal-citations))))
    715 	  (citeproc-append-citations structures processor))
    716 	(when nocite-ids
    717 	  (citeproc-add-uncited nocite-ids processor))
    718         ;; All bibliographies have to be rendered in order to have
    719         ;; correct citation numbers even if there are several
    720         ;; sub-bibliograhies.
    721         (org-cite-csl--rendered-bibliographies info)
    722 	(let (result
    723 	      (rendered (citeproc-render-citations
    724 			 processor
    725 			 (org-cite-csl--output-format info)
    726 			 (org-cite-csl--no-citelinks-p info))))
    727 	  (dolist (citation citations)
    728 	    (push (cons citation
    729 			(if (org-cite-csl--nocite-p citation info) "" (pop rendered)))
    730 		  result))
    731 	  (setq result (nreverse result))
    732 	  (plist-put info :cite-citeproc-rendered-citations result)
    733 	  result))))
    734 
    735 (defun org-cite-csl--bibliography-filter (bib-props)
    736   "Return the sub-bibliography filter corresponding to bibliography properties.
    737 
    738 BIB-PROPS should be a plist representing the properties
    739 associated with a \"print_bibliography\" keyword, as returned by
    740 `org-cite-bibliography-properties'."
    741   (let (result
    742 	(remove-keyword-colon (lambda (x) (intern (substring (symbol-name x) 1)))))
    743     (map-do
    744      (lambda (key value)
    745        (pcase key
    746          ((or :keyword :notkeyword :nottype :notcsltype :filter)
    747           (dolist (v (split-string value ","))
    748 	    (push (cons  (funcall remove-keyword-colon key) v) result)))
    749          ((or :type :csltype)
    750           (if (string-match-p "," value)
    751               (user-error "The \"%s\" print_bibliography option does not support comma-separated values" key)
    752             (push (cons (funcall remove-keyword-colon key) value) result)))))
    753      bib-props)
    754     result))
    755 
    756 (defun org-cite-csl--rendered-bibliographies (info)
    757   "Return the rendered bibliographies.
    758 
    759 INFO is the export state, as a property list.
    760 
    761 Return an (OUTPUTS PARAMETERS) list where OUTPUTS is an alist
    762 of (BIB-PROPS . OUTPUT) pairs where each key is a property list
    763 of a \"print_bibliography\" keyword and the corresponding OUTPUT
    764 value is the bibliography as rendered by Citeproc."
    765   (or (plist-get info :cite-citeproc-rendered-bibliographies)
    766       (let (bib-plists bib-filters)
    767         ;; Collect bibliography property lists and the corresponding
    768         ;; Citeproc sub-bib filters.
    769 	(org-element-map (plist-get info :parse-tree) 'keyword
    770           (lambda (keyword)
    771             (when (equal "PRINT_BIBLIOGRAPHY" (org-element-property :key keyword))
    772               (let ((bib-plist (org-cite-bibliography-properties keyword)))
    773                 (push bib-plist bib-plists)
    774                 (push (org-cite-csl--bibliography-filter bib-plist) bib-filters)))))
    775         (setq bib-filters (nreverse bib-filters)
    776               bib-plists (nreverse bib-plists))
    777         ;; Render and return all bibliographies.
    778         (let ((processor (org-cite-csl--processor info)))
    779           (citeproc-add-subbib-filters bib-filters processor)
    780           (pcase-let* ((format (org-cite-csl--output-format info))
    781                        (`(,rendered-bibs . ,parameters)
    782                         (citeproc-render-bib
    783                          (org-cite-csl--processor info)
    784                          format
    785                          (org-cite-csl--no-citelinks-p info)))
    786                        (outputs (cl-mapcar #'cons bib-plists rendered-bibs))
    787                        (result (list outputs parameters)))
    788             (plist-put info :cite-citeproc-rendered-bibliographies result)
    789             result)))))
    790 
    791 (defun org-cite-csl--generate-latex-preamble (info)
    792   "Generate the CSL-related part of the LaTeX preamble.
    793 INFO is the export state, as a property list."
    794   (let* ((parameters (cadr (org-cite-csl--rendered-bibliographies info)))
    795          (max-offset (cdr (assq 'max-offset parameters)))
    796          (result org-cite-csl-latex-preamble))
    797     (map-do (lambda (placeholder replacement)
    798               (when (string-match placeholder result)
    799                 (setq result (replace-match replacement t t result))))
    800             `("\\[CSL-HANGINDENT\\]" ,org-cite-csl-latex-hanging-indent
    801               "\\[CSL-LABELSEP\\]" ,org-cite-csl-latex-label-separator
    802               "\\[CSL-LABELWIDTH-PER-CHAR\\]" ,org-cite-csl-latex-label-width-per-char
    803               "\\[CSL-MAXLABEL-CHARS\\]" ,(number-to-string max-offset)))
    804     result))
    805 
    806 
    807 ;;; Export capability
    808 (defun org-cite-csl-render-citation (citation _style _backend info)
    809   "Export CITATION object.
    810 INFO is the export state, as a property list."
    811   (org-require-package 'citeproc)
    812   (let ((output (cdr (assq citation (org-cite-csl--rendered-citations info)))))
    813     (if (not (eq 'org (org-cite-csl--output-format info)))
    814         output
    815       ;; Parse Org output to re-export it during the regular export
    816       ;; process.
    817       (org-cite-parse-objects output))))
    818 
    819 (defun org-cite-csl-render-bibliography (_keys _files _style props _backend info)
    820   "Export bibliography.
    821 INFO is the export state, as a property list."
    822   (org-require-package 'citeproc)
    823   (pcase-let*  ((format (org-cite-csl--output-format info))
    824                 (`(,outputs ,parameters) (org-cite-csl--rendered-bibliographies info))
    825                 (output (cdr (assoc props outputs))))
    826     (pcase format
    827       ('html
    828        (concat
    829         (and (cdr (assq 'second-field-align parameters))
    830              (let* ((max-offset (cdr (assq 'max-offset parameters)))
    831                     (char-width
    832                      (string-to-number org-cite-csl-html-label-width-per-char))
    833                     (char-width-unit
    834                      (progn
    835                        (string-match (number-to-string char-width)
    836                                      org-cite-csl-html-label-width-per-char)
    837                        (substring org-cite-csl-html-label-width-per-char
    838                                   (match-end 0)))))
    839                (format
    840                 "<style>.csl-left-margin{float: left; padding-right: 0em;}
    841  .csl-right-inline{margin: 0 0 0 %d%s;}</style>"
    842                 (* max-offset char-width)
    843                 char-width-unit)))
    844         (and (cdr (assq 'hanging-indent parameters))
    845              (format
    846               "<style>.csl-entry{text-indent: -%s; margin-left: %s;}</style>"
    847               org-cite-csl-html-hanging-indent
    848               org-cite-csl-html-hanging-indent))
    849         output))
    850       ('org-latex output)
    851       (_
    852        ;; Parse Org output to re-export it during the regular export
    853        ;; process.
    854        (org-cite-parse-elements output)))))
    855 
    856 (defun org-cite-csl-finalizer (output _keys _files _style _backend info)
    857   "Add \"hanging\" package if missing from LaTeX output.
    858 OUTPUT is the export document, as a string.  INFO is the export state, as a
    859 property list."
    860   (org-require-package 'citeproc)
    861   (if (not (eq 'org-latex (org-cite-csl--output-format info)))
    862       output
    863     (with-temp-buffer
    864       (save-excursion (insert output))
    865       (when (search-forward "\\begin{document}" nil t)
    866 	(goto-char (match-beginning 0))
    867 	;; Insert the CSL-specific parts of the LaTeX preamble.
    868 	(insert (org-cite-csl--generate-latex-preamble info)))
    869       (buffer-string))))
    870 
    871 
    872 ;;; Register `csl' processor
    873 (org-cite-register-processor 'csl
    874   :export-citation #'org-cite-csl-render-citation
    875   :export-bibliography #'org-cite-csl-render-bibliography
    876   :export-finalizer #'org-cite-csl-finalizer
    877   :cite-styles
    878   '((("author" "a") ("bare" "b") ("caps" "c") ("full" "f") ("bare-caps" "bc") ("caps-full" "cf") ("bare-caps-full" "bcf"))
    879     (("noauthor" "na") ("bare" "b") ("caps" "c") ("bare-caps" "bc"))
    880     (("year" "y") ("bare" "b"))
    881     (("text" "t") ("caps" "c") ("full" "f") ("caps-full" "cf"))
    882     (("nil") ("bare" "b") ("caps" "c") ("bare-caps" "bc"))
    883     (("nocite" "n"))
    884     (("title" "ti") ("bare" "b"))
    885     (("bibentry" "b") ("bare" "b"))
    886     (("locators" "l") ("bare" "b"))))
    887 
    888 (provide 'oc-csl)
    889 ;;; oc-csl.el ends here