@manuscripts/transform 1.2.4 → 1.2.5-LEAN-2066

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 +33 -1
  2. package/dist/cjs/jats/importer/jats-body-transformations.js +67 -12
  3. package/dist/cjs/jats/importer/jats-comments.js +24 -0
  4. package/dist/cjs/jats/importer/jats-front-parser.js +0 -25
  5. package/dist/cjs/jats/importer/parse-jats-article.js +2 -7
  6. package/dist/cjs/jats/jats-exporter.js +52 -21
  7. package/dist/cjs/schema/index.js +2 -0
  8. package/dist/cjs/schema/nodes/keyword.js +1 -2
  9. package/dist/cjs/schema/nodes/keywords_element.js +2 -1
  10. package/dist/cjs/schema/nodes/keywords_group.js +48 -0
  11. package/dist/cjs/schema/nodes/keywords_section.js +1 -1
  12. package/dist/cjs/schema/nodes/section.js +1 -1
  13. package/dist/cjs/transformer/decode.js +29 -23
  14. package/dist/cjs/transformer/encode.js +16 -6
  15. package/dist/cjs/transformer/node-types.js +1 -0
  16. package/dist/cjs/transformer/object-types.js +2 -1
  17. package/dist/cjs/transformer/section-category.js +23 -1
  18. package/dist/es/jats/importer/jats-body-dom-parser.js +33 -1
  19. package/dist/es/jats/importer/jats-body-transformations.js +68 -13
  20. package/dist/es/jats/importer/jats-comments.js +25 -1
  21. package/dist/es/jats/importer/jats-front-parser.js +1 -26
  22. package/dist/es/jats/importer/parse-jats-article.js +2 -7
  23. package/dist/es/jats/jats-exporter.js +52 -21
  24. package/dist/es/schema/index.js +2 -0
  25. package/dist/es/schema/nodes/keyword.js +1 -2
  26. package/dist/es/schema/nodes/keywords_element.js +2 -1
  27. package/dist/es/schema/nodes/keywords_group.js +44 -0
  28. package/dist/es/schema/nodes/keywords_section.js +1 -1
  29. package/dist/es/schema/nodes/section.js +1 -1
  30. package/dist/es/transformer/decode.js +29 -23
  31. package/dist/es/transformer/encode.js +17 -7
  32. package/dist/es/transformer/node-types.js +1 -0
  33. package/dist/es/transformer/object-types.js +1 -0
  34. package/dist/es/transformer/section-category.js +19 -1
  35. package/dist/types/jats/importer/jats-body-transformations.d.ts +8 -1
  36. package/dist/types/jats/importer/jats-front-parser.d.ts +6 -11
  37. package/dist/types/jats/jats-exporter.d.ts +2 -0
  38. package/dist/types/schema/nodes/keywords_group.d.ts +26 -0
  39. package/dist/types/schema/types.d.ts +1 -1
  40. package/dist/types/transformer/decode.d.ts +1 -0
  41. package/dist/types/transformer/object-types.d.ts +2 -1
  42. package/dist/types/transformer/section-category.d.ts +3 -2
  43. package/package.json +2 -2
@@ -162,6 +162,15 @@ const childElements = (node) => {
162
162
  });
163
163
  return nodes;
164
164
  };
