@manuscripts/transform 1.2.4-LEAN-2066 → 1.2.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.
Files changed (43) hide show
  1. package/dist/cjs/jats/importer/jats-body-dom-parser.js +1 -33
  2. package/dist/cjs/jats/importer/jats-body-transformations.js +53 -26
  3. package/dist/cjs/jats/importer/jats-comments.js +0 -24
  4. package/dist/cjs/jats/importer/jats-front-parser.js +25 -0
  5. package/dist/cjs/jats/importer/parse-jats-article.js +6 -3
  6. package/dist/cjs/jats/jats-exporter.js +54 -19
  7. package/dist/cjs/schema/index.js +0 -2
  8. package/dist/cjs/schema/nodes/keyword.js +2 -1
  9. package/dist/cjs/schema/nodes/keywords_element.js +1 -2
  10. package/dist/cjs/schema/nodes/keywords_section.js +1 -1
  11. package/dist/cjs/schema/nodes/section.js +1 -1
  12. package/dist/cjs/transformer/decode.js +23 -29
  13. package/dist/cjs/transformer/encode.js +13 -9
  14. package/dist/cjs/transformer/node-types.js +0 -1
  15. package/dist/cjs/transformer/object-types.js +1 -2
  16. package/dist/cjs/transformer/section-category.js +23 -1
  17. package/dist/es/jats/importer/jats-body-dom-parser.js +1 -33
  18. package/dist/es/jats/importer/jats-body-transformations.js +54 -27
  19. package/dist/es/jats/importer/jats-comments.js +1 -25
  20. package/dist/es/jats/importer/jats-front-parser.js +26 -1
  21. package/dist/es/jats/importer/parse-jats-article.js +6 -3
  22. package/dist/es/jats/jats-exporter.js +54 -19
  23. package/dist/es/schema/index.js +0 -2
  24. package/dist/es/schema/nodes/keyword.js +2 -1
  25. package/dist/es/schema/nodes/keywords_element.js +1 -2
  26. package/dist/es/schema/nodes/keywords_section.js +1 -1
  27. package/dist/es/schema/nodes/section.js +1 -1
  28. package/dist/es/transformer/decode.js +23 -29
  29. package/dist/es/transformer/encode.js +14 -10
  30. package/dist/es/transformer/node-types.js +0 -1
  31. package/dist/es/transformer/object-types.js +0 -1
  32. package/dist/es/transformer/section-category.js +19 -1
  33. package/dist/types/jats/importer/jats-body-transformations.d.ts +7 -2
  34. package/dist/types/jats/importer/jats-front-parser.d.ts +11 -6
  35. package/dist/types/jats/jats-exporter.d.ts +2 -0
  36. package/dist/types/schema/types.d.ts +1 -1
  37. package/dist/types/transformer/decode.d.ts +0 -1
  38. package/dist/types/transformer/object-types.d.ts +1 -2
  39. package/dist/types/transformer/section-category.d.ts +3 -2
  40. package/package.json +2 -2
  41. package/dist/cjs/schema/nodes/keywords_group.js +0 -48
  42. package/dist/es/schema/nodes/keywords_group.js +0 -44
  43. package/dist/types/schema/nodes/keywords_group.d.ts +0 -26
@@ -14,8 +14,12 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
17
20
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.chooseSectionCategoryFromTitle = exports.chooseSectionCategory = exports.chooseSectionCategoryByType = exports.chooseSecType = exports.buildSectionCategory = exports.guessSectionCategory = exports.chooseSectionLableName = exports.chooseSectionNodeType = exports.isAnySectionNode = void 0;
21
+ exports.chooseSectionCategoryFromTitle = exports.chooseSectionCategory = exports.chooseSectionCategoryByType = exports.chooseSecType = exports.buildSectionCategory = exports.guessSectionCategory = exports.chooseSectionLableName = exports.chooseSectionNodeType = exports.isAnySectionNode = exports.getSectionTitles = void 0;
22
+ const section_categories_json_1 = __importDefault(require("@manuscripts/data/dist/shared/section-categories.json"));
19
23
  const json_schema_1 = require("@manuscripts/json-schema");
20
24
  const schema_1 = require("../schema");
