jats-xml 1.1.4 → 1.1.5

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/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { default as version } from './version.js';
2
2
  export { Jats } from './jats.js';
3
+ export { recordJatsMessage } from './messages.js';
4
+ export { knownXmlDefectRepairMessage, repairKnownXmlDefects, type AppliedXmlDefectRepair, type KnownXmlDefectRepair, type RepairKnownXmlDefectsResult, } from './repairKnownXmlDefects.js';
5
+ export { sanitizeXmlEntities, type SanitizeXmlEntitiesResult } from './sanitizeXmlEntities.js';
3
6
  export * from './session.js';
4
7
  export * from './types.js';
5
8
  export * from './utils.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,GACjC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,KAAK,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export { default as version } from './version.js';
2
2
  export { Jats } from './jats.js';
3
+ export { recordJatsMessage } from './messages.js';
4
+ export { knownXmlDefectRepairMessage, repairKnownXmlDefects, } from './repairKnownXmlDefects.js';
5
+ export { sanitizeXmlEntities } from './sanitizeXmlEntities.js';
3
6
  export * from './session.js';
4
7
  export * from './types.js';
5
8
  export * from './utils.js';
package/dist/jats.d.ts CHANGED
@@ -3,10 +3,12 @@ import type { Element, DeclarationAttributes } from 'xml-js';
3
3
  import { type PageFrontmatter } from 'myst-frontmatter';
4
4
  import type { Front, Body, Back, SubArticle, RefList, Reference, TitleGroup, ArticleTitle, Subtitle, Permissions, PubDate, License, Abstract, ContribGroup, Contrib, Affiliation, KeywordGroup, Keyword, ArticleCategories, ArticleMeta } from 'jats-tags';
5
5
  import type { Logger } from 'myst-cli-utils';
6
+ import type { VFile } from 'vfile';
6
7
  import { type SerializationOptions } from 'jats-utils';
7
8
  type Options = {
8
9
  log?: Logger;
9
10
  source?: string;
11
+ vfile?: VFile;
10
12
  };
