@manuscripts/transform 4.5.7 → 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.
- package/dist/cjs/jats/__tests__/jats-exporter.test.js +50 -20
- package/dist/cjs/jats/__tests__/jats-importer.test.js +26 -0
- package/dist/cjs/jats/__tests__/jats-roundtrip.test.js +6 -1
- package/dist/cjs/jats/exporter/JATSExporterBase.js +118 -0
- package/dist/cjs/jats/exporter/jats-exporter.js +227 -349
- package/dist/cjs/jats/exporter/lib.js +62 -0
- package/dist/cjs/jats/importer/jats-dom-parser.js +11 -3
- package/dist/cjs/schema/index.js +4 -1
- package/dist/cjs/schema/migration/migration-scripts/4.5.9.js +30 -0
- package/dist/cjs/schema/migration/migration-scripts/index.js +3 -1
- package/dist/cjs/schema/nodes/bibliography_element.js +0 -1
- package/dist/cjs/schema/nodes/bio.js +24 -0
- package/dist/cjs/schema/nodes/contributor.js +1 -2
- package/dist/cjs/schema/nodes/keywords_element.js +0 -1
- package/dist/cjs/types.js +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/es/jats/__tests__/jats-exporter.test.js +47 -20
- package/dist/es/jats/__tests__/jats-importer.test.js +26 -0
- package/dist/es/jats/__tests__/jats-roundtrip.test.js +3 -1
- package/dist/es/jats/exporter/JATSExporterBase.js +114 -0
- package/dist/es/jats/exporter/jats-exporter.js +201 -319
- package/dist/es/jats/exporter/lib.js +55 -0
- package/dist/es/jats/importer/jats-dom-parser.js +11 -3
- package/dist/es/schema/index.js +4 -1
- package/dist/es/schema/migration/migration-scripts/4.5.9.js +28 -0
- package/dist/es/schema/migration/migration-scripts/index.js +3 -1
- package/dist/es/schema/nodes/bibliography_element.js +0 -1
- package/dist/es/schema/nodes/bio.js +20 -0
- package/dist/es/schema/nodes/contributor.js +1 -2
- package/dist/es/schema/nodes/keywords_element.js +0 -1
- package/dist/es/types.js +1 -1
- package/dist/es/version.js +1 -1
- package/dist/types/jats/exporter/JATSExporterBase.d.ts +35 -0
- package/dist/types/jats/exporter/jats-exporter.d.ts +23 -19
- package/dist/types/jats/exporter/lib.d.ts +23 -0
- package/dist/types/schema/index.d.ts +2 -1
- package/dist/types/schema/migration/migration-scripts/4.5.9.d.ts +8 -0
- package/dist/types/schema/migration/migration-scripts/index.d.ts +3 -2
- package/dist/types/schema/nodes/bio.d.ts +10 -0
- package/dist/types/schema/types.d.ts +1 -1
- package/dist/types/types.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/src/jats/__tests__/__fixtures__/jats-import.xml +7 -0
- package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +272 -151
- package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +2 -2
- package/src/jats/__tests__/jats-exporter.test.ts +60 -20
- package/src/jats/__tests__/jats-importer.test.ts +37 -0
- package/src/jats/__tests__/jats-roundtrip.test.ts +3 -1
- package/src/jats/exporter/JATSExporterBase.ts +164 -0
- package/src/jats/exporter/jats-exporter.ts +217 -386
- package/src/jats/exporter/lib.ts +69 -0
- package/src/jats/importer/jats-dom-parser.ts +15 -5
- package/src/schema/index.ts +4 -1
- package/src/schema/migration/migration-scripts/4.5.9.ts +62 -0
- package/src/schema/migration/migration-scripts/index.ts +3 -1
- package/src/schema/nodes/bibliography_element.ts +0 -1
- package/src/schema/nodes/bio.ts +48 -0
- package/src/schema/nodes/contributor.ts +1 -2
- package/src/schema/nodes/keywords_element.ts +0 -1
- package/src/schema/types.ts +1 -0
- package/src/types.ts +1 -1
- 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
|
-
|
|
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',
|
package/dist/cjs/schema/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*!
|
|
3
|
-
* ©
|
|
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
|
-
* ©
|
|
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;
|
|
@@ -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: '
|
|
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) => {
|
package/dist/cjs/types.js
CHANGED
package/dist/cjs/version.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { parseXml } from 'libxmljs2';
|
|
17
|
+
import serializeToXML from 'w3c-xmlserializer';
|
|
17
18
|
import { JATSExporter } from '../exporter/jats-exporter';
|
|
18
19
|
import { parseJATSArticle } from '../importer/parse-jats-article';
|
|
19
20
|
import { DEFAULT_CSL_OPTIONS } from './citations';
|
|
@@ -30,10 +31,10 @@ describe('JATS exporter', () => {
|
|
|
30
31
|
const transformer = new JATSExporter();
|
|
31
32
|
const input = await readAndParseFixture('jats-example-full.xml');
|
|
32
33
|
const node = parseJATSArticle(input, sectionCategories);
|
|
33
|
-
const
|
|
34
|
+
const document = await transformer.export(node, {
|
|
34
35
|
csl: DEFAULT_CSL_OPTIONS,
|
|
35
36
|
});
|
|
36
|
-
expect(
|
|
37
|
+
expect(serializeToXML(document)).toMatchSnapshot('jats-export');
|
|
37
38
|
});
|
|
38
39
|
test('export with & and < in bibliography metadata', async () => {
|
|
39
40
|
const transformer = new JATSExporter();
|
|
@@ -46,27 +47,27 @@ describe('JATS exporter', () => {
|
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
biblio.attrs.title += ' & Sons 55 < 135';
|
|
49
|
-
const
|
|
50
|
+
const document = await transformer.export(node, {
|
|
50
51
|
csl: DEFAULT_CSL_OPTIONS,
|
|
51
52
|
});
|
|
52
|
-
expect(
|
|
53
|
+
expect(serializeToXML(document)).toMatchSnapshot('jats-export-with-xml-unsafe-in-biblios');
|
|
53
54
|
});
|
|
54
55
|
test('export v1.1', async () => {
|
|
55
56
|
const transformer = new JATSExporter();
|
|
56
57
|
const input = await readAndParseFixture('jats-example-full.xml');
|
|
57
58
|
const node = parseJATSArticle(input, sectionCategories);
|
|
58
|
-
const
|
|
59
|
+
const document = await transformer.export(node, {
|
|
59
60
|
csl: DEFAULT_CSL_OPTIONS,
|
|
60
61
|
version: '1.1',
|
|
61
62
|
});
|
|
62
|
-
expect(
|
|
63
|
+
expect(serializeToXML(document)).toMatchSnapshot('jats-export-1.1');
|
|
63
64
|
});
|
|
64
65
|
test('export unknown version', async () => {
|
|
65
66
|
const transformer = new JATSExporter();
|
|
66
67
|
const input = await readAndParseFixture('jats-example-full.xml');
|
|
67
68
|
const node = parseJATSArticle(input, sectionCategories);
|
|
68
69
|
await expect(async () => {
|
|
69
|
-
await transformer.
|
|
70
|
+
await transformer.export(node, {
|
|
70
71
|
csl: DEFAULT_CSL_OPTIONS,
|
|
71
72
|
version: '1.0',
|
|
72
73
|
});
|
|
@@ -76,9 +77,10 @@ describe('JATS exporter', () => {
|
|
|
76
77
|
const transformer = new JATSExporter();
|
|
77
78
|
const input = await readAndParseFixture('jats-tables-example.xml');
|
|
78
79
|
const node = parseJATSArticle(input, sectionCategories);
|
|
79
|
-
const
|
|
80
|
+
const document = await transformer.export(node, {
|
|
80
81
|
csl: DEFAULT_CSL_OPTIONS,
|
|
81
82
|
});
|
|
83
|
+
const xml = serializeToXML(document);
|
|
82
84
|
const resultDoc = parseXMLWithDTD(xml);
|
|
83
85
|
const tableWrapFoot = resultDoc.get('//table-wrap/table-wrap-foot');
|
|
84
86
|
const paragraph = resultDoc.get('//table-wrap/table-wrap-foot/p');
|
|
@@ -89,30 +91,30 @@ describe('JATS exporter', () => {
|
|
|
89
91
|
const transformer = new JATSExporter();
|
|
90
92
|
const input = await readAndParseFixture('jats-import.xml');
|
|
91
93
|
const node = parseJATSArticle(input, sectionCategories);
|
|
92
|
-
const
|
|
94
|
+
const document = await transformer.export(node, {
|
|
93
95
|
csl: DEFAULT_CSL_OPTIONS,
|
|
94
96
|
});
|
|
95
|
-
const { errors } = parseXMLWithDTD(
|
|
97
|
+
const { errors } = parseXMLWithDTD(serializeToXML(document));
|
|
96
98
|
expect(errors).toHaveLength(0);
|
|
97
99
|
});
|
|
98
100
|
test('DTD validation: article with title markup and citations', async () => {
|
|
99
101
|
const transformer = new JATSExporter();
|
|
100
102
|
const input = await readAndParseFixture('jats-document.xml');
|
|
101
103
|
const node = parseJATSArticle(input, sectionCategories);
|
|
102
|
-
const
|
|
104
|
+
const document = await transformer.export(node, {
|
|
103
105
|
csl: DEFAULT_CSL_OPTIONS,
|
|
104
106
|
});
|
|
105
|
-
const { errors } = parseXMLWithDTD(
|
|
107
|
+
const { errors } = parseXMLWithDTD(serializeToXML(document));
|
|
106
108
|
expect(errors).toHaveLength(0);
|
|
107
109
|
});
|
|
108
110
|
test('Export link', async () => {
|
|
109
111
|
const transformer = new JATSExporter();
|
|
110
112
|
const input = await readAndParseFixture('jats-import.xml');
|
|
111
113
|
const node = parseJATSArticle(input, sectionCategories);
|
|
112
|
-
const
|
|
114
|
+
const document = await transformer.export(node, {
|
|
113
115
|
csl: DEFAULT_CSL_OPTIONS,
|
|
114
116
|
});
|
|
115
|
-
const output = parseXMLWithDTD(
|
|
117
|
+
const output = parseXMLWithDTD(serializeToXML(document));
|
|
116
118
|
const link = output.get('//ext-link[@ext-link-type="uri"]');
|
|
117
119
|
expect(link).not.toBeNull();
|
|
118
120
|
expect(link.text()).toBe('https//orcid.org/0000-0003-2217-5904');
|
|
@@ -127,20 +129,20 @@ describe('JATS exporter', () => {
|
|
|
127
129
|
const transformer = new JATSExporter();
|
|
128
130
|
const input = await readAndParseFixture('jats-example-doc.xml');
|
|
129
131
|
const node = parseJATSArticle(input, sectionCategories);
|
|
130
|
-
const
|
|
132
|
+
const document = await transformer.export(node, {
|
|
131
133
|
csl: DEFAULT_CSL_OPTIONS,
|
|
132
134
|
});
|
|
133
|
-
const { errors } = parseXMLWithDTD(
|
|
135
|
+
const { errors } = parseXMLWithDTD(serializeToXML(document));
|
|
134
136
|
expect(errors).toHaveLength(0);
|
|
135
137
|
});
|
|
136
138
|
test('export with supplement', async () => {
|
|
137
139
|
const transformer = new JATSExporter();
|
|
138
140
|
const input = await readAndParseFixture('jats-import.xml');
|
|
139
141
|
const node = parseJATSArticle(input, sectionCategories);
|
|
140
|
-
const
|
|
142
|
+
const document = await transformer.export(node, {
|
|
141
143
|
csl: DEFAULT_CSL_OPTIONS,
|
|
142
144
|
});
|
|
143
|
-
const resultDoc = parseXMLWithDTD(
|
|
145
|
+
const resultDoc = parseXMLWithDTD(serializeToXML(document));
|
|
144
146
|
const supplementaryMaterial = resultDoc.get('//supplementary-material');
|
|
145
147
|
if (!supplementaryMaterial) {
|
|
146
148
|
throw new Error('No supplementary material found');
|
|
@@ -167,9 +169,10 @@ describe('JATS exporter', () => {
|
|
|
167
169
|
n.attrs.rids.forEach((rid) => citedIDs.add(rid));
|
|
168
170
|
}
|
|
169
171
|
});
|
|
170
|
-
const
|
|
172
|
+
const document = await new JATSExporter().export(node, {
|
|
171
173
|
csl: DEFAULT_CSL_OPTIONS,
|
|
172
174
|
});
|
|
175
|
+
const xml = serializeToXML(document);
|
|
173
176
|
const refCount = parseXMLWithDTD(xml).find('//ref-list/ref').length;
|
|
174
177
|
expect(refCount).toBe(bibliographyItemIDs.size);
|
|
175
178
|
const { errors } = parseXMLWithDTD(xml);
|
|
@@ -179,9 +182,10 @@ describe('JATS exporter', () => {
|
|
|
179
182
|
const transformer = new JATSExporter();
|
|
180
183
|
const input = await readAndParseFixture('jats-fn-group.xml');
|
|
181
184
|
const node = parseJATSArticle(input, sectionCategories);
|
|
182
|
-
const
|
|
185
|
+
const document = await transformer.export(node, {
|
|
183
186
|
csl: DEFAULT_CSL_OPTIONS,
|
|
184
187
|
});
|
|
188
|
+
const xml = serializeToXML(document);
|
|
185
189
|
const footnoteCategories = [
|
|
186
190
|
'con',
|
|
187
191
|
'deceased',
|
|
@@ -194,4 +198,27 @@ describe('JATS exporter', () => {
|
|
|
194
198
|
expect(fn).not.toBeUndefined();
|
|
195
199
|
}
|
|
196
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
|
+
});
|
|
197
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');
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { parseXml } from 'libxmljs2';
|
|
17
|
+
import serializeToXML from 'w3c-xmlserializer';
|
|
17
18
|
import { JATSExporter } from '../exporter/jats-exporter';
|
|
18
19
|
import { parseJATSArticle } from '../importer/parse-jats-article';
|
|
19
20
|
import { DEFAULT_CSL_OPTIONS } from './citations';
|
|
@@ -29,9 +30,10 @@ const roundtrip = async (filename) => {
|
|
|
29
30
|
const doc = new DOMParser().parseFromString(input, 'application/xml');
|
|
30
31
|
const node = parseJATSArticle(doc, sectionCategories);
|
|
31
32
|
const exporter = new JATSExporter();
|
|
32
|
-
|
|
33
|
+
const document = await exporter.export(node, {
|
|
33
34
|
csl: DEFAULT_CSL_OPTIONS,
|
|
34
35
|
});
|
|
36
|
+
return serializeToXML(document);
|
|
35
37
|
};
|
|
36
38
|
describe('JATS roundtrip', () => {
|
|
37
39
|
test('jats-import.xml roundtrip', async () => {
|
|
@@ -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
|
+
}
|