@publisle/research 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +31 -0
- package/README.md +14 -2
- package/compiler/Dockerfile +18 -0
- package/compiler/README.md +80 -0
- package/compiler/cacert.pem +2950 -0
- package/compiler/install-texlive.sh +38 -0
- package/compiler/release.json +13 -0
- package/dist/article.d.ts +10 -0
- package/dist/article.d.ts.map +1 -0
- package/dist/article.js +425 -0
- package/dist/article.js.map +1 -0
- package/dist/bibtex.d.ts +2 -0
- package/dist/bibtex.d.ts.map +1 -0
- package/dist/bibtex.js +2 -0
- package/dist/bibtex.js.map +1 -0
- package/dist/citeproc.d.ts +28 -0
- package/dist/csl.d.ts +35 -0
- package/dist/csl.d.ts.map +1 -0
- package/dist/csl.js +315 -0
- package/dist/csl.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/jats.d.ts +14 -0
- package/dist/jats.d.ts.map +1 -0
- package/dist/jats.js +197 -0
- package/dist/jats.js.map +1 -0
- package/dist/latex.d.ts +5 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +19 -0
- package/dist/latex.js.map +1 -0
- package/dist/locales.d.ts +2 -0
- package/dist/locales.d.ts.map +1 -0
- package/dist/locales.js +67 -0
- package/dist/locales.js.map +1 -0
- package/dist/node.d.ts +66 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +542 -0
- package/dist/node.js.map +1 -0
- package/dist/package.d.ts +19 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +48 -0
- package/dist/package.js.map +1 -0
- package/dist/pdf.d.ts +10 -0
- package/dist/pdf.d.ts.map +1 -0
- package/dist/pdf.js +207 -0
- package/dist/pdf.js.map +1 -0
- package/dist/portable-citations.d.ts +27 -0
- package/dist/portable-citations.d.ts.map +1 -0
- package/dist/portable-citations.js +185 -0
- package/dist/portable-citations.js.map +1 -0
- package/dist/profiles.d.ts +10 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +166 -0
- package/dist/profiles.js.map +1 -0
- package/dist/remote.d.ts +24 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +139 -0
- package/dist/remote.js.map +1 -0
- package/dist/resolve.d.ts +10 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +62 -0
- package/dist/resolve.js.map +1 -0
- package/package.json +53 -3
- package/src/article.ts +478 -0
- package/src/bibtex.ts +1 -0
- package/src/citeproc.d.ts +28 -0
- package/src/csl.ts +367 -0
- package/src/index.ts +29 -0
- package/src/jats.ts +242 -0
- package/src/latex.ts +21 -0
- package/src/locales.ts +126 -0
- package/src/node.ts +752 -0
- package/src/package.ts +83 -0
- package/src/pdf.ts +225 -0
- package/src/portable-citations.ts +250 -0
- package/src/profiles.ts +188 -0
- package/src/remote.ts +163 -0
- package/src/resolve.ts +91 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/BITS-embedded-index2.ent +308 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/BITS-question-answer2.ent +531 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-XHTMLtablesetup1-3.ent +629 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-ali-namespace1-3.ent +293 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-archivearticle1-3.dtd +1085 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-archivecustom-classes1-3.ent +409 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-archivecustom-mixes1-3.ent +601 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-archivecustom-models1-3.ent +1613 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-archivecustom-modules1-3.ent +404 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-articlemeta1-3.ent +2552 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-backmatter1-3.ent +540 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-chars1-3.ent +664 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-common-atts1-3.ent +360 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-common1-3.ent +4659 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-default-classes1-3.ent +1595 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-default-mixes1-3.ent +630 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-display1-3.ent +1299 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-format1-3.ent +997 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-funding1-3.ent +1100 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-journalmeta1-3.ent +593 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-link1-3.ent +604 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-list1-3.ent +663 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-math1-3.ent +563 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-mathmlsetup1-3.ent +491 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-modules1-3.ent +769 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-nlmcitation1-3.ent +428 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-notat1-3.ent +412 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-para1-3.ent +648 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-phrase1-3.ent +664 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-references1-3.ent +1428 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-related-object1-3.ent +435 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-section1-3.ent +463 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/JATS-xmlspecchars1-3.ent +528 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/catalog-jats-v1-3-no-base.xml +999 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/catalog-jats-v1-3-with-base.xml +986 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isobox.ent +61 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isocyr1.ent +88 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isocyr2.ent +47 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isodia.ent +35 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isolat1.ent +83 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isolat2.ent +142 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isonum.ent +97 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso8879/isopub.ent +105 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamsa.ent +167 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamsb.ent +143 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamsc.ent +43 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamsn.ent +114 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamso.ent +73 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isoamsr.ent +204 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isogrk3.ent +64 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isomfrk.ent +75 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isomopf.ent +49 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isomscr.ent +75 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/iso9573-13/isotech.ent +182 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/mathml/mmlalias.ent +564 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/mathml/mmlextra.ent +122 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/mathml2-qname-1.mod +286 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/mathml2.dtd +2206 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/xhtml-inlstyle-1.mod +34 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/xhtml-table-1.mod +333 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/xmlchars/isogrk1.ent +70 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/xmlchars/isogrk2.ent +41 -0
- package/vendor/jats/JATS-Archiving-1-3-MathML2-DTD/xmlchars/isogrk4.ent +66 -0
- package/vendor/jats/PROVENANCE.json +6 -0
- package/vendor/locales/PROVENANCE.json +5 -0
- package/vendor/locales/README.md +23 -0
package/src/csl.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import CSL from "citeproc";
|
|
2
|
+
import { locales } from "./locales.ts";
|
|
3
|
+
import { toCslJson } from "./bibtex.ts";
|
|
4
|
+
import type {
|
|
5
|
+
CitationCluster,
|
|
6
|
+
CitationEntry,
|
|
7
|
+
ResearchLoss,
|
|
8
|
+
} from "./article.ts";
|
|
9
|
+
|
|
10
|
+
export interface XmlElement {
|
|
11
|
+
readonly type: "element";
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly attributes: Readonly<Record<string, string>>;
|
|
14
|
+
readonly children: readonly XmlNode[];
|
|
15
|
+
}
|
|
16
|
+
export type XmlNode =
|
|
17
|
+
XmlElement | { readonly type: "text"; readonly value: string };
|
|
18
|
+
|
|
19
|
+
const NUMERIC_STYLE = `<?xml version="1.0"?>
|
|
20
|
+
<style class="in-text" version="1.0">
|
|
21
|
+
<info><title>Publisle numeric</title><category citation-format="numeric"/></info>
|
|
22
|
+
<citation collapse="citation-number">
|
|
23
|
+
<sort><key variable="citation-number"/></sort>
|
|
24
|
+
<layout prefix="[" suffix="]" delimiter=", ">
|
|
25
|
+
<text variable="citation-number"/><group prefix=", " delimiter=" "><label variable="locator" form="short"/><text variable="locator"/></group>
|
|
26
|
+
</layout>
|
|
27
|
+
</citation>
|
|
28
|
+
<bibliography>
|
|
29
|
+
<layout>
|
|
30
|
+
<text variable="citation-number" suffix=". "/>
|
|
31
|
+
<names variable="author"><name form="long" and="text" delimiter=", "/><et-al term="et-al"/></names>
|
|
32
|
+
<text variable="title" prefix=", "/>
|
|
33
|
+
<text variable="container-title" prefix=", "/>
|
|
34
|
+
<text variable="volume" prefix=" "/>
|
|
35
|
+
<text variable="issue" prefix="(" suffix=")"/>
|
|
36
|
+
<text variable="page" prefix=": "/>
|
|
37
|
+
<date variable="issued" prefix=" (" suffix=")"><date-part name="year"/></date>
|
|
38
|
+
<text variable="DOI" prefix=". https://doi.org/"/>
|
|
39
|
+
</layout>
|
|
40
|
+
</bibliography>
|
|
41
|
+
</style>`;
|
|
42
|
+
|
|
43
|
+
const AUTHOR_DATE_STYLE = `<?xml version="1.0"?>
|
|
44
|
+
<style class="in-text" version="1.0">
|
|
45
|
+
<info><title>Publisle author-date</title><category citation-format="author-date"/></info>
|
|
46
|
+
<macro name="author-short">
|
|
47
|
+
<names variable="author">
|
|
48
|
+
<name form="short" and="symbol" delimiter=", "/>
|
|
49
|
+
<et-al term="et-al"/>
|
|
50
|
+
</names>
|
|
51
|
+
</macro>
|
|
52
|
+
<citation et-al-min="3" et-al-use-first="1" disambiguate-add-year-suffix="true" disambiguate-add-givenname="true">
|
|
53
|
+
<layout prefix="(" suffix=")" delimiter="; ">
|
|
54
|
+
<group delimiter=", ">
|
|
55
|
+
<text macro="author-short"/>
|
|
56
|
+
<date variable="issued"><date-part name="year"/></date>
|
|
57
|
+
</group>
|
|
58
|
+
</layout>
|
|
59
|
+
</citation>
|
|
60
|
+
<bibliography>
|
|
61
|
+
<sort><key variable="author"/><key variable="issued"/></sort>
|
|
62
|
+
<layout suffix=".">
|
|
63
|
+
<names variable="author"><name and="text" name-as-sort-order="first" delimiter=", "/></names>
|
|
64
|
+
<date variable="issued" prefix=" (" suffix=")"><date-part name="year"/></date>
|
|
65
|
+
<text variable="title" prefix=". "/>
|
|
66
|
+
<text variable="container-title" prefix=". "/>
|
|
67
|
+
</layout>
|
|
68
|
+
</bibliography>
|
|
69
|
+
</style>`;
|
|
70
|
+
|
|
71
|
+
function decodeXml(value: string): string {
|
|
72
|
+
return value.replace(
|
|
73
|
+
/&(?:amp|lt|gt|quot|apos|#\d+|#x[a-f0-9]+);/giu,
|
|
74
|
+
(entity) => {
|
|
75
|
+
const named: Readonly<Record<string, string>> = {
|
|
76
|
+
"&": "&",
|
|
77
|
+
"<": "<",
|
|
78
|
+
">": ">",
|
|
79
|
+
""": '"',
|
|
80
|
+
"'": "'",
|
|
81
|
+
};
|
|
82
|
+
if (named[entity]) return named[entity];
|
|
83
|
+
return String.fromCodePoint(
|
|
84
|
+
entity.startsWith("&#x")
|
|
85
|
+
? parseInt(entity.slice(3, -1), 16)
|
|
86
|
+
: parseInt(entity.slice(2, -1), 10),
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function parseXml(source: string): XmlElement {
|
|
92
|
+
let index = 0;
|
|
93
|
+
const skip = () => {
|
|
94
|
+
while (index < source.length) {
|
|
95
|
+
if (source.startsWith("<!--", index)) {
|
|
96
|
+
const end = source.indexOf("-->", index);
|
|
97
|
+
if (end === -1) throw new Error("CSL comment is not closed.");
|
|
98
|
+
index = end + 3;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (source.startsWith("<?", index) || source.startsWith("<!", index)) {
|
|
102
|
+
const end = source.indexOf(">", index);
|
|
103
|
+
if (end === -1) throw new Error("CSL declaration is not closed.");
|
|
104
|
+
index = end + 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (/\s/u.test(source[index] ?? "")) {
|
|
108
|
+
index += 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const readAttributes = (): Record<string, string> => {
|
|
115
|
+
const attributes: Record<string, string> = {};
|
|
116
|
+
while (
|
|
117
|
+
index < source.length &&
|
|
118
|
+
source[index] !== ">" &&
|
|
119
|
+
source[index] !== "/"
|
|
120
|
+
) {
|
|
121
|
+
if (/\s/u.test(source[index] ?? "")) {
|
|
122
|
+
index += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const nameEnd = source.slice(index).search(/[\s=/>]/u);
|
|
126
|
+
if (nameEnd <= 0) throw new Error("CSL attribute name is missing.");
|
|
127
|
+
const name = source.slice(index, index + nameEnd);
|
|
128
|
+
index += nameEnd;
|
|
129
|
+
while (/\s/u.test(source[index] ?? "")) index += 1;
|
|
130
|
+
if (source[index] !== "=")
|
|
131
|
+
throw new Error(`CSL attribute ${name} is missing a value.`);
|
|
132
|
+
index += 1;
|
|
133
|
+
while (/\s/u.test(source[index] ?? "")) index += 1;
|
|
134
|
+
const quote = source[index];
|
|
135
|
+
if (quote !== '"' && quote !== "'")
|
|
136
|
+
throw new Error(`CSL attribute ${name} is not quoted.`);
|
|
137
|
+
const valueEnd = source.indexOf(quote, index + 1);
|
|
138
|
+
if (valueEnd === -1)
|
|
139
|
+
throw new Error(`CSL attribute ${name} is not closed.`);
|
|
140
|
+
attributes[name] = decodeXml(source.slice(index + 1, valueEnd));
|
|
141
|
+
index = valueEnd + 1;
|
|
142
|
+
}
|
|
143
|
+
return attributes;
|
|
144
|
+
};
|
|
145
|
+
const readNodes = (until: string): XmlNode[] => {
|
|
146
|
+
const children: XmlNode[] = [];
|
|
147
|
+
while (index < source.length && !source.startsWith(until, index)) {
|
|
148
|
+
if (source.startsWith("<!--", index)) {
|
|
149
|
+
const end = source.indexOf("-->", index);
|
|
150
|
+
if (end < 0) throw new Error("Unclosed CSL comment");
|
|
151
|
+
index = end + 3;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (source[index] === "<") children.push(readElement());
|
|
155
|
+
else {
|
|
156
|
+
const next = source.indexOf("<", index);
|
|
157
|
+
const value = source.slice(index, next === -1 ? source.length : next);
|
|
158
|
+
index = next === -1 ? source.length : next;
|
|
159
|
+
if (value.trim().length > 0)
|
|
160
|
+
children.push({ type: "text", value: decodeXml(value.trim()) });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return children;
|
|
164
|
+
};
|
|
165
|
+
const readElement = (): XmlElement => {
|
|
166
|
+
if (source[index] !== "<") throw new Error("Expected a CSL element.");
|
|
167
|
+
index += 1;
|
|
168
|
+
const nameEnd = source.slice(index).search(/[\s/>]/u);
|
|
169
|
+
if (nameEnd <= 0) throw new Error("CSL element name is missing.");
|
|
170
|
+
const name = source.slice(index, index + nameEnd);
|
|
171
|
+
index += nameEnd;
|
|
172
|
+
const attributes = readAttributes();
|
|
173
|
+
if (source.startsWith("/>", index)) {
|
|
174
|
+
index += 2;
|
|
175
|
+
return { type: "element", name, attributes, children: [] };
|
|
176
|
+
}
|
|
177
|
+
if (source[index] !== ">")
|
|
178
|
+
throw new Error(`CSL element ${name} is not closed.`);
|
|
179
|
+
index += 1;
|
|
180
|
+
const children = readNodes(`</${name}>`);
|
|
181
|
+
if (!source.startsWith(`</${name}>`, index))
|
|
182
|
+
throw new Error(`CSL element ${name} has no end tag.`);
|
|
183
|
+
index += name.length + 3;
|
|
184
|
+
return { type: "element", name, attributes, children };
|
|
185
|
+
};
|
|
186
|
+
skip();
|
|
187
|
+
const root = readElement();
|
|
188
|
+
skip();
|
|
189
|
+
if (index < source.length) throw new Error("CSL style has trailing content.");
|
|
190
|
+
if (root.name !== "style") throw new Error("CSL root element must be style.");
|
|
191
|
+
return root;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function builtinCsl(name: "numeric" | "author-date"): XmlElement {
|
|
195
|
+
return parseXml(name === "numeric" ? NUMERIC_STYLE : AUTHOR_DATE_STYLE);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function parseCslStyle(xml: string): XmlElement {
|
|
199
|
+
return parseXml(xml);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function descendants(node: XmlElement, name: string): XmlElement[] {
|
|
203
|
+
const found: XmlElement[] = [];
|
|
204
|
+
for (const child of node.children) {
|
|
205
|
+
if (child.type !== "element") continue;
|
|
206
|
+
if (child.name === name) found.push(child);
|
|
207
|
+
found.push(...descendants(child, name));
|
|
208
|
+
}
|
|
209
|
+
return found;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function attr(node: XmlElement, name: string): string | undefined {
|
|
213
|
+
return node.attributes[name];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type CitationForm = "numeric" | "author-date" | "note";
|
|
217
|
+
|
|
218
|
+
export function citationForm(style: XmlElement): CitationForm {
|
|
219
|
+
if (style.attributes["class"] === "note") return "note";
|
|
220
|
+
const category = descendants(style, "category").find((item) =>
|
|
221
|
+
attr(item, "citation-format"),
|
|
222
|
+
);
|
|
223
|
+
const format = category ? attr(category, "citation-format") : undefined;
|
|
224
|
+
if (format === "numeric") return "numeric";
|
|
225
|
+
if (format === "author-date") return "author-date";
|
|
226
|
+
return style.attributes["class"] === "in-text"
|
|
227
|
+
? descendants(style, "text").some(
|
|
228
|
+
(node) => node.attributes["variable"] === "citation-number",
|
|
229
|
+
)
|
|
230
|
+
? "numeric"
|
|
231
|
+
: "author-date"
|
|
232
|
+
: "note";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface FormattedResearch {
|
|
236
|
+
readonly form: CitationForm;
|
|
237
|
+
readonly citations: readonly string[];
|
|
238
|
+
readonly bibliography: readonly {
|
|
239
|
+
readonly id: string;
|
|
240
|
+
readonly label: string;
|
|
241
|
+
readonly text: string;
|
|
242
|
+
}[];
|
|
243
|
+
readonly unresolved: readonly string[];
|
|
244
|
+
readonly losses: readonly ResearchLoss[];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface CslOptions {
|
|
248
|
+
readonly locale?: string;
|
|
249
|
+
readonly locales?: Readonly<Record<string, string>>;
|
|
250
|
+
/** Complete CSL-JSON records, including structured names, dates and additional variables. */
|
|
251
|
+
readonly items?: readonly Readonly<Record<string, unknown>>[];
|
|
252
|
+
readonly noteIndices?: readonly number[];
|
|
253
|
+
}
|
|
254
|
+
function xml(node: XmlNode): string {
|
|
255
|
+
const escape = (value: string) =>
|
|
256
|
+
value
|
|
257
|
+
.replace(/&/gu, "&")
|
|
258
|
+
.replace(/"/gu, """)
|
|
259
|
+
.replace(/</gu, "<");
|
|
260
|
+
if (node.type === "text") return escape(node.value);
|
|
261
|
+
return `<${node.name}${Object.entries(node.attributes)
|
|
262
|
+
.map(([key, value]) => ` ${key}="${escape(value)}"`)
|
|
263
|
+
.join("")}>${node.children.map(xml).join("")}</${node.name}>`;
|
|
264
|
+
}
|
|
265
|
+
export function formatCitations(
|
|
266
|
+
entries: readonly CitationEntry[],
|
|
267
|
+
clusters: readonly CitationCluster[],
|
|
268
|
+
style: XmlElement,
|
|
269
|
+
options: CslOptions = {},
|
|
270
|
+
): FormattedResearch {
|
|
271
|
+
const records = JSON.parse(toCslJson(entries)) as Record<string, unknown>[];
|
|
272
|
+
const byId = new Map(records.map((item) => [String(item["id"]), item]));
|
|
273
|
+
for (const item of options.items ?? [])
|
|
274
|
+
byId.set(String(item["id"]), structuredClone(item));
|
|
275
|
+
const losses: ResearchLoss[] = [];
|
|
276
|
+
for (const entry of entries)
|
|
277
|
+
if (entry.raw && !entry.title && !entry.authors.length && !entry.issued) {
|
|
278
|
+
byId.set(entry.id, { id: entry.id, type: "document", title: entry.raw });
|
|
279
|
+
losses.push({
|
|
280
|
+
code: "opaque-reference-literal",
|
|
281
|
+
message: `Reference ${entry.id} is preserved literally and cannot be restyled.`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const unresolved = [
|
|
285
|
+
...new Set(
|
|
286
|
+
clusters.flatMap((cluster) =>
|
|
287
|
+
cluster.items
|
|
288
|
+
.filter((item) => !byId.has(item.id))
|
|
289
|
+
.map((item) => item.id),
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
];
|
|
293
|
+
for (const id of unresolved)
|
|
294
|
+
losses.push({
|
|
295
|
+
code: "unresolved-citation",
|
|
296
|
+
message: `Citation ${id} has no bibliography entry.`,
|
|
297
|
+
});
|
|
298
|
+
const localeMap = { ...locales, ...options.locales };
|
|
299
|
+
const processor = new CSL.Engine(
|
|
300
|
+
{
|
|
301
|
+
retrieveLocale: (language: string) => {
|
|
302
|
+
const exact = localeMap[language];
|
|
303
|
+
if (exact) return exact;
|
|
304
|
+
const match = Object.keys(localeMap).find(
|
|
305
|
+
(key) => key.split("-")[0] === language.split("-")[0],
|
|
306
|
+
);
|
|
307
|
+
if (match) return localeMap[match] ?? "";
|
|
308
|
+
throw new Error(`No CSL locale resource for ${language}`);
|
|
309
|
+
},
|
|
310
|
+
retrieveItem: (id: string) =>
|
|
311
|
+
byId.get(id) ?? { id, type: "document", title: id },
|
|
312
|
+
},
|
|
313
|
+
xml(style),
|
|
314
|
+
options.locale ?? "en-US",
|
|
315
|
+
Boolean(options.locale),
|
|
316
|
+
);
|
|
317
|
+
processor.setOutputFormat("text");
|
|
318
|
+
const form = citationForm(style);
|
|
319
|
+
const citations: string[] = clusters.map(() => "");
|
|
320
|
+
const history: [string, number][] = [];
|
|
321
|
+
const positions: number[] = [];
|
|
322
|
+
clusters.forEach((cluster, index) => {
|
|
323
|
+
const noteIndex =
|
|
324
|
+
options.noteIndices?.[index] ?? (form === "note" ? index + 1 : 0);
|
|
325
|
+
const citation = {
|
|
326
|
+
citationID: `publisle-${String(index)}`,
|
|
327
|
+
citationItems: cluster.items
|
|
328
|
+
.filter((item) => byId.has(item.id))
|
|
329
|
+
.map((item) => ({
|
|
330
|
+
id: item.id,
|
|
331
|
+
...(item.locator
|
|
332
|
+
? {
|
|
333
|
+
locator: item.locator,
|
|
334
|
+
label: item.label === "p." ? "page" : (item.label ?? "page"),
|
|
335
|
+
}
|
|
336
|
+
: {}),
|
|
337
|
+
...(item.suppressAuthor ? { "suppress-author": true } : {}),
|
|
338
|
+
})),
|
|
339
|
+
properties: { noteIndex },
|
|
340
|
+
};
|
|
341
|
+
if (citation.citationItems.length) {
|
|
342
|
+
const result = processor.processCitationCluster(citation, history, []);
|
|
343
|
+
positions.push(index);
|
|
344
|
+
for (const [position, value] of result[1]) {
|
|
345
|
+
const target = positions[position];
|
|
346
|
+
if (target !== undefined) citations[target] = value;
|
|
347
|
+
}
|
|
348
|
+
history.push([citation.citationID, noteIndex]);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
clusters.forEach((cluster, index) => {
|
|
352
|
+
const missing = cluster.items
|
|
353
|
+
.filter((item) => !byId.has(item.id))
|
|
354
|
+
.map((item) => item.id);
|
|
355
|
+
citations[index] =
|
|
356
|
+
`${cluster.prefix ?? ""}${citations[index] ?? ""}${missing.length ? `[${missing.join(", ")}]` : ""}${cluster.suffix ?? ""}`;
|
|
357
|
+
});
|
|
358
|
+
const bibliographyResult = processor.makeBibliography();
|
|
359
|
+
const bibliography = bibliographyResult
|
|
360
|
+
? bibliographyResult[1].map((text, index) => ({
|
|
361
|
+
id: bibliographyResult[0].entry_ids[index]?.[0] ?? "",
|
|
362
|
+
label: String(index + 1),
|
|
363
|
+
text: text.trim(),
|
|
364
|
+
}))
|
|
365
|
+
: [];
|
|
366
|
+
return { form, citations, bibliography, unresolved, losses };
|
|
367
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type { CitationEntry, ResearchLoss } from "./article.ts";
|
|
2
|
+
export { parseBibtex, toBibtex, toCslJson } from "./bibtex.ts";
|
|
3
|
+
export { parseCslStyle } from "./csl.ts";
|
|
4
|
+
export { toJats, createJatsPackage, type JatsPackage } from "./jats.ts";
|
|
5
|
+
export { toLatex } from "./latex.ts";
|
|
6
|
+
export {
|
|
7
|
+
researchPaperProfile,
|
|
8
|
+
scholarlyProfile,
|
|
9
|
+
type ResearchPaperProfileOptions,
|
|
10
|
+
} from "./profiles.ts";
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- retained migration export
|
|
12
|
+
export { toPdf } from "./pdf.ts";
|
|
13
|
+
export { resolveDocument, type ResolvedDocument } from "./resolve.ts";
|
|
14
|
+
export {
|
|
15
|
+
resolvePortableCitations,
|
|
16
|
+
PORTABLE_CITATION_CAPABILITIES,
|
|
17
|
+
type PortableCitations,
|
|
18
|
+
} from "./portable-citations.ts";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
createLatexPackage,
|
|
22
|
+
type LatexPackage,
|
|
23
|
+
type LatexPackageOptions,
|
|
24
|
+
type LatexEngine,
|
|
25
|
+
} from "./package.ts";
|
|
26
|
+
|
|
27
|
+
export { createSubmissionPackage } from "./package.ts";
|
|
28
|
+
|
|
29
|
+
export type { CslOptions } from "./csl.ts";
|
package/src/jats.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { ArticleInline } from "./article.ts";
|
|
2
|
+
import type { ResolvedDocument } from "./resolve.ts";
|
|
3
|
+
|
|
4
|
+
function xml(value: string): string {
|
|
5
|
+
return value
|
|
6
|
+
.replaceAll("&", "&")
|
|
7
|
+
.replaceAll("<", "<")
|
|
8
|
+
.replaceAll(">", ">")
|
|
9
|
+
.replaceAll('"', """);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function xmlId(value: string): string {
|
|
13
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/u.test(value) &&
|
|
14
|
+
!value.startsWith("publisle-id-")
|
|
15
|
+
? value
|
|
16
|
+
: "publisle-id-" +
|
|
17
|
+
Array.from(new TextEncoder().encode(value), (byte) =>
|
|
18
|
+
byte.toString(16).padStart(2, "0"),
|
|
19
|
+
).join("");
|
|
20
|
+
}
|
|
21
|
+
function inlineJats(
|
|
22
|
+
inlines: readonly ArticleInline[],
|
|
23
|
+
resolved: ResolvedDocument,
|
|
24
|
+
cursor: { index: number },
|
|
25
|
+
): string {
|
|
26
|
+
return inlines
|
|
27
|
+
.map((inline) => {
|
|
28
|
+
if (inline.type === "break") return "<break/>";
|
|
29
|
+
if (inline.type === "math")
|
|
30
|
+
return `<inline-formula><tex-math><![CDATA[${inline.value.replaceAll("]]>", "]]]]><![CDATA[>")}]]></tex-math></inline-formula>`;
|
|
31
|
+
if (inline.type === "link")
|
|
32
|
+
return `<ext-link ext-link-type="uri" xlink:href="${xml(inline.url)}">${xml(inline.value)}</ext-link>`;
|
|
33
|
+
if (inline.type === "citation") {
|
|
34
|
+
const text = resolved.citations[cursor.index] ?? "";
|
|
35
|
+
cursor.index += 1;
|
|
36
|
+
const rid = inline.cluster.items
|
|
37
|
+
.map((item) => xmlId(item.id))
|
|
38
|
+
.join(" ");
|
|
39
|
+
return `<xref ref-type="bibr" rid="${xml(rid)}">${xml(text)}</xref>`;
|
|
40
|
+
}
|
|
41
|
+
if (inline.reference) {
|
|
42
|
+
const target = resolved.article.blocks.find(
|
|
43
|
+
(block) => block.label === inline.reference?.target,
|
|
44
|
+
);
|
|
45
|
+
const kind =
|
|
46
|
+
inline.reference.kind === "footnote"
|
|
47
|
+
? "fn"
|
|
48
|
+
: target?.kind === "figure"
|
|
49
|
+
? "fig"
|
|
50
|
+
: target?.kind === "table"
|
|
51
|
+
? "table"
|
|
52
|
+
: target?.kind === "math"
|
|
53
|
+
? "disp-formula"
|
|
54
|
+
: "sec";
|
|
55
|
+
return `<xref ref-type="${kind}" rid="${xmlId(inline.reference.target)}">${xml(inline.value)}</xref>`;
|
|
56
|
+
}
|
|
57
|
+
const value = xml(inline.value);
|
|
58
|
+
if (inline.mark === "emphasis") return `<italic>${value}</italic>`;
|
|
59
|
+
if (inline.mark === "strong") return `<bold>${value}</bold>`;
|
|
60
|
+
if (inline.mark === "code") return `<monospace>${value}</monospace>`;
|
|
61
|
+
if (inline.mark === "strike") return `<strike>${value}</strike>`;
|
|
62
|
+
return value;
|
|
63
|
+
})
|
|
64
|
+
.join("");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function publicationType(type: string | undefined): string {
|
|
68
|
+
if (type === "article-journal") return "journal";
|
|
69
|
+
if (type === "book") return "book";
|
|
70
|
+
if (type === "paper-conference") return "confproc";
|
|
71
|
+
return "other";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function name(author: string): string {
|
|
75
|
+
const comma = author.indexOf(",");
|
|
76
|
+
const surname = xml((comma === -1 ? author : author.slice(0, comma)).trim());
|
|
77
|
+
const given = comma === -1 ? "" : author.slice(comma + 1).trim();
|
|
78
|
+
return `<name><surname>${surname}</surname>${given.length === 0 ? "" : `<given-names>${xml(given)}</given-names>`}</name>`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** JATS Archiving 1.3 article XML. It is not a publisher submission bundle. */
|
|
82
|
+
export function toJats(resolved: ResolvedDocument): string {
|
|
83
|
+
const cursor = { index: 0 };
|
|
84
|
+
const take = (inlines: readonly ArticleInline[]) =>
|
|
85
|
+
inlineJats(inlines, resolved, cursor);
|
|
86
|
+
const abstract: string[] = [];
|
|
87
|
+
const body: string[] = [];
|
|
88
|
+
const footnotes: string[] = [];
|
|
89
|
+
const sections: number[] = [];
|
|
90
|
+
const closeSections = (level = 0) => {
|
|
91
|
+
while (sections.length && (sections[sections.length - 1] ?? 0) >= level) {
|
|
92
|
+
body.push("</sec>");
|
|
93
|
+
sections.pop();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
for (const block of resolved.article.blocks) {
|
|
97
|
+
const target =
|
|
98
|
+
block.kind !== "footnote" &&
|
|
99
|
+
block.kind !== "bibliography" &&
|
|
100
|
+
block.abstract
|
|
101
|
+
? abstract
|
|
102
|
+
: body;
|
|
103
|
+
if (block.kind === "heading") {
|
|
104
|
+
if (!block.abstract) {
|
|
105
|
+
closeSections(block.level);
|
|
106
|
+
sections.push(block.level);
|
|
107
|
+
target.push(
|
|
108
|
+
`<sec${block.label ? ` id="${xmlId(block.label)}"` : ""}><title>${take(block.inlines)}</title>`,
|
|
109
|
+
);
|
|
110
|
+
} else abstract.push(`<title>${take(block.inlines)}</title>`);
|
|
111
|
+
} else if (block.kind === "paragraph")
|
|
112
|
+
target.push(`<p>${take(block.inlines)}</p>`);
|
|
113
|
+
else if (block.kind === "list") {
|
|
114
|
+
target.push(
|
|
115
|
+
`<list list-type="${block.ordered ? "order" : "bullet"}">${block.items
|
|
116
|
+
.map((item) => `<list-item><p>${take(item)}</p></list-item>`)
|
|
117
|
+
.join("")}</list>`,
|
|
118
|
+
);
|
|
119
|
+
} else if (block.kind === "quote") {
|
|
120
|
+
target.push(
|
|
121
|
+
`<disp-quote>${block.paragraphs.map((paragraph) => `<p>${take(paragraph)}</p>`).join("")}</disp-quote>`,
|
|
122
|
+
);
|
|
123
|
+
} else if (block.kind === "code")
|
|
124
|
+
target.push(`<preformat>${xml(block.value)}</preformat>`);
|
|
125
|
+
else if (block.kind === "math")
|
|
126
|
+
target.push(
|
|
127
|
+
`<disp-formula${block.label ? ` id="${xmlId(block.label)}"` : ""}><tex-math><![CDATA[${block.value.replaceAll("]]>", "]]]]><![CDATA[>")}]]></tex-math></disp-formula>`,
|
|
128
|
+
);
|
|
129
|
+
else if (block.kind === "figure")
|
|
130
|
+
target.push(
|
|
131
|
+
`<fig${block.label ? ` id="${xmlId(block.label)}"` : ""}><caption><p>${take(block.caption)}</p></caption><alt-text>${xml(block.alt)}</alt-text><graphic xlink:href="${xml(block.src)}"/></fig>`,
|
|
132
|
+
);
|
|
133
|
+
else if (block.kind === "table") {
|
|
134
|
+
const caption = block.caption
|
|
135
|
+
? `<caption><p>${take(block.caption)}</p></caption>`
|
|
136
|
+
: "";
|
|
137
|
+
const rows = block.rows
|
|
138
|
+
.map(
|
|
139
|
+
(row) =>
|
|
140
|
+
`<tr>${row.map((cell) => `<td>${take(cell)}</td>`).join("")}</tr>`,
|
|
141
|
+
)
|
|
142
|
+
.join("");
|
|
143
|
+
target.push(
|
|
144
|
+
`<table-wrap${block.label ? ` id="${xmlId(block.label)}"` : ""}>${caption}<table>${rows}</table></table-wrap>`,
|
|
145
|
+
);
|
|
146
|
+
} else if (block.kind === "footnote") {
|
|
147
|
+
footnotes.push(
|
|
148
|
+
`<fn id="${xmlId(block.id)}"><p>${block.paragraphs.map((paragraph) => take(paragraph)).join(" ")}</p></fn>`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
closeSections();
|
|
153
|
+
const entryById = new Map(
|
|
154
|
+
resolved.article.entries.map((entry) => [entry.id, entry]),
|
|
155
|
+
);
|
|
156
|
+
const refs = resolved.bibliography
|
|
157
|
+
.map((item) => {
|
|
158
|
+
const entry = entryById.get(item.id);
|
|
159
|
+
if (
|
|
160
|
+
!entry ||
|
|
161
|
+
(entry.raw && !entry.title && !entry.authors.length && !entry.issued)
|
|
162
|
+
)
|
|
163
|
+
return `<ref id="${xmlId(item.id)}"><mixed-citation>${xml(entry?.raw ?? item.text)}</mixed-citation></ref>`;
|
|
164
|
+
const pages = (entry.page ?? "").split(/--|-|–/u);
|
|
165
|
+
const first = pages[0]?.trim() ?? "";
|
|
166
|
+
const last = pages[1]?.trim();
|
|
167
|
+
return `<ref id="${xmlId(item.id)}"><element-citation publication-type="${publicationType(entry.type)}">${
|
|
168
|
+
entry.authors.length > 0
|
|
169
|
+
? `<person-group person-group-type="author">${entry.authors.map(name).join("")}</person-group>`
|
|
170
|
+
: ""
|
|
171
|
+
}${entry.title === undefined ? "" : `<article-title>${xml(entry.title)}</article-title>`}${
|
|
172
|
+
entry.containerTitle === undefined
|
|
173
|
+
? ""
|
|
174
|
+
: `<source>${xml(entry.containerTitle)}</source>`
|
|
175
|
+
}${entry.issued === undefined ? "" : `<year>${xml(entry.issued.slice(0, 4))}</year>`}${
|
|
176
|
+
entry.volume === undefined
|
|
177
|
+
? ""
|
|
178
|
+
: `<volume>${xml(entry.volume)}</volume>`
|
|
179
|
+
}${entry.issue === undefined ? "" : `<issue>${xml(entry.issue)}</issue>`}${
|
|
180
|
+
first.length === 0 ? "" : `<fpage>${xml(first)}</fpage>`
|
|
181
|
+
}${last === undefined || last.length === 0 ? "" : `<lpage>${xml(last)}</lpage>`}${
|
|
182
|
+
entry.doi === undefined
|
|
183
|
+
? ""
|
|
184
|
+
: `<pub-id pub-id-type="doi">${xml(entry.doi)}</pub-id>`
|
|
185
|
+
}</element-citation></ref>`;
|
|
186
|
+
})
|
|
187
|
+
.join("");
|
|
188
|
+
const authors = resolved.article.authors
|
|
189
|
+
.map(
|
|
190
|
+
(author) =>
|
|
191
|
+
`<contrib contrib-type="author"><name><surname>${xml(author)}</surname></name></contrib>`,
|
|
192
|
+
)
|
|
193
|
+
.join("");
|
|
194
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
195
|
+
<article xmlns:xlink="http://www.w3.org/1999/xlink" article-type="research-article" dtd-version="1.3"${resolved.article.language ? ` xml:lang="${xml(resolved.article.language)}"` : ""}>
|
|
196
|
+
<front><article-meta>
|
|
197
|
+
<title-group><article-title>${xml(resolved.article.title ?? "Untitled")}</article-title></title-group>
|
|
198
|
+
${authors.length === 0 ? "" : `<contrib-group>${authors}</contrib-group>`}
|
|
199
|
+
${abstract.length === 0 ? "" : `<abstract>${abstract.join("")}</abstract>`}
|
|
200
|
+
</article-meta></front>
|
|
201
|
+
<body>${body.join("")}</body>
|
|
202
|
+
<back>
|
|
203
|
+
${footnotes.length === 0 ? "" : `<fn-group>${footnotes.join("")}</fn-group>`}
|
|
204
|
+
<ref-list>${refs}</ref-list>
|
|
205
|
+
</back>
|
|
206
|
+
</article>
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface JatsPackage {
|
|
211
|
+
readonly files: Readonly<Record<string, string>>;
|
|
212
|
+
readonly assets: readonly {
|
|
213
|
+
readonly source: string;
|
|
214
|
+
readonly destination: string;
|
|
215
|
+
}[];
|
|
216
|
+
readonly diagnostics: ResolvedDocument["losses"];
|
|
217
|
+
}
|
|
218
|
+
/** Pure article-and-assets package. Node collection validates with the bundled offline DTD. */
|
|
219
|
+
export function createJatsPackage(resolved: ResolvedDocument): JatsPackage {
|
|
220
|
+
if (resolved.unresolved.length)
|
|
221
|
+
throw new Error(`Unresolved citations: ${resolved.unresolved.join(", ")}`);
|
|
222
|
+
const assets: { source: string; destination: string }[] = [];
|
|
223
|
+
const blocks = resolved.article.blocks.map((block) => {
|
|
224
|
+
if (block.kind !== "figure") return block;
|
|
225
|
+
if (/^(?:[a-z]+:|\/\/)/iu.test(block.src))
|
|
226
|
+
throw new Error("JATS package requires local figure assets.");
|
|
227
|
+
const extension = /\.([a-z0-9]+)$/iu.exec(block.src)?.[1] ?? "bin";
|
|
228
|
+
const destination = `assets/figure-${String(assets.length + 1)}.${extension}`;
|
|
229
|
+
assets.push({ source: block.src, destination });
|
|
230
|
+
return { ...block, src: destination };
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
files: {
|
|
234
|
+
"article.xml": toJats({
|
|
235
|
+
...resolved,
|
|
236
|
+
article: { ...resolved.article, blocks },
|
|
237
|
+
}),
|
|
238
|
+
},
|
|
239
|
+
assets,
|
|
240
|
+
diagnostics: resolved.losses,
|
|
241
|
+
};
|
|
242
|
+
}
|
package/src/latex.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { template } from "@publisle/template-article";
|
|
2
|
+
import { citationClusters } from "./article.ts";
|
|
3
|
+
import type { ResolvedDocument } from "./resolve.ts";
|
|
4
|
+
export { latexText, latexKey } from "@publisle/template-sdk";
|
|
5
|
+
/** Unicode article source. Use a submission package to collect referenced figure assets. */
|
|
6
|
+
export function toLatex(resolved: ResolvedDocument): string {
|
|
7
|
+
const citations = new Map(
|
|
8
|
+
citationClusters(resolved.article).map((cluster, index) => [
|
|
9
|
+
cluster,
|
|
10
|
+
resolved.citations[index] ?? "",
|
|
11
|
+
]),
|
|
12
|
+
);
|
|
13
|
+
const source = template.render(resolved.article, {
|
|
14
|
+
style: resolved.form,
|
|
15
|
+
citationForm: resolved.form,
|
|
16
|
+
citations: resolved.citations,
|
|
17
|
+
citationText: (cluster) => citations.get(cluster) ?? "",
|
|
18
|
+
bibliography: resolved.bibliography,
|
|
19
|
+
});
|
|
20
|
+
return source.files["manuscript.tex"] ?? "";
|
|
21
|
+
}
|