11
13
  type WriteOptions = SerializationOptions & {
12
14
  bodyOnly?: boolean;
@@ -18,6 +20,8 @@ export declare class Jats {
18
20
  log?: Logger;
19
21
  tree: GenericParent;
20
22
  source?: string;
23
+ vfile?: VFile;
24
+ private frontmatterMessagesRecorded;
21
25
  constructor(data: string, opts?: Options);
22
26
  get frontmatter(): PageFrontmatter;
23
27
  get front(): Front | undefined;
@@ -33,7 +37,24 @@ export declare class Jats {
33
37
  * often including an "accepted" date that is a better default for project/page `date`.
34
38
  */
35
39
  get historyDates(): GenericParent[];
40
+ /**
41
+ * Prefer a fully-specified publication date (day/month/year).
42
+ * If publication date is incomplete (e.g. year-only), fall back through history dates.
43
+ */
44
+ private resolvePublicationDate;
45
+ private recordFrontmatterMessages;
46
+ private pickPublicationDate;
47
+ private static readonly PUB_HISTORY_PRIMARY_DATE_TYPES;
48
+ private static readonly HISTORY_PRIMARY_DATE_TYPES;
49
+ private jatsDateType;
50
+ private dateHasDay;
51
+ /** Last `<date>` with a `<day>` whose `date-type` is not in `excludeTypes`. */
52
+ private lastDateWithDay;
53
+ private fallbackDatePick;
54
+ private findHistoryDateByType;
36
55
  get historyAcceptedDate(): GenericParent | undefined;
56
+ get pubHistoryFallbackDate(): GenericParent | undefined;
57
+ get historyFallbackDate(): GenericParent | undefined;
37
58
  /**
38
59
  * Some sources store important dates under:
39
60
  * `<article-meta><pub-history><event><date date-type="...">...</date></event></pub-history>`.
@@ -1 +1 @@
1
- {"version":3,"file":"jats.d.ts","sourceRoot":"","sources":["../src/jats.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jats.d.ts","sourceRoot":"","sources":["../src/jats.ts"],"names":[],"mappings":"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"}
package/dist/jats.js CHANGED
@@ -6,6 +6,9 @@ import { select as unistSelect, selectAll } from 'unist-util-select';
6
6
  import { Tags } from 'jats-tags';
7
7
  import { findArticleId, processAffiliation, processContributor } from './utils.js';
8
8
  import { tic } from 'myst-cli-utils';
9
+ import { recordJatsMessage } from './messages.js';
10
+ import { knownXmlDefectRepairMessage, repairKnownXmlDefects } from './repairKnownXmlDefects.js';
11
+ import { sanitizeXmlEntities } from './sanitizeXmlEntities.js';
9
12
  import { articleMetaOrder, tableWrapOrder } from './order.js';
10
13
  import { serializeJatsXml, convertToUnist, convertToXml, toDate, } from 'jats-utils';
11
14
  function select(selector, node) {
@@ -38,27 +41,45 @@ function significantChildElements(elements) {
38
41
  });
39
42
  }
40
43
  /**
41
- * Drop a leading processing instruction when it precedes doctype and the root element.
44
+ * Drop top-level processing instructions (e.g. xml-stylesheet) from the prolog.
42
45
  * Malformed prologs such as <?version xml="1.0"?> are parsed this way by xml-js.
43
46
  */
44
- function dropLeadingPrologInstruction(elements) {
45
- if ((elements === null || elements === void 0 ? void 0 : elements.length) === 3 &&
46
- elements[0].type === 'instruction' &&
47
- elements[1].type === 'doctype' &&
48
- elements[2].type === 'element') {
49
- return elements.slice(1);
50
- }
51
- return elements;
47
+ function dropTopLevelInstructions(elements, onInstruction) {
48
+ return elements === null || elements === void 0 ? void 0 : elements.filter((elem) => {
49
+ if (elem.type === 'instruction') {
50
+ onInstruction === null || onInstruction === void 0 ? void 0 : onInstruction(elem);
51
+ return false;
52
+ }
53
+ return true;
54
+ });
52
55
  }
53
56
  export class Jats {
54
57
  constructor(data, opts) {
55
- var _a, _b;
58
+ var _a;
59
+ this.frontmatterMessagesRecorded = false;
56
60
  const toc = tic();
57
61
  this.log = opts === null || opts === void 0 ? void 0 : opts.log;
58
62
  if (opts === null || opts === void 0 ? void 0 : opts.source)
59
63
  this.source = opts.source;
64
+ this.vfile = opts === null || opts === void 0 ? void 0 : opts.vfile;
65
+ const warnProlog = (reason, note) => {
66
+ recordJatsMessage(this.vfile, reason, { note });
67
+ };
68
+ const { xml: afterDefectRepairs, applied: defectRepairs } = repairKnownXmlDefects(data);
69
+ defectRepairs.forEach(({ repair, count }) => {
70
+ const reason = knownXmlDefectRepairMessage(repair.from, repair.to);
71
+ const note = count === 1 ? undefined : `${count} occurrences`;
72
+ warnProlog(reason, note);
73
+ });
74
+ const { xml: parseInput, escapedBareAmpersandCount } = sanitizeXmlEntities(afterDefectRepairs);
75
+ if (escapedBareAmpersandCount > 0) {
76
+ const note = escapedBareAmpersandCount === 1
77
+ ? '1 bare & rewritten to &#38;'
78
+ : `${escapedBareAmpersandCount} bare & rewritten to &#38;`;
79
+ warnProlog('Escaped bare ampersand(s) before XML parse', note);
80
+ }
60
81
  try {
61
- this.raw = xml2js(data, {
82
+ this.raw = xml2js(parseInput, {
62
83
  compact: false,
63
84
  // Preserve whitespace-only text nodes between elements. This is usually unnecessary except inside <preformat>.
64
85
  // convertToUnist drops these outside preformat so other content is processed independent of arbitrary xml whitespace.
@@ -70,9 +91,15 @@ export class Jats {
70
91
  }
71
92
  const { declaration, elements } = this.raw;
72
93
  this.declaration = declaration === null || declaration === void 0 ? void 0 : declaration.attributes;
73
- const filteredElements = dropLeadingPrologInstruction(significantChildElements(elements));
94
+ const filteredElements = dropTopLevelInstructions(significantChildElements(elements), (instruction) => {
95
+ var _a, _b;
96
+ const name = (_a = instruction.name) !== null && _a !== void 0 ? _a : '(unnamed)';
97
+ const body = String((_b = instruction.instruction) !== null && _b !== void 0 ? _b : '').trim();
98
+ const note = body ? `name=${name} ${body}` : `name=${name}`;
99
+ warnProlog('Removed top-level XML processing instruction from prolog', note);
100
+ });
74
101
  if ((filteredElements === null || filteredElements === void 0 ? void 0 : filteredElements.length) && filteredElements[0].type !== 'doctype') {
75
- (_a = this.log) === null || _a === void 0 ? void 0 : _a.warn('JATS is missing DOCTYPE declaration');
102
+ warnProlog('JATS is missing DOCTYPE declaration; inserted empty doctype');
76
103
  filteredElements.unshift({ type: 'doctype' });
77
104
  }
78
105
  if (!((filteredElements === null || filteredElements === void 0 ? void 0 : filteredElements.length) === 2 &&
@@ -81,36 +108,32 @@ export class Jats {
81
108
  throw new Error('JATS must be structured as <!DOCTYPE><article>...</article>');
82
109
  }
83
110
  this.doctype = filteredElements[0].doctype;
111
+ if (filteredElements[1].name === 'pmc-articleset') {
112
+ warnProlog('JATS root is pmc-articleset wrapper', 'Using nested article element');
113
+ }
84
114
  const converted = convertToUnist(filteredElements[1]);
85
115
  this.tree = select('article', converted);
86
- (_b = this.log) === null || _b === void 0 ? void 0 : _b.debug(toc('Parsed and converted JATS to unist tree in %s'));
116
+ (_a = this.log) === null || _a === void 0 ? void 0 : _a.debug(toc('Parsed and converted JATS to unist tree in %s'));
87
117
  }
88
118
  get frontmatter() {
89
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
119
+ var _a, _b, _c, _d, _e, _f, _g, _h;
90
120
  const title = this.articleTitle;
91
121
  const subtitle = this.articleSubtitle;
92
122
  const short_title = this.articleAltTitle;
93
- let date;
94
- // Prefer a fully-specified publication date (day/month/year).
95
- // If publication date is incomplete (e.g. year-only), fall back to history accepted date.
96
- const preferredDateNode = (_c = (_b = (_a = this.publicationDate) !== null && _a !== void 0 ? _a : this.pubHistoryPubDate) !== null && _b !== void 0 ? _b : this.pubHistoryAcceptedDate) !== null && _c !== void 0 ? _c : this.historyAcceptedDate;
97
- if (preferredDateNode) {
98
- const d = toDate(preferredDateNode);
99
- if (d) {
100
- const year = d.getUTCFullYear();
101
- const month = (d.getUTCMonth() + 1).toString().padStart(2, '0');
102
- const day = d.getUTCDate().toString().padStart(2, '0');
103
- date = `${year}-${month}-${day}`;
104
- }
105
- }
106
- const authors = (_d = this.articleAuthors) === null || _d === void 0 ? void 0 : _d.map((auth) => {
107
- return processContributor(auth);
123
+ const titleText = title ? toText(title).trim() : '';
124
+ const authors = ((_a = this.articleAuthors) !== null && _a !== void 0 ? _a : []).map((auth) => processContributor(auth));
125
+ const { date, datePick } = this.resolvePublicationDate();
126
+ this.recordFrontmatterMessages({
127
+ date,
128
+ datePick,
129
+ titleText,
130
+ authorCount: authors.length,
108
131
  });
109
- const affiliations = (_e = this.articleAffiliations) === null || _e === void 0 ? void 0 : _e.map((aff) => {
132
+ const affiliations = (_b = this.articleAffiliations) === null || _b === void 0 ? void 0 : _b.map((aff) => {
110
133
  return processAffiliation(aff);
111
134
  });
112
- const keywords = (_g = (_f = this.keywords) === null || _f === void 0 ? void 0 : _f.map((k) => toText(k))) !== null && _g !== void 0 ? _g : [];
113
- const subjectScope = (_h = this.articleCategories) !== null && _h !== void 0 ? _h : this.front;
135
+ const keywords = (_d = (_c = this.keywords) === null || _c === void 0 ? void 0 : _c.map((k) => toText(k))) !== null && _d !== void 0 ? _d : [];
136
+ const subjectScope = (_e = this.articleCategories) !== null && _e !== void 0 ? _e : this.front;
114
137
  const journalCollSubject = select(`${Tags.subjGroup}[subj-group-type="hwp-journal-coll"] ${Tags.subject}`, subjectScope);
115
138
  const articleSubject = journalCollSubject !== null && journalCollSubject !== void 0 ? journalCollSubject : select(Tags.subject, subjectScope);
116
139
  const journalTitle = select(Tags.journalTitle, this.front);
@@ -124,13 +147,13 @@ export class Jats {
124
147
  }
125
148
  else if (selectAll('ext-link', license).length === 1) {
126
149
  // this should only happen if there is only one ext-link
127
- licenseString = (_j = select('ext-link', license)['xlink:href']) !== null && _j !== void 0 ? _j : null;
150
+ licenseString = (_f = select('ext-link', license)['xlink:href']) !== null && _f !== void 0 ? _f : null;
128
151
  }
129
152
  else if (license) {
130
153
  licenseString = toText(license);
131
154
  }
132
155
  let openAccess;
133
- const licenseType = (_k = license === null || license === void 0 ? void 0 : license['license-type']) === null || _k === void 0 ? void 0 : _k.toLowerCase();
156
+ const licenseType = (_g = license === null || license === void 0 ? void 0 : license['license-type']) === null || _g === void 0 ? void 0 : _g.toLowerCase();
134
157
  if (licenseType && ['openaccess', 'open-access'].includes(licenseType)) {
135
158
  openAccess = true;
136
159
  }
@@ -144,10 +167,10 @@ export class Jats {
144
167
  const pmc = this.pmc;
145
168
  const identifiers = pmc ? { pmcid: `PMC${pmc}` } : undefined;
146
169
  const frontmatter = validatePageFrontmatter({
147
- title: title ? toText(title) : undefined,
170
+ title: titleText || undefined,
148
171
  subtitle: subtitle ? toText(subtitle) : undefined,
149
172
  short_title: short_title ? toText(short_title) : undefined,
150
- doi: (_l = this.doi) !== null && _l !== void 0 ? _l : undefined,
173
+ doi: (_h = this.doi) !== null && _h !== void 0 ? _h : undefined,
151
174
  identifiers,
152
175
  date,
153
176
  authors: authors.length ? authors : undefined,
@@ -195,16 +218,116 @@ export class Jats {
195
218
  var _a;
196
219
  return selectAll('history date', (_a = this.articleMeta) !== null && _a !== void 0 ? _a : this.front);
197
220
  }
198
- get historyAcceptedDate() {
199
- const accepted = this.historyDates.find((d) => {
200
- var _a;
201
- const dt = String((_a = d['date-type']) !== null && _a !== void 0 ? _a : '').toLowerCase();
202
- return dt === 'accepted' || dt === 'accept';
221
+ /**
222
+ * Prefer a fully-specified publication date (day/month/year).
223
+ * If publication date is incomplete (e.g. year-only), fall back through history dates.
224
+ */
225
+ resolvePublicationDate() {
226
+ const datePick = this.pickPublicationDate();
227
+ let date;
228
+ if (datePick) {
229
+ const d = toDate(datePick.node);
230
+ if (d) {
231
+ const year = d.getUTCFullYear();
232
+ const month = (d.getUTCMonth() + 1).toString().padStart(2, '0');
233
+ const day = d.getUTCDate().toString().padStart(2, '0');
234
+ date = `${year}-${month}-${day}`;
235
+ }
236
+ }
237
+ return { date, datePick };
238
+ }
239
+ recordFrontmatterMessages(input) {
240
+ var _a;
241
+ if (this.frontmatterMessagesRecorded)
242
+ return;
243
+ this.frontmatterMessagesRecorded = true;
244
+ if ((_a = input.datePick) === null || _a === void 0 ? void 0 : _a.warn) {
245
+ recordJatsMessage(this.vfile, input.datePick.warn, { note: input.datePick.note });
246
+ }
247
+ if (!input.date) {
248
+ recordJatsMessage(this.vfile, 'No publication date found in JATS', {
249
+ note: 'article-meta/pub-date or history/pub-history dates',
250
+ });
251
+ }
252
+ if (!input.titleText) {
253
+ recordJatsMessage(this.vfile, 'No article title found in JATS', {
254
+ note: 'article-meta/title-group/article-title',
255
+ });
256
+ }
257
+ if (input.authorCount === 0) {
258
+ recordJatsMessage(this.vfile, 'No authors found in JATS', {
259
+ note: 'article-meta/contrib-group/contrib[@contrib-type="author"]',
260
+ });
261
+ }
262
+ }
263
+ pickPublicationDate() {
264
+ if (this.publicationDate) {
265
+ return { node: this.publicationDate };
266
+ }
267
+ if (this.pubHistoryPubDate) {
268
+ return { node: this.pubHistoryPubDate };
269
+ }
270
+ if (this.pubHistoryAcceptedDate) {
271
+ return { node: this.pubHistoryAcceptedDate };
272
+ }
273
+ if (this.historyAcceptedDate) {
274
+ return { node: this.historyAcceptedDate };
275
+ }
276
+ if (this.pubHistoryFallbackDate) {
277
+ return this.fallbackDatePick(this.pubHistoryFallbackDate, 'pub-history');
278
+ }
279
+ if (this.historyFallbackDate) {
280
+ return this.fallbackDatePick(this.historyFallbackDate, 'history');
281
+ }
282
+ return undefined;
283
+ }
284
+ jatsDateType(node) {
285
+ var _a;
286
+ return String((_a = node['date-type']) !== null && _a !== void 0 ? _a : '')
287
+ .trim()
288
+ .toLowerCase();
289
+ }
290
+ dateHasDay(node) {
291
+ return !!select(Tags.day, node);
292
+ }
293
+ /** Last `<date>` with a `<day>` whose `date-type` is not in `excludeTypes`. */
294
+ lastDateWithDay(dates, excludeTypes) {
295
+ const eligible = dates.filter((d) => {
296
+ if (!this.dateHasDay(d))
297
+ return false;
298
+ return !excludeTypes.has(this.jatsDateType(d));
203
299
  });
204
- if (accepted && select(Tags.day, accepted))
205
- return accepted;
300
+ return eligible.at(-1);
301
+ }
302
+ fallbackDatePick(node, source) {
303
+ const dateType = this.jatsDateType(node) || 'unknown';
304
+ const xpath = source === 'pub-history'
305
+ ? `article-meta/pub-history/event/date[@date-type="${dateType}"]`
306
+ : `article-meta/history/date[@date-type="${dateType}"]`;
307
+ return {
308
+ node,
309
+ warn: `Using JATS ${source} date as publication date`,
310
+ note: xpath,
311
+ };
312
+ }
313
+ findHistoryDateByType(types) {
314
+ const wanted = new Set(types.map((t) => t.toLowerCase()));
315
+ const found = this.historyDates.find((d) => {
316
+ return wanted.has(this.jatsDateType(d));
317
+ });
318
+ if (found && this.dateHasDay(found))
319
+ return found;
206
320
  return undefined;
207
321
  }
322
+ get historyAcceptedDate() {
323
+ return this.findHistoryDateByType(['accepted', 'accept']);
324
+ }
325
+ get pubHistoryFallbackDate() {
326
+ return this.lastDateWithDay(this.pubHistoryDates, Jats.PUB_HISTORY_PRIMARY_DATE_TYPES);
327
+ }
328
+ get historyFallbackDate() {
329
+ return this.lastDateWithDay(this.historyDates, Jats.HISTORY_PRIMARY_DATE_TYPES);
330
+ }
208
331
  /**
209
332
  * Some sources store important dates under:
210
333
  * `<article-meta><pub-history><event><date date-type="...">...</date></event></pub-history>`.
@@ -215,12 +338,8 @@ export class Jats {
215
338
  }
216
339
  findPubHistoryDateByType(types) {
217
340
  const wanted = new Set(types.map((t) => t.toLowerCase()));
218
- const found = this.pubHistoryDates.find((d) => {
219
- var _a;
220
- const dt = String((_a = d['date-type']) !== null && _a !== void 0 ? _a : '').toLowerCase();
221
- return wanted.has(dt);
222
- });
223
- if (found && select(Tags.day, found))
341
+ const found = this.pubHistoryDates.find((d) => wanted.has(this.jatsDateType(d)));
342
+ if (found && this.dateHasDay(found))
224
343
  return found;
225
344
  return undefined;
226
345
  }
@@ -338,6 +457,14 @@ export class Jats {
338
457
  return xml;
339
458
  }
340
459
  }
460
+ Jats.PUB_HISTORY_PRIMARY_DATE_TYPES = new Set([
461
+ 'pub',
462
+ 'published',
463
+ 'publication',
464
+ 'accepted',
465
+ 'accept',
466
+ ]);
467
+ Jats.HISTORY_PRIMARY_DATE_TYPES = new Set(['accepted', 'accept']);
341
468
  function hasSingleArticle(element) {
342
469
  if (element.name === 'article') {
343
470
  return true;
@@ -0,0 +1,5 @@
1
+ import type { VFile } from 'vfile';
2
+ export declare function recordJatsMessage(vfile: VFile | undefined, reason: string, opts?: {
3
+ note?: string;
4
+ }): void;
5
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAEnC,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,QAIzB"}
@@ -0,0 +1,6 @@
1
+ import { fileWarn, RuleId } from 'myst-common';
2
+ export function recordJatsMessage(vfile, reason, opts) {
3
+ if (!vfile)
4
+ return;
5
+ fileWarn(vfile, reason, { source: 'jats-xml', ruleId: RuleId.jatsParses, note: opts === null || opts === void 0 ? void 0 : opts.note });
6
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Literal find/replace fixes for malformed JATS seen in the wild.
3
+ * Each entry emits at most one vfile message when it matches.
4
+ */
5
+ export type KnownXmlDefectRepair = {
6
+ from: string;
7
+ to: string;
8
+ };
9
+ export declare function knownXmlDefectRepairMessage(from: string, to: string): string;
10
+ export type AppliedXmlDefectRepair = {
11
+ repair: KnownXmlDefectRepair;
12
+ count: number;
13
+ };
14
+ export type RepairKnownXmlDefectsResult = {
15
+ xml: string;
16
+ applied: AppliedXmlDefectRepair[];
17
+ };
18
+ /**
19
+ * Apply KNOWN_XML_DEFECT_REPAIRS in order. Each rule uses a single literal
20
+ * `from` → `to` substitution (all occurrences of `from`).
21
+ */
22
+ export declare function repairKnownXmlDefects(xml: string): RepairKnownXmlDefectsResult;
23
+ //# sourceMappingURL=repairKnownXmlDefects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repairKnownXmlDefects.d.ts","sourceRoot":"","sources":["../src/repairKnownXmlDefects.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AASF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,2BAA2B,CAW9E"}
@@ -0,0 +1,26 @@
1
+ const KNOWN_XML_DEFECT_REPAIRS = [
2
+ {
3
+ from: '<fn id="n1"fn-type="equal">',
4
+ to: '<fn id="n1" fn-type="equal">',
5
+ },
6
+ ];
7
+ export function knownXmlDefectRepairMessage(from, to) {
8
+ return `replaced ${from} with ${to}`;
9
+ }
10
+ /**
11
+ * Apply KNOWN_XML_DEFECT_REPAIRS in order. Each rule uses a single literal
12
+ * `from` → `to` substitution (all occurrences of `from`).
13
+ */
14
+ export function repairKnownXmlDefects(xml) {
15
+ const applied = [];
16
+ let next = xml;
17
+ for (const repair of KNOWN_XML_DEFECT_REPAIRS) {
18
+ if (!next.includes(repair.from))
19
+ continue;
20
+ const parts = next.split(repair.from);
21
+ const count = parts.length - 1;
22
+ next = parts.join(repair.to);
23
+ applied.push({ repair, count });
24
+ }
25
+ return { xml: next, applied };
26
+ }
@@ -0,0 +1,14 @@
1
+ export type SanitizeXmlEntitiesResult = {
2
+ xml: string;
3
+ /** How many bare `&` were rewritten to `&#38;`. */
4
+ escapedBareAmpersandCount: number;
5
+ };
6
+ /**
7
+ * Escape bare ampersands so xml-js (and other XML parsers) do not fail with
8
+ * "Invalid character in entity name" / unterminated reference errors.
9
+ *
10
+ * Existing `&amp;`, `&ndash;`, `&#123;`, etc. are left unchanged; xml-js expands
11
+ * those when parsing.
12
+ */
13
+ export declare function sanitizeXmlEntities(xml: string): SanitizeXmlEntitiesResult;
14
+ //# sourceMappingURL=sanitizeXmlEntities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeXmlEntities.d.ts","sourceRoot":"","sources":["../src/sanitizeXmlEntities.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,yBAAyB,EAAE,MAAM,CAAC;CACnC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CAO1E"}
@@ -0,0 +1,26 @@
1
+ import { characterEntities } from 'character-entities';
2
+ /**
3
+ * Names of HTML/XML character references recognized when deciding whether `&` starts
4
+ * a valid entity (e.g. `&amp;`, `&ndash;`, `&#38;`, `&#x26;`).
5
+ */
6
+ const VALID_ENTITY_NAMES = Object.keys(characterEntities).map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
7
+ /**
8
+ * `&` not followed by a known named entity or numeric reference — e.g. "Bill & Melinda".
9
+ * Matches the escape step from common JATS preprocessors before XML parse.
10
+ */
11
+ const BARE_AMPERSAND_RE = new RegExp(`&(?!(?:${VALID_ENTITY_NAMES.join('|')}|#(?:x[0-9a-fA-F]+|\\d+));)`, 'g');
12
+ /**
13
+ * Escape bare ampersands so xml-js (and other XML parsers) do not fail with
14
+ * "Invalid character in entity name" / unterminated reference errors.
15
+ *
16
+ * Existing `&amp;`, `&ndash;`, `&#123;`, etc. are left unchanged; xml-js expands
17
+ * those when parsing.
18
+ */
19
+ export function sanitizeXmlEntities(xml) {
20
+ let escapedBareAmpersandCount = 0;
21
+ const sanitized = xml.replace(BARE_AMPERSAND_RE, () => {
22
+ escapedBareAmpersandCount += 1;
23
+ return '&#38;';
24
+ });
25
+ return { xml: sanitized, escapedBareAmpersandCount };
26
+ }
package/dist/utils.js CHANGED
@@ -50,7 +50,7 @@ function markForDeletion(nodes) {
50
50
  });
51
51
  }
52
52
  export function processAffiliation(aff) {
53
- var _a, _b;
53
+ var _a, _b, _c, _d;
54
54
  const id = aff.id;
55
55
  let ror;
56
56
  let isni;
@@ -84,7 +84,7 @@ export function processAffiliation(aff) {
84
84
  countryNode,
85
85
  ]);
86
86
  remove(aff, '__delete__');
87
- const affChildren = aff.children.filter((child) => child.type !== 'label');
87
+ const affChildren = (_d = (_c = aff.children) === null || _c === void 0 ? void 0 : _c.filter((child) => child.type !== 'label')) !== null && _d !== void 0 ? _d : [];
88
88
  let institution;
89
89
  if (affChildren.filter((child) => ['text', 'institution-wrap', 'institution'].includes(child.type))
90
90
  .length === affChildren.length) {
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare const version = "1.1.4";
1
+ declare const version = "1.1.5";
2
2
  export default version;
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- const version = '1.1.4';
1
+ const version = '1.1.5';
2
2
  export default version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jats-xml",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Types and utilities for working with JATS in Typescript",
5
5
  "author": "Rowan Cockett <rowan@continuousfoundation.org>",
6
6
  "homepage": "https://github.com/continuous-foundation/jats",
@@ -39,15 +39,17 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "adm-zip": "^0.5.10",
42
+ "character-entities": "^2.0.2",
42
43
  "doi-utils": "^2.0.6",
43
- "jats-tags": "^1.1.4",
44
- "jats-utils": "^1.1.4",
44
+ "jats-tags": "^1.1.5",
45
+ "jats-utils": "^1.1.5",
45
46
  "myst-cli-utils": "^2.0.11",
46
47
  "myst-common": "^1.7.5",
47
48
  "myst-frontmatter": "^1.7.5",
48
49
  "node-fetch": "^3.3.1",
49
- "unist-util-select": "^4.0.0",
50
50
  "unist-util-remove": "4.0.0",
51
+ "unist-util-select": "^4.0.0",
52
+ "vfile": "^5.0.0",
51
53
  "which": "^3.0.1",
52
54
  "xml-js": "^1.6.11"
53
55
  },