@manuscripts/transform 4.5.8 → 4.6.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.
Files changed (60) hide show
  1. package/dist/cjs/jats/__tests__/jats-exporter.test.js +23 -0
  2. package/dist/cjs/jats/__tests__/jats-importer.test.js +26 -0
  3. package/dist/cjs/jats/exporter/JATSExporterBase.js +118 -0
  4. package/dist/cjs/jats/exporter/jats-exporter.js +224 -342
  5. package/dist/cjs/jats/exporter/lib.js +62 -0
  6. package/dist/cjs/jats/importer/jats-dom-parser.js +11 -3
  7. package/dist/cjs/schema/index.js +4 -1
  8. package/dist/cjs/schema/migration/migration-scripts/4.5.9.js +30 -0
  9. package/dist/cjs/schema/migration/migration-scripts/index.js +3 -1
  10. package/dist/cjs/schema/nodes/bibliography_element.js +0 -1
  11. package/dist/cjs/schema/nodes/bio.js +24 -0
  12. package/dist/cjs/schema/nodes/contributor.js +1 -2
  13. package/dist/cjs/schema/nodes/keywords_element.js +0 -1
  14. package/dist/cjs/types.js +1 -1
  15. package/dist/cjs/version.js +1 -1
  16. package/dist/es/jats/__tests__/jats-exporter.test.js +23 -0
  17. package/dist/es/jats/__tests__/jats-importer.test.js +26 -0
  18. package/dist/es/jats/exporter/JATSExporterBase.js +114 -0
  19. package/dist/es/jats/exporter/jats-exporter.js +198 -315
  20. package/dist/es/jats/exporter/lib.js +55 -0
  21. package/dist/es/jats/importer/jats-dom-parser.js +11 -3
  22. package/dist/es/schema/index.js +4 -1
  23. package/dist/es/schema/migration/migration-scripts/4.5.9.js +28 -0
  24. package/dist/es/schema/migration/migration-scripts/index.js +3 -1
  25. package/dist/es/schema/nodes/bibliography_element.js +0 -1
  26. package/dist/es/schema/nodes/bio.js +20 -0
  27. package/dist/es/schema/nodes/contributor.js +1 -2
  28. package/dist/es/schema/nodes/keywords_element.js +0 -1
  29. package/dist/es/types.js +1 -1
  30. package/dist/es/version.js +1 -1
  31. package/dist/types/jats/exporter/JATSExporterBase.d.ts +35 -0
  32. package/dist/types/jats/exporter/jats-exporter.d.ts +22 -18
  33. package/dist/types/jats/exporter/lib.d.ts +23 -0
  34. package/dist/types/schema/index.d.ts +2 -1
  35. package/dist/types/schema/migration/migration-scripts/4.5.9.d.ts +8 -0
  36. package/dist/types/schema/migration/migration-scripts/index.d.ts +3 -2
  37. package/dist/types/schema/nodes/bio.d.ts +10 -0
  38. package/dist/types/schema/types.d.ts +1 -1
  39. package/dist/types/types.d.ts +1 -1
  40. package/dist/types/version.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/src/jats/__tests__/__fixtures__/jats-import.xml +7 -0
  43. package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +272 -151
  44. package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +2 -2
  45. package/src/jats/__tests__/jats-exporter.test.ts +34 -0
  46. package/src/jats/__tests__/jats-importer.test.ts +37 -0
  47. package/src/jats/exporter/JATSExporterBase.ts +164 -0
  48. package/src/jats/exporter/jats-exporter.ts +212 -380
  49. package/src/jats/exporter/lib.ts +69 -0
  50. package/src/jats/importer/jats-dom-parser.ts +15 -5
  51. package/src/schema/index.ts +4 -1
  52. package/src/schema/migration/migration-scripts/4.5.9.ts +62 -0
  53. package/src/schema/migration/migration-scripts/index.ts +3 -1
  54. package/src/schema/nodes/bibliography_element.ts +0 -1
  55. package/src/schema/nodes/bio.ts +48 -0
  56. package/src/schema/nodes/contributor.ts +1 -2
  57. package/src/schema/nodes/keywords_element.ts +0 -1
  58. package/src/schema/types.ts +1 -0
  59. package/src/types.ts +1 -1
  60. package/src/version.ts +1 -1
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /*!
3
+ * © 2026 Atypon Systems LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.chooseRefType = exports.createDefaultIDGenerator = exports.createCounter = exports.normalizeID = void 0;
19
+ const schema_1 = require("../../schema");
20
+ const normalizeID = (id) => id.replace(/:/g, '_');
21
+ exports.normalizeID = normalizeID;
22
+ const createCounter = () => {
23
+ const counts = new Map();
24
+ return {
25
+ increment: (field) => {
26
+ const value = counts.get(field);
27
+ const newValue = value === undefined ? 1 : value + 1;
28
+ counts.set(field, newValue);
29
+ return newValue;
30
+ },
31
+ };
32
+ };
33
+ exports.createCounter = createCounter;
34
+ const createDefaultIDGenerator = () => {
35
+ const counter = (0, exports.createCounter)();
36
+ return async (element) => {
37
+ const value = String(counter.increment(element.nodeName));
38
+ return `${element.localName}-${value}`;
39
+ };
40
+ };
41
+ exports.createDefaultIDGenerator = createDefaultIDGenerator;
42
+ const chooseRefType = (type) => {
43
+ switch (type) {
44
+ case schema_1.schema.nodes.figure:
45
+ case schema_1.schema.nodes.figure_element:
46
+ return 'fig';
47
+ case schema_1.schema.nodes.footnote:
48
+ return 'fn';
49
+ case schema_1.schema.nodes.table:
50
+ case schema_1.schema.nodes.table_element:
51
+ return 'table';
52
+ case schema_1.schema.nodes.section:
53
+ case schema_1.schema.nodes.abstract:
54
+ return 'sec';
55
+ case schema_1.schema.nodes.equation:
56
+ case schema_1.schema.nodes.equation_element:
57
+ return 'disp-formula';
58
+ case schema_1.schema.nodes.supplement:
59
+ return 'supplementary-material';
60
+ }
61
+ };
62
+ exports.chooseRefType = chooseRefType;
@@ -378,7 +378,7 @@ class JATSDOMParser {
378
378
  const footnoteIDs = [];
379
379
  const affiliationIDs = [];
380
380
  const correspIDs = [];
381
- const xrefs = element.querySelectorAll('xref');
381
+ const xrefs = element.querySelectorAll(':scope > xref');
382
382
  for (const xref of xrefs) {
383
383
  const rid = xref.getAttribute('rid');
384
384
  const type = xref.getAttribute('ref-type');
@@ -416,10 +416,18 @@ class JATSDOMParser {
416
416
  email: (0, utils_1.getTrimmedTextContent)(element, 'email') || '',
417
417
  };
418
418
  },
419
- getContent: () => {
420
- return prosemirror_model_1.Fragment.from(this.schema.text('_'));
419
+ getContent: (node) => {
420
+ const bio = node.querySelector('bio');
421
+ if (!bio) {
422
+ return prosemirror_model_1.Fragment.empty;
423
+ }
424
+ return prosemirror_model_1.Fragment.from(this.parse(bio, { topNode: this.schema.nodes.bio.create() }));
421
425
  },
422
426
  },
427
+ {
428
+ tag: 'bio',
429
+ node: 'bio',
430
+ },
423
431
  {
424
432
  tag: 'affiliations',
425
433
  node: 'affiliations',
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*!
3
- * © 2019 Atypon Systems LLC
3
+ * © 2026 Atypon Systems LLC
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ const backmatter_1 = require("./nodes/backmatter");
50
50
  const bibliography_element_1 = require("./nodes/bibliography_element");
51
51
  const bibliography_item_1 = require("./nodes/bibliography_item");
52
52
  const bibliography_section_1 = require("./nodes/bibliography_section");
53
+ const bio_1 = require("./nodes/bio");
53
54
  const blockquote_element_1 = require("./nodes/blockquote_element");
54
55
  const body_1 = require("./nodes/body");
55
56
  const box_element_1 = require("./nodes/box_element");
@@ -129,6 +130,7 @@ __exportStar(require("./nodes/awards"), exports);
129
130
  __exportStar(require("./nodes/bibliography_element"), exports);
130
131
  __exportStar(require("./nodes/bibliography_item"), exports);
131
132
  __exportStar(require("./nodes/bibliography_section"), exports);
133
+ __exportStar(require("./nodes/bio"), exports);
132
134
  __exportStar(require("./nodes/blockquote_element"), exports);
133
135
  __exportStar(require("./nodes/box_element"), exports);
134
136
  __exportStar(require("./nodes/caption"), exports);
@@ -214,6 +216,7 @@ exports.schema = new prosemirror_model_1.Schema({
214
216
  bibliography_element: bibliography_element_1.bibliographyElement,
215
217
  bibliography_section: bibliography_section_1.bibliographySection,
216
218
  blockquote_element: blockquote_element_1.blockquoteElement,
219
+ bio: bio_1.bio,
217
220
  list: list_1.list,
218
221
  caption: caption_1.caption,
219
222
  caption_title: caption_title_1.captionTitle,
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const LEGACY_CONTENTS_NODES = new Set([
4
+ 'contributor',
5
+ 'bibliography_element',
6
+ 'keywords_element',
7
+ ]);
8
+ class Migration459 {
9
+ constructor() {
10
+ this.fromVersion = '4.5.8';
11
+ this.toVersion = '4.5.9';
12
+ }
13
+ migrateNode(node) {
14
+ if (LEGACY_CONTENTS_NODES.has(node.type) &&
15
+ node.attrs &&
16
+ 'contents' in node.attrs) {
17
+ const attrs = { ...node.attrs };
18
+ delete attrs.contents;
19
+ node = { ...node, attrs };
20
+ }
21
+ if (node.type === 'contributor') {
22
+ const bio = node.content?.filter((child) => child.type === 'bio');
23
+ const migrated = { ...node };
24
+ delete migrated.content;
25
+ return bio?.length ? { ...migrated, content: bio } : migrated;
26
+ }
27
+ return node;
28
+ }
29
+ }
30
+ exports.default = Migration459;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*!
3
- * © 2024 Atypon Systems LLC
3
+ * © 2026 Atypon Systems LLC
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ const _4_3_35_1 = __importDefault(require("./4.3.35"));
35
35
  const _4_4_2_1 = __importDefault(require("./4.4.2"));
36
36
  const _4_4_7_1 = __importDefault(require("./4.4.7"));
37
37
  const _4_5_3_1 = __importDefault(require("./4.5.3"));
38
+ const _4_5_9_1 = __importDefault(require("./4.5.9"));
38
39
  const migrations = [
39
40
  new _1_2_5_1.default(),
40
41
  new _2_3_22_1.default(),
@@ -53,5 +54,6 @@ const migrations = [
53
54
  new _4_4_2_1.default(),
54
55
  new _4_4_7_1.default(),
55
56
  new _4_5_3_1.default(),
57
+ new _4_5_9_1.default(),
56
58
  ];
57
59
  exports.default = migrations;
@@ -20,7 +20,6 @@ exports.bibliographyElement = {
20
20
  content: 'bibliography_item*',
21
21
  attrs: {
22
22
  id: { default: '' },
23
- contents: { default: '' },
24
23
  dataTracked: { default: null },
25
24
  },
26
25
  selectable: false,
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isBioNode = exports.bio = void 0;
4
+ exports.bio = {
5
+ content: 'image_element? paragraph',
6
+ attrs: {
7
+ id: { default: '' },
8
+ dataTracked: { default: null },
9
+ },
10
+ group: 'block',
11
+ toDOM: (node) => {
12
+ const bioNode = node;
13
+ return [
14
+ 'div',
15
+ {
16
+ class: 'bio',
17
+ id: bioNode.attrs.id,
18
+ },
19
+ 0,
20
+ ];
21
+ },
22
+ };
23
+ const isBioNode = (node) => node.type === node.type.schema.nodes.bio;
24
+ exports.isBioNode = isBioNode;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isContributorNode = exports.contributor = void 0;
4
4
  exports.contributor = {
5
- content: 'inline*',
5
+ content: 'bio?',
6
6
  atom: true,
7
7
  attrs: {
8
8
  id: { default: '' },
@@ -23,7 +23,6 @@ exports.contributor = {
23
23
  priority: { default: undefined },
24
24
  creditRoles: { default: [] },
25
25
  dataTracked: { default: null },
26
- contents: { default: '' },
27
26
  },
28
27
  group: 'block',
29
28
  toDOM: (node) => {
@@ -21,7 +21,6 @@ exports.keywordsElement = {
21
21
  content: 'keyword_group*',
22
22
  attrs: {
23
23
  id: { default: '' },
24
- contents: { default: '' },
25
24
  dataTracked: { default: null },
26
25
  },
27
26
  group: 'block element',
package/dist/cjs/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*!
3
- * © 2019 Atypon Systems LLC
3
+ * © 2026 Atypon Systems LLC
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = "4.5.8";
4
+ exports.VERSION = "4.6.0";
@@ -198,4 +198,27 @@ describe('JATS exporter', () => {
198
198
  expect(fn).not.toBeUndefined();
199
199
  }
200
200
  });
201
+ test('export contributor bio', async () => {
202
+ const transformer = new JATSExporter();
203
+ const input = await readAndParseFixture('jats-import.xml');
204
+ const node = parseJATSArticle(input, sectionCategories);
205
+ const xml = await transformer.export(node, {
206
+ csl: DEFAULT_CSL_OPTIONS,
207
+ });
208
+ const serialized = serializeToXML(xml);
209
+ const resultDoc = parseXMLWithDTD(serialized);
210
+ expect(resultDoc.errors).toHaveLength(0);
211
+ const contribs = resultDoc.find('//contrib-group/contrib');
212
+ expect(contribs).toHaveLength(2);
213
+ expect(contribs[0].find('bio')).toHaveLength(1);
214
+ expect(contribs[1].find('bio')).toHaveLength(0);
215
+ const graphic = resultDoc.get('//contrib/bio/graphic');
216
+ expect(graphic).not.toBeNull();
217
+ expect(graphic.attr('href')?.value()).toBe('agnete-hornnes.png');
218
+ expect(graphic.find('caption')).toHaveLength(0);
219
+ expect(resultDoc.get('//contrib/bio/graphic/alt-text').text()).toBe('Headshot of Agnete Hviid Hornnes');
220
+ const paragraph = resultDoc.get('//contrib/bio/p');
221
+ expect(paragraph.text()).toBe('Agnete is a Professor of Neurology at Herlev og Gentofte Hospital, specializing in stroke research.');
222
+ expect(resultDoc.get('//contrib/bio/p/italic')?.text()).toBe('Professor of Neurology');
223
+ });
201
224
  });
@@ -58,6 +58,32 @@ describe('JATS importer', () => {
58
58
  expect(contributorsNode).toHaveLength(0);
59
59
  });
60
60
  });
61
+ describe('bio', () => {
62
+ it('should parse bio with graphic and paragraph into the contributor node', async () => {
63
+ const jats = await readAndParseFixture('jats-import.xml');
64
+ const node = parseJATSArticle(jats, sectionCategories);
65
+ changeIDs(node);
66
+ const [contributor] = findNodesByType(node, schema.nodes.contributor);
67
+ expect(contributor.childCount).toBe(1);
68
+ const bio = contributor.firstChild;
69
+ expect(bio.type).toBe(schema.nodes.bio);
70
+ expect(bio.childCount).toBe(2);
71
+ const image = findNodeByType(bio, schema.nodes.image_element);
72
+ expect(findNodeByType(image, schema.nodes.figure).attrs.src).toBe('agnete-hornnes.png');
73
+ expect(findNodeByType(image, schema.nodes.alt_text).textContent).toBe('Headshot of Agnete Hviid Hornnes');
74
+ const paragraph = bio.lastChild;
75
+ expect(paragraph.type).toBe(schema.nodes.paragraph);
76
+ expect(paragraph.textContent).toBe('Agnete is a Professor of Neurology at Herlev og Gentofte Hospital, specializing in stroke research.');
77
+ expect(bio).toMatchSnapshot();
78
+ });
79
+ it('should not add bio content to contributors without a bio element', async () => {
80
+ const jats = await readAndParseFixture('jats-import.xml');
81
+ const node = parseJATSArticle(jats, sectionCategories);
82
+ const [, contributor] = findNodesByType(node, schema.nodes.contributor);
83
+ expect(contributor.childCount).toBe(0);
84
+ expect(findNodesByType(node, schema.nodes.bio)).toHaveLength(1);
85
+ });
86
+ });
61
87
  describe('affiliations', () => {
62
88
  it('should correctly parse affiliation nodes', async () => {
63
89
  const jats = await readAndParseFixture('jats-import.xml');
@@ -0,0 +1,114 @@
1
+ /*!
2
+ * © 2026 Atypon Systems LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { findChildrenByAttr, findChildrenByType } from 'prosemirror-utils';
17
+ import { isNodeOfType } from '../../schema';
18
+ import { normalizeID } from './lib';
19
+ export class JATSExporterBase {
20
+ constructor() {
21
+ this.nodesMap = new Map();
22
+ this.populateNodesMap = () => {
23
+ this.manuscriptNode.descendants((node) => {
24
+ const type = node.type;
25
+ const nodes = this.nodesMap.get(type) ?? [];
26
+ nodes.push(node);
27
+ this.nodesMap.set(type, nodes);
28
+ });
29
+ };
30
+ this.createElement = (tag, content, attrs) => {
31
+ const $element = this.document.createElement(tag);
32
+ if (content) {
33
+ $element.textContent = content;
34
+ }
35
+ if (attrs) {
36
+ Object.entries(attrs).forEach(([k, v]) => {
37
+ if (v) {
38
+ $element.setAttribute(k, v);
39
+ }
40
+ });
41
+ }
42
+ return $element;
43
+ };
44
+ this.appendElement = ($parent, tag, content, attrs) => {
45
+ const $element = this.createElement(tag, content, attrs);
46
+ $parent.appendChild($element);
47
+ return $element;
48
+ };
49
+ this.changeTag = ($node, tag) => {
50
+ const $clone = this.createElement(tag);
51
+ for (const attr of $node.attributes) {
52
+ $clone.setAttributeNS(null, attr.name, attr.value);
53
+ }
54
+ while ($node.firstChild) {
55
+ $clone.appendChild($node.firstChild);
56
+ }
57
+ $node.replaceWith($clone);
58
+ return $clone;
59
+ };
60
+ this.processChildNodes = ($element, node, contentNodeType) => {
61
+ node.forEach((childNode) => {
62
+ if (childNode.type === contentNodeType) {
63
+ if (childNode.attrs.id) {
64
+ $element.appendChild(this.serializeNode(childNode));
65
+ }
66
+ }
67
+ else if (childNode.type === node.type.schema.nodes.paragraph) {
68
+ $element.appendChild(this.serializeNode(childNode));
69
+ }
70
+ else if (childNode.type === node.type.schema.nodes.missing_figure) {
71
+ $element.appendChild(this.serializeNode(childNode));
72
+ }
73
+ });
74
+ };
75
+ }
76
+ isChildOfNodeType(targetID, type, descend = false) {
77
+ const nodes = this.getChildrenOfType(type);
78
+ return nodes.some((node) => {
79
+ const result = findChildrenByAttr(node, (attrs) => attrs.id === targetID, descend)[0];
80
+ return !!result;
81
+ });
82
+ }
83
+ getChildrenOfType(type, node) {
84
+ const nodes = node
85
+ ? findChildrenByType(node, type).map(({ node }) => node)
86
+ : this.nodesMap.get(type);
87
+ return (nodes ?? []).filter((n) => isNodeOfType(n, type));
88
+ }
89
+ getFirstChildOfType(type, node) {
90
+ return this.getChildrenOfType(type, node)[0];
91
+ }
92
+ fillEmptyElements($article, selector, tagName = 'p') {
93
+ const $empty = Array.from($article.querySelectorAll(selector)).filter(($el) => !$el.innerHTML);
94
+ $empty.forEach(($element) => $element.appendChild(this.createElement(tagName)));
95
+ }
96
+ createElementWithID(node, nodeName) {
97
+ const $element = this.createElement(nodeName);
98
+ $element.setAttribute('id', normalizeID(node.attrs.id));
99
+ return $element;
100
+ }
101
+ serializeNode(node) {
102
+ return this.serializer.serializeNode(node, {
103
+ document: this.document,
104
+ });
105
+ }
106
+ appendChildIfPresent($parent, tagName, textContent) {
107
+ if (!textContent) {
108
+ return;
109
+ }
110
+ const $element = this.createElement(tagName);
111
+ $element.textContent = textContent;
112
+ $parent.appendChild($element);
113
+ }
114
+ }