21
25
  const sectionNodeTypes = [
@@ -25,6 +29,18 @@ const sectionNodeTypes = [
25
29
  schema_1.schema.nodes.section,
26
30
  schema_1.schema.nodes.toc_section,
27
31
  ];
32
+ const sectionCategoriesMap = new Map(section_categories_json_1.default.map((section) => [
33
+ section._id,
34
+ section,
35
+ ]));
36
+ const getSectionTitles = (sectionCategory) => {
37
+ const category = sectionCategoriesMap.get(sectionCategory);
38
+ if (category) {
39
+ return category.titles.length ? category.titles : [' '];
40
+ }
41
+ throw new Error(`${sectionCategory} not found in section categories`);
42
+ };
43
+ exports.getSectionTitles = getSectionTitles;
28
44
  const isAnySectionNode = (node) => sectionNodeTypes.includes(node.type);
29
45
  exports.isAnySectionNode = isAnySectionNode;
30
46
  const chooseSectionNodeType = (category) => {
@@ -161,6 +177,12 @@ const chooseSectionCategoryByType = (secType) => {
161
177
  return 'MPSectionCategory:supported-by';
162
178
  case 'ethics-statement':
163
179
  return 'MPSectionCategory:ethics-statement';
180
+ case 'body':
181
+ return 'MPSectionCategory:body';
182
+ case 'backmatter':
183
+ return 'MPSectionCategory:backmatter';
184
+ case 'abstracts':
185
+ return 'MPSectionCategory:abstracts';
164
186
  default:
165
187
  return undefined;
166
188
  }
@@ -499,22 +499,6 @@ const nodes = [
499
499
  };
500
500
  },
501
501
  },
502
- {
503
- tag: 'sec[sec-type="keywords"]',
504
- node: 'keywords_section',
505
- getAttrs: (node) => {
506
- const element = node;
507
- return {
508
- id: element.getAttribute('id'),
509
- category: chooseSectionCategory(element),
510
- };
511
- },
512
- },
513
- {
514
- tag: 'kwd-group-list',
515
- context: 'keywords_section/',
516
- node: 'keywords_element',
517
- },
518
502
  {
519
503
  tag: 'sec',
520
504
  node: 'section',
@@ -526,22 +510,6 @@ const nodes = [
526
510
  };
527
511
  },
528
512
  },
