Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

919 lines
28 KiB

5 years ago
  1. ;;; readme.org --- org-ref-citeproc - Citation processor for org-mode
  2. ;;; Commentary: This code is style agnostic. It will get information from the
  3. ;;; information in `citation-style' and `bibliography-style'. These are defined
  4. ;;; in a csl.el file.
  5. ;;
  6. ;; Conventions:
  7. ;; an "entry" is the result of `bibtex-parse-entry'.
  8. ;;
  9. ;;; Commentary:
  10. ;;
  11. (declare-function org-ref-get-bibtex-key-and-file "org-ref-core")
  12. (declare-function org-ref-get-bibtex-keys "org-ref-core")
  13. (declare-function parsebib-find-bibtex-dialect "parsebib")
  14. (defvar org-export-current-backend)
  15. (defvar org-ref-cite-types)
  16. (require 'org-element)
  17. ;;; Code:
  18. (defvar *orcp-citation-links* '()
  19. "List of citation links in the text.
  20. A link may have more than one citation in it. These links get
  21. replaced by the new citation text.")
  22. (defvar *orcp-unique-entries* '()
  23. "List of unique (key . entry) parsed bibtex entries in the document.
  24. The list is sorted according to the style. This list eventually
  25. makes up the bibliography.")
  26. (defvar citation-style '()
  27. "Style data for an in-text citation.
  28. For unsrt, a regular cite is superscripted, sorted,
  29. range-collapsed numbers.
  30. LABEL is a function that is run to get the label.
  31. PREFIX goes before a citation.
  32. SUFFIX goes after a citation.
  33. citations are separated by DELIMITER.
  34. SORT specifies that 3, 1, 2 be converted to 1,2,3
  35. COLLAPSE is a function to collapse multiple citations, e.g. 1,2,3 becomes 1-3.
  36. VERTICAL-ALIGN is a function that places the citation, e.g.
  37. superscript, nil for baseline, etc...
  38. Additional entries provide overrides for special citation types.")
  39. (defvar bibliography-style '()
  40. "Bibliography style data.
  41. SORT is a function that sorts the entries, e.g. by author, or
  42. year, or nil. It should take one argument, the list of unique
  43. entries (key . entry).
  44. LABEL is a function that returns how the entry is numbered, or
  45. referenced in the text.
  46. HANGING-INDENT is for the indentation of the entry on the left.
  47. JUSTIFICATION is the overall justification on the right.
  48. SPACING is the number of lines between entries.
  49. HEADER is a string that is inserted above the bibliography.
  50. ENTRIES is a alist of entry type and fields to make the entry from.")
  51. ;;* Collect citations
  52. (defun orcp-collect-citations ()
  53. "Return a list of citation links in the document."
  54. (setq *orcp-citation-links*
  55. (cl-loop for link in (org-element-map
  56. (org-element-parse-buffer) 'link 'identity)
  57. if (-contains?
  58. org-ref-cite-types
  59. (org-element-property :type link))
  60. collect link)))
  61. (defun orcp-key-to-entry (key)
  62. "Return a parsed bibtex entry for KEY.
  63. The KEY is found for the bibliography in the file."
  64. (let* ((results (org-ref-get-bibtex-key-and-file key))
  65. (bibfile (cdr results)))
  66. (save-excursion
  67. (with-temp-buffer
  68. (insert-file-contents bibfile)
  69. (bibtex-set-dialect (parsebib-find-bibtex-dialect) t)
  70. (bibtex-search-entry key)
  71. (let ((entry (bibtex-parse-entry t)))
  72. (dolist (cons-cell entry)
  73. (setf (car cons-cell) (downcase (car cons-cell))))
  74. (setf (cdr (assoc "=type=" entry))
  75. (downcase (cdr (assoc "=type=" entry))))
  76. entry)))))
  77. (defun orcp-collect-unique-entries ()
  78. "Return a list of unique entries, sorted as required by the style.
  79. Each entry is (key . entry)."
  80. (let ((keys (org-ref-get-bibtex-keys))
  81. sort-func
  82. entries)
  83. (setq entries
  84. (cl-loop for key in keys
  85. collect (cons key (orcp-key-to-entry key))))
  86. ;; Now we should sort them if the style requires it
  87. (setq sort-func (cdr (assoc 'sort bibliography-style)))
  88. (when sort-func
  89. (setq entries (funcall sort-func entries)))
  90. (setq *orcp-unique-entries* entries)))
  91. ;;** Unique entry sorting functions
  92. (defun orcp-sort-entries-increasing-year (unique-entries)
  93. "Sort UNIQUE-ENTRIES in increasing year of publication.
  94. i.e. oldest stuff first."
  95. (sort unique-entries
  96. (lambda (a b)
  97. (let* ((e1 (cdr a))
  98. (e2 (cdr b))
  99. (year1 (string-to-number (cdr (assoc "year" e1))))
  100. (year2 (string-to-number (cdr (assoc "year" e2)))))
  101. (> year2 year1)))))
  102. (defun orcp-sort-entries-decreasing-year (unique-entries)
  103. "Sort UNIQUE-ENTRIES in decreasing year.
  104. i.e. most current first."
  105. (reverse (orcp-sort-entries-increasing-year unique-entries)))
  106. (defun orcp-get-entry-field (field entry)
  107. "RETURN FIELD from ENTRY.
  108. Strip extra spaces and carriage returns."
  109. (let ((result (cdr (assoc field entry))))
  110. (when result
  111. (while (string-match "[\n\t\r]\\|[ \t][ \t]+" result)
  112. (setq result (replace-match " " nil t result))))
  113. result))
  114. (defun orcp-sort-entries-alphabetical (unique-entries)
  115. "Sort UNIQUE-ENTRIES alphabetically by first author last name."
  116. (sort unique-entries
  117. (lambda (a b)
  118. (let* ((e1 (cdr a))
  119. (e2 (cdr b))
  120. (authors1 (s-split
  121. " and "
  122. (orcp-get-entry-field "author" e1)))
  123. (author1 (orcp-parse-authorname (car authors1)))
  124. ;; lastname is "von last"
  125. (last1 (concat (nth 1 author1) " " (nth 2 author1)))
  126. (authors2 (s-split
  127. " and "
  128. (orcp-get-entry-field "author" e2)))
  129. (author2 (orcp-parse-authorname (car authors2)))
  130. (last2 (concat (nth 1 author2) " " (nth 2 author2))))
  131. (string-lessp last1 last2)))))
  132. ;;* Citation labels for one citation key
  133. ;; No styling is done here.
  134. (defun orcp-citation-number-label (key unique-entries)
  135. "Find the numeric index of KEY in UNIQUE-ENTRIES and return as a string.
  136. Indexing starts at 0 so we add one."
  137. (number-to-string
  138. (+ 1
  139. (-find-index
  140. (lambda (entry)
  141. (string= key (car entry)))
  142. unique-entries))))
  143. (defun orcp-footnote-label (key unique-entries)
  144. "Return an org footnote label for KEY in UNIQUE-ENTRIES."
  145. (format "[fn:%s]" (orcp-citation-number-label key unique-entries)))
  146. (defun orcp-citation-author-label (key unique-entries)
  147. "Return an author last name label for KEY.
  148. KEY is found in UNIQUE-ENTRIES."
  149. (let* ((i (-find-index
  150. (lambda (entry)
  151. (string= key (car entry)))
  152. unique-entries))
  153. (entry (cdr (nth i unique-entries)))
  154. (authors (s-split
  155. " and "
  156. (orcp-get-entry-field "author" entry)))
  157. (first-author (orcp-parse-authorname (car authors))))
  158. (format "%s" (concat (nth 1 first-author)
  159. (nth 2 first-author)))))
  160. (defun orcp-citation-year-label (key unique-entries)
  161. "Return a year label for KEY.
  162. KEY is found in UNIQUE-ENTRIES."
  163. (let* ((i (-find-index
  164. (lambda (entry)
  165. (string= key (car entry)))
  166. unique-entries))
  167. (entry (cdr (nth i unique-entries)))
  168. (year (orcp-get-entry-field "year" entry)))
  169. (format "%s" year)))
  170. (defun orcp-citation-author-year-label (key unique-entries)
  171. "Return an author last name year label for KEY.
  172. KEY is found in UNIQUE-ENTRIES.
  173. We do not have a disambiguation strategy yet."
  174. (let* ((i (-find-index
  175. (lambda (entry)
  176. (string= key (car entry)))
  177. unique-entries))
  178. (entry (cdr (nth i unique-entries)))
  179. (authors (s-split
  180. " and "
  181. (orcp-get-entry-field "author" entry)))
  182. (first-author (orcp-parse-authorname (car authors)))
  183. (year (orcp-get-entry-field "year" entry)))
  184. (format "%s %s" (concat (nth 1 first-author)
  185. (nth 2 first-author))
  186. year)))
  187. ;;* Replacements for citation links
  188. ;; Here we have to map over the keys in a citation, sort them according to the
  189. ;; style, get replacement labels, concat them together with the style delimiter,
  190. ;; add the prefix and suffix, and finally format for the type and output
  191. ;; backend.
  192. (defun orcp-get-citation-style (symbol type)
  193. "Get the style info for SYMBOL for a citation TYPE from `citation-style'.
  194. Styles have a default, but allow TYPE overrides. This function
  195. returns the style with the override."
  196. (let (style)
  197. ;; first get default style
  198. (setq style (cdr (assoc symbol citation-style)))
  199. ;; now check for an override
  200. ;; we need to find the type, and the symbol in the type
  201. (when (and (assoc type citation-style)
  202. (assoc symbol (assoc type citation-style)))
  203. (setq style (cdr (assoc symbol (assoc type citation-style)))))
  204. style))
  205. (defun orcp-get-text-replacement (citation-link)
  206. "Return replacement string for CITATION-LINK."
  207. (let* ((type (intern (org-element-property :type citation-link)))
  208. (path (org-element-property :path citation-link))
  209. (keys (s-split "," path))
  210. (entries (mapcar 'orcp-key-to-entry keys))
  211. (label-func (orcp-get-citation-style 'label type))
  212. (delimiter (orcp-get-citation-style 'delimiter type))
  213. (sort-func (orcp-get-citation-style 'sort type))
  214. labels
  215. replacement-text)
  216. ;; sort is not coded yet. I am not sure the best data to sort here. the keys?
  217. (when sort-func
  218. (setq keys (sort keys sort-func)))
  219. ;; get labels. This function is where you would, for example, create
  220. ;; hyperlinks to the bibliography. This function should return a list of
  221. ;; strings
  222. (setq labels
  223. (mapcar
  224. (lambda (key)
  225. (funcall label-func key *orcp-unique-entries*))
  226. keys))
  227. ;; collapse range - not used yet.
  228. ;; now get a string collecting everything
  229. (setq labels (mapconcat 'identity labels delimiter))
  230. (setq replacement-text (concat
  231. (orcp-get-citation-style 'prefix type)
  232. labels
  233. (orcp-get-citation-style 'suffix type)))
  234. ;; finally, call formatter
  235. (funcall (or (orcp-get-citation-style 'vertical-align type)
  236. 'baseline)
  237. replacement-text)))
  238. (defun orcp-get-citation-replacements ()
  239. "Get a list of replacements for all links in `*orcp-citation-links*'."
  240. (mapcar 'orcp-get-text-replacement *orcp-citation-links*))
  241. ;;* Formatted bibliography
  242. (defun orcp-formatted-bibliography ()
  243. "Return the formatted bibliography."
  244. (let* ((spacing (or (cdr (assoc 'spacing bibliography-style)) 1))
  245. (label-func (cdr (assoc 'label bibliography-style)))
  246. (label-prefix (cdr (assoc 'label-prefix bibliography-style)))
  247. (label-suffix (cdr (assoc 'label-suffix bibliography-style)))
  248. (justification (cdr (assoc 'justification bibliography-style)))
  249. (hanging-indent (cdr (assoc 'hanging-indent bibliography-style)))
  250. (header (cdr (assoc 'header bibliography-style)))
  251. (unique-entries (orcp-collect-unique-entries))
  252. (adaptive-fill-function '(lambda () " "))
  253. (indent-tabs-mode nil)
  254. bibliography-string)
  255. (setq bibliography-string
  256. (mapconcat
  257. 'identity
  258. ;; loop over the entries in the bibliography
  259. (cl-loop for entry in unique-entries
  260. collect
  261. (progn
  262. (let* ((entry-type (downcase
  263. (cdr (assoc "=type=" (cdr entry)))))
  264. (key (cdr (assoc "=key=" (cdr entry))))
  265. (entry-styles (cdr (assoc 'entries bibliography-style)))
  266. (entry-fields
  267. (progn
  268. (if (cdr (assoc (intern entry-type) entry-styles))
  269. (cdr (assoc (intern entry-type) entry-styles))
  270. (warn "%s not found. Using default." entry-type)
  271. (cdr (assoc 't entry-styles))
  272. )))
  273. (funcs (mapcar
  274. (lambda (field)
  275. (if (fboundp (intern
  276. (format "orcp-%s" field)))
  277. (intern
  278. (format "orcp-%s" field))
  279. ;; No formatter found. just get the data
  280. `(lambda (entry)
  281. (orcp-get-entry-field
  282. ,(symbol-name field) entry))))
  283. entry-fields))
  284. (label (concat label-prefix
  285. (funcall label-func key unique-entries)
  286. label-suffix)))
  287. ;; this is the entry. We do this in a buffer to make it
  288. ;; easy to indent, fill, etc...
  289. (with-temp-buffer
  290. (insert label)
  291. (insert (mapconcat (lambda (field-func)
  292. (funcall field-func entry))
  293. funcs
  294. ""))
  295. (goto-char (point-min))
  296. (forward-word)
  297. ;; It doesn't make sense to do this for all formats, e.g.HTML.
  298. ;; commenting out for now.
  299. ;; (increase-left-margin
  300. ;; (point-min) (point-max) hanging-indent)
  301. ;; (fill-region (point-min) (point-max) justification)
  302. (buffer-string)))))
  303. ;; Here we put in the separator between entries
  304. (cond
  305. ;; placeholder for other formats
  306. ((eq org-export-current-backend 'html)
  307. " @@html:<br>@@\n")
  308. (t
  309. ;; put in a \n for each spacing
  310. (mapconcat 'identity
  311. (cl-loop for i to spacing
  312. collect "\n")
  313. "")))))
  314. ;; TODO: figure out header. how do we insert it properly formatted?
  315. bibliography-string))
  316. ;;* Text formatting functions.
  317. ;; These take text, and format them according to a backend. We derive the
  318. ;; backend from `org-export-current-backend' because I anticipate using this
  319. ;; during export.
  320. (defun baseline (text)
  321. "Return TEXT."
  322. text)
  323. (defun superscript (text)
  324. "Format TEXT as superscripted."
  325. (cond
  326. ((eq org-export-current-backend 'html)
  327. (format "@@html:<sup>%s</sup>@@" text))
  328. ;; the catch-all case is org-syntax
  329. (t
  330. (format "^{%s}" text))))
  331. (defun italics (text)
  332. "Format TEXT as italics."
  333. (cond
  334. ((eq org-export-current-backend 'html)
  335. (format "@@html:<i>%s</i>@@" text))
  336. ;; the catch-all case is org-syntax
  337. (t
  338. (format "/%s/" text))))
  339. (defun bold (text)
  340. "Format TEXT in bold."
  341. (cond
  342. ((eq org-export-current-backend 'html)
  343. (format "@@html:<b>%s</b>@@" text))
  344. ;; the catch-all case is org-syntax
  345. (t
  346. (format "*%s*" text))))
  347. ;;* Field formatting functions
  348. ;;These should be style agnostic functions. They take an entry and return a
  349. ;; formatted field for the entry, using information from the csl file.
  350. (defun firstname (author-cell)
  351. "Return firstname from AUTHOR-CELL."
  352. (car author-cell))
  353. (defun lastname (author-cell)
  354. "Return lastname from AUTHOR-CELL."
  355. (cdr author-cell))
  356. (defun orcp-author (entry)
  357. "Return formatted author string from the ENTRY.
  358. ENTRY is from `bibtex-parse-entry'.
  359. Style information comes from `bibliography'"
  360. (let* ((style (cdr (assoc 'author bibliography-style)))
  361. (delimiter (cdr (assoc 'delimiter style)))
  362. (name1 (nth 0 (cdr (assoc 'name-order style))))
  363. (name2 (nth 1 (cdr (assoc 'name-order style))))
  364. (name-separator (cdr (assoc 'name-separator style)))
  365. (suffix (cdr (assoc 'suffix style)))
  366. (field-separator (cdr (assoc 'field-separator style)))
  367. (et-al (cdr (assoc 'et-al style)))
  368. (authors (s-split
  369. " and "
  370. (or
  371. (orcp-get-entry-field "author" entry)
  372. "")))
  373. ;; parse to list of (first von last jr)
  374. (author-data (mapcar
  375. (lambda (x)
  376. (let ((aud (orcp-parse-authorname x)))
  377. (cons (nth 0 aud)
  378. (concat
  379. (or (nth 1 aud) "")
  380. (or (nth 2 aud) "")
  381. (or (nth 3 aud) "")))))
  382. authors))
  383. ;; map first and last names, in order specified in style with separator
  384. (author-names
  385. (mapcar
  386. (lambda (x)
  387. (concat
  388. (funcall name1 x)
  389. name-separator
  390. (funcall name2 x)))
  391. author-data)))
  392. ;; check on et-al - not implemented yet
  393. ;; work on initialize - not implemented yet
  394. ;; mapconcat on delimiter then last author.
  395. (if (= 1 (length author-names))
  396. (concat (car author-names) suffix field-separator)
  397. (concat
  398. (mapconcat
  399. 'identity
  400. (butlast author-names)
  401. delimiter)
  402. (cdr (assoc 'last-author-delimiter style))
  403. (car (last author-names))
  404. suffix
  405. field-separator))))
  406. (defun orcp-title (entry)
  407. "Return formatted title for the bibtex ENTRY."
  408. (let* ((style (cdr (assoc 'title bibliography-style)))
  409. (font-style (cdr (assoc 'font-style style)))
  410. (suffix (cdr (assoc 'suffix style)))
  411. (field-separator (cdr (assoc 'field-separator style)))
  412. (title (orcp-get-entry-field "title" entry)))
  413. (concat
  414. (if font-style
  415. (funcall font-style title)
  416. title)
  417. suffix
  418. field-separator)))
  419. (defun orcp-journal (entry)
  420. "Return formatted journal for the bibtex ENTRY."
  421. (let* ((style (cdr (assoc 'journal bibliography-style)))
  422. (font-style (cdr (assoc 'font-style style)))
  423. (suffix (cdr (assoc 'suffix style)))
  424. (field-separator (cdr (assoc 'field-separator style)))
  425. (journal (orcp-get-entry-field "journal" entry)))
  426. (concat
  427. (if font-style
  428. (funcall font-style journal)
  429. journal)
  430. suffix
  431. field-separator)))
  432. (defun orcp-volume (entry)
  433. "Return formatted volume for the bibtex ENTRY."
  434. (let* ((style (cdr (assoc 'volume bibliography-style)))
  435. (font-style (cdr (assoc 'font-style style)))
  436. (prefix (cdr (assoc 'prefix style)))
  437. (suffix (eval (cdr (assoc 'suffix style))))
  438. (field-separator (cdr (assoc 'field-separator style)))
  439. (volume (orcp-get-entry-field "volume" entry)))
  440. (setq volume (concat prefix volume suffix))
  441. (concat
  442. (if font-style
  443. (funcall font-style volume)
  444. volume)
  445. field-separator)))
  446. (defun orcp-issue (entry)
  447. "Return formatted issue for the bibtex ENTRY."
  448. (let* ((style (cdr (assoc 'issue bibliography-style)))
  449. (font-style (cdr (assoc 'font-style style)))
  450. (prefix (cdr (assoc 'prefix style)))
  451. (suffix (cdr (assoc 'suffix style)))
  452. (field-separator (cdr (assoc 'field-separator style)))
  453. (issue (orcp-get-entry-field "number" entry)))
  454. ;; issue is optional and isn't always present.
  455. (if (not issue)
  456. field-separator
  457. (setq issue (concat prefix issue suffix))
  458. (if font-style
  459. (funcall font-style
  460. issue)
  461. issue))))
  462. (defun orcp-pages (entry)
  463. "Return formatted pages for the bibtex ENTRY."
  464. (let* ((style (cdr (assoc 'pages bibliography-style)))
  465. (font-style (cdr (assoc 'font-style style)))
  466. (prefix (cdr (assoc 'prefix style)))
  467. (suffix (cdr (assoc 'suffix style)))
  468. (field-separator (cdr (assoc 'field-separator style)))
  469. (pages (orcp-get-entry-field "pages" entry)))
  470. (setq pages (concat prefix pages suffix))
  471. ;; collapse-range not supported yet
  472. (concat (if font-style
  473. (funcall font-style
  474. pages)
  475. pages)
  476. field-separator)))
  477. (defun orcp-year (entry)
  478. "Return formatted year for the bibtex ENTRY."
  479. (let* ((style (cdr (assoc 'year bibliography-style)))
  480. (font-style (cdr (assoc 'font-style style)))
  481. (prefix (cdr (assoc 'prefix style)))
  482. (suffix (cdr (assoc 'suffix style)))
  483. (field-separator (cdr (assoc 'field-separator style)))
  484. (year (orcp-get-entry-field "year" entry)))
  485. (setq year (concat prefix year suffix))
  486. ;; collapse-range not supported yet
  487. (concat
  488. (if font-style
  489. (funcall font-style
  490. year)
  491. year)
  492. field-separator)))
  493. (defun orcp-doi-formatter (doi)
  494. "Return formatted DOI for different backends."
  495. (cond
  496. ((eq org-export-current-backend 'html)
  497. (format "http://dx.doi.org/%s" doi))
  498. (t
  499. (format "doi:%s" doi))))
  500. (defun orcp-doi (entry)
  501. "Return formatted doi for the bibtex ENTRY."
  502. (let* ((style (cdr (assoc 'doi bibliography-style)))
  503. (font-style (cdr (assoc 'font-style style)))
  504. (prefix (cdr (assoc 'prefix style)))
  505. (suffix (cdr (assoc 'suffix style)))
  506. (formatter (cdr (assoc 'formatter style)))
  507. (doi (orcp-get-entry-field "doi" entry)))
  508. (when formatter
  509. (setq doi (funcall formatter doi)))
  510. (setq doi (concat prefix doi suffix))
  511. (if font-style
  512. (funcall font-style
  513. (concat prefix doi suffix))
  514. doi)))
  515. (defun orcp-url (entry)
  516. "Return formatted url for the bibtex ENTRY."
  517. (let* ((style (cdr (assoc 'doi bibliography-style)))
  518. (font-style (cdr (assoc 'font-style style)))
  519. (prefix (cdr (assoc 'prefix style)))
  520. (suffix (cdr (assoc 'suffix style)))
  521. (formatter (cdr (assoc 'formatter style)))
  522. (url (orcp-get-entry-field "url" entry)))
  523. (when formatter
  524. (setq url (funcall formatter url)))
  525. (setq url (concat prefix url suffix))
  526. (if font-style
  527. (funcall font-style
  528. (concat prefix url suffix))
  529. url)))
  530. ;;* Data structures for Author names
  531. (defun orcp-unprotect-brackets (piece protected-strings)
  532. "Unprotect PIECE with the information in PROTECTED-STRINGS.
  533. PROTECTED-STRINGS is a list of cons-cells (\"protection\" .
  534. original text)."
  535. (when piece
  536. (mapc
  537. (lambda (cons-cell)
  538. (when (string-match (car cons-cell) piece)
  539. (setq piece (replace-match (cdr cons-cell) t t piece))))
  540. protected-strings))
  541. piece)
  542. ;; See http://maverick.inria.fr/~Xavier.Decoret/resources/xdkbibtex/bibtex_summary.html#names for the parsing rules.
  543. (defun orcp-parse-authorname (name)
  544. "Convert an author NAME to (first von last jr) data structure.
  545. Valid name forms are:
  546. First1 First2 Last
  547. First1 First 2 {Last1 Last2}
  548. First1 First2 von1 von2 Last1 Last2
  549. von1 von2 Last1 Last2, Jr., First1 First2
  550. Last1, First1 First2
  551. {Von Last1}, First1 First2
  552. We try to protect strings in curly brackets."
  553. (let* (protected-strings
  554. uuid
  555. ncommas
  556. fields
  557. first von last jr)
  558. ;; protect bracketed strings
  559. (while (string-match "{\\(.*\\)}" name)
  560. ;; We want our substitute to look like a name, not a von part so we add a
  561. ;; capital letter to the front.
  562. (setq uuid (concat "A" (md5 (format "%s%s%s%s%s%s%s"
  563. (random)
  564. (current-time)
  565. (user-uid)
  566. (emacs-pid)
  567. (user-full-name)
  568. user-mail-address
  569. (recent-keys)))))
  570. (add-to-list 'protected-strings (cons uuid (match-string 0 name)))
  571. (setq name (replace-match uuid nil nil name)))
  572. (setq ncommas (s-count-matches "," name))
  573. (cond
  574. ;; "First von Last"
  575. ((= 0 ncommas)
  576. (setq fields (s-split " " name))
  577. (while (and (s-capitalized? (car fields)) (> (length fields) 1))
  578. (setq first (append first (list (pop fields)))))
  579. (when first
  580. (setq first (mapconcat 'identity first " ")))
  581. ;; Next, we get the von part. this is the longest white space delimited
  582. ;; string that ends with a lowercase word, and is not the rest of the
  583. ;; string.
  584. (let ((last-lower-index nil))
  585. (cl-loop for i to (length fields)
  586. for word in (butlast fields)
  587. if (s-lowercase? word)
  588. do (setq last-lower-index i))
  589. (when last-lower-index
  590. (setq von (mapconcat
  591. 'identity
  592. (-slice fields 0 (+ 1 last-lower-index)) " "))
  593. (setq fields (-slice fields (+ 1 last-lower-index)))))
  594. ;; all that should be left is the last name but it might be more than one
  595. ;; word, e.g. with a Jr. or a two work last name.
  596. (setq last (mapconcat 'identity fields " "))
  597. (mapcar
  598. (lambda (x)
  599. (orcp-unprotect-brackets x protected-strings))
  600. (list first von last jr)))
  601. ;; "von Last, First"
  602. ((= 1 ncommas)
  603. (setq fields (s-split "," name))
  604. (setq first (nth 1 fields))
  605. ;; split first field which could be von Lastname.
  606. (setq fields (s-split " " (car fields)))
  607. (let ((last-lower-index nil))
  608. (cl-loop for i to (length fields)
  609. for word in fields
  610. if (s-lowercase? word)
  611. do (setq last-lower-index i))
  612. (when last-lower-index
  613. (setq von (mapconcat
  614. 'identity
  615. (-slice fields 0 (+ 1 last-lower-index)) " "))
  616. (setq fields (-slice fields (+ 1 last-lower-index)))))
  617. ;; all that should be left is the last name
  618. (setq last (mapconcat 'identity fields " "))
  619. (mapcar
  620. (lambda (x)
  621. (orcp-unprotect-brackets x protected-strings))
  622. (list first von last jr)))
  623. ;; "von Last, Jr, First"
  624. ((= 2 ncommas)
  625. (setq fields (s-split "," name))
  626. (setq first (nth 2 fields))
  627. (setq jr (nth 1 fields))
  628. ;; split first field which could be von Lastname.
  629. (setq fields (s-split " " (car fields)))
  630. (let ((last-lower-index nil))
  631. (cl-loop for i to (length fields)
  632. for word in fields
  633. if (s-lowercase? word)
  634. do (setq last-lower-index i))
  635. (when last-lower-index
  636. (setq von (mapconcat 'identity (-slice fields 0 (+ 1 last-lower-index)) " "))
  637. (setq fields (-slice fields (+ 1 last-lower-index)))))
  638. ;; all that should be left is the last name
  639. (setq last (mapconcat 'identity fields " "))
  640. (mapcar
  641. (lambda (x)
  642. (orcp-unprotect-brackets x protected-strings))
  643. (list first von last jr))))))
  644. ;;* Collapse numeric range
  645. (defun orcp-collapse-numeric-range (cites delimiter)
  646. "TODO use style info.
  647. Collapse a numeric list of CITES into a range.
  648. Collapsed ranges are separated by DELIMITER."
  649. (let (n
  650. (groups '()))
  651. (while cites
  652. (setq n (pop cites))
  653. (if (and (caar groups) (= (- n 1) (elt (car groups) 0)))
  654. (setf (car groups) (append `(,n) (car groups)))
  655. (setf groups (append `((,n)) groups))))
  656. ;; Now for each group
  657. (mapconcat 'identity
  658. (mapcar
  659. (lambda (lst)
  660. (cond
  661. ((>= (length lst) 3)
  662. (format "%s-%s" (car lst) (car (last lst))))
  663. ((= (length lst) 2)
  664. (format "%s,%s" (nth 0 lst) (nth 1 lst)))
  665. (t
  666. (number-to-string (car lst)))))
  667. (mapcar 'reverse (reverse groups)))
  668. delimiter)))
  669. ;;* Putting it all together
  670. (defun sentence-beginning-p ()
  671. "Determine if point is at the beginning of a sentence.
  672. The idea is to move forward a sentence, then back. If the point
  673. doesn't move, it means you were at the beginning of a sentence."
  674. (let ((cp (point)))
  675. (save-excursion
  676. (forward-sentence)
  677. (backward-sentence)
  678. (= cp (point)))))
  679. (defun orcp-citeproc (&optional backend)
  680. "Format citations and bibliography for BACKEND.
  681. Warning. Destructive to your document! Will replace links.
  682. Meant to be used in export on a temporary version of the
  683. documents."
  684. ;; Get the style from bibliographystyle link
  685. ;; and eliminate bibliography style links
  686. ;; This will load all style modules
  687. (cl-loop for link in (org-element-map
  688. (org-element-parse-buffer) 'link 'identity)
  689. if (string= "bibliographystyle"
  690. (org-element-property :type link))
  691. do
  692. ;; get path for style and load it
  693. (load-library (org-element-property :path link))
  694. ;; get rid of the link in the buffer
  695. (setf (buffer-substring (org-element-property :begin link)
  696. (org-element-property :end link))
  697. ""))
  698. (orcp-collect-citations)
  699. (orcp-collect-unique-entries)
  700. (let ((link-replacements (cl-loop for link in *orcp-citation-links*
  701. for repl in (orcp-get-citation-replacements)
  702. collect
  703. (list repl
  704. (org-element-property :begin link)
  705. (org-element-property :end link))))
  706. (bibliography-string (orcp-formatted-bibliography))
  707. punctuation
  708. trailing-space
  709. bibliography-link)
  710. ;; replace citation links
  711. (cl-loop for (repl start end) in (reverse link-replacements)
  712. for link in (reverse *orcp-citation-links*)
  713. do
  714. ;; chomp leading spaces if needed
  715. (when (orcp-get-citation-style
  716. 'chomp-leading-space
  717. (intern (org-element-property :type link)))
  718. (goto-char start)
  719. (while (and (not (sentence-beginning-p))
  720. (looking-back " " (- (point) 2)))
  721. (delete-char -1)
  722. (setq start (- start 1))
  723. (setq end (- end 1))))
  724. ;; chomp trailing spaces if needed
  725. (when (orcp-get-citation-style
  726. 'chomp-trailing-space
  727. (intern (org-element-property :type link)))
  728. (goto-char end)
  729. (while (looking-back " " (- (point) 2))
  730. (delete-char 1)))
  731. ;; Check for transposing punctuation
  732. (setq punctuation nil)
  733. (when (orcp-get-citation-style
  734. 'transpose-punctuation
  735. (intern (org-element-property :type link)))
  736. ;; goto end of link
  737. (goto-char end)
  738. (when (looking-at "\\.\\|,\\|;")
  739. (setq punctuation (buffer-substring end (+ 1 end)))
  740. (delete-char 1)))
  741. ;; preserve trailing space
  742. (goto-char end)
  743. (setq trailing-space (if (looking-back " " (line-beginning-position)) " " ""))
  744. (setf (buffer-substring start end) (concat repl trailing-space))
  745. (when punctuation
  746. (goto-char start)
  747. ;; I can't figure out why this is necessary. I would have thought
  748. ;; the chomp leading spaces would get it.
  749. (when (thing-at-point 'whitespace)
  750. (delete-char -1))
  751. (insert punctuation)))
  752. ;; Insert bibliography section at the bibliography link
  753. (setq bibliography-link (cl-loop for link
  754. in (org-element-map
  755. (org-element-parse-buffer)
  756. 'link 'identity)
  757. if (string= "bibliography"
  758. (org-element-property :type link))
  759. collect link))
  760. (pcase (length bibliography-link)
  761. ((pred (< 1)) (error "Only one bibliography link allowed"))
  762. ((pred (= 1))
  763. ;; replace bibliography link
  764. (setq bibliography-link (car bibliography-link))
  765. (setf (buffer-substring (org-element-property :begin bibliography-link)
  766. (org-element-property :end bibliography-link))
  767. bibliography-string))
  768. ((pred (= 0))
  769. ;; no bibliography link in document
  770. (when link-replacements
  771. (message "Warning: No bibliography link found although there are citations to process"))))))
  772. ;; * the end
  773. (provide 'org-ref-citeproc)
  774. ;;; org-ref-citeproc.el ends here