165
+ const sectionChildElementIds = (node) => {
166
+ const nodes = [];
167
+ node.forEach((childNode) => {
168
+ if (!(0, schema_1.hasGroup)(childNode.type, 'sections')) {
169
+ nodes.push(childNode);
170
+ }
171
+ });
172
+ return nodes.map((childNode) => childNode.attrs.id).filter((id) => id);
173
+ };
165
174
  const attributeOfNodeType = (node, type, attribute) => {
166
175
  for (const child of (0, utils_1.iterateChildren)(node)) {
167
176
  if (child.type.name === type) {
@@ -401,18 +410,21 @@ const encoders = {
401
410
  SVGGlyphs: svgDefs(node.attrs.SVGRepresentation),
402
411
  }),
403
412
  keyword: (node, parent) => ({
404
- containerID: parent.attrs.id,
413
+ containedGroup: parent.attrs.id,
405
414
  name: keywordContents(node),
406
415
  }),
407
416
  keywords_element: (node) => ({
408
- contents: elementContents(node),
417
+ contents: '<div></div>',
409
418
  elementType: 'div',
410
419
  paragraphStyle: node.attrs.paragraphStyle || undefined,
411
420
  }),
421
+ keywords_group: (node) => ({
422
+ type: node.attrs.type,
423
+ }),
412
424
  keywords_section: (node, parent, path, priority) => ({
413
425
  category: (0, section_category_1.buildSectionCategory)(node),
414
426
  priority: priority.value++,
415
- title: inlineContentsOfNodeType(node, node.type.schema.nodes.section_title_plain),
427
+ title: inlineContentsOfNodeType(node, node.type.schema.nodes.section_title),
416
428
  path: path.concat([node.attrs.id]),
417
429
  elementIDs: childElements(node)
418
430
  .map((childNode) => childNode.attrs.id)
@@ -449,9 +461,7 @@ const encoders = {
449
461
  label: inlineContentsOfNodeType(node, node.type.schema.nodes.section_label) ||
450
462
  undefined,
451
463
  path: path.concat([node.attrs.id]),
452
- elementIDs: childElements(node)
453
- .map((childNode) => childNode.attrs.id)
454
- .filter((id) => id),
464
+ elementIDs: sectionChildElementIds(node),
455
465
  titleSuppressed: node.attrs.titleSuppressed || undefined,
456
466
  generatedLabel: node.attrs.generatedLabel || undefined,
457
467
  pageBreakStyle: node.attrs.pageBreakStyle || undefined,
@@ -41,6 +41,7 @@ exports.nodeTypesMap = new Map([
41
41
  [schema_1.schema.nodes.keyword, json_schema_1.ObjectTypes.Keyword],
42
42
  [schema_1.schema.nodes.keywords_element, json_schema_1.ObjectTypes.KeywordsElement],
43
43
  [schema_1.schema.nodes.keywords_section, json_schema_1.ObjectTypes.Section],
44
+ [schema_1.schema.nodes.keywords_group, json_schema_1.ObjectTypes.KeywordGroup],
44
45
  [schema_1.schema.nodes.listing, json_schema_1.ObjectTypes.Listing],
45
46
  [schema_1.schema.nodes.listing_element, json_schema_1.ObjectTypes.ListingElement],
46
47
  [schema_1.schema.nodes.manuscript, json_schema_1.ObjectTypes.Manuscript],
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.isCommentAnnotation = exports.isUserProfile = exports.isTable = exports.isManuscript = exports.isFigure = exports.hasObjectType = exports.isManuscriptModel = exports.manuscriptObjects = exports.elementObjects = exports.ExtraObjectTypes = void 0;
18
+ exports.isKeyword = exports.isCommentAnnotation = exports.isUserProfile = exports.isTable = exports.isManuscript = exports.isFigure = exports.hasObjectType = exports.isManuscriptModel = exports.manuscriptObjects = exports.elementObjects = exports.ExtraObjectTypes = void 0;
19
19
  const json_schema_1 = require("@manuscripts/json-schema");
20
20
  var ExtraObjectTypes;
21
21
  (function (ExtraObjectTypes) {
@@ -55,3 +55,4 @@ exports.isManuscript = (0, exports.hasObjectType)(json_schema_1.ObjectTypes.Manu
55
55
  exports.isTable = (0, exports.hasObjectType)(json_schema_1.ObjectTypes.Table);
56
56
  exports.isUserProfile = (0, exports.hasObjectType)(json_schema_1.ObjectTypes.UserProfile);
57
57
  exports.isCommentAnnotation = (0, exports.hasObjectType)(json_schema_1.ObjectTypes.CommentAnnotation);
58
+ exports.isKeyword = (0, exports.hasObjectType)(json_schema_1.ObjectTypes.Keyword);
@@ -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,6 +499,22 @@ 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
+ },
502
518
  {
503
519
  tag: 'sec',
504
520
  node: 'section',
@@ -510,6 +526,22 @@ const nodes = [
510
526
  };
511
527
  },
512
528
  },
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
+ },
513
545
  {
514
546
  tag: 'label',
515
547
  context: 'section/',
@@ -562,7 +594,7 @@ const nodes = [
562
594
  {
563
595
  tag: 'title',
564
596
  node: 'section_title',
565
- context: 'section/|footnotes_section/|bibliography_section/',
597
+ context: 'section/|footnotes_section/|bibliography_section/|keywords_section/',
566
598
  },
567
599
  {
568
600
  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,4 +304,18 @@ export const jatsBodyTransformations = {
263
304
  paragraph === null || paragraph === void 0 ? void 0 : paragraph.replaceWith(parent);
264
305
  });
265
306
  },
307
+ moveKeywordsToBody(document, body, createElement) {
308
+ const keywordGroups = [...document.querySelectorAll('kwd-group')];
309
+ if (keywordGroups.length > 0) {
310
+ const section = createElement('sec');
311
+ section.setAttribute('sec-type', 'keywords');
312
+ const title = createElement('title');
313
+ title.textContent = 'Keywords';
314
+ section.append(title);
315
+ const kwdGroupsEl = createElement('kwd-group-list');
316
+ kwdGroupsEl.append(keywordGroups[0]);
317
+ section.append(kwdGroupsEl);
318
+ body.prepend(section);
319
+ }
320
+ },
266
321
  };
@@ -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, } from '../../transformer';
17
+ import { buildComment, buildContribution, isCommentAnnotation, isHighlightableModel, isKeyword, } 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,6 +58,21 @@ 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
+ };
61
76
  export const createComments = (authorQueriesMap, manuscriptModels) => {
62
77
  const tokens = [...authorQueriesMap.keys()];
63
78
  const commentAnnotations = [];
@@ -66,9 +81,18 @@ export const createComments = (authorQueriesMap, manuscriptModels) => {
66
81
  const comments = addCommentsFromMarkedProcessingInstructions(tokens, model, authorQueriesMap);
67
82
  commentAnnotations.push(...comments);
68
83
  }
84
+ else if (isKeyword(model)) {
85
+ const comments = extractCommentsFromKeywords(tokens, model, authorQueriesMap);
86
+ commentAnnotations.push(...comments);
87
+ }
69
88
  }
70
89
  return commentAnnotations;
71
90
  };
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
+ }
72
96
  const addCommentsFromMarkedProcessingInstructions = (tokens, model, authorQueriesMap) => {
73
97
  const commentAnnotations = [];
74
98
  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, buildKeyword, buildKeywordGroup, buildSupplementaryMaterial, } from '../../transformer/builders';
17
+ import { buildAffiliation, buildBibliographicName, buildContributor, buildCorresp, buildFootnote, 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,31 +62,6 @@ 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
- },
90
65
  parseDates(historyNode) {
91
66
  if (!historyNode) {
92
67
  return undefined;
@@ -51,9 +51,6 @@ 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;
57
54
  const { affiliations, affiliationIDs } = jatsFrontParser.parseAffiliationNodes([
58
55
  ...front.querySelectorAll('article-meta > contrib-group > aff'),
59
56
  ]);
@@ -70,14 +67,12 @@ export const parseJATSFront = async (front) => {
70
67
  const supplements = jatsFrontParser.parseSupplements([
71
68
  ...front.querySelectorAll('article-meta > supplementary-material'),
72
69
  ]);
73
- const manuscript = Object.assign(Object.assign(Object.assign(Object.assign({}, buildManuscript()), manuscriptMeta), { keywordIDs: manuscript_keywordIDs }), history);
70
+ const manuscript = Object.assign(Object.assign(Object.assign({}, buildManuscript()), manuscriptMeta), history);
74
71
  return {
75
72
  models: generateModelIDs([
76
73
  manuscript,
77
- ...keywords,
78
74
  ...affiliations,
79
75
  ...authors,
80
- ...keywordGroups,
81
76
  ...footnotes,
82
77
  ...correspondingList,
83
78
  journal,
@@ -111,11 +106,11 @@ export const parseJATSBody = (document, body, bibliographyItems, refModels, refe
111
106
  const orderedFootnotesIDs = createOrderedFootnotesIDs(document);
112
107
  jatsBodyTransformations.moveFloatsGroupToBody(document, body, createElement);
113
108
  jatsBodyTransformations.ensureSection(body, createElement);
114
- jatsBodyTransformations.mapFootnotesToSections(document, body, createElement);
115
109
  jatsBodyTransformations.moveSectionsToBody(document, body, bibliographyItems, createElement);
116
110
  jatsBodyTransformations.moveCaptionsToEnd(body);
117
111
  jatsBodyTransformations.moveTableFooterToEnd(body);
118
112
  jatsBodyTransformations.moveBlockNodesFromParagraph(document, body, createElement);
113
+ jatsBodyTransformations.moveKeywordsToBody(document, body, createElement);
119
114
  const node = jatsBodyDOMParser.parse(body);
120
115
  if (!node.firstChild) {
121
116
  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,9 +393,7 @@ export class JATSExporter {
391
393
  if (history.childElementCount) {
392
394
  articleMeta.appendChild(history);
393
395
  }
394
- if (manuscript.keywordIDs) {
395
- this.buildKeywords(articleMeta, manuscript.keywordIDs);
396
- }
396
+ this.buildKeywords(articleMeta);
397
397
  let countingElements = [];
398
398
  if (manuscript.genericCounts) {
399
399
  const elements = manuscript.genericCounts.map((el) => {
@@ -617,6 +617,7 @@ export class JATSExporter {
617
617
  bibliography_element: () => '',
618
618
  bibliography_item: () => '',
619
619
  comment_list: () => '',
620
+ keywords_group: () => '',
620
621
  bibliography_section: (node) => [
621
622
  'ref-list',
622
623
  { id: normalizeID(node.attrs.id) },
@@ -1385,21 +1386,49 @@ export class JATSExporter {
1385
1386
  table.insertBefore(tfoot, tbody);
1386
1387
  }
1387
1388
  };
1388
- this.moveAbstracts = (front, body) => {
1389
- const sections = body.querySelectorAll(':scope > sec');
1390
- const abstractSections = Array.from(sections).filter((section) => {
1391
- const sectionType = section.getAttribute('sec-type');
1392
- if (sectionType === 'abstract' ||
1393
- sectionType === 'abstract-teaser' ||
1394
- sectionType === 'abstract-graphical') {
1395
- return true;
1396
- }
1397
- const sectionTitle = section.querySelector(':scope > title');
1398
- if (!sectionTitle) {
1399
- return false;
1400
- }
1401
- return sectionTitle.textContent === 'Abstract';
1389
+ this.unwrapBody = (body) => {
1390
+ const container = body.querySelector(':scope > sec[sec-type="body"]');
1391
+ if (!container) {
1392
+ return;
1393
+ }
1394
+ const sections = container.querySelectorAll(':scope > sec');
1395
+ sections.forEach((section) => {
1396
+ body.appendChild(section.cloneNode(true));
1402
1397
  });
1398
+ body.removeChild(container);
1399
+ };
1400
+ this.removeBackContainer = (body) => {
1401
+ const container = body.querySelector(':scope > sec[sec-type="backmatter"]');
1402
+ if (!container) {
1403
+ return;
1404
+ }
1405
+ const isContainerEmpty = container.children.length === 0;
1406
+ if (!isContainerEmpty) {
1407
+ warn('Backmatter section is not empty.');
1408
+ }
1409
+ body.removeChild(container);
1410
+ };
1411
+ this.moveAbstracts = (front, body) => {
1412
+ const container = body.querySelector(':scope > sec[sec-type="abstracts"]');
1413
+ let abstractSections;
1414
+ if (container) {
1415
+ abstractSections = Array.from(container.querySelectorAll(':scope > sec'));
1416
+ }
1417
+ else {
1418
+ abstractSections = Array.from(body.querySelectorAll(':scope > sec')).filter((section) => {
1419
+ const sectionType = section.getAttribute('sec-type');
1420
+ if (sectionType === 'abstract' ||
1421
+ sectionType === 'abstract-teaser' ||
1422
+ sectionType === 'abstract-graphical') {
1423
+ return true;
1424
+ }
1425
+ const sectionTitle = section.querySelector(':scope > title');
1426
+ if (!sectionTitle) {
1427
+ return false;
1428
+ }
1429
+ return sectionTitle.textContent === 'Abstract';
1430
+ });
1431
+ }
1403
1432
  if (abstractSections.length) {
1404
1433
  for (const abstractSection of abstractSections) {
1405
1434
  const abstractNode = this.document.createElement('abstract');
@@ -1420,6 +1449,9 @@ export class JATSExporter {
1420
1449
  }
1421
1450
  }
1422
1451
  }
1452
+ if (container) {
1453
+ body.removeChild(container);
1454
+ }
1423
1455
  };
1424
1456
  this.moveSectionsToBack = (back, body) => {
1425
1457
  const availabilitySection = body.querySelector('sec[sec-type="availability"]');
@@ -1522,10 +1554,8 @@ export class JATSExporter {
1522
1554
  return name;
1523
1555
  };
1524
1556
  }
1525
- buildKeywords(articleMeta, keywordIDs) {
1526
- const keywords = keywordIDs
1527
- .map((id) => this.modelMap.get(id))
1528
- .filter((model) => model && model.name);
1557
+ buildKeywords(articleMeta) {
1558
+ const keywords = [...this.modelMap.values()].filter((model) => model.objectType === ObjectTypes.Keyword);
1529
1559
  const keywordGroups = new Map();
1530
1560
  keywords.forEach((keyword) => {
1531
1561
  const containedGroup = keyword.containedGroup || '';
@@ -1559,6 +1589,7 @@ export class JATSExporter {
1559
1589
  kwd.textContent = keyword.name;
1560
1590
  kwdGroup.appendChild(kwd);
1561
1591
  }
1592
+ articleMeta.appendChild(kwdGroup);
1562
1593
  }
1563
1594
  }
1564
1595
  }
@@ -42,6 +42,7 @@ 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';
45
46
  import { keywordsSection } from './nodes/keywords_section';
46
47
  import { link } from './nodes/link';
47
48
  import { bulletList, listItem, orderedList } from './nodes/list';
@@ -156,6 +157,7 @@ export const schema = new Schema({
156
157
  keyword,
157
158
  keywords_element: keywordsElement,
158
159
  keywords_section: keywordsSection,
160
+ keywords_group: keywordsGroup,
159
161
  link,
160
162
  list_item: listItem,
161
163
  listing,
@@ -22,8 +22,7 @@ export const keyword = {
22
22
  dataTracked: { default: null },
23
23
  comments: { default: null },
24
24
  },
25
- inline: true,
26
- group: 'inline',
25
+ group: 'block',
27
26
  selectable: false,
28
27
  parseDOM: [
29
28
  {
@@ -15,11 +15,12 @@
15
15
  */
16
16
  export const keywordsElement = {
17
17
  atom: true,
18
- content: 'keyword*',
18
+ content: 'keywords_group*',
19
19
  attrs: {
20
20
  id: { default: '' },
21
21
  contents: { default: '' },
22
22
  paragraphStyle: { default: '' },
23
+ type: { default: '' },
23
24
  dataTracked: { default: null },
24
25
  },
25
26
  group: 'block element',
@@ -0,0 +1,44 @@
1
+ /*!
2
+ * © 2019 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
+ export const keywordsGroup = {
17
+ content: 'keyword*',
18
+ attrs: {
19
+ id: { default: '' },
20
+ type: { default: '' },
21
+ dataTracked: { default: null },
22
+ },
23
+ group: 'block',
24
+ selectable: false,
25
+ parseDOM: [
26
+ {
27
+ tag: 'div.keywords',
28
+ },
29
+ ],
30
+ toDOM: (node) => {
31
+ const keywordsGroupNode = node;
32
+ return [
33
+ 'div',
34
+ {
35
+ id: keywordsGroupNode.attrs.id,
36
+ class: 'keywords',
37
+ spellcheck: 'false',
38
+ contenteditable: false,
39
+ },
40
+ 0,
41
+ ];
42
+ },
43
+ };
44
+ export const isKeywordsGroupNode = (node) => node.type === node.type.schema.nodes.keywords_group;