diff options
Diffstat (limited to 'support/texlab/src/citation')
-rw-r--r-- | support/texlab/src/citation/apa.csl | 1526 | ||||
-rw-r--r-- | support/texlab/src/citation/bibutils.rs | 195 | ||||
-rw-r--r-- | support/texlab/src/citation/name.rs | 279 | ||||
-rw-r--r-- | support/texlab/src/citation/name/parser.lalrpop | 150 | ||||
-rw-r--r-- | support/texlab/src/citation/ris.rs | 467 |
5 files changed, 2617 insertions, 0 deletions
diff --git a/support/texlab/src/citation/apa.csl b/support/texlab/src/citation/apa.csl new file mode 100644 index 0000000000..8454246a98 --- /dev/null +++ b/support/texlab/src/citation/apa.csl @@ -0,0 +1,1526 @@ +<?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. & trans.</single> + <multiple>eds. & 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=", & "> + <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–"/> + </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 new file mode 100644 index 0000000000..9931ba3f9f --- /dev/null +++ b/support/texlab/src/citation/bibutils.rs @@ -0,0 +1,195 @@ +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/name.rs b/support/texlab/src/citation/name.rs new file mode 100644 index 0000000000..9e22383a7d --- /dev/null +++ b/support/texlab/src/citation/name.rs @@ -0,0 +1,279 @@ +// Ported from: https://github.com/michel-kraemer/citeproc-java/blob/master/citeproc-java/grammars/InternalName.g4 +// Michel Kraemer +// Apache License 2.0 +use citeproc_io::Name; + +use parser::NamesParser; + +mod parser { + #![allow(warnings)] + include!(concat!(env!("OUT_DIR"), "/citation/name/parser.rs")); +} + +pub fn parse(input: &str) -> Vec<Name> { + let parser = NamesParser::new(); + parser.parse(input).unwrap_or_else(|_| { + vec![Name::Literal { + literal: input.into(), + }] + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use citeproc_io::PersonName; + + #[test] + fn test_family_only() { + let name = Name::Person(PersonName { + family: Some("Thompson".into()), + given: None, + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Thompson"), vec![name]); + } + + #[test] + fn test_simple() { + let name = Name::Person(PersonName { + family: Some("Thompson".into()), + given: Some("Ken".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Ken Thompson"), vec![name]); + } + + #[test] + fn test_middle_name() { + let name = Name::Person(PersonName { + family: Some("Ritchie".into()), + given: Some("Dennis M.".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Dennis M. Ritchie"), vec![name]); + } + + #[test] + fn test_initials() { + let name = Name::Person(PersonName { + family: Some("Johnson".into()), + given: Some("S. C.".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("S. C. Johnson"), vec![name]); + } + + #[test] + fn test_non_dropping_particle() { + let name = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Michael van Gerwen"), vec![name]); + } + + #[test] + fn test_non_dropping_particle_family_only() { + let name = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: None, + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("van Gerwen"), vec![name]); + } + + #[test] + fn test_comma() { + let name = Name::Person(PersonName { + family: Some("Thompson".into()), + given: Some("Ken".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Thompson, Ken"), vec![name]); + } + + #[test] + fn test_comma_junior() { + let name = Name::Person(PersonName { + family: Some("Friedman".into()), + given: Some("George".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: Some("Jr.".into()), + }); + assert_eq!(parse("Friedman, Jr., George"), vec![name]); + } + + #[test] + fn test_comma_no_junior() { + let name = Name::Person(PersonName { + family: Some("Familya Familyb".into()), + given: Some("Given".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Familya Familyb, Given"), vec![name]); + } + + #[test] + fn test_comma_initials() { + let name = Name::Person(PersonName { + family: Some("Ritchie".into()), + given: Some("Dennis M.".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Ritchie, Dennis M."), vec![name]); + } + + #[test] + fn test_comma_non_dropping_particle() { + let name = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("van Gerwen, Michael"), vec![name]); + } + + #[test] + fn test_comma_non_dropping_particles() { + let name = Name::Person(PersonName { + family: Some("Voort".into()), + given: Some("Vincent".into()), + non_dropping_particle: Some("Van der".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!(parse("Van der Voort, Vincent"), vec![name]); + } + + #[test] + fn test_and() { + let name1 = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + let name2 = Name::Person(PersonName { + family: Some("Voort".into()), + given: Some("Vincent".into()), + non_dropping_particle: Some("van der".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!( + parse("Michael van Gerwen and Vincent van der Voort"), + vec![name1, name2] + ); + } + + #[test] + fn test_and_comma1() { + let name1 = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + let name2 = Name::Person(PersonName { + family: Some("Voort".into()), + given: Some("Vincent".into()), + non_dropping_particle: Some("Van der".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!( + parse("van Gerwen, Michael and Van der Voort, Vincent"), + vec![name1, name2] + ); + } + + #[test] + fn test_and_comma2() { + let name1 = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + let name2 = Name::Person(PersonName { + family: Some("Voort".into()), + given: Some("Vincent".into()), + non_dropping_particle: Some("van der".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!( + parse("van Gerwen, Michael and van der Voort, Vincent"), + vec![name1, name2] + ); + } + + #[test] + fn test_and_comma_mix() { + let name1 = Name::Person(PersonName { + family: Some("Gerwen".into()), + given: Some("Michael".into()), + non_dropping_particle: Some("van".into()), + dropping_particle: None, + suffix: None, + }); + let name2 = Name::Person(PersonName { + family: Some("Voort".into()), + given: Some("Vincent".into()), + non_dropping_particle: Some("van der".into()), + dropping_particle: None, + suffix: None, + }); + assert_eq!( + parse("van Gerwen, Michael and Vincent van der Voort"), + vec![name1, name2] + ); + } + + #[test] + fn test_junior() { + let name = Name::Person(PersonName { + family: Some("Friedman".into()), + given: Some("George".into()), + non_dropping_particle: None, + dropping_particle: None, + suffix: Some("Jr.".into()), + }); + assert_eq!(parse("George Friedman, Jr."), vec![name]); + } + + #[test] + fn test_non_parseable() { + let literal = "Jerry Peek and Tim O'Reilly and Mike Loukides and other authors of the Nutshell handbooks"; + let name = Name::Literal { + literal: literal.into(), + }; + assert_eq!(parse(literal), vec![name]); + } +} diff --git a/support/texlab/src/citation/name/parser.lalrpop b/support/texlab/src/citation/name/parser.lalrpop new file mode 100644 index 0000000000..e7bce8a302 --- /dev/null +++ b/support/texlab/src/citation/name/parser.lalrpop @@ -0,0 +1,150 @@ +// Ported from: https://github.com/michel-kraemer/citeproc-java/blob/master/citeproc-java/grammars/InternalName.g4 +// Michel Kraemer +// Apache License 2.0 +use citeproc_io::{Name, PersonName}; +use itertools::Itertools; + +grammar; + +pub Names: Vec<Name> = And<Name>; + +And<T>: Vec<T> = { + <v:(<T> "and")*> <e:T?> => match e { + None => v, + Some(e) => { + let mut v = v; + v.push(e); + v + } + } +}; + +Name: Name = { + <np1:UWord+> <np2:Von> <fam:Last> "," <fst:First> => { + let (fst1, fst2) = fst; + let name = PersonName { + family: Some(fam.join(" ")), + given: fst2, + non_dropping_particle: Some(format!("{} {}", np1.join(" "), np2)), + dropping_particle: None, + suffix: fst1, + }; + Name::Person(name) + }, + <np:Von> <fam:Last> "," <fst:First> => { + let (fst1, fst2) = fst; + let name = PersonName { + family: Some(fam.join(" ")), + given: fst2, + non_dropping_particle: Some(np), + dropping_particle: None, + suffix: fst1, + }; + Name::Person(name) + }, + <last:Last> "," <fst:First> => { + let (fst1, fst2) = fst; + let first_text = format!( + "{} {}", + fst1.as_ref().map(|s| s.as_str()).unwrap_or_default(), + fst2.as_ref().map(|s| s.as_str()).unwrap_or_default(), + ).trim().to_owned(); + + let name = if first_text == "Jr." { + if last.len() == 1 { + PersonName { + family: Some(last.join(" ")), + given: None, + non_dropping_particle: None, + dropping_particle: None, + suffix: Some(first_text), + } + } else { + let mut last = last.into_iter(); + let given = last.next().map(ToOwned::to_owned); + PersonName { + family: Some(last.join(" ")), + given, + non_dropping_particle: None, + dropping_particle: None, + suffix: Some(first_text), + } + } + } else { + PersonName { + family: Some(last.join(" ")), + given: fst2, + non_dropping_particle: None, + dropping_particle: None, + suffix: fst1, + } + }; + Name::Person(name) + }, + <np:Von> <fam:Last> => { + let name = PersonName { + family: Some(fam.join(" ")), + given: None, + non_dropping_particle: Some(np), + dropping_particle: None, + suffix: None, + }; + Name::Person(name) + }, + <giv:UWord+> <np:Von> <fam:Last> => { + let name = PersonName { + family: Some(fam.join(" ")), + given: Some(giv.join(" ")), + non_dropping_particle: Some(np), + dropping_particle: None, + suffix: None, + }; + Name::Person(name) + }, + <giv:UWord+> <fam:Word> => { + let name = PersonName { + family: Some(fam.into()), + given: Some(giv.join(" ")), + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }; + Name::Person(name) + }, + <fam:Word> => { + let name = PersonName { + family: Some(fam.into()), + given: None, + non_dropping_particle: None, + dropping_particle: None, + suffix: None, + }; + Name::Person(name) + }, +}; + +First: (Option<String>, Option<String>) = { + <a:Word*> "," <b:Word*> => (Some(a.join(" ")), Some(b.join(" "))), + <a:Word*> => (None, Some(a.join(" "))), +}; + +Last: Vec<&'input str> = { + LWord => vec![(<>)], + UWord+ => (<>), +}; + +Von: String = { + LWord => String::from(<>), + <a:Von> <b:LWord> => format!("{} {}", a, b), + <a:Von> <b:UWord+> <c:LWord> => format!("{} {} {}", a, b.join(" "), c), +}; + +Word: &'input str = { + UWord => (<>), + LWord => (<>), +}; + + +UWord: &'input str = r"[A-Z\u00C0-\uFFFF(?][A-Z\u00C0-\uFFFF(?a-z\-)&/.]+" => (<>); + +LWord: &'input str = r"[a-z\-)&/.][A-Z\u00C0-\uFFFF(?a-z\-)&/.]+" => (<>); diff --git a/support/texlab/src/citation/ris.rs b/support/texlab/src/citation/ris.rs new file mode 100644 index 0000000000..5b666160a3 --- /dev/null +++ b/support/texlab/src/citation/ris.rs @@ -0,0 +1,467 @@ +// Ported from: https://github.com/michel-kraemer/citeproc-java/tree/master/citeproc-java/templates +// Michel Kraemer +// Apache License 2.0 +use citeproc_io::{Date, DateOrRange, Name, NumericValue, Reference}; +use csl::*; +use fnv::FnvHashMap; +use serde::{Deserialize, Serialize}; + +use super::name; + +#[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, NumericValue> = 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, + NumericValue::Str(format!("{}-{}", start_page, end_page)), + ); + } + (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) -> NumericValue { + match value.parse() { + Ok(value) => NumericValue::num(value), + Err(_) => NumericValue::Str(value), + } +} + +fn parse_authors(authors: Vec<String>) -> Vec<Name> { + authors + .into_iter() + .flat_map(|author| name::parse(&author)) + .collect() +} + +fn parse_date_or_range(value: String) -> DateOrRange { + parse_date(&value) + .map(DateOrRange::Single) + .unwrap_or_else(|| DateOrRange::Literal(value)) +} + +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)), + } +} |