summaryrefslogtreecommitdiff
path: root/support/texlab/src/citation
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-05-26 03:01:06 +0000
committerNorbert Preining <norbert@preining.info>2022-05-26 03:01:06 +0000
commit02d941fa9c9895bb08a84ac9afe3559abd1ba8ad (patch)
tree481d1d368c6d295a779b590d23bcd5397bcba628 /support/texlab/src/citation
parentf01a37f8311f33e32441d25bdadcda9dcdbd165d (diff)
CTAN sync 202205260301
Diffstat (limited to 'support/texlab/src/citation')
-rw-r--r--support/texlab/src/citation/apa.csl1526
-rw-r--r--support/texlab/src/citation/bibutils.rs195
-rw-r--r--support/texlab/src/citation/driver.rs733
-rw-r--r--support/texlab/src/citation/entry.rs123
-rw-r--r--support/texlab/src/citation/field.rs4
-rw-r--r--support/texlab/src/citation/field/author.rs73
-rw-r--r--support/texlab/src/citation/field/date.rs81
-rw-r--r--support/texlab/src/citation/field/number.rs54
-rw-r--r--support/texlab/src/citation/field/text.rs263
-rw-r--r--support/texlab/src/citation/output.rs87
-rw-r--r--support/texlab/src/citation/ris.rs471
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_aksin_2006.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_betram_1996.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_blom_2021.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_jain_1999.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_kastenholz_2006.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__article_rivest_1978.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__book_aho_2006.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__book_averroes_1998.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__book_knuth_1984.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__collection_matuz_1990.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_combi_2004.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_erwin_2007.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__mvbook_nietzsche_1988.snap6
-rw-r--r--support/texlab/src/citation/snapshots/texlab__citation__tests__patent_almendro_1998.snap6
-rw-r--r--support/texlab/src/citation/tests.rs351
26 files changed, 1853 insertions, 2192 deletions
diff --git a/support/texlab/src/citation/apa.csl b/support/texlab/src/citation/apa.csl
deleted file mode 100644
index 8454246a98..0000000000
--- a/support/texlab/src/citation/apa.csl
+++ /dev/null
@@ -1,1526 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="never" page-range-format="expanded">
- <info>
- <title>American Psychological Association 6th edition</title>
- <title-short>APA</title-short>
- <id>http://www.zotero.org/styles/apa</id>
- <link href="http://www.zotero.org/styles/apa" rel="self"/>
- <link href="http://owl.english.purdue.edu/owl/resource/560/01/" rel="documentation"/>
- <author>
- <name>Simon Kornblith</name>
- <email>simon@simonster.com</email>
- </author>
- <author>
- <name> Brenton M. Wiernik</name>
- <email>zotero@wiernik.org</email>
- </author>
- <contributor>
- <name>Bruce D'Arcus</name>
- </contributor>
- <contributor>
- <name>Curtis M. Humphrey</name>
- </contributor>
- <contributor>
- <name>Richard Karnesky</name>
- <email>karnesky+zotero@gmail.com</email>
- <uri>http://arc.nucapt.northwestern.edu/Richard_Karnesky</uri>
- </contributor>
- <contributor>
- <name>Sebastian Karcher</name>
- </contributor>
- <category citation-format="author-date"/>
- <category field="psychology"/>
- <category field="generic-base"/>
- <updated>2016-09-28T13:09:49+00:00</updated>
- <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
- </info>
- <locale xml:lang="en">
- <terms>
- <term name="editortranslator" form="short">
- <single>ed. &amp; trans.</single>
- <multiple>eds. &amp; trans.</multiple>
- </term>
- <term name="translator" form="short">trans.</term>
- <term name="interviewer" form="short">interviewer</term>
- <term name="circa" form="short">ca.</term>
- <term name="collection-editor" form="short">series ed.</term>
- </terms>
- </locale>
- <locale xml:lang="es">
- <terms>
- <term name="from">de</term>
- </terms>
- </locale>
- <locale xml:lang="de">
- <terms>
- <term name="et-al">et al.</term>
- </terms>
- </locale>
- <locale xml:lang="da">
- <terms>
- <term name="et-al">et al.</term>
- </terms>
- </locale>
- <locale xml:lang="nn">
- <terms>
- <term name="et-al">et al.</term>
- </terms>
- </locale>
- <locale xml:lang="nb">
- <terms>
- <term name="et-al">et al.</term>
- </terms>
- </locale>
- <macro name="container-contributors-booklike">
- <choose>
- <if variable="container-title">
- <names variable="editor translator" delimiter=", &amp; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=" (" text-case="title" suffix=")"/>
- <substitute>
- <names variable="editorial-director"/>
- <names variable="collection-editor"/>
- <names variable="container-author"/>
- </substitute>
- </names>
- </if>
- </choose>
- </macro>
- <macro name="container-contributors">
- <choose>
- <!-- book is here to catch software with container titles -->
- <if type="book broadcast chapter entry entry-dictionary entry-encyclopedia graphic map personal_communication report speech" match="any">
- <text macro="container-contributors-booklike"/>
- </if>
- <else-if type="paper-conference">
- <choose>
- <if variable="collection-editor container-author editor" match="any">
- <text macro="container-contributors-booklike"/>
- </if>
- </choose>
- </else-if>
- </choose>
- </macro>
- <macro name="secondary-contributors-booklike">
- <group delimiter="; ">
- <choose>
- <if variable="title">
- <names variable="interviewer">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </if>
- </choose>
- <choose>
- <if variable="container-title" match="none">
- <group delimiter="; ">
- <names variable="container-author">
- <label form="verb-short" suffix=" " text-case="title"/>
- <name and="symbol" initialize-with=". " delimiter=", "/>
- </names>
- <names variable="editor translator" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </group>
- </if>
- </choose>
- </group>
- </macro>
- <macro name="secondary-contributors">
- <choose>
- <!-- book is here to catch software with container titles -->
- <if type="book broadcast chapter entry entry-dictionary entry-encyclopedia graphic map report" match="any">
- <text macro="secondary-contributors-booklike"/>
- </if>
- <else-if type="personal_communication">
- <group delimiter="; ">
- <group delimiter=" ">
- <choose>
- <if variable="genre" match="any">
- <text variable="genre" text-case="capitalize-first"/>
- </if>
- <else>
- <text term="letter" text-case="capitalize-first"/>
- </else>
- </choose>
- <names variable="recipient" delimiter=", ">
- <label form="verb" suffix=" "/>
- <name and="symbol" delimiter=", "/>
- </names>
- </group>
- <text variable="medium" text-case="capitalize-first"/>
- <choose>
- <if variable="container-title" match="none">
- <names variable="editor translator" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </if>
- </choose>
- </group>
- </else-if>
- <else-if type="song">
- <choose>
- <if variable="original-author composer" match="any">
- <group delimiter="; ">
- <!-- Replace prefix with performer label as that becomes available -->
- <names variable="author" prefix="Recorded by ">
- <label form="verb" text-case="title"/>
- <name and="symbol" initialize-with=". " delimiter=", "/>
- </names>
- <names variable="translator">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </group>
- </if>
- </choose>
- </else-if>
- <else-if type="article-journal article-magazine article-newspaper" match="any">
- <group delimiter="; ">
- <choose>
- <if variable="title">
- <names variable="interviewer" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </if>
- </choose>
- <names variable="translator" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </group>
- </else-if>
- <else-if type="paper-conference">
- <choose>
- <if variable="collection-editor editor" match="any">
- <text macro="secondary-contributors-booklike"/>
- </if>
- <else>
- <group delimiter="; ">
- <choose>
- <if variable="title">
- <names variable="interviewer" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </if>
- </choose>
- <names variable="translator" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </group>
- </else>
- </choose>
- </else-if>
- <else>
- <group delimiter="; ">
- <choose>
- <if variable="title">
- <names variable="interviewer">
- <name and="symbol" initialize-with=". " delimiter="; "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </if>
- </choose>
- <names variable="editor translator" delimiter="; ">
- <name and="symbol" initialize-with=". " delimiter=", "/>
- <label form="short" prefix=", " text-case="title"/>
- </names>
- </group>
- </else>
- </choose>
- </macro>
- <macro name="author">
- <choose>
- <if type="song">
- <names variable="composer" delimiter=", ">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <substitute>
- <names variable="original-author"/>
- <names variable="author"/>
- <names variable="translator">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="short" prefix=" (" suffix=")" text-case="title"/>
- </names>
- <group delimiter=" ">
- <text macro="title"/>
- <text macro="description"/>
- <text macro="format"/>
- </group>
- </substitute>
- </names>
- </if>
- <else-if type="treaty"/>
- <else>
- <names variable="author" delimiter=", ">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <substitute>
- <names variable="illustrator"/>
- <names variable="composer"/>
- <names variable="director">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="long" prefix=" (" suffix=")" text-case="title"/>
- </names>
- <choose>
- <if variable="container-title">
- <choose>
- <if type="book entry entry-dictionary entry-encyclopedia">
- <text macro="title"/>
- </if>
- <else>
- <names variable="translator"/>
- </else>
- </choose>
- <names variable="translator">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="short" prefix=" (" suffix=")" text-case="title"/>
- </names>
- </if>
- </choose>
- <names variable="editor translator" delimiter=", ">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="short" prefix=" (" suffix=")" text-case="title"/>
- </names>
- <names variable="editorial-director">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="short" prefix=" (" suffix=")" text-case="title"/>
- </names>
- <names variable="collection-editor">
- <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
- <label form="short" prefix=" (" suffix=")" text-case="title"/>
- </names>
- <choose>
- <if type="report">
- <text variable="publisher"/>
- </if>
- </choose>
- <group delimiter=" ">
- <text macro="title"/>
- <text macro="description"/>
- <text macro="format"/>
- </group>
- </substitute>
- </names>
- </else>
- </choose>
- </macro>
- <macro name="author-short">
- <choose>
- <if type="patent" variable="number" match="all">
- <text macro="patent-number"/>
- </if>
- <else-if type="treaty">
- <text variable="title" form="short"/>
- </else-if>
- <else-if type="personal_communication">
- <choose>
- <if variable="archive DOI publisher URL" match="none">
- <group delimiter=", ">
- <names variable="author">
- <name and="symbol" delimiter=", " initialize-with=". "/>
- <substitute>
- <text variable="title" form="short" quotes="true"/>
- </substitute>
- </names>
- <!-- This should be localized -->
- <text value="personal communication"/>
- </group>
- </if>
- <else>
- <names variable="author" delimiter=", ">
- <name form="short" and="symbol" delimiter=", " initialize-with=". "/>
- <substitute>
- <names variable="editor"/>
- <names variable="translator"/>
- <choose>
- <if variable="container-title">
- <text variable="title" form="short" quotes="true"/>
- </if>
- <else>
- <text variable="title" form="short" font-style="italic"/>
- </else>
- </choose>
- <text macro="format-short" prefix="[" suffix="]"/>
- </substitute>
- </names>
- </else>
- </choose>
- </else-if>
- <else-if type="song">
- <names variable="composer" delimiter=", ">
- <name form="short" and="symbol" delimiter=", " initialize-with=". "/>
- <substitute>
- <names variable="original-author"/>
- <names variable="author"/>
- <names variable="translator"/>
- <choose>
- <if variable="container-title">
- <text variable="title" form="short" quotes="true"/>
- </if>
- <else>
- <text variable="title" form="short" font-style="italic"/>
- </else>
- </choose>
- <text macro="format-short" prefix="[" suffix="]"/>
- </substitute>
- </names>
- </else-if>
- <else>
- <names variable="author" delimiter=", ">
- <name form="short" and="symbol" delimiter=", " initialize-with=". "/>
- <substitute>
- <names variable="illustrator"/>
- <names variable="composer"/>
- <names variable="director"/>
- <choose>
- <if variable="container-title">
- <choose>
- <if type="book entry entry-dictionary entry-encyclopedia">
- <text variable="title" form="short" quotes="true"/>
- </if>
- <else>
- <names variable="translator"/>
- </else>
- </choose>
- </if>
- </choose>
- <names variable="editor"/>
- <names variable="editorial-director"/>
- <names variable="translator"/>
- <choose>
- <if type="report" variable="publisher" match="all">
- <text variable="publisher"/>
- </if>
- <else-if type="legal_case">
- <text variable="title" font-style="italic"/>
- </else-if>
- <else-if type="bill legislation" match="any">
- <text variable="title" form="short"/>
- </else-if>
- <else-if variable="reviewed-author" type="review review-book" match="any">
- <text macro="format-short" prefix="[" suffix="]"/>
- </else-if>
- <else-if type="post post-weblog webpage" variable="container-title" match="any">
- <text variable="title" form="short" quotes="true"/>
- </else-if>
- <else>
- <text variable="title" form="short" font-style="italic"/>
- </else>
- </choose>
- <text macro="format-short" prefix="[" suffix="]"/>
- </substitute>
- </names>
- </else>
- </choose>
- </macro>
- <macro name="patent-number">
- <!-- authority: U.S. ; genre: patent ; number: 123,445 -->
- <group delimiter=" ">
- <text variable="authority"/>
- <choose>
- <if variable="genre">
- <text variable="genre" text-case="capitalize-first"/>
- </if>
- <else>
- <!-- This should be localized -->
- <text value="patent" text-case="capitalize-first"/>
- </else>
- </choose>
- <group delimiter=" ">
- <text term="issue" form="short" text-case="capitalize-first"/>
- <text variable="number"/>
- </group>
- </group>
- </macro>
- <macro name="access">
- <choose>
- <if type="bill legal_case legislation" match="any"/>
- <else-if variable="DOI" match="any">
- <text variable="DOI" prefix="https://doi.org/"/>
- </else-if>
- <else-if variable="URL">
- <group delimiter=" ">
- <text term="retrieved" text-case="capitalize-first"/>
- <choose>
- <if type="post post-weblog webpage" match="any">
- <date variable="accessed" form="text" suffix=","/>
- </if>
- </choose>
- <text term="from"/>
- <choose>
- <if type="report">
- <choose>
- <if variable="author editor translator" match="any">
- <!-- This should be localized -->
- <text variable="publisher" suffix=" website:"/>
- </if>
- </choose>
- </if>
- <else-if type="post post-weblog webpage" match="any">
- <!-- This should be localized -->
- <text variable="container-title" suffix=" website:"/>
- </else-if>
- </choose>
- <text variable="URL"/>
- </group>
- </else-if>
- <else-if variable="archive">
- <choose>
- <if type="article article-journal article-magazine article-newspaper dataset paper-conference report speech thesis" match="any">
- <!-- This section is for electronic database locations. Physical archives for these and other item types are called in 'publisher' macro -->
- <choose>
- <if variable="archive-place" match="none">
- <group delimiter=" ">
- <text term="retrieved" text-case="capitalize-first"/>
- <text term="from"/>
- <text variable="archive" suffix="."/>
- <text variable="archive_location" prefix="(" suffix=")"/>
- </group>
- </if>
- <else>
- <text macro="publisher" suffix="."/>
- </else>
- </choose>
- </if>
- <else>
- <text macro="publisher" suffix="."/>
- </else>
- </choose>
- </else-if>
- <else>
- <text macro="publisher" suffix="."/>
- </else>
- </choose>
- </macro>
- <macro name="title">
- <choose>
- <if type="treaty">
- <group delimiter=", ">
- <text variable="title" text-case="title"/>
- <names variable="author">
- <name initialize-with="." form="short" delimiter="-"/>
- </names>
- </group>
- </if>
- <else-if type="patent" variable="number" match="all">
- <text macro="patent-number" font-style="italic"/>
- </else-if>
- <else-if variable="title">
- <choose>
- <if variable="version" type="book" match="all">
- <!---This is a hack until we have a software type -->
- <text variable="title"/>
- </if>
- <else-if variable="reviewed-author reviewed-title" type="review review-book" match="any">
- <choose>
- <if variable="reviewed-title">
- <choose>
- <if type="post post-weblog webpage" variable="container-title" match="any">
- <text variable="title"/>
- </if>
- <else>
- <text variable="title" font-style="italic"/>
- </else>
- </choose>
- </if>
- </choose>
- </else-if>
- <else-if type="post post-weblog webpage" variable="container-title" match="any">
- <text variable="title"/>
- </else-if>
- <else>
- <text variable="title" font-style="italic"/>
- </else>
- </choose>
- </else-if>
- <else-if variable="interviewer" type="interview" match="any">
- <names variable="interviewer">
- <label form="verb-short" suffix=" " text-case="capitalize-first"/>
- <name and="symbol" initialize-with=". " delimiter=", "/>
- </names>
- </else-if>
- </choose>
- </macro>
- <!-- APA has four descriptive sections following the title: -->
- <!-- (description), [format], container, event -->
- <macro name="description">
- <group prefix="(" suffix=")">
- <choose>
- <!-- book is here to catch software with container titles -->
- <if type="book report" match="any">
- <choose>
- <if variable="container-title">
- <text macro="secondary-contributors"/>
- </if>
- <else>
- <group delimiter="; ">
- <text macro="description-report"/>
- <text macro="secondary-contributors"/>
- </group>
- </else>
- </choose>
- </if>
- <else-if type="thesis">
- <group delimiter="; ">
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <choose>
- <!-- In APA journals, the university of a thesis is always cited, even if another locator is given -->
- <if variable="DOI URL archive" match="any">
- <text variable="publisher"/>
- </if>
- </choose>
- </group>
- <text macro="locators"/>
- <text macro="secondary-contributors"/>
- </group>
- </else-if>
- <else-if type="book interview manuscript motion_picture musical_score pamphlet post-weblog speech webpage" match="any">
- <group delimiter="; ">
- <text macro="locators"/>
- <text macro="secondary-contributors"/>
- </group>
- </else-if>
- <else-if type="song">
- <choose>
- <if variable="container-title" match="none">
- <text macro="locators"/>
- </if>
- </choose>
- </else-if>
- <else-if type="article dataset figure" match="any">
- <choose>
- <if variable="container-title">
- <text macro="secondary-contributors"/>
- </if>
- <else>
- <group delimiter="; ">
- <text macro="locators"/>
- <text macro="secondary-contributors"/>
- </group>
- </else>
- </choose>
- </else-if>
- <else-if type="bill legislation legal_case patent treaty personal_communication" match="none">
- <text macro="secondary-contributors"/>
- </else-if>
- </choose>
- </group>
- </macro>
- <macro name="format">
- <group prefix="[" suffix="]">
- <choose>
- <if variable="reviewed-author reviewed-title" type="review review-book" match="any">
- <group delimiter=", ">
- <choose>
- <if variable="genre">
- <!-- Delimiting by , rather than "of" to avoid incorrect grammar -->
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <choose>
- <if variable="reviewed-title">
- <text variable="reviewed-title" font-style="italic"/>
- </if>
- <else>
- <!-- Assume `title` is title of reviewed work -->
- <text variable="title" font-style="italic"/>
- </else>
- </choose>
- </group>
- </if>
- <else>
- <!-- This should be localized -->
- <group delimiter=" ">
- <text value="Review of"/>
- <choose>
- <if variable="reviewed-title">
- <text variable="reviewed-title" font-style="italic"/>
- </if>
- <else>
- <!-- Assume `title` is title of reviewed work -->
- <text variable="title" font-style="italic"/>
- </else>
- </choose>
- </group>
- </else>
- </choose>
- <names variable="reviewed-author">
- <label form="verb-short" suffix=" "/>
- <name and="symbol" initialize-with=". " delimiter=", "/>
- </names>
- </group>
- </if>
- <else>
- <text macro="format-short"/>
- </else>
- </choose>
- </group>
- </macro>
- <macro name="format-short">
- <choose>
- <if variable="reviewed-author reviewed-title" type="review review-book" match="any">
- <choose>
- <if variable="reviewed-title" match="none">
- <choose>
- <if variable="genre">
- <!-- Delimiting by , rather than "of" to avoid incorrect grammar -->
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <text variable="title" form="short" font-style="italic"/>
- </group>
- </if>
- <else>
- <!-- This should be localized -->
- <group delimiter=" ">
- <text value="Review of"/>
- <text variable="title" form="short" font-style="italic"/>
- </group>
- </else>
- </choose>
- </if>
- <else>
- <text variable="title" form="short" quotes="true"/>
- </else>
- </choose>
- </if>
- <else-if type="speech thesis" match="any">
- <text variable="medium" text-case="capitalize-first"/>
- </else-if>
- <!-- book is here to catch software with container titles -->
- <else-if type="book report" match="any">
- <choose>
- <if variable="container-title" match="none">
- <text macro="format-report"/>
- </if>
- </choose>
- </else-if>
- <else-if type="manuscript pamphlet" match="any">
- <text variable="medium" text-case="capitalize-first"/>
- </else-if>
- <else-if type="personal_communication">
- <text macro="secondary-contributors"/>
- </else-if>
- <else-if type="song">
- <group delimiter="; ">
- <text macro="secondary-contributors"/>
- <choose>
- <if variable="container-title" match="none">
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <text variable="medium" text-case="capitalize-first"/>
- </group>
- </if>
- </choose>
- </group>
- </else-if>
- <else-if type="paper-conference">
- <group delimiter=", ">
- <choose>
- <if variable="collection-editor editor issue page volume" match="any">
- <text variable="genre" text-case="capitalize-first"/>
- </if>
- </choose>
- <text variable="medium" text-case="capitalize-first"/>
- </group>
- </else-if>
- <else-if type="bill legislation legal_case patent treaty" match="none">
- <choose>
- <if variable="genre medium" match="any">
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <text variable="medium" text-case="capitalize-first"/>
- </group>
- </if>
- <else-if type="dataset">
- <!-- This should be localized -->
- <text value="Data set"/>
- </else-if>
- </choose>
- </else-if>
- </choose>
- </macro>
- <macro name="description-report">
- <choose>
- <if variable="number">
- <group delimiter="; ">
- <group delimiter=" ">
- <text variable="genre" text-case="title"/>
- <!-- Replace with term="number" if that becomes available -->
- <text term="issue" form="short" text-case="capitalize-first"/>
- <text variable="number"/>
- </group>
- <text macro="locators"/>
- </group>
- </if>
- <else>
- <text macro="locators"/>
- </else>
- </choose>
- </macro>
- <macro name="format-report">
- <choose>
- <if variable="number">
- <text variable="medium" text-case="capitalize-first"/>
- </if>
- <else>
- <group delimiter=", ">
- <text variable="genre" text-case="capitalize-first"/>
- <text variable="medium" text-case="capitalize-first"/>
- </group>
- </else>
- </choose>
- </macro>
- <macro name="archive">
- <group delimiter=". ">
- <group delimiter=", ">
- <choose>
- <if type="manuscript">
- <text variable="genre"/>
- </if>
- </choose>
- <group delimiter=" ">
- <!-- Replace "archive" with "archive_collection" as that becomes available -->
- <text variable="archive"/>
- <text variable="archive_location" prefix="(" suffix=")"/>
- </group>
- </group>
- <group delimiter=", ">
- <!-- Move "archive" here when "archive_collection" becomes available -->
- <text variable="archive-place"/>
- </group>
- </group>
- </macro>
- <macro name="publisher">
- <choose>
- <if type="manuscript pamphlet" match="any">
- <choose>
- <if variable="archive archive_location archive-place" match="any">
- <group delimiter=". ">
- <group delimiter=": ">
- <text variable="publisher-place"/>
- <text variable="publisher"/>
- </group>
- <text macro="archive"/>
- </group>
- </if>
- <else>
- <group delimiter=", ">
- <text variable="genre"/>
- <text variable="publisher"/>
- <text variable="publisher-place"/>
- </group>
- </else>
- </choose>
- </if>
- <else-if type="thesis" match="any">
- <group delimiter=". ">
- <group delimiter=", ">
- <text variable="publisher"/>
- <text variable="publisher-place"/>
- </group>
- <text macro="archive"/>
- </group>
- </else-if>
- <else-if type="patent">
- <group delimiter=". ">
- <group delimiter=": ">
- <text variable="publisher-place"/>
- <text variable="publisher"/>
- </group>
- <text macro="archive"/>
- </group>
- </else-if>
- <else-if type="article-journal article-magazine article-newspaper" match="any">
- <text macro="archive"/>
- </else-if>
- <else-if type="post post-weblog webpage" match="none">
- <group delimiter=". ">
- <choose>
- <if variable="event">
- <choose>
- <!-- Only print publisher info if published in a proceedings -->
- <if variable="collection-editor editor issue page volume" match="any">
- <group delimiter=": ">
- <text variable="publisher-place"/>
- <text variable="publisher"/>
- </group>
- </if>
- </choose>
- </if>
- <else>
- <group delimiter=": ">
- <text variable="publisher-place"/>
- <text variable="publisher"/>
- </group>
- </else>
- </choose>
- <text macro="archive"/>
- </group>
- </else-if>
- </choose>
- </macro>
- <macro name="event">
- <choose>
- <if variable="event" type="speech paper-conference" match="any">
- <choose>
- <!-- Don't print event info if published in a proceedings -->
- <if variable="collection-editor editor issue page volume" match="none">
- <group delimiter=" ">
- <text variable="genre" text-case="capitalize-first"/>
- <group delimiter=" ">
- <choose>
- <if variable="genre">
- <text term="presented at"/>
- </if>
- <else>
- <text term="presented at" text-case="capitalize-first"/>
- </else>
- </choose>
- <group delimiter=", ">
- <text variable="event"/>
- <text variable="event-place"/>
- </group>
- </group>
- </group>
- </if>
- </choose>
- </if>
- </choose>
- </macro>
- <macro name="issued">
- <choose>
- <if type="bill legal_case legislation" match="any"/>
- <else-if variable="issued">
- <group>
- <date variable="issued">
- <date-part name="year"/>
- </date>
- <text variable="year-suffix"/>
- <choose>
- <if type="speech">
- <date variable="issued" delimiter=" ">
- <date-part prefix=", " name="month"/>
- </date>
- </if>
- <else-if type="article article-magazine article-newspaper broadcast interview pamphlet personal_communication post post-weblog treaty webpage" match="any">
- <date variable="issued">
- <date-part prefix=", " name="month"/>
- <date-part prefix=" " name="day"/>
- </date>
- </else-if>
- <else-if type="paper-conference">
- <choose>
- <if variable="container-title" match="none">
- <date variable="issued">
- <date-part prefix=", " name="month"/>
- <date-part prefix=" " name="day"/>
- </date>
- </if>
- </choose>
- </else-if>
- <!-- Only year: article-journal chapter entry entry-dictionary entry-encyclopedia dataset figure graphic motion_picture manuscript map musical_score paper-conference [published] patent report review review-book song thesis -->
- </choose>
- </group>
- </else-if>
- <else-if variable="status">
- <group>
- <text variable="status" text-case="lowercase"/>
- <text variable="year-suffix" prefix="-"/>
- </group>
- </else-if>
- <else>
- <group>
- <text term="no date" form="short"/>
- <text variable="year-suffix" prefix="-"/>
- </group>
- </else>
- </choose>
- </macro>
- <macro name="issued-sort">
- <choose>
- <if type="article article-magazine article-newspaper broadcast interview pamphlet personal_communication post post-weblog speech treaty webpage" match="any">
- <date variable="issued">
- <date-part name="year"/>
- <date-part name="month"/>
- <date-part name="day"/>
- </date>
- </if>
- <else>
- <date variable="issued">
- <date-part name="year"/>
- </date>
- </else>
- </choose>
- </macro>
- <macro name="issued-year">
- <group>
- <choose>
- <if type="personal_communication">
- <choose>
- <if variable="archive DOI publisher URL" match="none">
- <!-- These variables indicate that the letter is retrievable by the reader. If not, then use the APA in-text-only personal communication format -->
- <date variable="issued" form="text"/>
- </if>
- <else>
- <date variable="issued">
- <date-part name="year"/>
- </date>
- </else>
- </choose>
- </if>
- <else>
- <date variable="issued">
- <date-part name="year"/>
- </date>
- </else>
- </choose>
- <text variable="year-suffix"/>
- </group>
- </macro>
- <macro name="issued-citation">
- <choose>
- <if variable="issued">
- <group delimiter="/">
- <choose>
- <if is-uncertain-date="original-date">
- <group prefix="[" suffix="]" delimiter=" ">
- <text term="circa" form="short"/>
- <date variable="original-date">
- <date-part name="year"/>
- </date>
- </group>
- </if>
- <else>
- <date variable="original-date">
- <date-part name="year"/>
- </date>
- </else>
- </choose>
- <choose>
- <if is-uncertain-date="issued">
- <group prefix="[" suffix="]" delimiter=" ">
- <text term="circa" form="short"/>
- <text macro="issued-year"/>
- </group>
- </if>
- <else>
- <text macro="issued-year"/>
- </else>
- </choose>
- </group>
- </if>
- <else-if variable="status">
- <text variable="status" text-case="lowercase"/>
- <text variable="year-suffix" prefix="-"/>
- </else-if>
- <else>
- <text term="no date" form="short"/>
- <text variable="year-suffix" prefix="-"/>
- </else>
- </choose>
- </macro>
- <macro name="original-date">
- <choose>
- <if type="bill legal_case legislation" match="any"/>
- <else-if type="speech">
- <date variable="original-date" delimiter=" ">
- <date-part name="month"/>
- <date-part name="year"/>
- </date>
- </else-if>
- <else-if type="article article-magazine article-newspaper broadcast interview pamphlet personal_communication post post-weblog treaty webpage" match="any">
- <date variable="original-date" form="text"/>
- </else-if>
- <else>
- <date variable="original-date">
- <date-part name="year"/>
- </date>
- </else>
- </choose>
- </macro>
- <macro name="original-published">
- <!--This should be localized -->
- <choose>
- <if type="bill legal_case legislation" match="any"/>
- <else-if type="interview motion_picture song" match="any">
- <text value="Original work recorded"/>
- </else-if>
- <else-if type="broadcast">
- <text value="Original work broadcast"/>
- </else-if>
- <else>
- <text value="Original work published"/>
- </else>
- </choose>
- </macro>
- <macro name="edition">
- <choose>
- <if is-numeric="edition">
- <group delimiter=" ">
- <number variable="edition" form="ordinal"/>
- <text term="edition" form="short"/>
- </group>
- </if>
- <else>
- <text variable="edition"/>
- </else>
- </choose>
- </macro>
- <macro name="locators">
- <choose>
- <if type="article-journal article-magazine figure review review-book" match="any">
- <group delimiter=", ">
- <group>
- <text variable="volume" font-style="italic"/>
- <text variable="issue" prefix="(" suffix=")"/>
- </group>
- <text variable="page"/>
- </group>
- </if>
- <else-if type="article-newspaper">
- <group delimiter=" ">
- <label variable="page" form="short"/>
- <text variable="page"/>
- </group>
- </else-if>
- <else-if type="paper-conference">
- <choose>
- <if variable="collection-editor editor" match="any">
- <text macro="locators-booklike"/>
- </if>
- <else>
- <group delimiter=", ">
- <group>
- <text variable="volume" font-style="italic"/>
- <text variable="issue" prefix="(" suffix=")"/>
- </group>
- <text variable="page"/>
- </group>
- </else>
- </choose>
- </else-if>
- <else-if type="bill broadcast interview legal_case legislation patent post post-weblog speech treaty webpage" match="none">
- <text macro="locators-booklike"/>
- </else-if>
- </choose>
- </macro>
- <macro name="locators-booklike">
- <group delimiter=", ">
- <text macro="edition"/>
- <group delimiter=" ">
- <text term="version" text-case="capitalize-first"/>
- <text variable="version"/>
- </group>
- <choose>
- <if variable="volume" match="any">
- <choose>
- <if is-numeric="volume" match="none"/>
- <else-if variable="collection-title">
- <choose>
- <if variable="editor translator" match="none">
- <choose>
- <if variable="collection-number">
- <group>
- <text term="volume" form="short" text-case="capitalize-first" suffix=" "/>
- <number variable="volume" form="numeric"/>
- </group>
- </if>
- </choose>
- </if>
- </choose>
- </else-if>
- <else>
- <group>
- <text term="volume" form="short" text-case="capitalize-first" suffix=" "/>
- <number variable="volume" form="numeric"/>
- </group>
- </else>
- </choose>
- </if>
- <else>
- <group>
- <text term="volume" form="short" plural="true" text-case="capitalize-first" suffix=" "/>
- <number variable="number-of-volumes" form="numeric" prefix="1&#8211;"/>
- </group>
- </else>
- </choose>
- <group>
- <label variable="page" form="short" suffix=" "/>
- <text variable="page"/>
- </group>
- </group>
- </macro>
- <macro name="citation-locator">
- <group>
- <choose>
- <if locator="chapter">
- <label variable="locator" text-case="capitalize-first"/>
- </if>
- <else>
- <label variable="locator" form="short"/>
- </else>
- </choose>
- <text variable="locator" prefix=" "/>
- </group>
- </macro>
- <macro name="container">
- <choose>
- <if type="article article-journal article-magazine article-newspaper review review-book" match="any">
- <group delimiter=", ">
- <text macro="container-title"/>
- <text macro="locators"/>
- </group>
- <choose>
- <!--for advance online publication-->
- <if variable="issued">
- <choose>
- <if variable="page issue" match="none">
- <text variable="status" text-case="capitalize-first" prefix=". "/>
- </if>
- </choose>
- </if>
- </choose>
- </if>
- <else-if type="article dataset figure" match="any">
- <choose>
- <if variable="container-title">
- <group delimiter=", ">
- <text macro="container-title"/>
- <text macro="locators"/>
- </group>
- <choose>
- <!--for advance online publication-->
- <if variable="issued">
- <choose>
- <if variable="page issue" match="none">
- <text variable="status" text-case="capitalize-first" prefix=". "/>
- </if>
- </choose>
- </if>
- </choose>
- </if>
- </choose>
- </else-if>
- <!-- book is here to catch software with container titles -->
- <else-if type="book" variable="container-title" match="all">
- <group delimiter=" ">
- <text term="in" text-case="capitalize-first" suffix=" "/>
- <group delimiter=", ">
- <text macro="container-contributors"/>
- <group delimiter=" ">
- <text macro="container-title"/>
- <text macro="description-report" prefix="(" suffix=")"/>
- <text macro="format-report" prefix="[" suffix="]"/>
- </group>
- </group>
- </group>
- </else-if>
- <else-if type="report" variable="container-title" match="all">
- <group delimiter=" ">
- <text term="in" text-case="capitalize-first" suffix=" "/>
- <group delimiter=", ">
- <text macro="container-contributors"/>
- <group delimiter=" ">
- <text macro="container-title"/>
- <text macro="description-report" prefix="(" suffix=")"/>
- <text macro="format-report" prefix="[" suffix="]"/>
- </group>
- </group>
- </group>
- </else-if>
- <else-if type="song" variable="container-title" match="all">
- <group delimiter=" ">
- <text term="in" text-case="capitalize-first" suffix=" "/>
- <group delimiter=", ">
- <text macro="container-contributors"/>
- <group delimiter=" ">
- <text macro="container-title"/>
- <text macro="locators" prefix="(" suffix=")"/>
- <group delimiter=", " prefix="[" suffix="]">
- <text variable="genre" text-case="capitalize-first"/>
- <text variable="medium" text-case="capitalize-first"/>
- </group>
- </group>
- </group>
- </group>
- </else-if>
- <else-if type="paper-conference">
- <choose>
- <if variable="editor collection-editor container-author" match="any">
- <text macro="container-booklike"/>
- </if>
- <else>
- <group delimiter=", ">
- <text macro="container-title"/>
- <text macro="locators"/>
- </group>
- </else>
- </choose>
- </else-if>
- <else-if type="book">
- <choose>
- <!-- book and software should not cite collection-title, only container-title -->
- <if variable="container-title">
- <text macro="container-booklike"/>
- </if>
- </choose>
- </else-if>
- <else-if type="broadcast chapter entry entry-dictionary entry-encyclopedia graphic map speech" match="any">
- <text macro="container-booklike"/>
- </else-if>
- <else-if type="bill legal_case legislation treaty" match="any">
- <text macro="legal-cites"/>
- </else-if>
- </choose>
- </macro>
- <macro name="container-booklike">
- <choose>
- <if variable="container-title collection-title" match="any">
- <group delimiter=" ">
- <text term="in" text-case="capitalize-first"/>
- <group delimiter=", ">
- <text macro="container-contributors"/>
- <choose>
- <if variable="container-author editor translator" match="none">
- <group delimiter=". ">
- <group delimiter=": ">
- <text variable="collection-title" font-style="italic" text-case="title"/>
- <choose>
- <if variable="collection-title">
- <group delimiter=" ">
- <text term="volume" form="short" font-style="italic" text-case="capitalize-first"/>
- <number variable="collection-number" font-style="italic" form="numeric"/>
- <choose>
- <if variable="collection-number" match="none">
- <number variable="volume" font-style="italic" form="numeric"/>
- </if>
- </choose>
- </group>
- </if>
- </choose>
- </group>
- <!-- Replace with volume-title as that becomes available -->
- <group delimiter=": ">
- <text macro="container-title"/>
- <choose>
- <if variable="collection-title" is-numeric="volume" match="none">
- <group delimiter=" ">
- <text term="volume" form="short" font-style="italic" text-case="capitalize-first"/>
- <text variable="volume" font-style="italic"/>
- </group>
- </if>
- </choose>
- </group>
- </group>
- </if>
- <else>
- <!-- Replace with volume-title as that becomes available -->
- <group delimiter=": ">
- <text macro="container-title"/>
- <choose>
- <if is-numeric="volume" match="none">
- <group delimiter=" ">
- <text term="volume" form="short" font-style="italic" text-case="capitalize-first"/>
- <text variable="volume" font-style="italic"/>
- </group>
- </if>
- </choose>
- </group>
- </else>
- </choose>
- </group>
- <group delimiter="; " prefix="(" suffix=")">
- <text macro="locators"/>
- <names variable="container-author">
- <label form="verb-short" suffix=" " text-case="title"/>
- <name and="symbol" initialize-with=". " delimiter=", "/>
- </names>
- </group>
- </group>
- </if>
- </choose>
- </macro>
- <macro name="container-title">
- <choose>
- <if type="article article-journal article-magazine article-newspaper dataset" match="any">
- <text variable="container-title" font-style="italic" text-case="title"/>
- </if>
- <else-if type="paper-conference speech">
- <choose>
- <if variable="collection-editor container-author editor" match="any">
- <text variable="container-title" font-style="italic"/>
- </if>
- <else>
- <text variable="container-title" font-style="italic" text-case="title"/>
- </else>
- </choose>
- </else-if>
- <else-if type="bill legal_case legislation post-weblog webpage" match="none">
- <text variable="container-title" font-style="italic"/>
- </else-if>
- </choose>
- </macro>
- <macro name="legal-cites">
- <choose>
- <if type="legal_case">
- <group prefix=", " delimiter=" ">
- <group delimiter=" ">
- <choose>
- <if variable="container-title">
- <text variable="volume"/>
- <text variable="container-title"/>
- <group delimiter=" ">
- <!--change to label variable="section" as that becomes available -->
- <text term="section" form="symbol"/>
- <text variable="section"/>
- </group>
- <text variable="page"/>
- </if>
- <else>
- <group delimiter=" ">
- <choose>
- <if is-numeric="number">
- <!-- Replace with term="number" if that becomes available -->
- <text term="issue" form="short" text-case="capitalize-first"/>
- </if>
- </choose>
- <text variable="number"/>
- </group>
- </else>
- </choose>
- </group>
- <group prefix="(" suffix=")" delimiter=" ">
- <text variable="authority"/>
- <choose>
- <if variable="container-title" match="any">
- <!--Only print year for cases published in reporters-->
- <date variable="issued" form="numeric" date-parts="year"/>
- </if>
- <else>
- <date variable="issued" form="text"/>
- </else>
- </choose>
- </group>
- </group>
- </if>
- <else-if type="bill legislation" match="any">
- <group prefix=", " delimiter=" ">
- <group delimiter=", ">
- <choose>
- <if variable="number">
- <!--There's a public law number-->
- <text variable="number" prefix="Pub. L. No. "/>
- <group delimiter=" ">
- <!--change to label variable="section" as that becomes available -->
- <text term="section" form="symbol"/>
- <text variable="section"/>
- </group>
- <group delimiter=" ">
- <text variable="volume"/>
- <text variable="container-title"/>
- <text variable="page-first"/>
- </group>
- </if>
- <else>
- <group delimiter=" ">
- <text variable="volume"/>
- <text variable="container-title"/>
- <!--change to label variable="section" as that becomes available -->
- <text term="section" form="symbol"/>
- <text variable="section"/>
- </group>
- </else>
- </choose>
- </group>
- <date variable="issued" prefix="(" suffix=")">
- <date-part name="year"/>
- </date>
- </group>
- </else-if>
- <else-if type="treaty">
- <group delimiter=" ">
- <number variable="volume"/>
- <text variable="container-title"/>
- <text variable="page"/>
- </group>
- </else-if>
- </choose>
- </macro>
- <citation et-al-min="6" et-al-use-first="1" et-al-subsequent-min="3" et-al-subsequent-use-first="1" disambiguate-add-year-suffix="true" disambiguate-add-names="true" disambiguate-add-givenname="true" collapse="year" givenname-disambiguation-rule="primary-name">
- <sort>
- <key macro="author" names-min="8" names-use-first="6"/>
- <key macro="issued-sort"/>
- </sort>
- <layout prefix="(" suffix=")" delimiter="; ">
- <group delimiter=", ">
- <text macro="author-short"/>
- <text macro="issued-citation"/>
- <text macro="citation-locator"/>
- </group>
- </layout>
- </citation>
- <bibliography hanging-indent="true" et-al-min="8" et-al-use-first="6" et-al-use-last="true" entry-spacing="0" line-spacing="2">
- <sort>
- <key macro="author"/>
- <key macro="issued-sort" sort="ascending"/>
- <key macro="title"/>
- </sort>
- <layout>
- <group suffix=".">
- <group delimiter=". ">
- <text macro="author"/>
- <choose>
- <if is-uncertain-date="issued">
- <group prefix=" [" suffix="]" delimiter=" ">
- <text term="circa" form="short"/>
- <text macro="issued"/>
- </group>
- </if>
- <else>
- <text macro="issued" prefix=" (" suffix=")"/>
- </else>
- </choose>
- <group delimiter=" ">
- <text macro="title"/>
- <choose>
- <if variable="title interviewer" type="interview" match="any">
- <group delimiter=" ">
- <text macro="description"/>
- <text macro="format"/>
- </group>
- </if>
- <else>
- <group delimiter=" ">
- <text macro="format"/>
- <text macro="description"/>
- </group>
- </else>
- </choose>
- </group>
- <text macro="container"/>
- </group>
- <text macro="event" prefix=". "/>
- </group>
- <text macro="access" prefix=" "/>
- <choose>
- <if is-uncertain-date="original-date">
- <group prefix=" [" suffix="]" delimiter=" ">
- <text macro="original-published"/>
- <text term="circa" form="short"/>
- <text macro="original-date"/>
- </group>
- </if>
- <else-if variable="original-date">
- <group prefix=" (" suffix=")" delimiter=" ">
- <text macro="original-published"/>
- <text macro="original-date"/>
- </group>
- </else-if>
- </choose>
- </layout>
- </bibliography>
-</style> \ No newline at end of file
diff --git a/support/texlab/src/citation/bibutils.rs b/support/texlab/src/citation/bibutils.rs
deleted file mode 100644
index 9931ba3f9f..0000000000
--- a/support/texlab/src/citation/bibutils.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-use std::{ffi::CString, fs, mem::MaybeUninit, path::Path};
-
-use bibutils_sys::{
- bibl, bibl_free, bibl_freeparams, bibl_init, bibl_initparams, bibl_read, bibl_write, fclose,
- fopen, param, BIBL_ADSABSOUT, BIBL_BIBLATEXIN, BIBL_BIBTEXIN, BIBL_BIBTEXOUT, BIBL_COPACIN,
- BIBL_EBIIN, BIBL_ENDNOTEIN, BIBL_ENDNOTEOUT, BIBL_ENDNOTEXMLIN, BIBL_ISIOUT, BIBL_MEDLINEIN,
- BIBL_MODSIN, BIBL_MODSOUT, BIBL_NBIBIN, BIBL_NBIBOUT, BIBL_OK, BIBL_RISIN, BIBL_RISOUT,
- BIBL_WORD2007OUT, BIBL_WORDIN, FILE,
-};
-use tempfile::tempdir;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[allow(unused)]
-pub enum InputFormat {
- Bibtex,
- Biblatex,
- Copac,
- Ebi,
- Endnote,
- EndnoteXml,
- Medline,
- Mods,
- Nbib,
- Ris,
- Word,
-}
-
-impl InputFormat {
- fn read_mode(self) -> u32 {
- match self {
- Self::Bibtex => BIBL_BIBTEXIN,
- Self::Biblatex => BIBL_BIBLATEXIN,
- Self::Copac => BIBL_COPACIN,
- Self::Ebi => BIBL_EBIIN,
- Self::Endnote => BIBL_ENDNOTEIN,
- Self::EndnoteXml => BIBL_ENDNOTEXMLIN,
- Self::Medline => BIBL_MEDLINEIN,
- Self::Mods => BIBL_MODSIN,
- Self::Nbib => BIBL_NBIBIN,
- Self::Ris => BIBL_RISIN,
- Self::Word => BIBL_WORDIN,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-#[allow(unused)]
-pub enum OutputFormat {
- Adsabs,
- Bibtex,
- Endnote,
- Isi,
- Mods,
- Nbib,
- Ris,
- Word2007,
-}
-
-impl OutputFormat {
- fn write_mode(self) -> u32 {
- match self {
- Self::Adsabs => BIBL_ADSABSOUT,
- Self::Bibtex => BIBL_BIBTEXOUT,
- Self::Endnote => BIBL_ENDNOTEOUT,
- Self::Isi => BIBL_ISIOUT,
- Self::Mods => BIBL_MODSOUT,
- Self::Nbib => BIBL_NBIBOUT,
- Self::Ris => BIBL_RISOUT,
- Self::Word2007 => BIBL_WORD2007OUT,
- }
- }
-}
-
-struct Context {
- inner: MaybeUninit<bibl>,
-}
-
-impl Context {
- fn new() -> Self {
- let mut inner = MaybeUninit::zeroed();
- unsafe {
- bibl_init(inner.as_mut_ptr());
- }
- Self { inner }
- }
-}
-
-impl Drop for Context {
- fn drop(&mut self) {
- unsafe {
- bibl_free(self.inner.as_mut_ptr());
- }
- }
-}
-
-unsafe impl Send for Context {}
-
-struct Params {
- inner: MaybeUninit<param>,
-}
-
-impl Params {
- fn new(from: InputFormat, to: OutputFormat) -> Self {
- let program = CString::new("texlab").unwrap();
- let mut inner = MaybeUninit::zeroed();
- unsafe {
- bibl_initparams(
- inner.as_mut_ptr(),
- from.read_mode() as i32,
- to.write_mode() as i32,
- program.as_ptr() as *mut std::os::raw::c_char,
- );
- }
- Self { inner }
- }
-}
-
-impl Drop for Params {
- fn drop(&mut self) {
- unsafe {
- bibl_freeparams(self.inner.as_mut_ptr());
- }
- }
-}
-
-unsafe impl Send for Params {}
-
-struct File {
- path: CString,
- handle: *mut FILE,
-}
-
-impl File {
- fn new<M: Into<Vec<u8>>>(path: &Path, mode: M) -> Self {
- let path = CString::new(path.to_str().unwrap()).unwrap();
- let mode = CString::new(mode).unwrap();
- let handle = unsafe { fopen(path.as_ptr(), mode.as_ptr()) };
- Self { path, handle }
- }
-}
-
-impl Drop for File {
- fn drop(&mut self) {
- unsafe {
- fclose(self.handle);
- }
- }
-}
-
-unsafe impl Send for File {}
-
-pub fn convert(input: &str, from: InputFormat, to: OutputFormat) -> Option<String> {
- let mut context = Context::new();
- let mut params = Params::new(from, to);
- let dir = tempdir().expect("failed to create a temporary directory");
-
- let input_path = dir.path().join("input");
- fs::write(&input_path, input).ok()?;
- let input_file = File::new(&input_path, "r");
- unsafe {
- let status = bibl_read(
- context.inner.as_mut_ptr(),
- input_file.handle,
- input_file.path.as_ptr() as *mut std::os::raw::c_char,
- params.inner.as_mut_ptr(),
- );
-
- if status != BIBL_OK as i32 {
- return None;
- }
- }
-
- let output_path = dir.path().join("output");
- let output_file = File::new(&output_path, "w");
- unsafe {
- let status = bibl_write(
- context.inner.as_mut_ptr(),
- output_file.handle,
- params.inner.as_mut_ptr(),
- );
-
- if status != BIBL_OK as i32 {
- return None;
- }
- }
-
- // Remove BOM
- let data = fs::read(&output_path).ok()?;
- if data.is_empty() {
- return None;
- }
-
- let text = String::from_utf8_lossy(&data[3..]).into_owned();
- Some(text)
-}
diff --git a/support/texlab/src/citation/driver.rs b/support/texlab/src/citation/driver.rs
new file mode 100644
index 0000000000..eaf98304c4
--- /dev/null
+++ b/support/texlab/src/citation/driver.rs
@@ -0,0 +1,733 @@
+use isocountry::CountryCode;
+use itertools::Itertools;
+use titlecase::titlecase;
+use url::Url;
+
+use crate::syntax::bibtex;
+
+use super::{
+ entry::{EntryData, EntryKind},
+ field::{
+ author::AuthorField,
+ date::DateField,
+ number::{NumberField, NumberFieldData},
+ text::TextField,
+ },
+ output::{Inline, InlineBuilder, Punct},
+};
+
+#[derive(Debug, Default)]
+pub struct Driver {
+ builder: InlineBuilder,
+}
+
+impl Driver {
+ pub fn process(&mut self, entry: &bibtex::Entry) -> Option<()> {
+ let entry = EntryData::parse(entry)?;
+ match entry.kind {
+ EntryKind::Article
+ | EntryKind::DataSet
+ | EntryKind::Manual
+ | EntryKind::Misc
+ | EntryKind::Online
+ | EntryKind::Electronic
+ | EntryKind::Www
+ | EntryKind::Proceedings
+ | EntryKind::MVProceedings
+ | EntryKind::Set
+ | EntryKind::Software
+ | EntryKind::Thesis
+ | EntryKind::MasterThesis
+ | EntryKind::PhdThesis
+ | EntryKind::Unknown => self.article_like(entry),
+ EntryKind::Book
+ | EntryKind::MVBook
+ | EntryKind::InBook
+ | EntryKind::BookInBook
+ | EntryKind::SuppBook
+ | EntryKind::Booklet
+ | EntryKind::Collection
+ | EntryKind::MVCollection
+ | EntryKind::InCollection
+ | EntryKind::SuppCollection
+ | EntryKind::Periodical
+ | EntryKind::SuppPeriodical
+ | EntryKind::InProceedings
+ | EntryKind::Conference
+ | EntryKind::Reference
+ | EntryKind::MVReference
+ | EntryKind::InReference
+ | EntryKind::Report
+ | EntryKind::TechReport => self.book_like(entry),
+ EntryKind::Patent => self.patent(entry),
+ }
+ }
+
+ fn article_like(&mut self, mut entry: EntryData) -> Option<()> {
+ self.author(&mut entry);
+ self.title(&mut entry);
+ self.translator(&mut entry);
+ self.commentator(&mut entry);
+ self.annotator(&mut entry);
+ self.version(&mut entry);
+ self.journal(&mut entry);
+ self.article_series(&mut entry);
+
+ if let Some(volume) = entry.number.remove(&NumberField::Volume) {
+ let number = entry
+ .number
+ .remove(&NumberField::Number)
+ .map(|data| format!(".{}", data))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Regular(format!("{}{}", volume, number)),
+ Punct::Space,
+ Punct::Space,
+ );
+ }
+
+ self.eid(&mut entry);
+ self.issue_and_date(&mut entry);
+ self.issue_title(&mut entry);
+ self.editors(&mut entry);
+ self.note(&mut entry);
+ self.pages(&mut entry);
+ self.issn(&mut entry);
+ self.doi(&mut entry);
+ self.eprint(&mut entry);
+ self.url(&mut entry);
+ self.addendum(&mut entry);
+ self.pubstate(&mut entry);
+ Some(())
+ }
+
+ fn book_like(&mut self, mut entry: EntryData) -> Option<()> {
+ self.author(&mut entry);
+ self.title(&mut entry);
+ self.inbook_title(&mut entry);
+ self.main_title(&mut entry);
+ self.event(&mut entry);
+ self.editors(&mut entry);
+ self.translator(&mut entry);
+ self.commentator(&mut entry);
+ self.annotator(&mut entry);
+ self.introduction(&mut entry);
+ self.foreword(&mut entry);
+ self.afterword(&mut entry);
+ self.edition(&mut entry);
+ self.book_volume(&mut entry);
+ self.volumes(&mut entry);
+ self.book_series(&mut entry);
+ self.how_published(&mut entry);
+ self.note(&mut entry);
+ self.publisher(&mut entry);
+ self.date(&mut entry);
+ self.chapter(&mut entry);
+ self.eid(&mut entry);
+ self.pages(&mut entry);
+ self.page_total(&mut entry);
+ self.isbn(&mut entry);
+ self.doi(&mut entry);
+ self.eprint(&mut entry);
+ self.url(&mut entry);
+ self.addendum(&mut entry);
+ self.pubstate(&mut entry);
+ Some(())
+ }
+
+ fn patent(&mut self, mut entry: EntryData) -> Option<()> {
+ self.author(&mut entry);
+ self.title(&mut entry);
+ self.patent_number(&mut entry);
+ self.holder(&mut entry);
+ self.date(&mut entry);
+ self.doi(&mut entry);
+ self.eprint(&mut entry);
+ self.url(&mut entry);
+ self.addendum(&mut entry);
+ self.pubstate(&mut entry);
+ Some(())
+ }
+
+ fn author(&mut self, entry: &mut EntryData) -> Option<()> {
+ let author = entry.author.remove(&AuthorField::Author)?;
+ self.builder.push(
+ Inline::Regular(author.to_string()),
+ Punct::Nothing,
+ Punct::Colon,
+ );
+
+ Some(())
+ }
+
+ fn commentator(&mut self, entry: &mut EntryData) -> Option<()> {
+ let commentator = entry.author.remove(&AuthorField::Commentator)?;
+ self.builder.push(
+ Inline::Regular(format!("With a comment. by {}", commentator)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn translator(&mut self, entry: &mut EntryData) -> Option<()> {
+ let translator = entry.author.remove(&AuthorField::Translator)?;
+ self.builder.push(
+ Inline::Regular(format!("Trans. by {}", translator)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn annotator(&mut self, entry: &mut EntryData) -> Option<()> {
+ let annotator = entry.author.remove(&AuthorField::Annotator)?;
+ self.builder.push(
+ Inline::Regular(format!("With annots. by {}", annotator)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn editors(&mut self, entry: &mut EntryData) {
+ self.editor(entry, AuthorField::Editor, TextField::EditorType);
+ self.editor(entry, AuthorField::EditorA, TextField::EditorTypeA);
+ self.editor(entry, AuthorField::EditorB, TextField::EditorTypeB);
+ self.editor(entry, AuthorField::EditorC, TextField::EditorTypeC);
+ }
+
+ fn editor(
+ &mut self,
+ entry: &mut EntryData,
+ name_field: AuthorField,
+ type_field: TextField,
+ ) -> Option<()> {
+ let editor = entry.author.remove(&name_field)?;
+ let editor_type = entry
+ .text
+ .remove(&type_field)
+ .map(|data| data.text)
+ .unwrap_or_else(|| "Ed. by".to_string());
+
+ self.builder.push(
+ Inline::Regular(format!("{editor_type} {editor}")),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn title(&mut self, entry: &mut EntryData) -> Option<()> {
+ let title = entry.text.remove(&TextField::Title)?;
+ let subtitle = entry
+ .text
+ .remove(&TextField::Subtitle)
+ .map(|data| format!(". {}", data.text))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Quoted(format!("{}{}", title.text, subtitle)),
+ Punct::Nothing,
+ Punct::Dot,
+ );
+
+ let addon = entry.text.remove(&TextField::TitleAddon)?;
+ self.builder
+ .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn main_title(&mut self, entry: &mut EntryData) -> Option<()> {
+ let title = entry.text.remove(&TextField::MainTitle)?;
+ let subtitle = entry
+ .text
+ .remove(&TextField::MainSubtitle)
+ .map(|data| format!(". {}", data.text))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ let addon = entry.text.remove(&TextField::MainTitleAddon)?;
+ self.builder
+ .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn inbook_title(&mut self, entry: &mut EntryData) -> Option<()> {
+ let title = entry.text.remove(&TextField::BookTitle)?;
+ let subtitle = entry
+ .text
+ .remove(&TextField::BookSubtitle)
+ .map(|data| format!(". {}", data.text))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ let addon = entry.text.remove(&TextField::BookTitleAddon)?;
+ self.builder
+ .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn issue_title(&mut self, entry: &mut EntryData) -> Option<()> {
+ let issue = entry.text.remove(&TextField::IssueTitle)?;
+ let subtitle = entry
+ .text
+ .remove(&TextField::IssueSubtitle)
+ .map(|data| format!(" {}", data.text))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Italic(titlecase(&format!("{}{}", issue.text, subtitle))),
+ Punct::Colon,
+ Punct::Space,
+ );
+
+ if let Some(addon) = entry.text.remove(&TextField::IssueTitleAddon) {
+ self.builder
+ .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot);
+ }
+
+ Some(())
+ }
+
+ fn event(&mut self, entry: &mut EntryData) -> Option<()> {
+ let title = entry.text.remove(&TextField::EventTitle)?;
+ let addon = entry
+ .text
+ .remove(&TextField::EventTitleAddon)
+ .map_or(String::new(), |addon| format!(". {}", addon.text));
+
+ let venue = entry.text.remove(&TextField::Venue);
+ let date = entry.date.remove(&DateField::EventDate);
+ let venue_and_date = match (venue, date) {
+ (None, None) => String::new(),
+ (None, Some(date)) => format!(" ({date})"),
+ (Some(venue), None) => format!(" ({})", venue.text),
+ (Some(venue), Some(date)) => format!(" ({}, {})", venue.text, date),
+ };
+
+ self.builder.push(
+ Inline::Regular(format!("{}{}{}", title.text, addon, venue_and_date)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn patent_number(&mut self, entry: &mut EntryData) -> Option<()> {
+ let number = entry.number.remove(&NumberField::Number)?;
+
+ let location = entry
+ .text
+ .remove(&TextField::Location)
+ .map_or(String::new(), |location| {
+ let text = location
+ .text
+ .split_whitespace()
+ .map(|word| {
+ if word == "countryuk" {
+ "United Kingdom"
+ } else {
+ word.strip_prefix("country")
+ .and_then(|code| CountryCode::for_alpha2_caseless(code).ok())
+ .map_or(word, |country| country.name())
+ }
+ })
+ .join(" ");
+
+ format!(" ({})", text)
+ });
+
+ self.builder.push(
+ Inline::Regular(format!("{}{}", number, location)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn holder(&mut self, entry: &mut EntryData) -> Option<()> {
+ let holder = entry.text.remove(&TextField::Holder)?;
+ self.builder
+ .push(Inline::Regular(holder.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn version(&mut self, entry: &mut EntryData) -> Option<()> {
+ let version = entry.text.remove(&TextField::Version)?;
+ self.builder.push(
+ Inline::Regular(format!("Version {}", version.text)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn article_series(&mut self, entry: &mut EntryData) -> Option<()> {
+ let series = entry.text.remove(&TextField::Series)?;
+ self.builder
+ .push(Inline::Regular(series.text), Punct::Space, Punct::Space);
+
+ Some(())
+ }
+
+ fn issue_and_date(&mut self, entry: &mut EntryData) -> Option<()> {
+ let date = [DateField::Date, DateField::Year, DateField::Month]
+ .iter()
+ .filter_map(|field| entry.date.remove(field))
+ .reduce(|a, b| a + b)?;
+
+ let issue = entry
+ .text
+ .remove(&TextField::Issue)
+ .map(|data| format!("{} ", data.text))
+ .unwrap_or_default();
+
+ self.builder.push(
+ Inline::Regular(format!("({issue}{date})")),
+ Punct::Space,
+ Punct::Colon,
+ );
+
+ Some(())
+ }
+
+ fn chapter(&mut self, entry: &mut EntryData) -> Option<()> {
+ let chapter = entry.text.remove(&TextField::Chapter)?;
+ self.builder.push(
+ Inline::Regular(format!("Chap. {}", chapter.text)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn date(&mut self, entry: &mut EntryData) -> Option<()> {
+ let date = [DateField::Date, DateField::Year, DateField::Month]
+ .iter()
+ .filter_map(|field| entry.date.remove(field))
+ .reduce(|a, b| a + b)?;
+
+ self.builder
+ .push(Inline::Regular(format!("{date}")), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn how_published(&mut self, entry: &mut EntryData) -> Option<()> {
+ let how_published = entry.text.remove(&TextField::HowPublished)?;
+ self.builder
+ .push(Inline::Regular(how_published.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn publisher(&mut self, entry: &mut EntryData) -> Option<()> {
+ let publisher = entry.text.remove(&TextField::Publisher)?.text;
+ let location = entry
+ .text
+ .remove(&TextField::Location)
+ .map_or(String::new(), |location| format!("{}: ", location.text));
+
+ self.builder.push(
+ Inline::Regular(format!("{location}{publisher}")),
+ Punct::Dot,
+ Punct::Comma,
+ );
+
+ Some(())
+ }
+
+ fn book_series(&mut self, entry: &mut EntryData) -> Option<()> {
+ let series = entry.text.remove(&TextField::Series)?;
+ let number = entry
+ .number
+ .remove(&NumberField::Number)
+ .map_or(String::new(), |number| format!(" {}", number));
+
+ self.builder.push(
+ Inline::Regular(format!("{}{}", series.text, number)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn volumes(&mut self, entry: &mut EntryData) -> Option<()> {
+ let volumes = entry.number.remove(&NumberField::Volume)?;
+ self.builder.push(
+ Inline::Regular(format!("{volumes} vols")),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn book_volume(&mut self, entry: &mut EntryData) -> Option<()> {
+ let volume = entry.number.remove(&NumberField::Volume)?;
+ let part = entry
+ .number
+ .remove(&NumberField::Part)
+ .map_or(String::new(), |part| format!(".{part}"));
+
+ self.builder.push(
+ Inline::Regular(format!("Vol. {}{}", volume, part)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn pubstate(&mut self, entry: &mut EntryData) -> Option<()> {
+ let pubstate = entry.text.remove(&TextField::Pubstate)?;
+ self.builder
+ .push(Inline::Regular(pubstate.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn addendum(&mut self, entry: &mut EntryData) -> Option<()> {
+ let addendum = entry.text.remove(&TextField::Addendum)?;
+ self.builder
+ .push(Inline::Regular(addendum.text), Punct::Dot, Punct::Dot);
+
+ Some(())
+ }
+
+ fn edition(&mut self, entry: &mut EntryData) -> Option<()> {
+ let text = match entry.number.remove(&NumberField::Edition)? {
+ NumberFieldData::Scalar(1) => "1st".to_string(),
+ NumberFieldData::Scalar(2) => "2nd".to_string(),
+ NumberFieldData::Scalar(3) => "3rd".to_string(),
+ NumberFieldData::Scalar(number) => format!("{}th", number),
+ NumberFieldData::Range(_, _) => return None,
+ NumberFieldData::Other(text) => text,
+ };
+
+ self.builder
+ .push(Inline::Regular(text), Punct::Dot, Punct::Dot);
+ Some(())
+ }
+
+ fn introduction(&mut self, entry: &mut EntryData) -> Option<()> {
+ let author = entry.author.remove(&AuthorField::Introduction)?;
+ self.builder.push(
+ Inline::Regular(format!("With an intro. by {}", author)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn foreword(&mut self, entry: &mut EntryData) -> Option<()> {
+ let author = entry.author.remove(&AuthorField::Commentator)?;
+ self.builder.push(
+ Inline::Regular(format!("With a forew. by {}", author)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn afterword(&mut self, entry: &mut EntryData) -> Option<()> {
+ let author = entry.author.remove(&AuthorField::Commentator)?;
+ self.builder.push(
+ Inline::Regular(format!("With an afterw. by {}", author)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn note(&mut self, entry: &mut EntryData) -> Option<()> {
+ let note = entry.text.remove(&TextField::Note)?;
+ self.builder
+ .push(Inline::Regular(note.text), Punct::Dot, Punct::Comma);
+
+ Some(())
+ }
+
+ fn pages(&mut self, entry: &mut EntryData) -> Option<()> {
+ let pages = entry.number.remove(&NumberField::Pages)?;
+ self.builder.push(
+ Inline::Regular(pages.to_string()),
+ Punct::Comma,
+ Punct::Space,
+ );
+
+ Some(())
+ }
+
+ fn page_total(&mut self, entry: &mut EntryData) -> Option<()> {
+ let page_total = entry.number.remove(&NumberField::PageTotal)?;
+ self.builder.push(
+ Inline::Regular(format!("{} pp", page_total)),
+ Punct::Dot,
+ Punct::Dot,
+ );
+
+ Some(())
+ }
+
+ fn journal(&mut self, entry: &mut EntryData) -> Option<()> {
+ let title = entry
+ .text
+ .remove(&TextField::Journal)
+ .or_else(|| entry.text.remove(&TextField::JournalTitle))?;
+
+ let subtitle = entry
+ .text
+ .remove(&TextField::JournalSubtitle)
+ .map_or(String::new(), |data| format!(" {}", data.text));
+
+ self.builder.push(
+ Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))),
+ Punct::Dot,
+ Punct::Space,
+ );
+
+ Some(())
+ }
+
+ fn eid(&mut self, entry: &mut EntryData) -> Option<()> {
+ let eid = entry.text.remove(&TextField::Eid)?;
+ self.builder
+ .push(Inline::Regular(eid.text), Punct::Comma, Punct::Space);
+
+ Some(())
+ }
+
+ fn isbn(&mut self, entry: &mut EntryData) -> Option<()> {
+ let isbn = entry.text.remove(&TextField::Isbn)?;
+ self.builder.push(
+ Inline::Regular("ISBN".to_string()),
+ Punct::Dot,
+ Punct::Colon,
+ );
+
+ self.builder
+ .push(Inline::Regular(isbn.text), Punct::Space, Punct::Dot);
+
+ Some(())
+ }
+
+ fn issn(&mut self, entry: &mut EntryData) -> Option<()> {
+ let issn = entry.text.remove(&TextField::Issn)?;
+ self.builder.push(
+ Inline::Regular("ISSN".to_string()),
+ Punct::Dot,
+ Punct::Colon,
+ );
+
+ self.builder
+ .push(Inline::Regular(issn.text), Punct::Space, Punct::Dot);
+
+ Some(())
+ }
+
+ fn url(&mut self, entry: &mut EntryData) -> Option<()> {
+ let url = entry.text.remove(&TextField::Url)?;
+
+ self.builder
+ .push(Inline::Regular("URL".to_string()), Punct::Dot, Punct::Colon);
+
+ let url = url.text;
+ let alt = url.clone();
+
+ self.builder
+ .push(Inline::Link { url, alt }, Punct::Space, Punct::Space);
+
+ let date = entry.date.remove(&DateField::UrlDate)?;
+ self.builder.push(
+ Inline::Regular(format!("({date})")),
+ Punct::Space,
+ Punct::Space,
+ );
+
+ Some(())
+ }
+
+ fn doi(&mut self, entry: &mut EntryData) -> Option<()> {
+ let doi = entry.text.remove(&TextField::Doi)?;
+ self.builder
+ .push(Inline::Regular("DOI".to_string()), Punct::Dot, Punct::Colon);
+
+ let alt = Url::parse(&doi.text)
+ .ok()
+ .filter(|url| !url.cannot_be_a_base())
+ .map_or(doi.text, |url| url.path()[1..].to_string());
+
+ let url = format!("https://doi.org/{alt}");
+
+ self.builder
+ .push(Inline::Link { url, alt }, Punct::Space, Punct::Dot);
+
+ Some(())
+ }
+
+ fn eprint(&mut self, entry: &mut EntryData) -> Option<()> {
+ let eprint = entry.text.remove(&TextField::Eprint)?;
+ let eprint_type = entry
+ .text
+ .remove(&TextField::EprintType)
+ .map_or_else(|| "eprint".to_string(), |data| data.text);
+
+ if eprint_type.eq_ignore_ascii_case("arxiv") {
+ self.builder.push(
+ Inline::Regular("arXiv".to_string()),
+ Punct::Dot,
+ Punct::Colon,
+ );
+
+ self.builder.push(
+ Inline::Link {
+ url: format!("https://arxiv.org/abs/{}", eprint.text),
+ alt: eprint.text,
+ },
+ Punct::Space,
+ Punct::Dot,
+ );
+ } else {
+ self.builder
+ .push(Inline::Regular(eprint_type), Punct::Dot, Punct::Colon);
+
+ self.builder
+ .push(Inline::Regular(eprint.text), Punct::Space, Punct::Dot);
+ }
+
+ Some(())
+ }
+
+ pub fn finish(self) -> impl Iterator<Item = (Inline, Punct)> {
+ self.builder.finish()
+ }
+}
diff --git a/support/texlab/src/citation/entry.rs b/support/texlab/src/citation/entry.rs
new file mode 100644
index 0000000000..c8b3f98753
--- /dev/null
+++ b/support/texlab/src/citation/entry.rs
@@ -0,0 +1,123 @@
+use std::str::FromStr;
+
+use rustc_hash::FxHashMap;
+use strum::EnumString;
+
+use crate::syntax::bibtex::{Entry, Field, HasName, HasType};
+
+use super::field::{
+ author::{AuthorField, AuthorFieldData},
+ date::{DateField, DateFieldData},
+ number::{NumberField, NumberFieldData},
+ text::{TextField, TextFieldData},
+};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)]
+#[strum(ascii_case_insensitive)]
+pub enum EntryKind {
+ Article,
+ Book,
+ MVBook,
+ InBook,
+ BookInBook,
+ SuppBook,
+ Booklet,
+ Collection,
+ MVCollection,
+ InCollection,
+ SuppCollection,
+ DataSet,
+ Manual,
+ Misc,
+ Online,
+ Electronic,
+ Www,
+ Patent,
+ Periodical,
+ SuppPeriodical,
+ Proceedings,
+ MVProceedings,
+ InProceedings,
+ Conference,
+ Reference,
+ MVReference,
+ InReference,
+ Report,
+ Set,
+ Software,
+ Thesis,
+ MasterThesis,
+ PhdThesis,
+ TechReport,
+ Unknown,
+}
+
+impl Default for EntryKind {
+ fn default() -> Self {
+ Self::Unknown
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct EntryData {
+ pub kind: EntryKind,
+ pub text: FxHashMap<TextField, TextFieldData>,
+ pub author: FxHashMap<AuthorField, AuthorFieldData>,
+ pub date: FxHashMap<DateField, DateFieldData>,
+ pub number: FxHashMap<NumberField, NumberFieldData>,
+}
+
+impl EntryData {
+ pub fn parse(entry: &Entry) -> Option<Self> {
+ let mut data = EntryData {
+ kind: entry
+ .type_token()
+ .and_then(|token| EntryKind::from_str(&token.text()[1..]).ok())
+ .unwrap_or(EntryKind::Unknown),
+ ..EntryData::default()
+ };
+
+ for field in entry.fields() {
+ let _ = data.parse_field(&field);
+ }
+
+ Some(data)
+ }
+
+ fn parse_field(&mut self, field: &Field) -> Option<()> {
+ let name = field.name_token()?;
+ let name = name.text();
+ self.parse_author_field(field, name)
+ .or_else(|| self.parse_date_field(field, name))
+ .or_else(|| self.parse_number_field(field, name))
+ .or_else(|| self.parse_text_field(field, name))
+ }
+
+ fn parse_author_field(&mut self, field: &Field, name: &str) -> Option<()> {
+ let name = AuthorField::parse(name)?;
+ let data = AuthorFieldData::parse(field)?;
+ self.author.insert(name, data);
+ Some(())
+ }
+
+ fn parse_date_field(&mut self, field: &Field, name: &str) -> Option<()> {
+ let name = DateField::parse(name)?;
+ let data = DateFieldData::parse(field)?;
+ self.date.insert(name, data);
+ Some(())
+ }
+
+ fn parse_number_field(&mut self, field: &Field, name: &str) -> Option<()> {
+ let name = NumberField::parse(name)?;
+ let data = NumberFieldData::parse(field)?;
+ self.number.insert(name, data);
+ Some(())
+ }
+
+ fn parse_text_field(&mut self, field: &Field, name: &str) -> Option<()> {
+ let name = TextField::parse(name).unwrap_or(TextField::Unknown);
+ let data = TextFieldData::parse(field)?;
+ self.text.insert(name, data);
+ Some(())
+ }
+}
diff --git a/support/texlab/src/citation/field.rs b/support/texlab/src/citation/field.rs
new file mode 100644
index 0000000000..d9f3adacab
--- /dev/null
+++ b/support/texlab/src/citation/field.rs
@@ -0,0 +1,4 @@
+pub mod author;
+pub mod date;
+pub mod number;
+pub mod text;
diff --git a/support/texlab/src/citation/field/author.rs b/support/texlab/src/citation/field/author.rs
new file mode 100644
index 0000000000..a5e4368320
--- /dev/null
+++ b/support/texlab/src/citation/field/author.rs
@@ -0,0 +1,73 @@
+use std::{borrow::Cow, fmt, str::FromStr};
+
+use human_name::Name;
+use itertools::Itertools;
+use strum::EnumString;
+
+use crate::syntax::bibtex::Field;
+
+use super::text::TextFieldData;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)]
+#[strum(ascii_case_insensitive)]
+pub enum AuthorField {
+ Afterword,
+ Annotator,
+ Author,
+ Commentator,
+ Editor,
+ EditorA,
+ EditorB,
+ EditorC,
+ Foreword,
+ Introduction,
+ Translator,
+}
+
+impl AuthorField {
+ pub fn parse(input: &str) -> Option<Self> {
+ Self::from_str(input).ok()
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct AuthorFieldData {
+ pub authors: Vec<Name>,
+}
+
+impl fmt::Display for AuthorFieldData {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let names = self
+ .authors
+ .iter()
+ .map(|author| author.display_initial_surname());
+
+ for part in Itertools::intersperse(names, Cow::Borrowed(", ")) {
+ write!(f, "{}", part)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl AuthorFieldData {
+ pub fn parse(field: &Field) -> Option<Self> {
+ let TextFieldData { text } = TextFieldData::parse(field)?;
+ let mut authors = Vec::new();
+ let mut words = Vec::new();
+ for word in text.split_whitespace() {
+ if word.eq_ignore_ascii_case("and") {
+ authors.push(Name::parse(&words.join(" "))?);
+ words.clear();
+ } else {
+ words.push(word);
+ }
+ }
+
+ if !words.is_empty() {
+ authors.push(Name::parse(&words.join(" "))?);
+ }
+
+ Some(Self { authors })
+ }
+}
diff --git a/support/texlab/src/citation/field/date.rs b/support/texlab/src/citation/field/date.rs
new file mode 100644
index 0000000000..b9b3dbb438
--- /dev/null
+++ b/support/texlab/src/citation/field/date.rs
@@ -0,0 +1,81 @@
+use std::{fmt, ops::Add, str::FromStr};
+
+use chrono::{Datelike, Month, NaiveDate};
+use strum::EnumString;
+
+use crate::syntax::bibtex::Field;
+
+use super::text::TextFieldData;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)]
+#[strum(ascii_case_insensitive)]
+pub enum DateField {
+ Date,
+ EventDate,
+ Month,
+ UrlDate,
+ Year,
+}
+
+impl DateField {
+ pub fn parse(input: &str) -> Option<Self> {
+ Self::from_str(input).ok()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub enum DateFieldData {
+ Date(NaiveDate),
+ Year(i32),
+ Month(Month),
+ Other(String),
+}
+
+impl Add for DateFieldData {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ match (self, rhs) {
+ (date @ Self::Year(_), Self::Year(_))
+ | (date @ Self::Month(_), Self::Month(_))
+ | (date @ Self::Date(_), Self::Date(_))
+ | (Self::Other(_), date)
+ | (date, Self::Other(_)) => date,
+ (Self::Year(year), Self::Month(month)) | (Self::Month(month), Self::Year(year)) => {
+ let new_date = NaiveDate::from_ymd(year, month.number_from_month(), 1);
+ Self::Date(new_date)
+ }
+ (Self::Year(year), Self::Date(date)) | (Self::Date(date), Self::Year(year)) => {
+ let new_date = date.with_year(year).unwrap_or(date);
+ Self::Date(new_date)
+ }
+ (Self::Date(date), Self::Month(month)) | (Self::Month(month), Self::Date(date)) => {
+ let new_date = date.with_month(month.number_from_month()).unwrap_or(date);
+ Self::Date(new_date)
+ }
+ }
+ }
+}
+
+impl fmt::Display for DateFieldData {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Year(year) => write!(f, "{}", year),
+ Self::Date(date) => write!(f, "{}", date.format("%b. %Y")),
+ Self::Month(month) => write!(f, "{}", month.name()),
+ Self::Other(text) => write!(f, "{}", text),
+ }
+ }
+}
+
+impl DateFieldData {
+ pub fn parse(field: &Field) -> Option<Self> {
+ let TextFieldData { text } = TextFieldData::parse(field)?;
+ NaiveDate::from_str(&text)
+ .ok()
+ .map(Self::Date)
+ .or_else(|| text.parse().ok().map(Self::Year))
+ .or_else(|| text.parse().ok().map(Self::Month))
+ .or(Some(Self::Other(text)))
+ }
+}
diff --git a/support/texlab/src/citation/field/number.rs b/support/texlab/src/citation/field/number.rs
new file mode 100644
index 0000000000..9f51ccef90
--- /dev/null
+++ b/support/texlab/src/citation/field/number.rs
@@ -0,0 +1,54 @@
+use std::{fmt, str::FromStr};
+
+use strum::EnumString;
+
+use crate::syntax::bibtex::Field;
+
+use super::text::TextFieldData;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)]
+#[strum(ascii_case_insensitive)]
+pub enum NumberField {
+ Edition,
+ Number,
+ Pages,
+ PageTotal,
+ Part,
+ Volume,
+ Volumes,
+}
+
+impl NumberField {
+ pub fn parse(input: &str) -> Option<Self> {
+ Self::from_str(input).ok()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub enum NumberFieldData {
+ Scalar(u32),
+ Range(u32, u32),
+ Other(String),
+}
+
+impl fmt::Display for NumberFieldData {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Scalar(value) => write!(f, "{}", value),
+ Self::Range(start, end) => write!(f, "{}-{}", start, end),
+ Self::Other(value) => write!(f, "{}", value.replace("--", "-")),
+ }
+ }
+}
+
+impl NumberFieldData {
+ pub fn parse(field: &Field) -> Option<Self> {
+ let TextFieldData { text } = TextFieldData::parse(field)?;
+ text.split_once("--")
+ .or_else(|| text.split_once('-'))
+ .and_then(|(a, b)| Some((a.parse().ok()?, b.parse().ok()?)))
+ .map(|(a, b)| Self::Range(a, b))
+ .or_else(|| text.parse().ok().map(Self::Scalar))
+ .or(Some(Self::Other(text)))
+ }
+}
diff --git a/support/texlab/src/citation/field/text.rs b/support/texlab/src/citation/field/text.rs
new file mode 100644
index 0000000000..87119bd47c
--- /dev/null
+++ b/support/texlab/src/citation/field/text.rs
@@ -0,0 +1,263 @@
+use std::str::FromStr;
+
+use rowan::{ast::AstNode, NodeOrToken};
+use rustc_hash::FxHashSet;
+use strum::EnumString;
+
+use crate::syntax::bibtex::{
+ Accent, Command, CurlyGroup, Field, HasAccentName, HasCommandName, HasName, HasValue, HasWord,
+ Join, Literal, QuoteGroup, Root, SyntaxKind::*, SyntaxToken, Value,
+};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, EnumString)]
+#[strum(ascii_case_insensitive)]
+pub enum TextField {
+ Abstract,
+ Addendum,
+ BookSubtitle,
+ BookTitle,
+ BookTitleAddon,
+ Chapter,
+ Doi,
+ EditorType,
+ EditorTypeA,
+ EditorTypeB,
+ EditorTypeC,
+ Eid,
+ Eprint,
+ EprintClass,
+ EprintType,
+ EventTitle,
+ EventTitleAddon,
+ Holder,
+ HowPublished,
+ Isbn,
+ Issn,
+ Issue,
+ IssueSubtitle,
+ IssueTitle,
+ IssueTitleAddon,
+ Journal,
+ JournalSubtitle,
+ JournalTitle,
+ JournalTitleAddon,
+ Language,
+ Location,
+ MainTitle,
+ MainSubtitle,
+ MainTitleAddon,
+ Note,
+ OrigLanguage,
+ Publisher,
+ Pubstate,
+ Series,
+ Subtitle,
+ Title,
+ TitleAddon,
+ Type,
+ Unknown,
+ Url,
+ Venue,
+ Version,
+}
+
+impl TextField {
+ pub fn parse(input: &str) -> Option<Self> {
+ Self::from_str(input).ok()
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Default)]
+pub struct TextFieldData {
+ pub text: String,
+}
+
+impl TextFieldData {
+ pub fn parse(field: &Field) -> Option<Self> {
+ let mut builder = TextFieldDataBuilder::default();
+ builder.visit_value(&field.value()?)?;
+ Some(builder.data)
+ }
+}
+
+#[derive(Default)]
+struct TextFieldDataBuilder {
+ data: TextFieldData,
+ string_stack: FxHashSet<String>,
+}
+
+impl TextFieldDataBuilder {
+ fn visit_value(&mut self, value: &Value) -> Option<()> {
+ match value {
+ Value::Literal(lit) => {
+ self.visit_literal(lit)?;
+ }
+ Value::CurlyGroup(group) => {
+ self.visit_curly_group(group)?;
+ }
+ Value::QuoteGroup(group) => {
+ self.visit_quote_group(group)?;
+ }
+ Value::Join(join) => {
+ self.visit_join(join)?;
+ }
+ Value::Accent(accent) => {
+ let _ = self.visit_accent(accent);
+ }
+ Value::Command(command) => {
+ let _ = self.visit_command(command);
+ }
+ };
+
+ Some(())
+ }
+
+ fn visit_literal(&mut self, lit: &Literal) -> Option<()> {
+ if lit
+ .name_token()
+ .and_then(|name| self.visit_string_reference(&name))
+ .is_none()
+ {
+ lit.syntax()
+ .text()
+ .for_each_chunk(|text| self.data.text.push_str(text));
+ }
+
+ Some(())
+ }
+
+ fn visit_string_reference(&mut self, name: &SyntaxToken) -> Option<()> {
+ let root = Root::cast(name.ancestors().last()?)?;
+ let name = name.text();
+
+ let value = root
+ .strings()
+ .filter(|string| {
+ string
+ .name_token()
+ .map_or(false, |token| token.text() == name)
+ })
+ .find_map(|string| string.value())?;
+
+ if !self.string_stack.insert(name.to_string()) {
+ return None;
+ }
+
+ let _ = self.visit_value(&value);
+ self.string_stack.remove(name);
+ Some(())
+ }
+
+ fn visit_curly_group(&mut self, group: &CurlyGroup) -> Option<()> {
+ for child in group.syntax().children_with_tokens() {
+ match child {
+ NodeOrToken::Node(node) => {
+ self.visit_value(&Value::cast(node)?)?;
+ }
+ NodeOrToken::Token(token) => {
+ match token.kind() {
+ L_CURLY | R_CURLY => (),
+ WHITESPACE | NBSP => self.data.text.push(' '),
+ _ => self.data.text.push_str(token.text()),
+ };
+ }
+ };
+ }
+
+ Some(())
+ }
+
+ fn visit_quote_group(&mut self, group: &QuoteGroup) -> Option<()> {
+ for child in group.syntax().children_with_tokens() {
+ match child {
+ NodeOrToken::Node(node) => {
+ self.visit_value(&Value::cast(node)?)?;
+ }
+ NodeOrToken::Token(token) => {
+ match token.kind() {
+ QUOTE => (),
+ WHITESPACE | NBSP => self.data.text.push(' '),
+ _ => self.data.text.push_str(token.text()),
+ };
+ }
+ };
+ }
+
+ Some(())
+ }
+
+ fn visit_join(&mut self, join: &Join) -> Option<()> {
+ if let Some(left) = join.left_value() {
+ self.visit_value(&left)?;
+ }
+
+ if let Some(right) = join.right_value() {
+ self.visit_value(&right)?;
+ }
+
+ Some(())
+ }
+
+ fn visit_accent(&mut self, accent: &Accent) -> Option<()> {
+ let name = accent.accent_name_token()?;
+ let word = accent.word_token()?;
+
+ let mut chars = word.text().chars();
+ let a = chars.next()?;
+
+ if chars.next().is_some() {
+ self.data.text.push_str(word.text());
+ } else {
+ let b = match name.text() {
+ r#"\`"# => '\u{0300}',
+ r#"\'"# => '\u{0301}',
+ r#"\^"# => '\u{0302}',
+ r#"\""# => '\u{0308}',
+ r#"\H"# => '\u{030B}',
+ r#"\~"# => '\u{0303}',
+
+ r#"\c"# => '\u{0327}',
+ r#"\k"# => '\u{0328}',
+ r#"\="# => '\u{0304}',
+ r#"\b"# => '\u{0331}',
+ r#"\."# => '\u{0307}',
+ r#"\d"# => '\u{0323}',
+ r#"\r"# => '\u{030A}',
+ r#"\u"# => '\u{0306}',
+ r#"\v"# => '\u{030C}',
+ _ => '\u{0000}',
+ };
+
+ match unicode_normalization::char::compose(a, b) {
+ Some(c) => self.data.text.push(c),
+ None => self.data.text.push_str(word.text()),
+ };
+ }
+
+ Some(())
+ }
+
+ fn visit_command(&mut self, command: &Command) -> Option<()> {
+ let name = command.command_name_token()?;
+ let replacement = match name.text() {
+ r#"\l"# => "\u{0142}",
+ r#"\o"# => "\u{00F8}",
+ r#"\i"# => "\u{0131}",
+ r#"\&"# => "&",
+ r#"\$"# => "$",
+ r#"\{"# => "{",
+ r#"\}"# => "}",
+ r#"\%"# => "%",
+ r#"\#"# => "#",
+ r#"\_"# => "_",
+ r#"\ "# | r#"\,"# | r#"\;"# => " ",
+ r#"\hyphen"# => "-",
+ r#"\TeX"# => "TeX",
+ r#"\LaTeX"# => "LaTeX",
+ text => text,
+ };
+
+ self.data.text.push_str(replacement);
+ Some(())
+ }
+}
diff --git a/support/texlab/src/citation/output.rs b/support/texlab/src/citation/output.rs
new file mode 100644
index 0000000000..555ec138db
--- /dev/null
+++ b/support/texlab/src/citation/output.rs
@@ -0,0 +1,87 @@
+use std::ops::Add;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
+pub enum Punct {
+ Nothing,
+ Space,
+ Comma,
+ Dot,
+ Colon,
+}
+
+impl Punct {
+ pub fn as_str(self) -> &'static str {
+ match self {
+ Self::Nothing => "",
+ Self::Space => " ",
+ Self::Comma => ", ",
+ Self::Dot => ". ",
+ Self::Colon => ": ",
+ }
+ }
+}
+
+impl Add for Punct {
+ type Output = Punct;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ match (self, rhs) {
+ (Self::Nothing, Self::Nothing) => Self::Nothing,
+ (Self::Nothing, Self::Space)
+ | (Self::Space, Self::Nothing)
+ | (Self::Space, Self::Space) => Self::Space,
+ (Self::Nothing, Self::Comma)
+ | (Self::Space, Self::Comma)
+ | (Self::Comma, Self::Nothing)
+ | (Self::Comma, Self::Space)
+ | (Self::Comma, Self::Comma)
+ | (Self::Comma, Self::Dot)
+ | (Self::Dot, Self::Comma) => Self::Comma,
+ (Self::Nothing, Self::Dot)
+ | (Self::Space, Self::Dot)
+ | (Self::Dot, Self::Nothing)
+ | (Self::Dot, Self::Space)
+ | (Self::Dot, Self::Dot) => Self::Dot,
+ (Self::Nothing, Self::Colon)
+ | (Self::Space, Self::Colon)
+ | (Self::Comma, Self::Colon)
+ | (Self::Dot, Self::Colon)
+ | (Self::Colon, Self::Nothing)
+ | (Self::Colon, Self::Space)
+ | (Self::Colon, Self::Comma)
+ | (Self::Colon, Self::Dot)
+ | (Self::Colon, Self::Colon) => Self::Colon,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
+pub enum Inline {
+ Regular(String),
+ Italic(String),
+ Quoted(String),
+ Link { url: String, alt: String },
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Default)]
+pub struct InlineBuilder {
+ items: Vec<(Inline, Punct)>,
+}
+
+impl InlineBuilder {
+ pub fn push(&mut self, inline: Inline, leading: Punct, trailing: Punct) {
+ if let Some((_, last)) = self.items.last_mut() {
+ *last = *last + leading;
+ }
+
+ self.items.push((inline, trailing));
+ }
+
+ pub fn finish(mut self) -> impl Iterator<Item = (Inline, Punct)> {
+ if let Some((_, last)) = self.items.last_mut() {
+ *last = Punct::Nothing;
+ }
+
+ self.items.into_iter()
+ }
+}
diff --git a/support/texlab/src/citation/ris.rs b/support/texlab/src/citation/ris.rs
deleted file mode 100644
index 041fb654a0..0000000000
--- a/support/texlab/src/citation/ris.rs
+++ /dev/null
@@ -1,471 +0,0 @@
-// Ported from: https://github.com/michel-kraemer/citeproc-java/tree/master/citeproc-java/templates
-// Michel Kraemer
-// Apache License 2.0
-use citeproc_io::{unicode::is_latin_cyrillic, Date, DateOrRange, Name, NumberLike, Reference};
-use csl::*;
-use fnv::FnvHashMap;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
-#[serde(rename_all = "UPPERCASE")]
-pub enum RisType {
- Abst,
- Advs,
- Aggr,
- Ancient,
- Art,
- Bill,
- Blog,
- Book,
- Case,
- Chap,
- Chart,
- Clswk,
- Comp,
- Conf,
- Cpaper,
- Ctlg,
- Data,
- Dbase,
- Dict,
- Ebook,
- Echap,
- Edbook,
- Ejour,
- Elec,
- Encyc,
- Equa,
- Figure,
- Gen,
- Govdoc,
- Grant,
- Hear,
- Icomm,
- Inpr,
- Jfull,
- Jour,
- Legal,
- Manscpt,
- Map,
- Mgzn,
- Mpct,
- Multi,
- Music,
- News,
- Pamp,
- Pat,
- Pcomm,
- Rprt,
- Ser,
- Slide,
- Sound,
- Stand,
- Stat,
- Std,
- Thes,
- Unpb,
- Video,
-}
-
-impl RisType {
- pub fn parse(ty: &str) -> Option<Self> {
- let ty = format!("\"{}\"", ty);
- serde_json::from_str(&ty).ok()
- }
-
- pub fn csl(self) -> CslType {
- match self {
- Self::Abst => CslType::Article,
- Self::Advs => CslType::Article,
- Self::Aggr => CslType::Dataset,
- Self::Ancient => CslType::Article,
- Self::Art => CslType::Article,
- Self::Bill => CslType::Bill,
- Self::Blog => CslType::Webpage,
- Self::Book => CslType::Book,
- Self::Case => CslType::LegalCase,
- Self::Chap => CslType::Chapter,
- Self::Chart => CslType::Article,
- Self::Clswk => CslType::Article,
- Self::Comp => CslType::Article,
- Self::Conf => CslType::PaperConference,
- Self::Cpaper => CslType::PaperConference,
- Self::Ctlg => CslType::Book,
- Self::Data => CslType::Dataset,
- Self::Dbase => CslType::Dataset,
- Self::Dict => CslType::EntryDictionary,
- Self::Ebook => CslType::Book,
- Self::Echap => CslType::Chapter,
- Self::Edbook => CslType::Book,
- Self::Ejour => CslType::ArticleJournal,
- Self::Elec => CslType::Article,
- Self::Encyc => CslType::EntryEncyclopedia,
- Self::Equa => CslType::Article,
- Self::Figure => CslType::Figure,
- Self::Gen => CslType::Article,
- Self::Govdoc => CslType::Legislation,
- Self::Grant => CslType::Legislation,
- Self::Hear => CslType::Article,
- Self::Icomm => CslType::PersonalCommunication,
- Self::Inpr => CslType::PaperConference,
- Self::Jfull => CslType::ArticleJournal,
- Self::Jour => CslType::ArticleJournal,
- Self::Legal => CslType::Legislation,
- Self::Manscpt => CslType::Manuscript,
- Self::Map => CslType::Map,
- Self::Mgzn => CslType::ArticleMagazine,
- Self::Mpct => CslType::MotionPicture,
- Self::Multi => CslType::Webpage,
- Self::Music => CslType::Song,
- Self::News => CslType::ArticleNewspaper,
- Self::Pamp => CslType::Pamphlet,
- Self::Pat => CslType::Patent,
- Self::Pcomm => CslType::PersonalCommunication,
- Self::Rprt => CslType::Report,
- Self::Ser => CslType::Article,
- Self::Slide => CslType::Article,
- Self::Sound => CslType::Song,
- Self::Stand => CslType::Article,
- Self::Stat => CslType::Legislation,
- Self::Std => CslType::Article,
- Self::Thes => CslType::Thesis,
- Self::Unpb => CslType::Article,
- Self::Video => CslType::MotionPicture,
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
-pub struct RisReference {
- pub id: Option<String>,
- pub ty: Option<RisType>,
- pub editors: Vec<String>,
- pub tertiary_authors: Vec<String>,
- pub subsidiary_authors: Vec<String>,
- pub abstrct: Option<String>,
- pub author_address: Option<String>,
- pub accession_number: Option<String>,
- pub authors: Vec<String>,
- pub book_or_conference: Option<String>,
- pub custom1: Option<String>,
- pub custom2: Option<String>,
- pub custom3: Option<String>,
- pub custom4: Option<String>,
- pub custom5: Option<String>,
- pub custom6: Option<String>,
- pub custom7: Option<String>,
- pub custom8: Option<String>,
- pub caption: Option<String>,
- pub call_number: Option<String>,
- pub place: Option<String>,
- pub date: Option<String>,
- pub name_of_database: Option<String>,
- pub doi: Option<String>,
- pub database_provider: Option<String>,
- pub end_page: Option<String>,
- pub edition: Option<String>,
- pub issue: Option<String>,
- pub journal: Option<String>,
- pub keywords: Vec<String>,
- pub file_attachments: Vec<String>,
- pub figure: Option<String>,
- pub language: Option<String>,
- pub label: Option<String>,
- pub number: Option<String>,
- pub type_of_work: Option<String>,
- pub notes: Vec<String>,
- pub number_of_volumes: Option<String>,
- pub original_publication: Option<String>,
- pub publisher: Option<String>,
- pub year: Option<String>,
- pub reviewed_item: Option<String>,
- pub research_notes: Option<String>,
- pub reprint_edition: Option<String>,
- pub section: Option<String>,
- pub isbn_or_issn: Option<String>,
- pub start_page: Option<String>,
- pub short_title: Option<String>,
- pub primary_title: Option<String>,
- pub secondardy_title: Option<String>,
- pub tertiary_title: Option<String>,
- pub translated_authors: Vec<String>,
- pub title: Option<String>,
- pub translated_title: Option<String>,
- pub url: Option<String>,
- pub volume: Option<String>,
- pub access_date: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
-pub struct RisLibrary {
- pub references: Vec<RisReference>,
-}
-
-impl RisLibrary {
- pub fn parse<'a, I: Iterator<Item = &'a str>>(lines: I) -> Self {
- let mut library = Self {
- references: Vec::new(),
- };
-
- let mut reference: RisReference = RisReference::default();
- for line in lines {
- let line = line.trim();
- if line == "" {
- continue;
- }
-
- if line == "ER -" {
- library.references.push(reference);
- reference = RisReference::default();
- continue;
- }
-
- let chars: Vec<_> = line.chars().collect();
- if chars.len() < 7 || chars[4] != '-' {
- continue;
- }
-
- let key: String = (&chars[..2]).iter().collect();
- let value: String = (&chars[6..]).iter().collect();
- match key.to_uppercase().as_str() {
- "TY" => reference.ty = RisType::parse(&value),
- "A2" => reference.editors.push(value),
- "A3" => reference.tertiary_authors.push(value),
- "A4" => reference.subsidiary_authors.push(value),
- "AB" => reference.abstrct = Some(value),
- "AD" => reference.author_address = Some(value),
- "AN" => reference.accession_number = Some(value),
- "AU" => reference.authors.push(value),
- "BT" => reference.book_or_conference = Some(value),
- "C1" => reference.custom1 = Some(value),
- "C2" => reference.custom2 = Some(value),
- "C3" => reference.custom3 = Some(value),
- "C4" => reference.custom4 = Some(value),
- "C5" => reference.custom5 = Some(value),
- "C6" => reference.custom6 = Some(value),
- "C7" => reference.custom7 = Some(value),
- "C8" => reference.custom8 = Some(value),
- "CA" => reference.caption = Some(value),
- "CN" => reference.call_number = Some(value),
- "CY" => reference.place = Some(value),
- "DA" => reference.date = Some(value),
- "DB" => reference.name_of_database = Some(value),
- "DO" => reference.doi = Some(value),
- "DP" => reference.database_provider = Some(value),
- "ED" => reference.editors.push(value),
- "EP" => reference.end_page = Some(value),
- "ET" => reference.edition = Some(value),
- "ID" => reference.id = Some(value),
- "IS" => reference.issue = Some(value),
- "JO" => reference.journal = Some(value),
- "J2" => reference.journal = Some(value),
- "KW" => reference.keywords.push(value),
- "L1" => reference.file_attachments.push(value),
- "L4" => reference.figure = Some(value),
- "LA" => reference.language = Some(value),
- "LB" => reference.label = Some(value),
- "M1" => reference.number = Some(value),
- "M3" => reference.type_of_work = Some(value),
- "N1" => reference.notes.push(value),
- "N2" => reference.abstrct = Some(value),
- "NV" => reference.number_of_volumes = Some(value),
- "OP" => reference.original_publication = Some(value),
- "PB" => reference.publisher = Some(value),
- "PY" => reference.year = Some(value),
- "RI" => reference.reviewed_item = Some(value),
- "RN" => reference.research_notes = Some(value),
- "RP" => reference.reprint_edition = Some(value),
- "SE" => reference.section = Some(value),
- "SN" => reference.isbn_or_issn = Some(value),
- "SP" => reference.start_page = Some(value),
- "ST" => reference.short_title = Some(value),
- "T1" => reference.primary_title = Some(value),
- "T2" => reference.secondardy_title = Some(value),
- "T3" => reference.tertiary_title = Some(value),
- "TA" => reference.translated_authors.push(value),
- "TI" => reference.title = Some(value),
- "TT" => reference.translated_title = Some(value),
- "U1" => reference.type_of_work = Some(value),
- "UR" => reference.url = Some(value),
- "VL" => reference.volume = Some(value),
- "Y2" => reference.access_date = Some(value),
- _ => (),
- }
- }
- library
- }
-}
-
-impl Into<Reference> for RisReference {
- fn into(self) -> Reference {
- let csl_type = self.ty.expect("RIS type is missing").csl();
- let mut date: FnvHashMap<DateVariable, DateOrRange> = FnvHashMap::default();
- let mut name: FnvHashMap<NameVariable, Vec<Name>> = FnvHashMap::default();
- let mut number: FnvHashMap<NumberVariable, NumberLike> = FnvHashMap::default();
- let mut ordinary: FnvHashMap<Variable, String> = FnvHashMap::default();
-
- if let Some(access_date) = self.access_date {
- date.insert(DateVariable::Accessed, parse_date_or_range(access_date));
- }
-
- name.insert(NameVariable::Author, parse_authors(self.authors));
- name.insert(NameVariable::Editor, parse_authors(self.editors));
-
- if let Some(container_title) = self
- .journal
- .or(self.name_of_database)
- .or(self.book_or_conference)
- {
- ordinary.insert(Variable::ContainerTitle, container_title.clone());
- ordinary.insert(Variable::CollectionTitle, container_title);
- }
-
- if let Some(value) = self.date.or(self.year) {
- let value = parse_date_or_range(value);
- date.insert(DateVariable::Issued, value.clone());
- date.insert(DateVariable::EventDate, value);
- }
-
- if let Some(url) = self.url {
- ordinary.insert(Variable::URL, url);
- }
-
- let notes = self.notes;
- ordinary.insert(
- Variable::Note,
- self.research_notes.unwrap_or_else(|| notes.join("\n")),
- );
-
- if let Some(issue) = self.issue {
- number.insert(NumberVariable::Issue, parse_number(issue));
- }
-
- if let Some(num) = self.number {
- number.insert(NumberVariable::Number, parse_number(num));
- }
-
- if let Some(place) = self.place {
- ordinary.insert(Variable::EventPlace, place.clone());
- ordinary.insert(Variable::PublisherPlace, place);
- }
-
- if let Some(abstrct) = self.abstrct {
- ordinary.insert(Variable::Abstract, abstrct);
- }
-
- if let Some(call_number) = self.call_number {
- ordinary.insert(Variable::CallNumber, call_number);
- }
-
- if let Some(doi) = self.doi {
- ordinary.insert(Variable::DOI, doi);
- }
-
- if let Some(edition) = self.edition {
- number.insert(NumberVariable::Edition, parse_number(edition));
- }
-
- if let Some(isbn_or_issn) = self.isbn_or_issn {
- ordinary.insert(Variable::ISBN, isbn_or_issn.clone());
- ordinary.insert(Variable::ISSN, isbn_or_issn);
- }
-
- ordinary.insert(Variable::Keyword, self.keywords.join(", "));
-
- if let Some(number_of_volumes) = self.number_of_volumes {
- number.insert(
- NumberVariable::NumberOfVolumes,
- parse_number(number_of_volumes),
- );
- }
-
- if let Some(original_publication) = self.original_publication {
- ordinary.insert(Variable::OriginalTitle, original_publication);
- }
-
- match (self.start_page, self.end_page) {
- (Some(start_page), Some(end_page)) => {
- number.insert(
- NumberVariable::Page,
- NumberLike::Str(format!("{}-{}", start_page, end_page).into()),
- );
- }
- (Some(page), None) | (None, Some(page)) => {
- number.insert(NumberVariable::Page, parse_number(page));
- }
- (None, None) => {}
- }
-
- if let Some(publisher) = self.publisher {
- ordinary.insert(Variable::Publisher, publisher);
- }
-
- if let Some(reviewed_item) = self.reviewed_item {
- ordinary.insert(Variable::ReviewedTitle, reviewed_item);
- }
-
- if let Some(section) = self.section {
- ordinary.insert(Variable::Section, section);
- }
-
- if let Some(short_title) = self.short_title {
- ordinary.insert(Variable::TitleShort, short_title);
- }
-
- if let Some(title) = self.title {
- ordinary.insert(Variable::Title, title);
- }
-
- if let Some(volume) = self.volume {
- number.insert(NumberVariable::Volume, parse_number(volume));
- }
-
- Reference {
- id: Atom::from(self.id.or(self.label).expect("RIS id is missing").as_str()),
- csl_type,
- language: None,
- name,
- number,
- date,
- ordinary,
- }
- }
-}
-
-fn parse_number(value: String) -> NumberLike {
- match value.parse() {
- Ok(value) => NumberLike::Num(value),
- Err(_) => NumberLike::Str(value.into()),
- }
-}
-
-fn parse_authors(authors: Vec<String>) -> Vec<Name> {
- authors
- .into_iter()
- .map(|author| Name::Literal {
- is_latin_cyrillic: is_latin_cyrillic(&author),
- literal: author.into(),
- })
- .collect()
-}
-
-fn parse_date_or_range(value: String) -> DateOrRange {
- parse_date(&value)
- .map(DateOrRange::Single)
- .unwrap_or_else(|| DateOrRange::Literal {
- literal: value.into(),
- circa: false,
- })
-}
-
-fn parse_date(value: &str) -> Option<Date> {
- let mut parts = value.split('/');
- let year: i32 = parts.next()?.parse().ok()?;
- match parts.next().and_then(|p| p.parse().ok()) {
- Some(month) => match parts.next().and_then(|p| p.parse().ok()) {
- Some(day) => Some(Date::new(year, month, day)),
- None => Some(Date::new(year, month, 0)),
- },
- None => Some(Date::new(year, 0, 0)),
- }
-}
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_aksin_2006.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_aksin_2006.snap
new file mode 100644
index 0000000000..4598d47ed9
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_aksin_2006.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 64
+expression: "render_entry(r#\"\n@string{jomch = {J.~Organomet. Chem.}}\n\n@article{aksin,\n author = {Aks{\\i}n, {\\\"O}zge and T{\\\"u}rkmen, Hayati and Artok, Levent\n and {\\c{C}}etinkaya, Bekir and Ni, Chaoying and\n B{\\\"u}y{\\\"u}kg{\\\"u}ng{\\\"o}r, Orhan and {\\\"O}zkal, Erhan},\n title = {Effect of immobilization on catalytic characteristics of\n saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}\n reactions},\n journaltitle = jomch,\n date = 2006,\n volume = 691,\n number = 13,\n pages = {3027-3036},\n indextitle = {Effect of immobilization on catalytic characteristics},\n}\"#)"
+---
+O. Aksın, H. Türkmen, L. Artok, B. Çetinkaya, C. Ni, O. Büyükgüngör, E. Özkal: "Effect of immobilization on catalytic characteristics of saturated Pd-N-heterocyclic carbenes in Mizoroki-Heck reactions". *J. Organomet. Chem.* 691.13 (2006): 3027-3036.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_betram_1996.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_betram_1996.snap
new file mode 100644
index 0000000000..dd857419a5
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_betram_1996.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 87
+expression: "render_entry(r#\"\n@string{jams = {J.~Amer. Math. Soc.}}\n\n@article{bertram,\n author = {Bertram, Aaron and Wentworth, Richard},\n title = {Gromov invariants for holomorphic maps on {Riemann} surfaces},\n journaltitle = jams,\n date = 1996,\n volume = 9,\n number = 2,\n pages = {529-571},\n langid = {english},\n langidopts = {variant=american},\n shorttitle = {Gromov invariants},\n annotation = {An \\texttt{article} entry with a \\texttt{volume} and a\n \\texttt{number} field},\n}\"#)"
+---
+A. Bertram, R. Wentworth: "Gromov invariants for holomorphic maps on Riemann surfaces". *J. Amer. Math. Soc.* 9.2 (1996): 529-571.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_blom_2021.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_blom_2021.snap
new file mode 100644
index 0000000000..851b874fff
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_blom_2021.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 138
+expression: "render_entry(r#\"\n@article{DBLP:journals/corr/abs-2107-11903,\n author = {Michelle L. Blom and\n Jurlind Budurushi and\n Ronald L. Rivest and\n Philip B. Stark and\n Peter J. Stuckey and\n Vanessa Teague and\n Damjan Vukcevic},\n title = {Assertion-based Approaches to Auditing Complex Elections, with application\n to party-list proportional elections},\n journal = {CoRR},\n volume = {abs/2107.11903},\n year = {2021},\n url = {https://arxiv.org/abs/2107.11903},\n eprinttype = {arXiv},\n eprint = {2107.11903},\n timestamp = {Thu, 29 Jul 2021 16:14:15 +0200},\n biburl = {https://dblp.org/rec/journals/corr/abs-2107-11903.bib},\n bibsource = {dblp computer science bibliography, https://dblp.org}\n}\"#)"
+---
+M. Blom, J. Budurushi, R. Rivest, P. Stark, P. Stuckey, V. Teague, D. Vukcevic: "Assertion-based Approaches to Auditing Complex Elections, with application to party-list proportional elections". *CoRR* abs/2107.11903 (2021): arXiv: [2107.11903](https://arxiv.org/abs/2107.11903). URL: [https://arxiv.org/abs/2107.11903](https://arxiv.org/abs/2107.11903).
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_jain_1999.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_jain_1999.snap
new file mode 100644
index 0000000000..2eefc3a871
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_jain_1999.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 39
+expression: "render_entry(r#\"\n@article{10.1145/331499.331504,\n author = {Jain, A. K. and Murty, M. N. and Flynn, P. J.},\n title = {Data Clustering: A Review},\n year = {1999},\n issue_date = {Sept. 1999},\n publisher = {Association for Computing Machinery},\n address = {New York, NY, USA},\n volume = {31},\n number = {3},\n issn = {0360-0300},\n url = {https://doi.org/10.1145/331499.331504},\n doi = {10.1145/331499.331504},\n journal = {ACM Comput. Surv.},\n month = {sep},\n pages = {264-323},\n numpages = {60},\n keywords = {incremental clustering, clustering applications, exploratory data analysis, cluster analysis, similarity indices, unsupervised learning}\n}\"#)"
+---
+A. Jain, M. Murty, P. Flynn: "Data Clustering: A Review". *ACM Comput. Surv.* 31.3 (Sep. 1999): 264-323. ISSN: 0360-0300. DOI: [10.1145/331499.331504](https://doi.org/10.1145/331499.331504). URL: [https://doi.org/10.1145/331499.331504](https://doi.org/10.1145/331499.331504).
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_kastenholz_2006.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_kastenholz_2006.snap
new file mode 100644
index 0000000000..d1cae5db36
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_kastenholz_2006.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 110
+expression: "render_entry(r#\"\n@string{jchph = {J.~Chem. Phys.}}\n\n@article{kastenholz,\n author = {Kastenholz, M. A. and H{\\\"u}nenberger, Philippe H.},\n title = {Computation of methodology\\hyphen independent ionic solvation\n free energies from molecular simulations},\n journaltitle = jchph,\n date = 2006,\n subtitle = {{I}. {The} electrostatic potential in molecular liquids},\n volume = 124,\n eid = 124106,\n doi = {10.1063/1.2172593},\n langid = {english},\n langidopts = {variant=american},\n indextitle = {Computation of ionic solvation free energies},\n annotation = {An \\texttt{article} entry with an \\texttt{eid} and a\n \\texttt{doi} field. Note that the \\textsc{doi} is transformed\n into a clickable link if \\texttt{hyperref} support has been\n enabled},\n}\n \"#)"
+---
+M. Kastenholz, P. Hünenberger: "Computation of methodology- independent ionic solvation free energies from molecular simulations. I. The electrostatic potential in molecular liquids". *J. Chem. Phys.* 124, 124106 (2006): DOI: [10.1063/1.2172593](https://doi.org/10.1063/1.2172593).
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__article_rivest_1978.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_rivest_1978.snap
new file mode 100644
index 0000000000..3dfcb2967c
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__article_rivest_1978.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 15
+expression: "render_entry(r#\"\n@article{10.1145/359340.359342,\n author = {Rivest, R. L. and Shamir, A. and Adleman, L.},\n title = {A Method for Obtaining Digital Signatures and Public-Key Cryptosystems},\n year = {1978},\n issue_date = {Feb. 1978},\n publisher = {Association for Computing Machinery},\n address = {New York, NY, USA},\n volume = {21},\n number = {2},\n issn = {0001-0782},\n url = {https://doi.org/10.1145/359340.359342},\n doi = {10.1145/359340.359342},\n journal = {Commun. ACM},\n month = {feb},\n pages = {120-126},\n numpages = {7},\n}\"#)"
+---
+R. Rivest, A. Shamir, L. Adleman: "A Method for Obtaining Digital Signatures and Public-Key Cryptosystems". *Commun. ACM* 21.2 (Feb. 1978): 120-126. ISSN: 0001-0782. DOI: [10.1145/359340.359342](https://doi.org/10.1145/359340.359342). URL: [https://doi.org/10.1145/359340.359342](https://doi.org/10.1145/359340.359342).
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__book_aho_2006.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_aho_2006.snap
new file mode 100644
index 0000000000..34c0aeda3b
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_aho_2006.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 165
+expression: "render_entry(r#\"\n@book{10.5555/1177220,\n author = {Aho, Alfred V. and Lam, Monica S. and Sethi, Ravi and Ullman, Jeffrey D.},\n title = {Compilers: Principles, Techniques, and Tools (2nd Edition)},\n year = {2006},\n isbn = {0321486811},\n publisher = {Addison-Wesley Longman Publishing Co., Inc.},\n address = {USA}\n}\"#)"
+---
+A. Aho, M. Lam, R. Sethi, J. Ullman: "Compilers: Principles, Techniques, and Tools (2nd Edition)". Addison-Wesley Longman Publishing Co., Inc., 2006. ISBN: 0321486811.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__book_averroes_1998.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_averroes_1998.snap
new file mode 100644
index 0000000000..32b8a6ca5f
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_averroes_1998.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 180
+expression: "render_entry(r#\"\n@book{averroes/bland,\n author = {Averroes},\n title = {The Epistle on the Possibility of Conjunction with the Active\n Intellect by {Ibn Rushd} with the Commentary of {Moses Narboni}},\n date = 1982,\n editor = {Bland, Kalman P.},\n translator = {Bland, Kalman P.},\n series = {Moreshet: Studies in {Jewish} History, Literature and Thought},\n number = 7,\n publisher = {Jewish Theological Seminary of America},\n location = {New York},\n keywords = {primary},\n langid = {english},\n langidopts = {variant=american},\n indextitle = {Epistle on the Possibility of Conjunction, The},\n shorttitle = {Possibility of Conjunction},\n annotation = {A \\texttt{book} entry with a \\texttt{series} and a\n \\texttt{number}. Note the concatenation of the \\texttt{editor}\n and \\texttt{translator} fields as well as the\n \\texttt{indextitle} field},\n}\"#)"
+---
+"The Epistle on the Possibility of Conjunction with the Active Intellect by Ibn Rushd with the Commentary of Moses Narboni". Ed. by K. Bland. Trans. by K. Bland. Moreshet: Studies in Jewish History, Literature and Thought 7. New York: Jewish Theological Seminary of America, 1982.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__book_knuth_1984.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_knuth_1984.snap
new file mode 100644
index 0000000000..0276be0589
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__book_knuth_1984.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 208
+expression: "render_entry(r#\"\n@book{knuth:ct:a,\n author = {Knuth, Donald E.},\n title = {The {\\TeX book}},\n date = 1984,\n maintitle = {Computers \\& Typesetting},\n volume = {A},\n publisher = {Addison-Wesley},\n location = {Reading, Mass.},\n langid = {english},\n langidopts = {variant=american},\n sorttitle = {Computers & Typesetting A},\n indexsorttitle= {The TeXbook},\n indextitle = {\\protect\\TeX book, The},\n shorttitle = {\\TeX book},\n annotation = {The first volume of a five-volume book. Note the\n \\texttt{sorttitle} field. We want this\n volume to be listed after the entry referring to the entire\n five-volume set. Also note the \\texttt{indextitle} and\n \\texttt{indexsorttitle} fields. Indexing packages that don't\n generate robust index entries require some control sequences\n to be protected from expansion},\n}\n \"#)"
+---
+D. Knuth: "The TeX book". *Computers & Typesetting*. Vol. A. Reading, Mass.: Addison-Wesley, 1984.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__collection_matuz_1990.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__collection_matuz_1990.snap
new file mode 100644
index 0000000000..9ea78c80cf
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__collection_matuz_1990.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 313
+expression: "render_entry(r#\"\n@collection{matuz:doody,\n editor = {Matuz, Roger},\n title = {Contemporary Literary Criticism},\n year = 1990,\n volume = 61,\n publisher = {Gale},\n location = {Detroit},\n pages = {204-208},\n langid = {english},\n langidopts = {variant=american},\n annotation = {A \\texttt{collection} entry providing the excerpt information\n for the \\texttt{doody} entry. Note the format of the\n \\texttt{pages} field},\n}\"#)"
+---
+"Contemporary Literary Criticism". Ed. by R. Matuz. Vol. 61. Detroit: Gale, 1990, 204-208.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_combi_2004.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_combi_2004.snap
new file mode 100644
index 0000000000..9baef38be7
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_combi_2004.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 290
+expression: "render_entry(r#\"\n@inproceedings{10.1145/967900.968040,\n author = {Combi, Carlo and Pozzi, Giuseppe},\n title = {Architectures for a Temporal Workflow Management System},\n year = {2004},\n isbn = {1581138121},\n publisher = {Association for Computing Machinery},\n address = {New York, NY, USA},\n url = {https://doi.org/10.1145/967900.968040},\n doi = {10.1145/967900.968040},\n booktitle = {Proceedings of the 2004 ACM Symposium on Applied Computing},\n pages = {659-666},\n numpages = {8},\n keywords = {active DBMS, temporal DBMS, workflow management system - WfMS, temporal workflow management system},\n location = {Nicosia, Cyprus},\n series = {SAC '04}\n}\"#)"
+---
+C. Combi, G. Pozzi: "Architectures for a Temporal Workflow Management System". *Proceedings of the 2004 ACM Symposium on Applied Computing*. SAC '04. Nicosia, Cyprus: Association for Computing Machinery, 2004, 659-666. ISBN: 1581138121. DOI: [10.1145/967900.968040](https://doi.org/10.1145/967900.968040). URL: [https://doi.org/10.1145/967900.968040](https://doi.org/10.1145/967900.968040).
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_erwin_2007.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_erwin_2007.snap
new file mode 100644
index 0000000000..df9ab6569f
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__inproceedings_erwin_2007.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 269
+expression: "render_entry(r#\"\n@inproceedings{10.5555/1386993.1386994,\n author = {Erwin, Alva and Gopalan, Raj P. and Achuthan, N. R.},\n title = {A Bottom-up Projection Based Algorithm for Mining High Utility Itemsets},\n year = {2007},\n isbn = {9781920682651},\n publisher = {Australian Computer Society, Inc.},\n address = {AUS},\n booktitle = {Proceedings of the 2nd International Workshop on Integrating Artificial Intelligence and Data Mining - Volume 84},\n pages = {3-11},\n numpages = {9},\n keywords = {pattern growth, high utility itemset mining},\n location = {Gold Coast, Australia},\n series = {AIDM '07}\n}\"#)"
+---
+A. Erwin, R. Gopalan, N. Achuthan: "A Bottom-up Projection Based Algorithm for Mining High Utility Itemsets". *Proceedings of the 2nd International Workshop on Integrating Artificial Intelligence and Data Mining - Volume 84*. AIDM '07. Gold Coast, Australia: Australian Computer Society, Inc., 2007, 3-11. ISBN: 9781920682651.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__mvbook_nietzsche_1988.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__mvbook_nietzsche_1988.snap
new file mode 100644
index 0000000000..5162b3114b
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__mvbook_nietzsche_1988.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 238
+expression: "render_entry(r#\"\n@string{dtv = {Deutscher Taschenbuch-Verlag}}\n\n@mvbook{nietzsche:ksa,\n author = {Nietzsche, Friedrich},\n title = {S{\\\"a}mtliche Werke},\n date = 1988,\n editor = {Colli, Giorgio and Montinari, Mazzino},\n edition = 2,\n volumes = 15,\n publisher = dtv # { and Walter de Gruyter},\n location = {M{\\\"u}nchen and Berlin and New York},\n langid = {german},\n sorttitle = {Werke-00-000},\n indexsorttitle= {Samtliche Werke},\n subtitle = {Kritische Studienausgabe},\n annotation = {The critical edition of Nietzsche's works. This is a\n \\texttt{mvbook} entry referring to a 15-volume work as a\n whole. Note the \\texttt{volumes} field and the format of the\n \\texttt{publisher} and \\texttt{location} fields in the\n database file. Also note the \\texttt{sorttitle} and\n field which is used to fine-tune the\n sorting order of the bibliography. We want this item listed\n first in the bibliography},\n}\"#)"
+---
+F. Nietzsche: "Sämtliche Werke. Kritische Studienausgabe". Ed. by G. Colli, M. Montinari. 2nd. München and Berlin and New York: Deutscher Taschenbuch-Verlag and Walter de Gruyter, 1988.
diff --git a/support/texlab/src/citation/snapshots/texlab__citation__tests__patent_almendro_1998.snap b/support/texlab/src/citation/snapshots/texlab__citation__tests__patent_almendro_1998.snap
new file mode 100644
index 0000000000..6f8c17f059
--- /dev/null
+++ b/support/texlab/src/citation/snapshots/texlab__citation__tests__patent_almendro_1998.snap
@@ -0,0 +1,6 @@
+---
+source: src/citation/tests.rs
+assertion_line: 334
+expression: "render_entry(r#\"\n@patent{almendro,\n author = {Almendro, Jos{\\'e} L. and Mart{\\'i}n, Jacinto and S{\\'a}nchez,\n Alberto and Nozal, Fernando},\n title = {Elektromagnetisches Signalhorn},\n number = {EU-29702195U},\n date = 1998,\n location = {countryfr and countryuk and countryde},\n langid = {german},\n annotation = {This is a \\texttt{patent} entry with a \\texttt{location}\n field. The number is given in the \\texttt{number} field. Note\n the format of the \\texttt{location} field in the database\n file. Compare \\texttt{laufenberg}, \\texttt{sorace}, and\n \\texttt{kowalik}},\n}\"#)"
+---
+J. Almendro, J. Martín, A. Sánchez, F. Nozal: "Elektromagnetisches Signalhorn". EU-29702195U (France and United Kingdom and Germany). 1998.
diff --git a/support/texlab/src/citation/tests.rs b/support/texlab/src/citation/tests.rs
new file mode 100644
index 0000000000..16aefea11d
--- /dev/null
+++ b/support/texlab/src/citation/tests.rs
@@ -0,0 +1,351 @@
+use insta::assert_snapshot;
+use rowan::ast::AstNode;
+
+use crate::syntax::bibtex;
+
+fn render_entry(input: &str) -> String {
+ let green = bibtex::parse(input);
+ let root = bibtex::Root::cast(bibtex::SyntaxNode::new_root(green)).unwrap();
+ let entry = root.entries().next().unwrap();
+ super::render(&entry).unwrap()
+}
+
+#[test]
+fn article_rivest_1978() {
+ assert_snapshot!(render_entry(
+ r#"
+@article{10.1145/359340.359342,
+ author = {Rivest, R. L. and Shamir, A. and Adleman, L.},
+ title = {A Method for Obtaining Digital Signatures and Public-Key Cryptosystems},
+ year = {1978},
+ issue_date = {Feb. 1978},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ volume = {21},
+ number = {2},
+ issn = {0001-0782},
+ url = {https://doi.org/10.1145/359340.359342},
+ doi = {10.1145/359340.359342},
+ journal = {Commun. ACM},
+ month = {feb},
+ pages = {120-126},
+ numpages = {7},
+}"#
+ ));
+}
+
+#[test]
+fn article_jain_1999() {
+ assert_snapshot!(render_entry(
+ r#"
+@article{10.1145/331499.331504,
+ author = {Jain, A. K. and Murty, M. N. and Flynn, P. J.},
+ title = {Data Clustering: A Review},
+ year = {1999},
+ issue_date = {Sept. 1999},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ volume = {31},
+ number = {3},
+ issn = {0360-0300},
+ url = {https://doi.org/10.1145/331499.331504},
+ doi = {10.1145/331499.331504},
+ journal = {ACM Comput. Surv.},
+ month = {sep},
+ pages = {264-323},
+ numpages = {60},
+ keywords = {incremental clustering, clustering applications, exploratory data analysis, cluster analysis, similarity indices, unsupervised learning}
+}"#
+ ));
+}
+
+#[test]
+fn article_aksin_2006() {
+ assert_snapshot!(render_entry(
+ r#"
+@string{jomch = {J.~Organomet. Chem.}}
+
+@article{aksin,
+ author = {Aks{\i}n, {\"O}zge and T{\"u}rkmen, Hayati and Artok, Levent
+ and {\c{C}}etinkaya, Bekir and Ni, Chaoying and
+ B{\"u}y{\"u}kg{\"u}ng{\"o}r, Orhan and {\"O}zkal, Erhan},
+ title = {Effect of immobilization on catalytic characteristics of
+ saturated {Pd-N}-heterocyclic carbenes in {Mizoroki-Heck}
+ reactions},
+ journaltitle = jomch,
+ date = 2006,
+ volume = 691,
+ number = 13,
+ pages = {3027-3036},
+ indextitle = {Effect of immobilization on catalytic characteristics},
+}"#
+ ));
+}
+
+#[test]
+fn article_betram_1996() {
+ assert_snapshot!(render_entry(
+ r#"
+@string{jams = {J.~Amer. Math. Soc.}}
+
+@article{bertram,
+ author = {Bertram, Aaron and Wentworth, Richard},
+ title = {Gromov invariants for holomorphic maps on {Riemann} surfaces},
+ journaltitle = jams,
+ date = 1996,
+ volume = 9,
+ number = 2,
+ pages = {529-571},
+ langid = {english},
+ langidopts = {variant=american},
+ shorttitle = {Gromov invariants},
+ annotation = {An \texttt{article} entry with a \texttt{volume} and a
+ \texttt{number} field},
+}"#
+ ));
+}
+
+#[test]
+fn article_kastenholz_2006() {
+ assert_snapshot!(render_entry(
+ r#"
+@string{jchph = {J.~Chem. Phys.}}
+
+@article{kastenholz,
+ author = {Kastenholz, M. A. and H{\"u}nenberger, Philippe H.},
+ title = {Computation of methodology\hyphen independent ionic solvation
+ free energies from molecular simulations},
+ journaltitle = jchph,
+ date = 2006,
+ subtitle = {{I}. {The} electrostatic potential in molecular liquids},
+ volume = 124,
+ eid = 124106,
+ doi = {10.1063/1.2172593},
+ langid = {english},
+ langidopts = {variant=american},
+ indextitle = {Computation of ionic solvation free energies},
+ annotation = {An \texttt{article} entry with an \texttt{eid} and a
+ \texttt{doi} field. Note that the \textsc{doi} is transformed
+ into a clickable link if \texttt{hyperref} support has been
+ enabled},
+}
+ "#
+ ));
+}
+
+#[test]
+fn article_blom_2021() {
+ assert_snapshot!(render_entry(
+ r#"
+@article{DBLP:journals/corr/abs-2107-11903,
+ author = {Michelle L. Blom and
+ Jurlind Budurushi and
+ Ronald L. Rivest and
+ Philip B. Stark and
+ Peter J. Stuckey and
+ Vanessa Teague and
+ Damjan Vukcevic},
+ title = {Assertion-based Approaches to Auditing Complex Elections, with application
+ to party-list proportional elections},
+ journal = {CoRR},
+ volume = {abs/2107.11903},
+ year = {2021},
+ url = {https://arxiv.org/abs/2107.11903},
+ eprinttype = {arXiv},
+ eprint = {2107.11903},
+ timestamp = {Thu, 29 Jul 2021 16:14:15 +0200},
+ biburl = {https://dblp.org/rec/journals/corr/abs-2107-11903.bib},
+ bibsource = {dblp computer science bibliography, https://dblp.org}
+}"#
+ ));
+}
+
+#[test]
+fn book_aho_2006() {
+ assert_snapshot!(render_entry(
+ r#"
+@book{10.5555/1177220,
+ author = {Aho, Alfred V. and Lam, Monica S. and Sethi, Ravi and Ullman, Jeffrey D.},
+ title = {Compilers: Principles, Techniques, and Tools (2nd Edition)},
+ year = {2006},
+ isbn = {0321486811},
+ publisher = {Addison-Wesley Longman Publishing Co., Inc.},
+ address = {USA}
+}"#
+ ));
+}
+
+#[test]
+fn book_averroes_1998() {
+ assert_snapshot!(render_entry(
+ r#"
+@book{averroes/bland,
+ author = {Averroes},
+ title = {The Epistle on the Possibility of Conjunction with the Active
+ Intellect by {Ibn Rushd} with the Commentary of {Moses Narboni}},
+ date = 1982,
+ editor = {Bland, Kalman P.},
+ translator = {Bland, Kalman P.},
+ series = {Moreshet: Studies in {Jewish} History, Literature and Thought},
+ number = 7,
+ publisher = {Jewish Theological Seminary of America},
+ location = {New York},
+ keywords = {primary},
+ langid = {english},
+ langidopts = {variant=american},
+ indextitle = {Epistle on the Possibility of Conjunction, The},
+ shorttitle = {Possibility of Conjunction},
+ annotation = {A \texttt{book} entry with a \texttt{series} and a
+ \texttt{number}. Note the concatenation of the \texttt{editor}
+ and \texttt{translator} fields as well as the
+ \texttt{indextitle} field},
+}"#
+ ));
+}
+
+#[test]
+fn book_knuth_1984() {
+ assert_snapshot!(render_entry(
+ r#"
+@book{knuth:ct:a,
+ author = {Knuth, Donald E.},
+ title = {The {\TeX book}},
+ date = 1984,
+ maintitle = {Computers \& Typesetting},
+ volume = {A},
+ publisher = {Addison-Wesley},
+ location = {Reading, Mass.},
+ langid = {english},
+ langidopts = {variant=american},
+ sorttitle = {Computers & Typesetting A},
+ indexsorttitle= {The TeXbook},
+ indextitle = {\protect\TeX book, The},
+ shorttitle = {\TeX book},
+ annotation = {The first volume of a five-volume book. Note the
+ \texttt{sorttitle} field. We want this
+ volume to be listed after the entry referring to the entire
+ five-volume set. Also note the \texttt{indextitle} and
+ \texttt{indexsorttitle} fields. Indexing packages that don't
+ generate robust index entries require some control sequences
+ to be protected from expansion},
+}
+ "#
+ ));
+}
+
+#[test]
+fn mvbook_nietzsche_1988() {
+ assert_snapshot!(render_entry(
+ r#"
+@string{dtv = {Deutscher Taschenbuch-Verlag}}
+
+@mvbook{nietzsche:ksa,
+ author = {Nietzsche, Friedrich},
+ title = {S{\"a}mtliche Werke},
+ date = 1988,
+ editor = {Colli, Giorgio and Montinari, Mazzino},
+ edition = 2,
+ volumes = 15,
+ publisher = dtv # { and Walter de Gruyter},
+ location = {M{\"u}nchen and Berlin and New York},
+ langid = {german},
+ sorttitle = {Werke-00-000},
+ indexsorttitle= {Samtliche Werke},
+ subtitle = {Kritische Studienausgabe},
+ annotation = {The critical edition of Nietzsche's works. This is a
+ \texttt{mvbook} entry referring to a 15-volume work as a
+ whole. Note the \texttt{volumes} field and the format of the
+ \texttt{publisher} and \texttt{location} fields in the
+ database file. Also note the \texttt{sorttitle} and
+ field which is used to fine-tune the
+ sorting order of the bibliography. We want this item listed
+ first in the bibliography},
+}"#
+ ));
+}
+
+#[test]
+fn inproceedings_erwin_2007() {
+ assert_snapshot!(render_entry(
+ r#"
+@inproceedings{10.5555/1386993.1386994,
+ author = {Erwin, Alva and Gopalan, Raj P. and Achuthan, N. R.},
+ title = {A Bottom-up Projection Based Algorithm for Mining High Utility Itemsets},
+ year = {2007},
+ isbn = {9781920682651},
+ publisher = {Australian Computer Society, Inc.},
+ address = {AUS},
+ booktitle = {Proceedings of the 2nd International Workshop on Integrating Artificial Intelligence and Data Mining - Volume 84},
+ pages = {3-11},
+ numpages = {9},
+ keywords = {pattern growth, high utility itemset mining},
+ location = {Gold Coast, Australia},
+ series = {AIDM '07}
+}"#
+ ));
+}
+
+#[test]
+fn inproceedings_combi_2004() {
+ assert_snapshot!(render_entry(
+ r#"
+@inproceedings{10.1145/967900.968040,
+ author = {Combi, Carlo and Pozzi, Giuseppe},
+ title = {Architectures for a Temporal Workflow Management System},
+ year = {2004},
+ isbn = {1581138121},
+ publisher = {Association for Computing Machinery},
+ address = {New York, NY, USA},
+ url = {https://doi.org/10.1145/967900.968040},
+ doi = {10.1145/967900.968040},
+ booktitle = {Proceedings of the 2004 ACM Symposium on Applied Computing},
+ pages = {659-666},
+ numpages = {8},
+ keywords = {active DBMS, temporal DBMS, workflow management system - WfMS, temporal workflow management system},
+ location = {Nicosia, Cyprus},
+ series = {SAC '04}
+}"#
+ ));
+}
+
+#[test]
+fn collection_matuz_1990() {
+ assert_snapshot!(render_entry(
+ r#"
+@collection{matuz:doody,
+ editor = {Matuz, Roger},
+ title = {Contemporary Literary Criticism},
+ year = 1990,
+ volume = 61,
+ publisher = {Gale},
+ location = {Detroit},
+ pages = {204-208},
+ langid = {english},
+ langidopts = {variant=american},
+ annotation = {A \texttt{collection} entry providing the excerpt information
+ for the \texttt{doody} entry. Note the format of the
+ \texttt{pages} field},
+}"#
+ ));
+}
+
+#[test]
+fn patent_almendro_1998() {
+ assert_snapshot!(render_entry(
+ r#"
+@patent{almendro,
+ author = {Almendro, Jos{\'e} L. and Mart{\'i}n, Jacinto and S{\'a}nchez,
+ Alberto and Nozal, Fernando},
+ title = {Elektromagnetisches Signalhorn},
+ number = {EU-29702195U},
+ date = 1998,
+ location = {countryfr and countryuk and countryde},
+ langid = {german},
+ annotation = {This is a \texttt{patent} entry with a \texttt{location}
+ field. The number is given in the \texttt{number} field. Note
+ the format of the \texttt{location} field in the database
+ file. Compare \texttt{laufenberg}, \texttt{sorace}, and
+ \texttt{kowalik}},
+}"#
+ ));
+}