529
- {
530
- tag: 'kwd-group',
531
- context: 'keywords_element/',
532
- node: 'keywords_group',
533
- getAttrs: (node) => {
534
- const element = node;
535
- return {
536
- type: element.getAttribute('kwd-group-type'),
537
- };
538
- },
539
- },
540
- {
541
- tag: 'kwd',
542
- context: 'keywords_group//',
543
- node: 'keyword',
544
- },
545
513
  {
546
514
  tag: 'label',
547
515
  context: 'section/',
@@ -594,7 +562,7 @@ const nodes = [
594
562
  {
595
563
  tag: 'title',
596
564
  node: 'section_title',
597
- context: 'section/|footnotes_section/|bibliography_section/|keywords_section/',
565
+ context: 'section/|footnotes_section/|bibliography_section/',
598
566
  },
599
567
  {
600
568
  tag: 'title',
@@ -13,9 +13,20 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { chooseSectionCategoryByType, chooseSecType } from '../../transformer';
16
+ import { chooseSectionCategoryByType, chooseSecType, getSectionTitles, } from '../../transformer';
17
17
  const removeNodeFromParent = (node) => node.parentNode && node.parentNode.removeChild(node);
18
18
  const capitalizeFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);
19
+ const createSectionContainer = (type, createElement) => {
20
+ const sectionContainer = createElement('sec');
21
+ const sectionCategory = chooseSectionCategoryByType(type);
22
+ sectionContainer.setAttribute('sec-type', sectionCategory ? chooseSecType(sectionCategory) : '');
23
+ const title = createElement('title');
24
+ title.textContent = sectionCategory
25
+ ? getSectionTitles(sectionCategory)[0]
26
+ : ' ';
27
+ sectionContainer.appendChild(title);
28
+ return sectionContainer;
29
+ };
19
30
  export const jatsBodyTransformations = {
20
31
  ensureSection(body, createElement) {
21
32
  let section = createElement('sec');
@@ -143,34 +154,64 @@ export const jatsBodyTransformations = {
143
154
  section.append(...floatsGroup.children);
144
155
  return section;
145
156
  },
146
- moveSectionsToBody(doc, body, references, createElement) {
157
+ moveAbstractsIntoContainer(doc, abstractsContainer, createElement) {
147
158
  const abstractNodes = doc.querySelectorAll('front > article-meta > abstract');
148
- for (const abstractNode of abstractNodes) {
159
+ abstractNodes.forEach((abstractNode) => {
149
160
  const abstract = this.createAbstract(abstractNode, createElement);
150
161
  removeNodeFromParent(abstractNode);
151
- body.insertBefore(abstract, body.firstChild);
152
- }
162
+ abstractsContainer.appendChild(abstract);
163
+ });
164
+ },
165
+ wrapBodySections(doc, bodyContainer) {
166
+ const bodySections = doc.querySelectorAll('body > sec:not([sec-type="backmatter"]), body > sec:not([sec-type])');
167
+ bodySections.forEach((section) => {
168
+ removeNodeFromParent(section);
169
+ bodyContainer.appendChild(section);
170
+ });
171
+ },
172
+ moveBackSectionsIntoContainer(doc, backmatterContainer) {
153
173
  for (const section of doc.querySelectorAll('back > sec')) {
154
174
  removeNodeFromParent(section);
155
- body.appendChild(section);
175
+ backmatterContainer.appendChild(section);
156
176
  }
177
+ },
178
+ moveAcknowledgmentsIntoContainer(doc, backmatterContainer, createElement) {
157
179
  const ackNode = doc.querySelector('back > ack');
158
180
  if (ackNode) {
159
181
  const acknowledgements = this.createAcknowledgments(ackNode, createElement);
160
182
  removeNodeFromParent(ackNode);
161
- body.appendChild(acknowledgements);
183
+ backmatterContainer.appendChild(acknowledgements);
162
184
  }
185
+ },
186
+ moveAppendicesIntoContainer(doc, backmatterContainer, createElement) {
163
187
  const appGroup = doc.querySelectorAll('back > app-group > app');
164
188
  for (const app of appGroup) {
165
189
  const appendix = this.createAppendixSection(app, createElement);
166
190
  removeNodeFromParent(app);
167
- body.appendChild(appendix);
191
+ backmatterContainer.appendChild(appendix);
168
192
  }
193
+ },
194
+ moveBibliographyIntoContainer(doc, backmatterContainer, references, createElement) {
169
195
  if (references) {
170
- body.appendChild(this.createBibliography(doc, references, createElement));
196
+ backmatterContainer.appendChild(this.createBibliography(doc, references, createElement));
171
197
  }
172
198
  },
173
- mapFootnotesToSections(doc, body, createElement) {
199
+ moveSectionsToBody(doc, body, references, createElement) {
200
+ const bodyContainer = createSectionContainer('body', createElement);
201
+ const abstractsContainer = createSectionContainer('abstracts', createElement);
202
+ const backmatterContainer = createSectionContainer('backmatter', createElement);
203
+ this.mapFootnotesToSections(doc, backmatterContainer, createElement);
204
+ this.wrapBodySections(doc, bodyContainer);
205
+ this.moveAbstractsIntoContainer(doc, abstractsContainer, createElement);
206
+ this.moveBackSectionsIntoContainer(doc, backmatterContainer);
207
+ this.moveAcknowledgmentsIntoContainer(doc, backmatterContainer, createElement);
208
+ this.moveAppendicesIntoContainer(doc, backmatterContainer, createElement);
209
+ this.moveBibliographyIntoContainer(doc, backmatterContainer, references, createElement);
210
+ body.insertBefore(abstractsContainer, body.firstChild);
211
+ body.insertBefore(bodyContainer, abstractsContainer.nextSibling);
212
+ body.append(backmatterContainer);
213
+ },
214
+ mapFootnotesToSections(doc, backmatterContainer, createElement) {
174
215
  const footnoteGroups = [...doc.querySelectorAll('fn[fn-type]')];
175
216
  for (const footnote of footnoteGroups) {
176
217
  const type = footnote.getAttribute('fn-type') || '';
@@ -187,7 +228,7 @@ export const jatsBodyTransformations = {
187
228
  section.append(...footnote.children);
188
229
  removeNodeFromParent(footnote);
189
230
  section.setAttribute('sec-type', chooseSecType(category));
190
- body.append(section);
231
+ backmatterContainer.append(section);
191
232
  }
192
233
  }
193
234
  const footnotes = [...doc.querySelectorAll('fn')];
@@ -202,7 +243,7 @@ export const jatsBodyTransformations = {
202
243
  }
203
244
  if (!footnotesSection && containingGroup.innerHTML) {
204
245
  const section = this.createFootnotes([containingGroup], createElement);
205
- body.append(section);
246
+ backmatterContainer.append(section);
206
247
  }
207
248
  let regularFootnoteGroups = [
208
249
  ...doc.querySelectorAll('back > fn-group:not([fn-type])'),
@@ -215,7 +256,7 @@ export const jatsBodyTransformations = {
215
256
  if (regularFootnoteGroups.length > 0) {
216
257
  regularFootnoteGroups.map((g) => removeNodeFromParent(g));
217
258
  const footnotes = this.createFootnotes(regularFootnoteGroups, createElement);
218
- body.appendChild(footnotes);
259
+ backmatterContainer.appendChild(footnotes);
219
260
  }
220
261
  },
221
262
  moveCaptionsToEnd(body) {
@@ -263,18 +304,4 @@ export const jatsBodyTransformations = {
263
304
  paragraph === null || paragraph === void 0 ? void 0 : paragraph.replaceWith(parent);
264
305
  });
265
306
  },
266
- moveKeywordsToBody(document, body, createElement) {
267
- const keywordGroups = [...document.querySelectorAll('kwd-group')];
268
- if (keywordGroups.length > 0) {
269
- const section = createElement('sec');
270
- section.setAttribute('sec-type', 'keywords');
271
- const title = createElement('title');
272
- title.textContent = 'Keywords';
273
- section.append(title);
274
- const kwdGroupsEl = createElement('kwd-group-list');
275
- kwdGroupsEl.append(keywordGroups[0]);
276
- section.append(kwdGroupsEl);
277
- body.prepend(section);
278
- }
279
- },
280
307
  };
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { v4 as uuidv4 } from 'uuid';
17
- import { buildComment, buildContribution, isCommentAnnotation, isHighlightableModel, isKeyword, } from '../../transformer';
17
+ import { buildComment, buildContribution, isCommentAnnotation, isHighlightableModel, } from '../../transformer';
18
18
  const DEFAULT_ANNOTATION_COLOR = 'rgb(250, 224, 150)';
19
19
  const DEFAULT_PROFILE_ID = 'MPUserProfile:0000000000000000000000000000000000000001';
20
20
  export const parseProcessingInstruction = (node) => {
@@ -58,21 +58,6 @@ const insertToken = (doc, processingInstructionNode, authorQueriesMap) => {
58
58
  return parentElement.insertBefore(tokenNode, processingInstructionNode);
59
59
  }
60
60
  };
61
- const extractCommentsFromKeywords = (tokens, model, authorQueriesMap) => {
62
- var _a;
63
- const commentAnnotations = [];
64
- const name = model.name;
65
- const filteredTokens = filterAndSortTokens(tokens, name);
66
- let content = name;
67
- for (const token of filteredTokens) {
68
- content = name.replace(token, '');
69
- const query = authorQueriesMap.get(token);
70
- const commentAnnotation = buildComment((_a = model.containedGroup) !== null && _a !== void 0 ? _a : uuidv4(), `${query}`, undefined, [buildContribution(DEFAULT_PROFILE_ID)], DEFAULT_ANNOTATION_COLOR);
71
- model['name'] = content;
72
- commentAnnotations.push(commentAnnotation);
73
- }
74
- return commentAnnotations;
75
- };
76
61
  export const createComments = (authorQueriesMap, manuscriptModels) => {
77
62
  const tokens = [...authorQueriesMap.keys()];
78
63
  const commentAnnotations = [];
@@ -81,18 +66,9 @@ export const createComments = (authorQueriesMap, manuscriptModels) => {
81
66
  const comments = addCommentsFromMarkedProcessingInstructions(tokens, model, authorQueriesMap);
82
67
  commentAnnotations.push(...comments);
83
68
  }
84
- else if (isKeyword(model)) {
85
- const comments = extractCommentsFromKeywords(tokens, model, authorQueriesMap);
86
- commentAnnotations.push(...comments);
87
- }
88
69
  }
89
70
  return commentAnnotations;
90
71
  };
91
- function filterAndSortTokens(tokens, content) {
92
- return tokens
93
- .filter((token) => content.indexOf(token) >= 0)
94
- .sort((a, b) => content.indexOf(a) - content.indexOf(b));
95
- }
96
72
  const addCommentsFromMarkedProcessingInstructions = (tokens, model, authorQueriesMap) => {
97
73
  const commentAnnotations = [];
98
74
  for (const field of ['contents', 'caption', 'title']) {
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import debug from 'debug';
17
- import { buildAffiliation, buildBibliographicName, buildContributor, buildCorresp, buildFootnote, buildSupplementaryMaterial, } from '../../transformer/builders';
17
+ import { buildAffiliation, buildBibliographicName, buildContributor, buildCorresp, buildFootnote, buildKeyword, buildKeywordGroup, buildSupplementaryMaterial, } from '../../transformer/builders';
18
18
  import { parseJournalMeta } from './jats-journal-meta-parser';
19
19
  const warn = debug('manuscripts-transform');
20
20
  const XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink';
@@ -62,6 +62,31 @@ export const jatsFrontParser = {
62
62
  }
63
63
  return parseJournalMeta(journalMeta);
64
64
  },
65
+ parseKeywords(keywordGroupNodes) {
66
+ var _a, _b;
67
+ if (!keywordGroupNodes) {
68
+ return { groups: [], keywords: [] };
69
+ }
70
+ let keywordPriority = 1;
71
+ const keywordGroups = { groups: [], keywords: [] };
72
+ for (const keywordGroupNode of keywordGroupNodes) {
73
+ const manuscriptKeywordGroup = buildKeywordGroup({
74
+ title: ((_a = keywordGroupNode.querySelector('title')) === null || _a === void 0 ? void 0 : _a.textContent) || undefined,
75
+ label: ((_b = keywordGroupNode.querySelector('label')) === null || _b === void 0 ? void 0 : _b.textContent) || undefined,
76
+ type: keywordGroupNode.getAttribute('kwd-group-type') || undefined,
77
+ });
78
+ keywordGroups.groups.push(manuscriptKeywordGroup);
79
+ for (const keywordNode of keywordGroupNode.querySelectorAll('kwd')) {
80
+ if (keywordNode.textContent) {
81
+ const keyword = buildKeyword(keywordNode.textContent);
82
+ keyword.priority = keywordPriority++;
83
+ keyword.containedGroup = manuscriptKeywordGroup._id;
84
+ keywordGroups.keywords.push(keyword);
85
+ }
86
+ }
87
+ }
88
+ return keywordGroups;
89
+ },
65
90
  parseDates(historyNode) {
66
91
  if (!historyNode) {
67
92
  return undefined;
@@ -51,6 +51,9 @@ export const parseJATSFront = async (front) => {
51
51
  const manuscriptMeta = Object.assign({ title: title ? inlineContentsFromJATSTitle(title) : undefined, subtitle: subtitle ? inlineContentsFromJATSTitle(subtitle) : undefined, runningTitle: runningTitle
52
52
  ? inlineContentsFromJATSTitle(runningTitle)
53
53
  : undefined }, jatsFrontParser.parseCounts(articleMeta === null || articleMeta === void 0 ? void 0 : articleMeta.querySelector('counts')));
54
+ const keywordGroupNodes = articleMeta === null || articleMeta === void 0 ? void 0 : articleMeta.querySelectorAll('kwd-group');
55
+ const { keywords, groups: keywordGroups } = jatsFrontParser.parseKeywords(keywordGroupNodes);
56
+ const manuscript_keywordIDs = keywords.length > 0 ? keywords.map((k) => k._id) : undefined;
54
57
  const { affiliations, affiliationIDs } = jatsFrontParser.parseAffiliationNodes([
55
58
  ...front.querySelectorAll('article-meta > contrib-group > aff'),
56
59
  ]);
@@ -67,12 +70,14 @@ export const parseJATSFront = async (front) => {
67
70
  const supplements = jatsFrontParser.parseSupplements([
68
71
  ...front.querySelectorAll('article-meta > supplementary-material'),
69
72
  ]);
70
- const manuscript = Object.assign(Object.assign(Object.assign({}, buildManuscript()), manuscriptMeta), history);
73
+ const manuscript = Object.assign(Object.assign(Object.assign(Object.assign({}, buildManuscript()), manuscriptMeta), { keywordIDs: manuscript_keywordIDs }), history);
71
74
  return {
72
75
  models: generateModelIDs([
73
76
  manuscript,
77
+ ...keywords,
74
78
  ...affiliations,
75
79
  ...authors,
80
+ ...keywordGroups,
76
81
  ...footnotes,
77
82
  ...correspondingList,
78
83
  journal,
@@ -106,12 +111,10 @@ export const parseJATSBody = (document, body, bibliographyItems, refModels, refe
106
111
  const orderedFootnotesIDs = createOrderedFootnotesIDs(document);
107
112
  jatsBodyTransformations.moveFloatsGroupToBody(document, body, createElement);
108
113
  jatsBodyTransformations.ensureSection(body, createElement);
109
- jatsBodyTransformations.mapFootnotesToSections(document, body, createElement);
110
114
  jatsBodyTransformations.moveSectionsToBody(document, body, bibliographyItems, createElement);
111
115
  jatsBodyTransformations.moveCaptionsToEnd(body);
112
116
  jatsBodyTransformations.moveTableFooterToEnd(body);
113
117
  jatsBodyTransformations.moveBlockNodesFromParagraph(document, body, createElement);
114
- jatsBodyTransformations.moveKeywordsToBody(document, body, createElement);
115
118
  const node = jatsBodyDOMParser.parse(body);
116
119
  if (!node.firstChild) {
117
120
  throw new Error('No content was parsed from the JATS article body');
@@ -129,8 +129,10 @@ export class JATSExporter {
129
129
  article.appendChild(body);
130
130
  const back = this.buildBack(body);
131
131
  article.appendChild(back);
132
+ this.unwrapBody(body);
132
133
  this.moveAbstracts(front, body);
133
134
  this.moveFloatsGroup(body, article);
135
+ this.removeBackContainer(body);
134
136
  }
135
137
  await this.rewriteIDs(idGenerator);
136
138
  if (mediaPathGenerator) {
@@ -391,7 +393,9 @@ export class JATSExporter {
391
393
  if (history.childElementCount) {
392
394
  articleMeta.appendChild(history);
393
395
  }
394
- this.buildKeywords(articleMeta);
396
+ if (manuscript.keywordIDs) {
397
+ this.buildKeywords(articleMeta, manuscript.keywordIDs);
398
+ }
395
399
  let countingElements = [];
396
400
  if (manuscript.genericCounts) {
397
401
  const elements = manuscript.genericCounts.map((el) => {
@@ -615,7 +619,6 @@ export class JATSExporter {
615
619
  bibliography_element: () => '',
616
620
  bibliography_item: () => '',
617
621
  comment_list: () => '',
618
- keywords_group: () => '',
619
622
  bibliography_section: (node) => [
620
623
  'ref-list',
621
624
  { id: normalizeID(node.attrs.id) },
@@ -1384,21 +1387,49 @@ export class JATSExporter {
1384
1387
  table.insertBefore(tfoot, tbody);
1385
1388
  }
1386
1389
  };
1387
- this.moveAbstracts = (front, body) => {
1388
- const sections = body.querySelectorAll(':scope > sec');
1389
- const abstractSections = Array.from(sections).filter((section) => {
1390
- const sectionType = section.getAttribute('sec-type');
1391
- if (sectionType === 'abstract' ||
1392
- sectionType === 'abstract-teaser' ||
1393
- sectionType === 'abstract-graphical') {
1394
- return true;
1395
- }
1396
- const sectionTitle = section.querySelector(':scope > title');
1397
- if (!sectionTitle) {
1398
- return false;
1399
- }
1400
- return sectionTitle.textContent === 'Abstract';
1390
+ this.unwrapBody = (body) => {
1391
+ const container = body.querySelector(':scope > sec[sec-type="body"]');
1392
+ if (!container) {
1393
+ return;
1394
+ }
1395
+ const sections = container.querySelectorAll(':scope > sec');
1396
+ sections.forEach((section) => {
1397
+ body.appendChild(section.cloneNode(true));
1401
1398
  });
1399
+ body.removeChild(container);
1400
+ };
1401
+ this.removeBackContainer = (body) => {
1402
+ const container = body.querySelector(':scope > sec[sec-type="backmatter"]');
1403
+ if (!container) {
1404
+ return;
1405
+ }
1406
+ const isContainerEmpty = container.children.length === 0;
1407
+ if (!isContainerEmpty) {
1408
+ warn('Backmatter section is not empty.');
1409
+ }
1410
+ body.removeChild(container);
1411
+ };
1412
+ this.moveAbstracts = (front, body) => {
1413
+ const container = body.querySelector(':scope > sec[sec-type="abstracts"]');
1414
+ let abstractSections;
1415
+ if (container) {
1416
+ abstractSections = Array.from(container.querySelectorAll(':scope > sec'));
1417
+ }
1418
+ else {
1419
+ abstractSections = Array.from(body.querySelectorAll(':scope > sec')).filter((section) => {
1420
+ const sectionType = section.getAttribute('sec-type');
1421
+ if (sectionType === 'abstract' ||
1422
+ sectionType === 'abstract-teaser' ||
1423
+ sectionType === 'abstract-graphical') {
1424
+ return true;
1425
+ }
1426
+ const sectionTitle = section.querySelector(':scope > title');
1427
+ if (!sectionTitle) {
1428
+ return false;
1429
+ }
1430
+ return sectionTitle.textContent === 'Abstract';
1431
+ });
1432
+ }
1402
1433
  if (abstractSections.length) {
1403
1434
  for (const abstractSection of abstractSections) {
1404
1435
  const abstractNode = this.document.createElement('abstract');
@@ -1419,6 +1450,9 @@ export class JATSExporter {
1419
1450
  }
1420
1451
  }
1421
1452
  }
1453
+ if (container) {
1454
+ body.removeChild(container);
1455
+ }
1422
1456
  };
1423
1457
  this.moveSectionsToBack = (back, body) => {
1424
1458
  const availabilitySection = body.querySelector('sec[sec-type="availability"]');
@@ -1521,8 +1555,10 @@ export class JATSExporter {
1521
1555
  return name;
1522
1556
  };
1523
1557
  }
1524
- buildKeywords(articleMeta) {
1525
- const keywords = [...this.modelMap.values()].filter((model) => model.objectType === ObjectTypes.Keyword);
1558
+ buildKeywords(articleMeta, keywordIDs) {
1559
+ const keywords = keywordIDs
1560
+ .map((id) => this.modelMap.get(id))
1561
+ .filter((model) => model && model.name);
1526
1562
  const keywordGroups = new Map();
1527
1563
  keywords.forEach((keyword) => {
1528
1564
  const containedGroup = keyword.containedGroup || '';
@@ -1556,7 +1592,6 @@ export class JATSExporter {
1556
1592
  kwd.textContent = keyword.name;
1557
1593
  kwdGroup.appendChild(kwd);
1558
1594
  }
1559
- articleMeta.appendChild(kwdGroup);
1560
1595
  }
1561
1596
  }
1562
1597
  }
@@ -42,7 +42,6 @@ import { inlineEquation } from './nodes/inline_equation';
42
42
  import { inlineFootnote } from './nodes/inline_footnote';
43
43
  import { keyword } from './nodes/keyword';
44
44
  import { keywordsElement } from './nodes/keywords_element';
45
- import { keywordsGroup } from './nodes/keywords_group';
46
45
  import { keywordsSection } from './nodes/keywords_section';
47
46
  import { link } from './nodes/link';
48
47
  import { bulletList, listItem, orderedList } from './nodes/list';
@@ -157,7 +156,6 @@ export const schema = new Schema({
157
156
  keyword,
158
157
  keywords_element: keywordsElement,
159
158
  keywords_section: keywordsSection,
160
- keywords_group: keywordsGroup,
161
159
  link,
162
160
  list_item: listItem,
163
161
  listing,
@@ -22,7 +22,8 @@ export const keyword = {
22
22
  dataTracked: { default: null },
23
23
  comments: { default: null },
24
24
  },
25
- group: 'block',
25
+ inline: true,
26
+ group: 'inline',
26
27
  selectable: false,
27
28
  parseDOM: [
28
29
  {
@@ -15,12 +15,11 @@
15
15
  */
16
16
  export const keywordsElement = {
17
17
  atom: true,
18
- content: 'keywords_group*',
18
+ content: 'keyword*',
19
19
  attrs: {
20
20
  id: { default: '' },
21
21
  contents: { default: '' },
22
22
  paragraphStyle: { default: '' },
23
- type: { default: '' },
24
23
  dataTracked: { default: null },
25
24
  },
26
25
  group: 'block element',
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  export const keywordsSection = {
17
- content: 'section_title (keywords_element | placeholder_element)',
17
+ content: 'section_title_plain (keywords_element | placeholder_element)',
18
18
  attrs: {
19
19
  id: { default: '' },
20
20
  dataTracked: { default: null },
@@ -32,7 +32,7 @@ const choosePageBreakStyle = (element) => {
32
32
  return PAGE_BREAK_NONE;
33
33
  };
34
34
  export const section = {
35
- content: 'section_label? section_title (paragraph | element)* section*',
35
+ content: 'section_label? section_title (paragraph | element)* sections*',
36
36
  attrs: {
37
37
  id: { default: '' },
38
38
  category: { default: '' },
@@ -265,11 +265,11 @@ export class Decoder {
265
265
  },
266
266
  [ObjectTypes.KeywordsElement]: (data) => {
267
267
  const model = data;
268
- const keywordGroups = this.getKeywordGroups();
268
+ const keywords = this.getKeywords();
269
269
  return schema.nodes.keywords_element.create({
270
270
  id: model._id,
271
271
  paragraphStyle: model.paragraphStyle,
272
- }, keywordGroups);
272
+ }, keywords);
273
273
  },
274
274
  [ObjectTypes.Keyword]: (data) => {
275
275
  const model = data;
@@ -416,7 +416,9 @@ export class Decoder {
416
416
  const elementNodes = elements
417
417
  .map(this.decode)
418
418
  .filter(isManuscriptNode);
419
- const sectionTitle = 'section_title';
419
+ const sectionTitle = isKeywordsSection
420
+ ? 'section_title_plain'
421
+ : 'section_title';
420
422
  const sectionTitleNode = model.title
421
423
  ? this.parseContents(model.title, 'h1', this.getComments(model), {
422
424
  topNode: schema.nodes[sectionTitle].create(),
@@ -560,6 +562,19 @@ export class Decoder {
560
562
  id: generateNodeID(schema.nodes.section),
561
563
  }));
562
564
  }
565
+ const keywordsSection = getSections(this.modelMap).filter((section) => section.category === 'MPSectionCategory:keywords');
566
+ const keywords = this.getKeywords();
567
+ if (keywordsSection.length === 0 && keywords.length > 0) {
568
+ const keywordsSection = schema.nodes.keywords_section.createAndFill({
569
+ id: generateNodeID(schema.nodes.keywords_section),
570
+ }, [
571
+ schema.nodes.section_title_plain.create({}, schema.text('Keywords')),
572
+ schema.nodes.keywords_element.create({
573
+ id: generateNodeID(schema.nodes.keywords_element),
574
+ }, keywords),
575
+ ]);
576
+ rootSectionNodes.unshift(keywordsSection);
577
+ }
563
578
  const contents = rootSectionNodes;
564
579
  if (this.comments.size) {
565
580
  const comments = schema.nodes.comment_list.createAndFill({
@@ -607,16 +622,17 @@ export class Decoder {
607
622
  }
608
623
  return parser.parse(template.content.firstElementChild, options);
609
624
  };
610
- this.getKeywords = (id) => {
625
+ this.getKeywords = () => {
611
626
  const keywordsOfKind = [];
612
- const keywordsByGroup = [...this.modelMap.values()].filter((m) => m.objectType === ObjectTypes.Keyword &&
613
- m.containedGroup === id);
614
- for (const model of keywordsByGroup) {
627
+ for (const model of this.modelMap.values()) {
628
+ const commentNodes = this.createCommentsNode(model);
629
+ commentNodes.forEach((c) => this.comments.set(c.attrs.id, c));
615
630
  if (isKeyword(model)) {
616
631
  const keyword = this.parseContents(model.name || '', 'div', this.getComments(model), {
617
632
  topNode: schema.nodes.keyword.create({
618
633
  id: model._id,
619
634
  contents: model.name,
635
+ comments: commentNodes.map((c) => c.attrs.id),
620
636
  }),
621
637
  });
622
638
  keywordsOfKind.push(keyword);
@@ -663,26 +679,4 @@ export class Decoder {
663
679
  this.modelMap = modelMap;
664
680
  this.allowMissingElements = allowMissingElements;
665
681
  }
666
- getKeywordGroups() {
667
- const kwdGroupNodes = [];
668
- const kwdGroupsModels = [
669
- ...this.modelMap.values(),
670
- ].filter((model) => model.objectType === ObjectTypes.KeywordGroup);
671
- if (kwdGroupsModels.length > 0) {
672
- for (const kwdGroupModel of kwdGroupsModels) {
673
- const keywords = this.getKeywords(kwdGroupModel._id);
674
- const commentNodes = this.createCommentsNode(kwdGroupModel);
675
- commentNodes.forEach((c) => this.comments.set(c.attrs.id, c));
676
- const contents = [];
677
- contents.push(...keywords);
678
- const kwdGroupNode = schema.nodes.keywords_group.create({
679
- id: kwdGroupModel._id,
680
- type: kwdGroupModel.type,
681
- comments: commentNodes.map((c) => c.attrs.id),
682
- }, contents);
683
- kwdGroupNodes.push(kwdGroupNode);
684
- }
685
- }
686
- return kwdGroupNodes;
687
- }
688
682
  }