@citestyle/compiler 0.0.2
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/README.md +138 -0
- package/locales/locales-de-DE.xml +734 -0
- package/locales/locales-en-US.xml +864 -0
- package/locales/locales-pt-BR.xml +746 -0
- package/package.json +36 -0
- package/src/cli.js +322 -0
- package/src/codegen.js +992 -0
- package/src/compile.js +43 -0
- package/src/index.js +9 -0
- package/src/locale.js +235 -0
- package/src/parser.js +606 -0
- package/types.d.ts +1 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSL XML Parser — transforms CSL XML into a typed AST.
|
|
3
|
+
*
|
|
4
|
+
* Uses @xmldom/xmldom for DOM parsing (build-time tool, parser weight irrelevant).
|
|
5
|
+
* Walks the DOM into CSL-specific AST nodes.
|
|
6
|
+
*/
|
|
7
|
+
import { DOMParser } from '@xmldom/xmldom'
|
|
8
|
+
|
|
9
|
+
const CSL_NS = 'http://purl.org/net/xbiblio/csl'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse a CSL XML string into a CSL AST.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} xml - CSL XML source
|
|
15
|
+
* @returns {CslStyleNode} Parsed AST
|
|
16
|
+
*/
|
|
17
|
+
export function parse(xml) {
|
|
18
|
+
const doc = new DOMParser().parseFromString(xml, 'text/xml')
|
|
19
|
+
const styleEl = doc.documentElement
|
|
20
|
+
|
|
21
|
+
if (!styleEl || localName(styleEl) !== 'style') {
|
|
22
|
+
throw new Error('Invalid CSL: root element must be <style>')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return parseStyle(styleEl)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse the <style> root element.
|
|
30
|
+
*/
|
|
31
|
+
function parseStyle(el) {
|
|
32
|
+
const node = {
|
|
33
|
+
type: 'style',
|
|
34
|
+
class: attr(el, 'class') || 'in-text',
|
|
35
|
+
version: attr(el, 'version'),
|
|
36
|
+
defaultLocale: attr(el, 'default-locale') || 'en-US',
|
|
37
|
+
demoteNonDroppingParticle: attr(el, 'demote-non-dropping-particle') || 'display-and-sort',
|
|
38
|
+
// Inheritable name options (global defaults)
|
|
39
|
+
nameOptions: parseInheritableNameOptions(el),
|
|
40
|
+
pageRangeFormat: attr(el, 'page-range-format'),
|
|
41
|
+
info: null,
|
|
42
|
+
localeOverrides: [],
|
|
43
|
+
macros: {},
|
|
44
|
+
citation: null,
|
|
45
|
+
bibliography: null,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const child of childElements(el)) {
|
|
49
|
+
const name = localName(child)
|
|
50
|
+
if (name === 'info') {
|
|
51
|
+
node.info = parseInfo(child)
|
|
52
|
+
} else if (name === 'locale') {
|
|
53
|
+
node.localeOverrides.push(parseLocaleOverride(child))
|
|
54
|
+
} else if (name === 'macro') {
|
|
55
|
+
const macroName = attr(child, 'name')
|
|
56
|
+
node.macros[macroName] = {
|
|
57
|
+
type: 'macro',
|
|
58
|
+
name: macroName,
|
|
59
|
+
children: parseRenderingElements(child),
|
|
60
|
+
}
|
|
61
|
+
} else if (name === 'citation') {
|
|
62
|
+
node.citation = parseCitationOrBibliography(child, 'citation')
|
|
63
|
+
} else if (name === 'bibliography') {
|
|
64
|
+
node.bibliography = parseCitationOrBibliography(child, 'bibliography')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return node
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse <info> element for metadata.
|
|
73
|
+
*/
|
|
74
|
+
function parseInfo(el) {
|
|
75
|
+
const info = { title: '', titleShort: '', id: '', links: [], categories: [], rights: '' }
|
|
76
|
+
for (const child of childElements(el)) {
|
|
77
|
+
const name = localName(child)
|
|
78
|
+
if (name === 'title') info.title = textContent(child)
|
|
79
|
+
else if (name === 'title-short') info.titleShort = textContent(child)
|
|
80
|
+
else if (name === 'id') info.id = textContent(child)
|
|
81
|
+
else if (name === 'rights') info.rights = textContent(child)
|
|
82
|
+
else if (name === 'link') {
|
|
83
|
+
info.links.push({ href: attr(child, 'href'), rel: attr(child, 'rel') })
|
|
84
|
+
} else if (name === 'category') {
|
|
85
|
+
const field = attr(child, 'field')
|
|
86
|
+
const citFormat = attr(child, 'citation-format')
|
|
87
|
+
if (field) info.categories.push(field)
|
|
88
|
+
if (citFormat) info.citationFormat = citFormat
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return info
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse a locale override within the style.
|
|
96
|
+
*/
|
|
97
|
+
function parseLocaleOverride(el) {
|
|
98
|
+
const override = {
|
|
99
|
+
lang: attr(el, 'xml:lang') || attr(el, 'lang'),
|
|
100
|
+
terms: {},
|
|
101
|
+
dateFormats: {},
|
|
102
|
+
styleOptions: {},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const child of childElements(el)) {
|
|
106
|
+
const name = localName(child)
|
|
107
|
+
if (name === 'terms') {
|
|
108
|
+
for (const termEl of childElements(child)) {
|
|
109
|
+
if (localName(termEl) === 'term') {
|
|
110
|
+
const termName = attr(termEl, 'name')
|
|
111
|
+
const form = attr(termEl, 'form') || 'long'
|
|
112
|
+
const key = form === 'long' ? termName : termName + ':' + form
|
|
113
|
+
|
|
114
|
+
// Check for single/multiple child elements
|
|
115
|
+
const single = findChild(termEl, 'single')
|
|
116
|
+
const multiple = findChild(termEl, 'multiple')
|
|
117
|
+
if (single && multiple) {
|
|
118
|
+
override.terms[key] = {
|
|
119
|
+
single: textContent(single),
|
|
120
|
+
multiple: textContent(multiple),
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
override.terms[key] = textContent(termEl)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else if (name === 'date') {
|
|
128
|
+
const form = attr(child, 'form')
|
|
129
|
+
override.dateFormats[form] = parseDateNode(child)
|
|
130
|
+
} else if (name === 'style-options') {
|
|
131
|
+
override.styleOptions = {
|
|
132
|
+
punctuationInQuote: attr(child, 'punctuation-in-quote'),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return override
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse <citation> or <bibliography> element.
|
|
142
|
+
*/
|
|
143
|
+
function parseCitationOrBibliography(el, type) {
|
|
144
|
+
const node = {
|
|
145
|
+
type,
|
|
146
|
+
nameOptions: parseInheritableNameOptions(el),
|
|
147
|
+
sort: null,
|
|
148
|
+
layout: null,
|
|
149
|
+
// Bibliography-specific
|
|
150
|
+
subsequentAuthorSubstitute: attr(el, 'subsequent-author-substitute'),
|
|
151
|
+
secondFieldAlign: attr(el, 'second-field-align'),
|
|
152
|
+
hangingIndent: attr(el, 'hanging-indent') === 'true',
|
|
153
|
+
entrySpacing: attr(el, 'entry-spacing'),
|
|
154
|
+
lineSpacing: attr(el, 'line-spacing'),
|
|
155
|
+
// Citation-specific
|
|
156
|
+
disambiguateAddYearSuffix: attr(el, 'disambiguate-add-year-suffix') === 'true',
|
|
157
|
+
disambiguateAddNames: attr(el, 'disambiguate-add-names') === 'true',
|
|
158
|
+
disambiguateAddGivenname: attr(el, 'disambiguate-add-givenname') === 'true',
|
|
159
|
+
givennameDisambiguationRule: attr(el, 'givenname-disambiguation-rule'),
|
|
160
|
+
collapseAttr: attr(el, 'collapse'),
|
|
161
|
+
citeGroupDelimiter: attr(el, 'cite-group-delimiter'),
|
|
162
|
+
yearSuffixDelimiter: attr(el, 'year-suffix-delimiter'),
|
|
163
|
+
afterCollapseDelimiter: attr(el, 'after-collapse-delimiter'),
|
|
164
|
+
nearNoteDistance: attr(el, 'near-note-distance'),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const child of childElements(el)) {
|
|
168
|
+
const name = localName(child)
|
|
169
|
+
if (name === 'sort') {
|
|
170
|
+
node.sort = parseSort(child)
|
|
171
|
+
} else if (name === 'layout') {
|
|
172
|
+
node.layout = parseLayout(child)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return node
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse <layout> element.
|
|
181
|
+
*/
|
|
182
|
+
function parseLayout(el) {
|
|
183
|
+
return {
|
|
184
|
+
type: 'layout',
|
|
185
|
+
...parseFormatting(el),
|
|
186
|
+
delimiter: attr(el, 'delimiter'),
|
|
187
|
+
verticalAlign: attr(el, 'vertical-align'),
|
|
188
|
+
children: parseRenderingElements(el),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse <sort> element.
|
|
194
|
+
*/
|
|
195
|
+
function parseSort(el) {
|
|
196
|
+
const keys = []
|
|
197
|
+
for (const child of childElements(el)) {
|
|
198
|
+
if (localName(child) === 'key') {
|
|
199
|
+
keys.push({
|
|
200
|
+
type: 'sort-key',
|
|
201
|
+
variable: attr(child, 'variable'),
|
|
202
|
+
macro: attr(child, 'macro'),
|
|
203
|
+
sort: attr(child, 'sort') || 'ascending',
|
|
204
|
+
namesMin: attr(child, 'names-min'),
|
|
205
|
+
namesUseFirst: attr(child, 'names-use-first'),
|
|
206
|
+
namesUseLast: attr(child, 'names-use-last'),
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { type: 'sort', keys }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse all rendering elements within a parent.
|
|
215
|
+
*/
|
|
216
|
+
function parseRenderingElements(parent) {
|
|
217
|
+
const children = []
|
|
218
|
+
for (const el of childElements(parent)) {
|
|
219
|
+
const node = parseRenderingElement(el)
|
|
220
|
+
if (node) children.push(node)
|
|
221
|
+
}
|
|
222
|
+
return children
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Parse a single rendering element.
|
|
227
|
+
*/
|
|
228
|
+
function parseRenderingElement(el) {
|
|
229
|
+
const name = localName(el)
|
|
230
|
+
|
|
231
|
+
switch (name) {
|
|
232
|
+
case 'text': return parseText(el)
|
|
233
|
+
case 'number': return parseNumber(el)
|
|
234
|
+
case 'label': return parseLabel(el)
|
|
235
|
+
case 'group': return parseGroup(el)
|
|
236
|
+
case 'choose': return parseChoose(el)
|
|
237
|
+
case 'names': return parseNames(el)
|
|
238
|
+
case 'date': return parseDateNode(el)
|
|
239
|
+
default: return null
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Parse <text> element.
|
|
245
|
+
*/
|
|
246
|
+
function parseText(el) {
|
|
247
|
+
return {
|
|
248
|
+
type: 'text',
|
|
249
|
+
variable: attr(el, 'variable'),
|
|
250
|
+
macro: attr(el, 'macro'),
|
|
251
|
+
term: attr(el, 'term'),
|
|
252
|
+
value: attr(el, 'value'),
|
|
253
|
+
form: attr(el, 'form'),
|
|
254
|
+
plural: attr(el, 'plural'),
|
|
255
|
+
...parseFormatting(el),
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Parse <number> element.
|
|
261
|
+
*/
|
|
262
|
+
function parseNumber(el) {
|
|
263
|
+
return {
|
|
264
|
+
type: 'number',
|
|
265
|
+
variable: attr(el, 'variable'),
|
|
266
|
+
form: attr(el, 'form') || 'numeric',
|
|
267
|
+
...parseFormatting(el),
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Parse <label> element.
|
|
273
|
+
*/
|
|
274
|
+
function parseLabel(el) {
|
|
275
|
+
return {
|
|
276
|
+
type: 'label',
|
|
277
|
+
variable: attr(el, 'variable'),
|
|
278
|
+
form: attr(el, 'form') || 'long',
|
|
279
|
+
plural: attr(el, 'plural') || 'contextual',
|
|
280
|
+
...parseFormatting(el),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Parse <group> element.
|
|
286
|
+
*/
|
|
287
|
+
function parseGroup(el) {
|
|
288
|
+
return {
|
|
289
|
+
type: 'group',
|
|
290
|
+
...parseFormatting(el),
|
|
291
|
+
delimiter: attr(el, 'delimiter'),
|
|
292
|
+
children: parseRenderingElements(el),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parse <choose> element.
|
|
298
|
+
*/
|
|
299
|
+
function parseChoose(el) {
|
|
300
|
+
const conditions = []
|
|
301
|
+
let elseNode = null
|
|
302
|
+
|
|
303
|
+
for (const child of childElements(el)) {
|
|
304
|
+
const name = localName(child)
|
|
305
|
+
if (name === 'if' || name === 'else-if') {
|
|
306
|
+
conditions.push({
|
|
307
|
+
type: 'condition',
|
|
308
|
+
match: attr(child, 'match') || 'all',
|
|
309
|
+
tests: parseConditionTests(child),
|
|
310
|
+
children: parseRenderingElements(child),
|
|
311
|
+
})
|
|
312
|
+
} else if (name === 'else') {
|
|
313
|
+
elseNode = {
|
|
314
|
+
type: 'else',
|
|
315
|
+
children: parseRenderingElements(child),
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
type: 'choose',
|
|
322
|
+
conditions,
|
|
323
|
+
else: elseNode,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Parse condition tests from if/else-if attributes.
|
|
329
|
+
*/
|
|
330
|
+
function parseConditionTests(el) {
|
|
331
|
+
const tests = []
|
|
332
|
+
|
|
333
|
+
const typeAttr = attr(el, 'type')
|
|
334
|
+
if (typeAttr) {
|
|
335
|
+
tests.push({ test: 'type', values: typeAttr.split(/\s+/) })
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const variableAttr = attr(el, 'variable')
|
|
339
|
+
if (variableAttr) {
|
|
340
|
+
tests.push({ test: 'variable', values: variableAttr.split(/\s+/) })
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const isNumericAttr = attr(el, 'is-numeric')
|
|
344
|
+
if (isNumericAttr) {
|
|
345
|
+
tests.push({ test: 'is-numeric', values: isNumericAttr.split(/\s+/) })
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const locatorAttr = attr(el, 'locator')
|
|
349
|
+
if (locatorAttr) {
|
|
350
|
+
tests.push({ test: 'locator', values: locatorAttr.split(/\s+/) })
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const positionAttr = attr(el, 'position')
|
|
354
|
+
if (positionAttr) {
|
|
355
|
+
tests.push({ test: 'position', values: positionAttr.split(/\s+/) })
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const disambiguateAttr = attr(el, 'disambiguate')
|
|
359
|
+
if (disambiguateAttr) {
|
|
360
|
+
tests.push({ test: 'disambiguate', values: [disambiguateAttr] })
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const isUncertainDate = attr(el, 'is-uncertain-date')
|
|
364
|
+
if (isUncertainDate) {
|
|
365
|
+
tests.push({ test: 'is-uncertain-date', values: isUncertainDate.split(/\s+/) })
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return tests
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parse <names> element.
|
|
373
|
+
*/
|
|
374
|
+
function parseNames(el) {
|
|
375
|
+
const node = {
|
|
376
|
+
type: 'names',
|
|
377
|
+
variables: (attr(el, 'variable') || '').split(/\s+/).filter(Boolean),
|
|
378
|
+
...parseFormatting(el),
|
|
379
|
+
nameNode: null,
|
|
380
|
+
etAlNode: null,
|
|
381
|
+
labelNode: null,
|
|
382
|
+
substitute: null,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const child of childElements(el)) {
|
|
386
|
+
const name = localName(child)
|
|
387
|
+
if (name === 'name') {
|
|
388
|
+
node.nameNode = parseNameNode(child)
|
|
389
|
+
} else if (name === 'et-al') {
|
|
390
|
+
node.etAlNode = {
|
|
391
|
+
type: 'et-al',
|
|
392
|
+
term: attr(child, 'term') || 'et-al',
|
|
393
|
+
...parseFormatting(child),
|
|
394
|
+
}
|
|
395
|
+
} else if (name === 'label') {
|
|
396
|
+
node.labelNode = parseLabel(child)
|
|
397
|
+
} else if (name === 'substitute') {
|
|
398
|
+
node.substitute = {
|
|
399
|
+
type: 'substitute',
|
|
400
|
+
children: parseRenderingElements(child),
|
|
401
|
+
}
|
|
402
|
+
// CSL spec: substitute <names> without children inherit from parent
|
|
403
|
+
for (const sub of node.substitute.children) {
|
|
404
|
+
if (sub.type === 'names' && !sub.nameNode) {
|
|
405
|
+
sub.nameNode = node.nameNode
|
|
406
|
+
if (!sub.etAlNode) sub.etAlNode = node.etAlNode
|
|
407
|
+
if (!sub.labelNode) sub.labelNode = node.labelNode
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return node
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Parse <name> element.
|
|
418
|
+
*/
|
|
419
|
+
function parseNameNode(el) {
|
|
420
|
+
const node = {
|
|
421
|
+
type: 'name',
|
|
422
|
+
and: attr(el, 'and'),
|
|
423
|
+
delimiter: attr(el, 'delimiter') ?? ', ',
|
|
424
|
+
delimiterPrecedesEtAl: attr(el, 'delimiter-precedes-et-al'),
|
|
425
|
+
delimiterPrecedesLast: attr(el, 'delimiter-precedes-last'),
|
|
426
|
+
etAlMin: attrInt(el, 'et-al-min'),
|
|
427
|
+
etAlUseFirst: attrInt(el, 'et-al-use-first'),
|
|
428
|
+
etAlUseLast: attr(el, 'et-al-use-last') === 'true',
|
|
429
|
+
etAlSubsequentMin: attrInt(el, 'et-al-subsequent-min'),
|
|
430
|
+
etAlSubsequentUseFirst: attrInt(el, 'et-al-subsequent-use-first'),
|
|
431
|
+
form: attr(el, 'form') || 'long',
|
|
432
|
+
initialize: attr(el, 'initialize') !== 'false',
|
|
433
|
+
initializeWith: attr(el, 'initialize-with'),
|
|
434
|
+
nameAsSortOrder: attr(el, 'name-as-sort-order'),
|
|
435
|
+
sortSeparator: attr(el, 'sort-separator') ?? ', ',
|
|
436
|
+
...parseFormatting(el),
|
|
437
|
+
nameParts: [],
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
for (const child of childElements(el)) {
|
|
441
|
+
if (localName(child) === 'name-part') {
|
|
442
|
+
node.nameParts.push({
|
|
443
|
+
name: attr(child, 'name'),
|
|
444
|
+
...parseFormatting(child),
|
|
445
|
+
})
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return node
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Parse <date> or locale <date> element.
|
|
454
|
+
*/
|
|
455
|
+
function parseDateNode(el) {
|
|
456
|
+
const node = {
|
|
457
|
+
type: 'date',
|
|
458
|
+
variable: attr(el, 'variable'),
|
|
459
|
+
form: attr(el, 'form'),
|
|
460
|
+
datePartsAttr: attr(el, 'date-parts'),
|
|
461
|
+
datePartsDelimiter: attr(el, 'delimiter'),
|
|
462
|
+
...parseFormatting(el),
|
|
463
|
+
dateParts: [],
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (const child of childElements(el)) {
|
|
467
|
+
if (localName(child) === 'date-part') {
|
|
468
|
+
node.dateParts.push({
|
|
469
|
+
type: 'date-part',
|
|
470
|
+
name: attr(child, 'name'),
|
|
471
|
+
form: attr(child, 'form'),
|
|
472
|
+
rangeDelimiter: attr(child, 'range-delimiter'),
|
|
473
|
+
...parseFormatting(child),
|
|
474
|
+
})
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return node
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Parse inheritable name options from style, citation, or bibliography elements.
|
|
483
|
+
*/
|
|
484
|
+
function parseInheritableNameOptions(el) {
|
|
485
|
+
const opts = {}
|
|
486
|
+
const mappings = {
|
|
487
|
+
'names-delimiter': 'namesDelimiter',
|
|
488
|
+
'name-form': 'form',
|
|
489
|
+
'name-delimiter': 'delimiter',
|
|
490
|
+
'initialize': 'initialize',
|
|
491
|
+
'initialize-with': 'initializeWith',
|
|
492
|
+
'name-as-sort-order': 'nameAsSortOrder',
|
|
493
|
+
'sort-separator': 'sortSeparator',
|
|
494
|
+
'and': 'and',
|
|
495
|
+
'delimiter-precedes-last': 'delimiterPrecedesLast',
|
|
496
|
+
'delimiter-precedes-et-al': 'delimiterPrecedesEtAl',
|
|
497
|
+
'et-al-min': 'etAlMin',
|
|
498
|
+
'et-al-use-first': 'etAlUseFirst',
|
|
499
|
+
'et-al-use-last': 'etAlUseLast',
|
|
500
|
+
'et-al-subsequent-min': 'etAlSubsequentMin',
|
|
501
|
+
'et-al-subsequent-use-first': 'etAlSubsequentUseFirst',
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
for (const [xmlAttr, jsName] of Object.entries(mappings)) {
|
|
505
|
+
const val = attr(el, xmlAttr)
|
|
506
|
+
if (val != null) {
|
|
507
|
+
if (xmlAttr.includes('min') || xmlAttr.includes('first')) {
|
|
508
|
+
opts[jsName] = parseInt(val, 10)
|
|
509
|
+
} else if (val === 'true') {
|
|
510
|
+
opts[jsName] = true
|
|
511
|
+
} else if (val === 'false') {
|
|
512
|
+
opts[jsName] = false
|
|
513
|
+
} else {
|
|
514
|
+
opts[jsName] = val
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return Object.keys(opts).length > 0 ? opts : null
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Parse formatting and affix attributes common to most elements.
|
|
524
|
+
*/
|
|
525
|
+
function parseFormatting(el) {
|
|
526
|
+
const f = {}
|
|
527
|
+
|
|
528
|
+
const fontStyle = attr(el, 'font-style')
|
|
529
|
+
if (fontStyle) f.fontStyle = fontStyle
|
|
530
|
+
|
|
531
|
+
const fontWeight = attr(el, 'font-weight')
|
|
532
|
+
if (fontWeight) f.fontWeight = fontWeight
|
|
533
|
+
|
|
534
|
+
const fontVariant = attr(el, 'font-variant')
|
|
535
|
+
if (fontVariant) f.fontVariant = fontVariant
|
|
536
|
+
|
|
537
|
+
const textDecoration = attr(el, 'text-decoration')
|
|
538
|
+
if (textDecoration) f.textDecoration = textDecoration
|
|
539
|
+
|
|
540
|
+
const textCase = attr(el, 'text-case')
|
|
541
|
+
if (textCase) f.textCase = textCase
|
|
542
|
+
|
|
543
|
+
const display = attr(el, 'display')
|
|
544
|
+
if (display) f.display = display
|
|
545
|
+
|
|
546
|
+
const quotes = attr(el, 'quotes')
|
|
547
|
+
if (quotes) f.quotes = quotes === 'true'
|
|
548
|
+
|
|
549
|
+
const stripPeriods = attr(el, 'strip-periods')
|
|
550
|
+
if (stripPeriods) f.stripPeriods = stripPeriods === 'true'
|
|
551
|
+
|
|
552
|
+
const prefix = attr(el, 'prefix')
|
|
553
|
+
if (prefix != null) f.prefix = prefix
|
|
554
|
+
|
|
555
|
+
const suffix = attr(el, 'suffix')
|
|
556
|
+
if (suffix != null) f.suffix = suffix
|
|
557
|
+
|
|
558
|
+
return f
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ── DOM helpers ──────────────────────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
function localName(el) {
|
|
564
|
+
return el.localName || el.nodeName?.replace(/^.*:/, '')
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function attr(el, name) {
|
|
568
|
+
if (!el.hasAttribute?.(name)) return null
|
|
569
|
+
return el.getAttribute(name)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function attrInt(el, name) {
|
|
573
|
+
const val = attr(el, name)
|
|
574
|
+
return val != null ? parseInt(val, 10) : undefined
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function textContent(el) {
|
|
578
|
+
let text = ''
|
|
579
|
+
for (let i = 0; i < el.childNodes.length; i++) {
|
|
580
|
+
const child = el.childNodes[i]
|
|
581
|
+
if (child.nodeType === 3 /* TEXT_NODE */ || child.nodeType === 4 /* CDATA */) {
|
|
582
|
+
text += child.nodeValue || ''
|
|
583
|
+
} else if (child.nodeType === 1 /* ELEMENT_NODE */) {
|
|
584
|
+
text += textContent(child)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return text.trim()
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function childElements(el) {
|
|
591
|
+
const result = []
|
|
592
|
+
if (!el.childNodes) return result
|
|
593
|
+
for (let i = 0; i < el.childNodes.length; i++) {
|
|
594
|
+
if (el.childNodes[i].nodeType === 1) {
|
|
595
|
+
result.push(el.childNodes[i])
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return result
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function findChild(el, name) {
|
|
602
|
+
for (const child of childElements(el)) {
|
|
603
|
+
if (localName(child) === name) return child
|
|
604
|
+
}
|
|
605
|
+
return null
|
|
606
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { compile, parse, resolveLocale, generate, CompileOptions, CompileResult } from '@citestyle/types'
|