@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.
Files changed (63) hide show
  1. package/dist/cjs/jats/__tests__/jats-exporter.test.js +50 -20
  2. package/dist/cjs/jats/__tests__/jats-importer.test.js +26 -0
  3. package/dist/cjs/jats/__tests__/jats-roundtrip.test.js +6 -1
  4. package/dist/cjs/jats/exporter/JATSExporterBase.js +118 -0
  5. package/dist/cjs/jats/exporter/jats-exporter.js +227 -349
  6. package/dist/cjs/jats/exporter/lib.js +62 -0
  7. package/dist/cjs/jats/importer/jats-dom-parser.js +11 -3
  8. package/dist/cjs/schema/index.js +4 -1
  9. package/dist/cjs/schema/migration/migration-scripts/4.5.9.js +30 -0
  10. package/dist/cjs/schema/migration/migration-scripts/index.js +3 -1
  11. package/dist/cjs/schema/nodes/bibliography_element.js +0 -1
  12. package/dist/cjs/schema/nodes/bio.js +24 -0
  13. package/dist/cjs/schema/nodes/contributor.js +1 -2
  14. package/dist/cjs/schema/nodes/keywords_element.js +0 -1
  15. package/dist/cjs/types.js +1 -1
  16. package/dist/cjs/version.js +1 -1
  17. package/dist/es/jats/__tests__/jats-exporter.test.js +47 -20
  18. package/dist/es/jats/__tests__/jats-importer.test.js +26 -0
  19. package/dist/es/jats/__tests__/jats-roundtrip.test.js +3 -1
  20. package/dist/es/jats/exporter/JATSExporterBase.js +114 -0
  21. package/dist/es/jats/exporter/jats-exporter.js +201 -319
  22. package/dist/es/jats/exporter/lib.js +55 -0
  23. package/dist/es/jats/importer/jats-dom-parser.js +11 -3
  24. package/dist/es/schema/index.js +4 -1
  25. package/dist/es/schema/migration/migration-scripts/4.5.9.js +28 -0
  26. package/dist/es/schema/migration/migration-scripts/index.js +3 -1
  27. package/dist/es/schema/nodes/bibliography_element.js +0 -1
  28. package/dist/es/schema/nodes/bio.js +20 -0
  29. package/dist/es/schema/nodes/contributor.js +1 -2
  30. package/dist/es/schema/nodes/keywords_element.js +0 -1
  31. package/dist/es/types.js +1 -1
  32. package/dist/es/version.js +1 -1
  33. package/dist/types/jats/exporter/JATSExporterBase.d.ts +35 -0
  34. package/dist/types/jats/exporter/jats-exporter.d.ts +23 -19
  35. package/dist/types/jats/exporter/lib.d.ts +23 -0
  36. package/dist/types/schema/index.d.ts +2 -1
  37. package/dist/types/schema/migration/migration-scripts/4.5.9.d.ts +8 -0
  38. package/dist/types/schema/migration/migration-scripts/index.d.ts +3 -2
  39. package/dist/types/schema/nodes/bio.d.ts +10 -0
  40. package/dist/types/schema/types.d.ts +1 -1
  41. package/dist/types/types.d.ts +1 -1
  42. package/dist/types/version.d.ts +1 -1
  43. package/package.json +4 -4
  44. package/src/jats/__tests__/__fixtures__/jats-import.xml +7 -0
  45. package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +272 -151
  46. package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +2 -2
  47. package/src/jats/__tests__/jats-exporter.test.ts +60 -20
  48. package/src/jats/__tests__/jats-importer.test.ts +37 -0
  49. package/src/jats/__tests__/jats-roundtrip.test.ts +3 -1
  50. package/src/jats/exporter/JATSExporterBase.ts +164 -0
  51. package/src/jats/exporter/jats-exporter.ts +217 -386
  52. package/src/jats/exporter/lib.ts +69 -0
  53. package/src/jats/importer/jats-dom-parser.ts +15 -5
  54. package/src/schema/index.ts +4 -1
  55. package/src/schema/migration/migration-scripts/4.5.9.ts +62 -0
  56. package/src/schema/migration/migration-scripts/index.ts +3 -1
  57. package/src/schema/nodes/bibliography_element.ts +0 -1
  58. package/src/schema/nodes/bio.ts +48 -0
  59. package/src/schema/nodes/contributor.ts +1 -2
  60. package/src/schema/nodes/keywords_element.ts +0 -1
  61. package/src/schema/types.ts +1 -0
  62. package/src/types.ts +1 -1
  63. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * © 2019 Atypon Systems LLC
2
+ * © 2026 Atypon Systems LLC
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -15,70 +15,24 @@
15
15
  */
16
16
  import * as Citeproc from 'citeproc';
17
17
  import { DOMSerializer } from 'prosemirror-model';
18
- import { findChildrenByAttr, findChildrenByType } from 'prosemirror-utils';
19
- import serializeToXML from 'w3c-xmlserializer';
18
+ import { findChildrenByAttr } from 'prosemirror-utils';
20
19
  import { buildCiteprocCitation } from '../../lib/citeproc';
21
20
  import { CreditRoleUrls } from '../../lib/credit-roles';
22
21
  import { generateFootnoteLabels } from '../../lib/footnotes';
23
22
  import { FOOTNOTE_SECTION_CATEGORY_IDS } from '../../lib/section-categories';
24
23
  import { sanitizeXmlString, XLINK_NAMESPACE, XML_NAMESPACE, } from '../../lib/xml';
25
- import { isBibliographyItemNode, isCitationNode, isNodeOfType, schema, } from '../../schema';
24
+ import { isBibliographyItemNode, isCitationNode, schema, } from '../../schema';
26
25
  import { isExecutableNodeType } from '../../transformer';
27
26
  import { initJats, jatsVariableWrapper } from './citeproc';
28
27
  import { selectVersionIDs } from './jats-versions';
29
28
  import { buildTargets } from './labels';
30
- const normalizeID = (id) => id.replace(/:/g, '_');
31
- export const createCounter = () => {
32
- const counts = new Map();
33
- return {
34
- increment: (field) => {
35
- const value = counts.get(field);
36
- const newValue = value === undefined ? 1 : value + 1;
37
- counts.set(field, newValue);
38
- return newValue;
39
- },
40
- };
41
- };
42
- const createDefaultIDGenerator = () => {
43
- const counter = createCounter();
44
- return async (element) => {
45
- const value = String(counter.increment(element.nodeName));
46
- return `${element.localName}-${value}`;
47
- };
48
- };
49
- const chooseRefType = (type) => {
50
- switch (type) {
51
- case schema.nodes.figure:
52
- case schema.nodes.figure_element:
53
- return 'fig';
54
- case schema.nodes.footnote:
55
- return 'fn';
56
- case schema.nodes.table:
57
- case schema.nodes.table_element:
58
- return 'table';
59
- case schema.nodes.section:
60
- case schema.nodes.abstract:
61
- return 'sec';
62
- case schema.nodes.equation:
63
- case schema.nodes.equation_element:
64
- return 'disp-formula';
65
- case schema.nodes.supplement:
66
- return 'supplementary-material';
67
- }
68
- };
69
- export class JATSExporter {
29
+ import { JATSExporterBase } from './JATSExporterBase';
30
+ import { chooseRefType, createDefaultIDGenerator, normalizeID } from './lib';
31
+ export class JATSExporter extends JATSExporterBase {
70
32
  constructor() {
71
- this.nodesMap = new Map();
33
+ super(...arguments);
72
34
  this.contributorLabels = new Map();
73
- this.populateNodesMap = () => {
74
- this.manuscriptNode.descendants((node) => {
75
- const type = node.type;
76
- const nodes = this.nodesMap.get(type) ?? [];
77
- nodes.push(node);
78
- this.nodesMap.set(type, nodes);
79
- });
80
- };
81
- this.serializeToJATS = async (manuscriptNode, options) => {
35
+ this.export = async (manuscriptNode, options) => {
82
36
  this.manuscriptNode = manuscriptNode;
83
37
  this.populateNodesMap();
84
38
  this.initCiteprocEngine(options);
@@ -109,7 +63,7 @@ export class JATSExporter {
109
63
  $article.appendChild($floatsGroup);
110
64
  }
111
65
  await this.rewriteIDs();
112
- return serializeToXML(this.document);
66
+ return this.document;
113
67
  };
114
68
  this.appendChildNodeOfType = ($element, node, type) => {
115
69
  const childNode = this.getFirstChildOfType(type, node);
@@ -400,7 +354,7 @@ export class JATSExporter {
400
354
  const [, bibliography] = this.engine.makeBibliography();
401
355
  const parser = new DOMParser();
402
356
  bibliography.forEach((item) => {
403
- const fragment = `<template xmlns:xlink="${XLINK_NAMESPACE}">${sanitizeXmlString(item)}</template>`;
357
+ const fragment = `<wrapper xmlns:xlink="${XLINK_NAMESPACE}">${sanitizeXmlString(item)}</wrapper>`;
404
358
  const $ref = parser
405
359
  .parseFromString(fragment, 'text/xml')
406
360
  .querySelector('ref');
@@ -410,32 +364,13 @@ export class JATSExporter {
410
364
  });
411
365
  return $refList;
412
366
  };
413
- this.createElement = (tag, content, attrs) => {
414
- const $element = this.document.createElement(tag);
415
- if (content) {
416
- $element.textContent = content;
417
- }
418
- if (attrs) {
419
- Object.entries(attrs).forEach(([k, v]) => {
420
- if (v) {
421
- $element.setAttribute(k, v);
422
- }
423
- });
424
- }
425
- return $element;
426
- };
427
- this.appendElement = ($parent, tag, content, attrs) => {
428
- const $element = this.createElement(tag, content, attrs);
429
- $parent.appendChild($element);
430
- return $element;
431
- };
432
367
  this.createSerializer = () => {
433
368
  const nodes = {
434
- trans_abstract: (node) => createTransAbstract(node),
435
- trans_graphical_abstract: (node) => createTransAbstract(node),
369
+ trans_abstract: (node) => this.createTransAbstract(node),
370
+ trans_graphical_abstract: (node) => this.createTransAbstract(node),
436
371
  hero_image: () => '',
437
372
  headshot_grid: () => ['p', { 'content-type': 'headshots' }, 0],
438
- headshot_element: (node) => createImage(node),
373
+ headshot_element: (node) => this.createImage(node),
439
374
  headshot_image: () => '',
440
375
  alt_text: (node) => {
441
376
  if (node.textContent) {
@@ -460,7 +395,7 @@ export class JATSExporter {
460
395
  return $selfUri;
461
396
  },
462
397
  attachments: () => '',
463
- image_element: (node) => createImage(node),
398
+ image_element: (node) => this.createImage(node),
464
399
  embed: (node) => {
465
400
  const { id, href, mimetype, mimeSubtype, extLinks } = node.attrs;
466
401
  if (!href) {
@@ -476,7 +411,7 @@ export class JATSExporter {
476
411
  if (mimeSubtype) {
477
412
  $media.setAttribute('mime-subtype', node.attrs.mimeSubtype);
478
413
  }
479
- appendLabels($media, node);
414
+ this.appendLabels($media, node);
480
415
  this.appendChildNodeOfType($media, node, schema.nodes.alt_text);
481
416
  this.appendChildNodeOfType($media, node, schema.nodes.long_desc);
482
417
  this.appendCaption($media, node);
@@ -503,14 +438,14 @@ export class JATSExporter {
503
438
  const awardGroup = node;
504
439
  const $awardGroup = this.createElement('award-group');
505
440
  $awardGroup.setAttribute('id', normalizeID(awardGroup.attrs.id));
506
- appendChildIfPresent($awardGroup, 'funding-source', awardGroup.attrs.source);
441
+ this.appendChildIfPresent($awardGroup, 'funding-source', awardGroup.attrs.source);
507
442
  awardGroup.attrs.code
508
443
  ?.split(';')
509
- .forEach((code) => appendChildIfPresent($awardGroup, 'award-id', code));
510
- appendChildIfPresent($awardGroup, 'principal-award-recipient', awardGroup.attrs.recipient);
444
+ .forEach((code) => this.appendChildIfPresent($awardGroup, 'award-id', code));
445
+ this.appendChildIfPresent($awardGroup, 'principal-award-recipient', awardGroup.attrs.recipient);
511
446
  return $awardGroup;
512
447
  },
513
- box_element: (node) => createBoxElement(node),
448
+ box_element: (node) => this.createBoxElement(node),
514
449
  author_notes: () => '',
515
450
  corresp: () => '',
516
451
  title: () => ['article-title', 0],
@@ -529,6 +464,7 @@ export class JATSExporter {
529
464
  ? ['table-wrap-foot', ['fn-group', ['fn', ['p']]]]
530
465
  : ['table-wrap-foot', 0],
531
466
  contributor: (node) => this.buildContributorElement(node),
467
+ bio: () => ['bio', 0],
532
468
  affiliation: (node) => this.buildAffiliationElement(node),
533
469
  attribution: () => ['attrib', 0],
534
470
  bibliography_element: () => '',
@@ -550,7 +486,7 @@ export class JATSExporter {
550
486
  $supplementaryMaterial.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.href ?? '');
551
487
  $supplementaryMaterial.setAttribute('mimetype', node.attrs.mimeType ?? '');
552
488
  $supplementaryMaterial.setAttribute('mime-subtype', node.attrs.mimeSubType ?? '');
553
- appendLabels($supplementaryMaterial, node);
489
+ this.appendLabels($supplementaryMaterial, node);
554
490
  this.appendCaption($supplementaryMaterial, node);
555
491
  return $supplementaryMaterial;
556
492
  },
@@ -636,12 +572,12 @@ export class JATSExporter {
636
572
  equation_element: (node) => {
637
573
  const $dispFormula = this.createElement('disp-formula');
638
574
  $dispFormula.setAttribute('id', normalizeID(node.attrs.id));
639
- appendLabels($dispFormula, node);
640
- processChildNodes($dispFormula, node, schema.nodes.equation);
575
+ this.appendLabels($dispFormula, node);
576
+ this.processChildNodes($dispFormula, node, schema.nodes.equation);
641
577
  return $dispFormula;
642
578
  },
643
- figure: (node) => createGraphic(node),
644
- figure_element: (node) => createFigureElement(node, node.type.schema.nodes.figure),
579
+ figure: (node) => this.createGraphic(node),
580
+ figure_element: (node) => this.createFigureElement(node, node.type.schema.nodes.figure),
645
581
  footnote: (node) => {
646
582
  const attrs = {};
647
583
  if (node.attrs.id) {
@@ -716,7 +652,7 @@ export class JATSExporter {
716
652
  $code.textContent = node.attrs.contents;
717
653
  return $code;
718
654
  },
719
- listing_element: (node) => createFigureElement(node, node.type.schema.nodes.listing),
655
+ listing_element: (node) => this.createFigureElement(node, node.type.schema.nodes.listing),
720
656
  manuscript: (node) => ['article', { id: normalizeID(node.attrs.id) }, 0],
721
657
  missing_figure: () => {
722
658
  const $graphic = this.createElement('graphic');
@@ -753,11 +689,11 @@ export class JATSExporter {
753
689
  quote_image: (node) => {
754
690
  const img = node;
755
691
  if (img.attrs.src) {
756
- return createGraphic(node);
692
+ return this.createGraphic(node);
757
693
  }
758
694
  return '';
759
695
  },
760
- graphical_abstract_section: (node) => createAbstract(node),
696
+ graphical_abstract_section: (node) => this.createAbstract(node),
761
697
  section: (node) => {
762
698
  const attrs = {
763
699
  id: normalizeID(node.attrs.id),
@@ -767,13 +703,13 @@ export class JATSExporter {
767
703
  }
768
704
  return ['sec', attrs, 0];
769
705
  },
770
- abstract: (node) => createAbstract(node),
706
+ abstract: (node) => this.createAbstract(node),
771
707
  section_label: () => ['label', 0],
772
708
  section_title: () => ['title', 0],
773
709
  section_title_plain: () => ['title', 0],
774
710
  table: (node) => ['table', { id: normalizeID(node.attrs.id) }, 0],
775
711
  table_element: (node) => {
776
- const $tableWrap = createTableElement(node);
712
+ const $tableWrap = this.createTableElement(node);
777
713
  $tableWrap.setAttribute('position', 'anchor');
778
714
  if (node.attrs.type) {
779
715
  $tableWrap.setAttribute('content-type', node.attrs.type);
@@ -824,210 +760,7 @@ export class JATSExporter {
824
760
  tracked_delete: () => ['del'],
825
761
  };
826
762
  this.serializer = new DOMSerializer(nodes, marks);
827
- const appendChildIfPresent = ($parent, tagName, textContent) => {
828
- if (!textContent) {
829
- return;
830
- }
831
- const $element = this.createElement(tagName);
832
- $element.textContent = textContent;
833
- $parent.appendChild($element);
834
- };
835
- const processChildNodes = ($element, node, contentNodeType) => {
836
- node.forEach((childNode) => {
837
- if (childNode.type === contentNodeType) {
838
- if (childNode.attrs.id) {
839
- $element.appendChild(this.serializeNode(childNode));
840
- }
841
- }
842
- else if (childNode.type === node.type.schema.nodes.paragraph) {
843
- $element.appendChild(this.serializeNode(childNode));
844
- }
845
- else if (childNode.type === node.type.schema.nodes.missing_figure) {
846
- $element.appendChild(this.serializeNode(childNode));
847
- }
848
- });
849
- };
850
- const createElement = (node, nodeName) => {
851
- const $element = this.createElement(nodeName);
852
- $element.setAttribute('id', normalizeID(node.attrs.id));
853
- return $element;
854
- };
855
- const appendLabels = ($element, node) => {
856
- if (this.labelTargets) {
857
- const target = this.labelTargets.get(node.attrs.id);
858
- if (target) {
859
- const $label = this.createElement('label');
860
- $label.textContent = target.label;
861
- $element.appendChild($label);
862
- }
863
- }
864
- };
865
- const appendAttributions = ($element, node) => {
866
- if (node.attrs.attribution) {
867
- const $attrib = this.createElement('attrib');
868
- $attrib.textContent = node.attrs.attribution.literal;
869
- $element.appendChild($attrib);
870
- }
871
- };
872
- const appendTable = ($element, node) => {
873
- const tableNode = this.getFirstChildOfType(schema.nodes.table, node);
874
- const colGroupNode = this.getFirstChildOfType(schema.nodes.table_colgroup, node);
875
- if (!tableNode) {
876
- return;
877
- }
878
- const $table = this.serializeNode(tableNode);
879
- const $tbody = this.createElement('tbody');
880
- while ($table.firstChild) {
881
- const $child = $table.firstChild;
882
- $table.removeChild($child);
883
- $tbody.appendChild($child);
884
- }
885
- $table.appendChild($tbody);
886
- this.normalizeTable($table);
887
- if (colGroupNode) {
888
- const $colGroup = this.serializeNode(colGroupNode);
889
- $table.insertBefore($colGroup, $table.firstChild);
890
- }
891
- $element.appendChild($table);
892
- };
893
- const createBoxElement = (node) => {
894
- const $boxedText = createElement(node, 'boxed-text');
895
- if (node.attrs.type) {
896
- $boxedText.setAttribute('content-type', node.attrs.type);
897
- }
898
- appendLabels($boxedText, node);
899
- const child = node.firstChild;
900
- if (child?.type === schema.nodes.caption_title) {
901
- this.appendCaption($boxedText, node);
902
- }
903
- processChildNodes($boxedText, node, node.type.schema.nodes.section);
904
- return $boxedText;
905
- };
906
- const abstractTypeAttrs = (category) => category && category !== 'abstract' ? { 'abstract-type': category } : {};
907
- const createAbstract = (node) => [
908
- 'abstract',
909
- abstractTypeAttrs(node.attrs.category),
910
- 0,
911
- ];
912
- const createTransAbstract = (node) => [
913
- 'trans-abstract',
914
- {
915
- [`${XML_NAMESPACE} lang`]: node.attrs.lang ?? '',
916
- ...abstractTypeAttrs(node.attrs.category),
917
- },
918
- 0,
919
- ];
920
- const isChildOfNodeType = (targetID, type, descend = false) => {
921
- const nodes = this.getChildrenOfType(type);
922
- return nodes.some((node) => {
923
- const result = findChildrenByAttr(node, (attrs) => attrs.id === targetID, descend)[0];
924
- return !!result;
925
- });
926
- };
927
- const findParentHeroImage = (targetID) => {
928
- const heroes = this.getChildrenOfType(schema.nodes.hero_image);
929
- return heroes.find((hero) => !!findChildrenByAttr(hero, (attrs) => attrs.id === targetID)[0]);
930
- };
931
- const createImage = (node) => {
932
- const graphicNode = node.content.firstChild;
933
- if (!graphicNode) {
934
- return '';
935
- }
936
- const $graphic = createGraphic(graphicNode);
937
- if (node.attrs.extLink) {
938
- const $extLink = this.appendElement($graphic, 'ext-link');
939
- $extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink);
940
- }
941
- this.appendCaption($graphic, node);
942
- this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text);
943
- this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc);
944
- return $graphic;
945
- };
946
- const createGraphic = (node) => {
947
- const $graphic = this.createElement('graphic');
948
- $graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src);
949
- const hero = findParentHeroImage(node.attrs.id);
950
- if (hero) {
951
- $graphic.setAttribute('content-type', hero.attrs.type || 'leading');
952
- }
953
- else if (!isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
954
- node.attrs.type) {
955
- $graphic.setAttribute('content-type', node.attrs.type);
956
- }
957
- return $graphic;
958
- };
959
- const createFigureElement = (node, contentNodeType) => {
960
- const $fig = createElement(node, 'fig');
961
- const figNode = this.getFirstChildOfType(schema.nodes.figure, node);
962
- const figType = figNode?.attrs.type;
963
- if (figType) {
964
- $fig.setAttribute('fig-type', figType);
965
- }
966
- appendLabels($fig, node);
967
- this.appendCaption($fig, node);
968
- this.appendChildNodeOfType($fig, node, schema.nodes.alt_text);
969
- this.appendChildNodeOfType($fig, node, schema.nodes.long_desc);
970
- this.appendChildNodeOfType($fig, node, node.type.schema.nodes.footnotes_element);
971
- processChildNodes($fig, node, contentNodeType);
972
- appendAttributions($fig, node);
973
- if (isExecutableNodeType(node.type)) {
974
- processExecutableNode(node, $fig);
975
- }
976
- moveAltTextAndLongDescToGraphics($fig);
977
- return $fig;
978
- };
979
- const moveAltTextAndLongDescToGraphics = ($element) => {
980
- const $altText = $element.querySelector('alt-text');
981
- const $longDesc = $element.querySelector('long-desc');
982
- const $graphics = $element.querySelectorAll('graphic');
983
- if ($graphics.length === 0) {
984
- return;
985
- }
986
- $graphics.forEach(($graphic) => {
987
- if ($longDesc) {
988
- $graphic.prepend($longDesc.cloneNode(true));
989
- }
990
- if ($altText) {
991
- $graphic.prepend($altText.cloneNode(true));
992
- }
993
- });
994
- $altText?.remove();
995
- $longDesc?.remove();
996
- };
997
- const createTableElement = (node) => {
998
- const nodeName = 'table-wrap';
999
- const $tableWrap = createElement(node, nodeName);
1000
- appendLabels($tableWrap, node);
1001
- this.appendCaption($tableWrap, node);
1002
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text);
1003
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc);
1004
- appendTable($tableWrap, node);
1005
- this.appendChildNodeOfType($tableWrap, node, node.type.schema.nodes.table_element_footer);
1006
- if (isExecutableNodeType(node.type)) {
1007
- processExecutableNode(node, $tableWrap);
1008
- }
1009
- return $tableWrap;
1010
- };
1011
- const processExecutableNode = (node, $element) => {
1012
- const listingNode = this.getFirstChildOfType(schema.nodes.listing, node);
1013
- if (listingNode) {
1014
- const { contents, languageKey } = listingNode.attrs;
1015
- if (contents && languageKey) {
1016
- const $fig = this.createElement('fig');
1017
- $fig.setAttribute('specific-use', 'source');
1018
- $element.appendChild($fig);
1019
- const $code = this.createElement('code');
1020
- $code.setAttribute('executable', 'true');
1021
- $code.setAttribute('language', languageKey);
1022
- $code.textContent = contents;
1023
- $fig.appendChild($code);
1024
- }
1025
- }
1026
- };
1027
763
  };
1028
- this.serializeNode = (node) => this.serializer.serializeNode(node, {
1029
- document: this.document,
1030
- });
1031
764
  this.buildContributorGroup = (contributors) => {
1032
765
  if (!contributors.length) {
1033
766
  return;
@@ -1146,6 +879,10 @@ export class JATSExporter {
1146
879
  'vocab-term-identifier': url,
1147
880
  });
1148
881
  });
882
+ const bio = this.getFirstChildOfType(schema.nodes.bio, contributor);
883
+ if (bio?.childCount) {
884
+ $contrib.appendChild(this.serializeNode(bio));
885
+ }
1149
886
  return $contrib;
1150
887
  };
1151
888
  this.buildAffiliationElement = (affiliation) => {
@@ -1260,17 +997,6 @@ export class JATSExporter {
1260
997
  });
1261
998
  };
1262
999
  this.buildKeywords = () => this.getChildrenOfType(schema.nodes.keyword_group).map((group) => this.serializeNode(group));
1263
- this.changeTag = ($node, tag) => {
1264
- const $clone = this.createElement(tag);
1265
- for (const attr of $node.attributes) {
1266
- $clone.setAttributeNS(null, attr.name, attr.value);
1267
- }
1268
- while ($node.firstChild) {
1269
- $clone.appendChild($node.firstChild);
1270
- }
1271
- $node.replaceWith($clone);
1272
- return $clone;
1273
- };
1274
1000
  this.normalizeTable = ($table) => {
1275
1001
  let $tbody;
1276
1002
  Array.from($table.childNodes).forEach(($child) => {
@@ -1355,14 +1081,174 @@ export class JATSExporter {
1355
1081
  }
1356
1082
  };
1357
1083
  }
1358
- getFirstChildOfType(type, node) {
1359
- return this.getChildrenOfType(type, node)[0];
1084
+ appendLabels($element, node) {
1085
+ if (this.labelTargets) {
1086
+ const target = this.labelTargets.get(node.attrs.id);
1087
+ if (target) {
1088
+ const $label = this.createElement('label');
1089
+ $label.textContent = target.label;
1090
+ $element.appendChild($label);
1091
+ }
1092
+ }
1093
+ }
1094
+ appendAttributions($element, node) {
1095
+ if (node.attrs.attribution) {
1096
+ const $attrib = this.createElement('attrib');
1097
+ $attrib.textContent = node.attrs.attribution.literal;
1098
+ $element.appendChild($attrib);
1099
+ }
1100
+ }
1101
+ appendTable($element, node) {
1102
+ const tableNode = this.getFirstChildOfType(schema.nodes.table, node);
1103
+ const colGroupNode = this.getFirstChildOfType(schema.nodes.table_colgroup, node);
1104
+ if (!tableNode) {
1105
+ return;
1106
+ }
1107
+ const $table = this.serializeNode(tableNode);
1108
+ const $tbody = this.createElement('tbody');
1109
+ while ($table.firstChild) {
1110
+ const $child = $table.firstChild;
1111
+ $table.removeChild($child);
1112
+ $tbody.appendChild($child);
1113
+ }
1114
+ $table.appendChild($tbody);
1115
+ this.normalizeTable($table);
1116
+ if (colGroupNode) {
1117
+ const $colGroup = this.serializeNode(colGroupNode);
1118
+ $table.insertBefore($colGroup, $table.firstChild);
1119
+ }
1120
+ $element.appendChild($table);
1360
1121
  }
1361
- getChildrenOfType(type, node) {
1362
- const nodes = node
1363
- ? findChildrenByType(node, type).map(({ node }) => node)
1364
- : this.nodesMap.get(type);
1365
- return (nodes ?? []).filter((n) => isNodeOfType(n, type));
1122
+ createBoxElement(node) {
1123
+ const $boxedText = this.createElementWithID(node, 'boxed-text');
1124
+ if (node.attrs.type) {
1125
+ $boxedText.setAttribute('content-type', node.attrs.type);
1126
+ }
1127
+ this.appendLabels($boxedText, node);
1128
+ const child = node.firstChild;
1129
+ if (child?.type === schema.nodes.caption_title) {
1130
+ this.appendCaption($boxedText, node);
1131
+ }
1132
+ this.processChildNodes($boxedText, node, node.type.schema.nodes.section);
1133
+ return $boxedText;
1134
+ }
1135
+ abstractTypeAttrs(category) {
1136
+ return category && category !== 'abstract'
1137
+ ? { 'abstract-type': category }
1138
+ : {};
1139
+ }
1140
+ createAbstract(node) {
1141
+ return ['abstract', this.abstractTypeAttrs(node.attrs.category), 0];
1142
+ }
1143
+ createTransAbstract(node) {
1144
+ return [
1145
+ 'trans-abstract',
1146
+ {
1147
+ [`${XML_NAMESPACE} lang`]: node.attrs.lang ?? '',
1148
+ ...this.abstractTypeAttrs(node.attrs.category),
1149
+ },
1150
+ 0,
1151
+ ];
1152
+ }
1153
+ createGraphic(node) {
1154
+ const $graphic = this.createElement('graphic');
1155
+ $graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src);
1156
+ const findParentHeroImage = (targetID) => {
1157
+ const heroes = this.getChildrenOfType(schema.nodes.hero_image);
1158
+ return heroes.find((hero) => !!findChildrenByAttr(hero, (attrs) => attrs.id === targetID)[0]);
1159
+ };
1160
+ const hero = findParentHeroImage(node.attrs.id);
1161
+ if (hero) {
1162
+ $graphic.setAttribute('content-type', hero.attrs.type || 'leading');
1163
+ }
1164
+ else if (!this.isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
1165
+ node.attrs.type) {
1166
+ $graphic.setAttribute('content-type', node.attrs.type);
1167
+ }
1168
+ return $graphic;
1169
+ }
1170
+ createImage(node) {
1171
+ const graphicNode = node.content.firstChild;
1172
+ if (!graphicNode) {
1173
+ return '';
1174
+ }
1175
+ const $graphic = this.createGraphic(graphicNode);
1176
+ if (node.attrs.extLink) {
1177
+ const $extLink = this.appendElement($graphic, 'ext-link');
1178
+ $extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink);
1179
+ }
1180
+ this.appendCaption($graphic, node);
1181
+ this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text);
1182
+ this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc);
1183
+ return $graphic;
1184
+ }
1185
+ createFigureElement(node, contentNodeType) {
1186
+ const $fig = this.createElementWithID(node, 'fig');
1187
+ const figNode = this.getFirstChildOfType(schema.nodes.figure, node);
1188
+ const figType = figNode?.attrs.type;
1189
+ if (figType) {
1190
+ $fig.setAttribute('fig-type', figType);
1191
+ }
1192
+ this.appendLabels($fig, node);
1193
+ this.appendCaption($fig, node);
1194
+ this.appendChildNodeOfType($fig, node, schema.nodes.alt_text);
1195
+ this.appendChildNodeOfType($fig, node, schema.nodes.long_desc);
1196
+ this.appendChildNodeOfType($fig, node, node.type.schema.nodes.footnotes_element);
1197
+ this.processChildNodes($fig, node, contentNodeType);
1198
+ this.appendAttributions($fig, node);
1199
+ if (isExecutableNodeType(node.type)) {
1200
+ this.processExecutableNode(node, $fig);
1201
+ }
1202
+ this.moveAltTextAndLongDescToGraphics($fig);
1203
+ return $fig;
1204
+ }
1205
+ moveAltTextAndLongDescToGraphics($element) {
1206
+ const $altText = $element.querySelector('alt-text');
1207
+ const $longDesc = $element.querySelector('long-desc');
1208
+ const $graphics = $element.querySelectorAll('graphic');
1209
+ if ($graphics.length === 0) {
1210
+ return;
1211
+ }
1212
+ $graphics.forEach(($graphic) => {
1213
+ if ($longDesc) {
1214
+ $graphic.prepend($longDesc.cloneNode(true));
1215
+ }
1216
+ if ($altText) {
1217
+ $graphic.prepend($altText.cloneNode(true));
1218
+ }
1219
+ });
1220
+ $altText?.remove();
1221
+ $longDesc?.remove();
1222
+ }
1223
+ createTableElement(node) {
1224
+ const nodeName = 'table-wrap';
1225
+ const $tableWrap = this.createElementWithID(node, nodeName);
1226
+ this.appendLabels($tableWrap, node);
1227
+ this.appendCaption($tableWrap, node);
1228
+ this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text);
1229
+ this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc);
1230
+ this.appendTable($tableWrap, node);
1231
+ this.appendChildNodeOfType($tableWrap, node, node.type.schema.nodes.table_element_footer);
1232
+ if (isExecutableNodeType(node.type)) {
1233
+ this.processExecutableNode(node, $tableWrap);
1234
+ }
1235
+ return $tableWrap;
1236
+ }
1237
+ processExecutableNode(node, $element) {
1238
+ const listingNode = this.getFirstChildOfType(schema.nodes.listing, node);
1239
+ if (listingNode) {
1240
+ const { contents, languageKey } = listingNode.attrs;
1241
+ if (contents && languageKey) {
1242
+ const $fig = this.createElement('fig');
1243
+ $fig.setAttribute('specific-use', 'source');
1244
+ $element.appendChild($fig);
1245
+ const $code = this.createElement('code');
1246
+ $code.setAttribute('executable', 'true');
1247
+ $code.setAttribute('language', languageKey);
1248
+ $code.textContent = contents;
1249
+ $fig.appendChild($code);
1250
+ }
1251
+ }
1366
1252
  }
1367
1253
  createEquation(node, isInline = false) {
1368
1254
  if (node.attrs.format === 'tex') {
@@ -1386,10 +1272,6 @@ export class JATSExporter {
1386
1272
  return $mathml;
1387
1273
  }
1388
1274
  }
1389
- fillEmptyElements($article, selector, tagName = 'p') {
1390
- const $empty = Array.from($article.querySelectorAll(selector)).filter(($el) => !$el.innerHTML);
1391
- $empty.forEach(($element) => $element.appendChild(this.createElement(tagName)));
1392
- }
1393
1275
  addParagraphsToSections($article) {
1394
1276
  const $sections = $article.querySelectorAll('sec, abstract');
1395
1277
  const TITLE_TAGS = new Set(['title', 'label', 'sec-meta']);