@manuscripts/transform 4.5.8 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/cjs/jats/__tests__/jats-exporter.test.js +23 -0
  2. package/dist/cjs/jats/__tests__/jats-importer.test.js +26 -0
  3. package/dist/cjs/jats/exporter/JATSExporterBase.js +118 -0
  4. package/dist/cjs/jats/exporter/jats-exporter.js +224 -342
  5. package/dist/cjs/jats/exporter/lib.js +62 -0
  6. package/dist/cjs/jats/importer/jats-dom-parser.js +11 -3
  7. package/dist/cjs/schema/index.js +4 -1
  8. package/dist/cjs/schema/migration/migration-scripts/4.5.9.js +30 -0
  9. package/dist/cjs/schema/migration/migration-scripts/index.js +3 -1
  10. package/dist/cjs/schema/nodes/bibliography_element.js +0 -1
  11. package/dist/cjs/schema/nodes/bio.js +24 -0
  12. package/dist/cjs/schema/nodes/contributor.js +1 -2
  13. package/dist/cjs/schema/nodes/keywords_element.js +0 -1
  14. package/dist/cjs/types.js +1 -1
  15. package/dist/cjs/version.js +1 -1
  16. package/dist/es/jats/__tests__/jats-exporter.test.js +23 -0
  17. package/dist/es/jats/__tests__/jats-importer.test.js +26 -0
  18. package/dist/es/jats/exporter/JATSExporterBase.js +114 -0
  19. package/dist/es/jats/exporter/jats-exporter.js +198 -315
  20. package/dist/es/jats/exporter/lib.js +55 -0
  21. package/dist/es/jats/importer/jats-dom-parser.js +11 -3
  22. package/dist/es/schema/index.js +4 -1
  23. package/dist/es/schema/migration/migration-scripts/4.5.9.js +28 -0
  24. package/dist/es/schema/migration/migration-scripts/index.js +3 -1
  25. package/dist/es/schema/nodes/bibliography_element.js +0 -1
  26. package/dist/es/schema/nodes/bio.js +20 -0
  27. package/dist/es/schema/nodes/contributor.js +1 -2
  28. package/dist/es/schema/nodes/keywords_element.js +0 -1
  29. package/dist/es/types.js +1 -1
  30. package/dist/es/version.js +1 -1
  31. package/dist/types/jats/exporter/JATSExporterBase.d.ts +35 -0
  32. package/dist/types/jats/exporter/jats-exporter.d.ts +22 -18
  33. package/dist/types/jats/exporter/lib.d.ts +23 -0
  34. package/dist/types/schema/index.d.ts +2 -1
  35. package/dist/types/schema/migration/migration-scripts/4.5.9.d.ts +8 -0
  36. package/dist/types/schema/migration/migration-scripts/index.d.ts +3 -2
  37. package/dist/types/schema/nodes/bio.d.ts +10 -0
  38. package/dist/types/schema/types.d.ts +1 -1
  39. package/dist/types/types.d.ts +1 -1
  40. package/dist/types/version.d.ts +1 -1
  41. package/package.json +1 -1
  42. package/src/jats/__tests__/__fixtures__/jats-import.xml +7 -0
  43. package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +272 -151
  44. package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +2 -2
  45. package/src/jats/__tests__/jats-exporter.test.ts +34 -0
  46. package/src/jats/__tests__/jats-importer.test.ts +37 -0
  47. package/src/jats/exporter/JATSExporterBase.ts +164 -0
  48. package/src/jats/exporter/jats-exporter.ts +212 -380
  49. package/src/jats/exporter/lib.ts +69 -0
  50. package/src/jats/importer/jats-dom-parser.ts +15 -5
  51. package/src/schema/index.ts +4 -1
  52. package/src/schema/migration/migration-scripts/4.5.9.ts +62 -0
  53. package/src/schema/migration/migration-scripts/index.ts +3 -1
  54. package/src/schema/nodes/bibliography_element.ts +0 -1
  55. package/src/schema/nodes/bio.ts +48 -0
  56. package/src/schema/nodes/contributor.ts +1 -2
  57. package/src/schema/nodes/keywords_element.ts +0 -1
  58. package/src/schema/types.ts +1 -0
  59. package/src/types.ts +1 -1
  60. package/src/version.ts +1 -1
@@ -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,68 +15,23 @@
15
15
  */
16
16
  import * as Citeproc from 'citeproc';
17
17
  import { DOMSerializer } from 'prosemirror-model';
18
- import { findChildrenByAttr, findChildrenByType } from 'prosemirror-utils';
18
+ import { findChildrenByAttr } from 'prosemirror-utils';
19
19
  import { buildCiteprocCitation } from '../../lib/citeproc';
20
20
  import { CreditRoleUrls } from '../../lib/credit-roles';
21
21
  import { generateFootnoteLabels } from '../../lib/footnotes';
22
22
  import { FOOTNOTE_SECTION_CATEGORY_IDS } from '../../lib/section-categories';
23
23
  import { sanitizeXmlString, XLINK_NAMESPACE, XML_NAMESPACE, } from '../../lib/xml';
24
- import { isBibliographyItemNode, isCitationNode, isNodeOfType, schema, } from '../../schema';
24
+ import { isBibliographyItemNode, isCitationNode, schema, } from '../../schema';
25
25
  import { isExecutableNodeType } from '../../transformer';
26
26
  import { initJats, jatsVariableWrapper } from './citeproc';
27
27
  import { selectVersionIDs } from './jats-versions';
28
28
  import { buildTargets } from './labels';
29
- const normalizeID = (id) => id.replace(/:/g, '_');
30
- export const createCounter = () => {
31
- const counts = new Map();
32
- return {
33
- increment: (field) => {
34
- const value = counts.get(field);
35
- const newValue = value === undefined ? 1 : value + 1;
36
- counts.set(field, newValue);
37
- return newValue;
38
- },
39
- };
40
- };
41
- const createDefaultIDGenerator = () => {
42
- const counter = createCounter();
43
- return async (element) => {
44
- const value = String(counter.increment(element.nodeName));
45
- return `${element.localName}-${value}`;
46
- };
47
- };
48
- const chooseRefType = (type) => {
49
- switch (type) {
50
- case schema.nodes.figure:
51
- case schema.nodes.figure_element:
52
- return 'fig';
53
- case schema.nodes.footnote:
54
- return 'fn';
55
- case schema.nodes.table:
56
- case schema.nodes.table_element:
57
- return 'table';
58
- case schema.nodes.section:
59
- case schema.nodes.abstract:
60
- return 'sec';
61
- case schema.nodes.equation:
62
- case schema.nodes.equation_element:
63
- return 'disp-formula';
64
- case schema.nodes.supplement:
65
- return 'supplementary-material';
66
- }
67
- };
68
- export class JATSExporter {
29
+ import { JATSExporterBase } from './JATSExporterBase';
30
+ import { chooseRefType, createDefaultIDGenerator, normalizeID } from './lib';
31
+ export class JATSExporter extends JATSExporterBase {
69
32
  constructor() {
70
- this.nodesMap = new Map();
33
+ super(...arguments);
71
34
  this.contributorLabels = new Map();
72
- this.populateNodesMap = () => {
73
- this.manuscriptNode.descendants((node) => {
74
- const type = node.type;
75
- const nodes = this.nodesMap.get(type) ?? [];
76
- nodes.push(node);
77
- this.nodesMap.set(type, nodes);
78
- });
79
- };
80
35
  this.export = async (manuscriptNode, options) => {
81
36
  this.manuscriptNode = manuscriptNode;
82
37
  this.populateNodesMap();
@@ -409,32 +364,13 @@ export class JATSExporter {
409
364
  });
410
365
  return $refList;
411
366
  };
412
- this.createElement = (tag, content, attrs) => {
413
- const $element = this.document.createElement(tag);
414
- if (content) {
415
- $element.textContent = content;
416
- }
417
- if (attrs) {
418
- Object.entries(attrs).forEach(([k, v]) => {
419
- if (v) {
420
- $element.setAttribute(k, v);
421
- }
422
- });
423
- }
424
- return $element;
425
- };
426
- this.appendElement = ($parent, tag, content, attrs) => {
427
- const $element = this.createElement(tag, content, attrs);
428
- $parent.appendChild($element);
429
- return $element;
430
- };
431
367
  this.createSerializer = () => {
432
368
  const nodes = {
433
- trans_abstract: (node) => createTransAbstract(node),
434
- trans_graphical_abstract: (node) => createTransAbstract(node),
369
+ trans_abstract: (node) => this.createTransAbstract(node),
370
+ trans_graphical_abstract: (node) => this.createTransAbstract(node),
435
371
  hero_image: () => '',
436
372
  headshot_grid: () => ['p', { 'content-type': 'headshots' }, 0],
437
- headshot_element: (node) => createImage(node),
373
+ headshot_element: (node) => this.createImage(node),
438
374
  headshot_image: () => '',
439
375
  alt_text: (node) => {
440
376
  if (node.textContent) {
@@ -459,7 +395,7 @@ export class JATSExporter {
459
395
  return $selfUri;
460
396
  },
461
397
  attachments: () => '',
462
- image_element: (node) => createImage(node),
398
+ image_element: (node) => this.createImage(node),
463
399
  embed: (node) => {
464
400
  const { id, href, mimetype, mimeSubtype, extLinks } = node.attrs;
465
401
  if (!href) {
@@ -475,7 +411,7 @@ export class JATSExporter {
475
411
  if (mimeSubtype) {
476
412
  $media.setAttribute('mime-subtype', node.attrs.mimeSubtype);
477
413
  }
478
- appendLabels($media, node);
414
+ this.appendLabels($media, node);
479
415
  this.appendChildNodeOfType($media, node, schema.nodes.alt_text);
480
416
  this.appendChildNodeOfType($media, node, schema.nodes.long_desc);
481
417
  this.appendCaption($media, node);
@@ -502,14 +438,14 @@ export class JATSExporter {
502
438
  const awardGroup = node;
503
439
  const $awardGroup = this.createElement('award-group');
504
440
  $awardGroup.setAttribute('id', normalizeID(awardGroup.attrs.id));
505
- appendChildIfPresent($awardGroup, 'funding-source', awardGroup.attrs.source);
441
+ this.appendChildIfPresent($awardGroup, 'funding-source', awardGroup.attrs.source);
506
442
  awardGroup.attrs.code
507
443
  ?.split(';')
508
- .forEach((code) => appendChildIfPresent($awardGroup, 'award-id', code));
509
- 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);
510
446
  return $awardGroup;
511
447
  },
512
- box_element: (node) => createBoxElement(node),
448
+ box_element: (node) => this.createBoxElement(node),
513
449
  author_notes: () => '',
514
450
  corresp: () => '',
515
451
  title: () => ['article-title', 0],
@@ -528,6 +464,7 @@ export class JATSExporter {
528
464
  ? ['table-wrap-foot', ['fn-group', ['fn', ['p']]]]
529
465
  : ['table-wrap-foot', 0],
530
466
  contributor: (node) => this.buildContributorElement(node),
467
+ bio: () => ['bio', 0],
531
468
  affiliation: (node) => this.buildAffiliationElement(node),
532
469
  attribution: () => ['attrib', 0],
533
470
  bibliography_element: () => '',
@@ -549,7 +486,7 @@ export class JATSExporter {
549
486
  $supplementaryMaterial.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.href ?? '');
550
487
  $supplementaryMaterial.setAttribute('mimetype', node.attrs.mimeType ?? '');
551
488
  $supplementaryMaterial.setAttribute('mime-subtype', node.attrs.mimeSubType ?? '');
552
- appendLabels($supplementaryMaterial, node);
489
+ this.appendLabels($supplementaryMaterial, node);
553
490
  this.appendCaption($supplementaryMaterial, node);
554
491
  return $supplementaryMaterial;
555
492
  },
@@ -635,12 +572,12 @@ export class JATSExporter {
635
572
  equation_element: (node) => {
636
573
  const $dispFormula = this.createElement('disp-formula');
637
574
  $dispFormula.setAttribute('id', normalizeID(node.attrs.id));
638
- appendLabels($dispFormula, node);
639
- processChildNodes($dispFormula, node, schema.nodes.equation);
575
+ this.appendLabels($dispFormula, node);
576
+ this.processChildNodes($dispFormula, node, schema.nodes.equation);
640
577
  return $dispFormula;
641
578
  },
642
- figure: (node) => createGraphic(node),
643
- 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),
644
581
  footnote: (node) => {
645
582
  const attrs = {};
646
583
  if (node.attrs.id) {
@@ -715,7 +652,7 @@ export class JATSExporter {
715
652
  $code.textContent = node.attrs.contents;
716
653
  return $code;
717
654
  },
718
- listing_element: (node) => createFigureElement(node, node.type.schema.nodes.listing),
655
+ listing_element: (node) => this.createFigureElement(node, node.type.schema.nodes.listing),
719
656
  manuscript: (node) => ['article', { id: normalizeID(node.attrs.id) }, 0],
720
657
  missing_figure: () => {
721
658
  const $graphic = this.createElement('graphic');
@@ -752,11 +689,11 @@ export class JATSExporter {
752
689
  quote_image: (node) => {
753
690
  const img = node;
754
691
  if (img.attrs.src) {
755
- return createGraphic(node);
692
+ return this.createGraphic(node);
756
693
  }
757
694
  return '';
758
695
  },
759
- graphical_abstract_section: (node) => createAbstract(node),
696
+ graphical_abstract_section: (node) => this.createAbstract(node),
760
697
  section: (node) => {
761
698
  const attrs = {
762
699
  id: normalizeID(node.attrs.id),
@@ -766,13 +703,13 @@ export class JATSExporter {
766
703
  }
767
704
  return ['sec', attrs, 0];
768
705
  },
769
- abstract: (node) => createAbstract(node),
706
+ abstract: (node) => this.createAbstract(node),
770
707
  section_label: () => ['label', 0],
771
708
  section_title: () => ['title', 0],
772
709
  section_title_plain: () => ['title', 0],
773
710
  table: (node) => ['table', { id: normalizeID(node.attrs.id) }, 0],
774
711
  table_element: (node) => {
775
- const $tableWrap = createTableElement(node);
712
+ const $tableWrap = this.createTableElement(node);
776
713
  $tableWrap.setAttribute('position', 'anchor');
777
714
  if (node.attrs.type) {
778
715
  $tableWrap.setAttribute('content-type', node.attrs.type);
@@ -823,210 +760,7 @@ export class JATSExporter {
823
760
  tracked_delete: () => ['del'],
824
761
  };
825
762
  this.serializer = new DOMSerializer(nodes, marks);
826
- const appendChildIfPresent = ($parent, tagName, textContent) => {
827
- if (!textContent) {
828
- return;
829
- }
830
- const $element = this.createElement(tagName);
831
- $element.textContent = textContent;
832
- $parent.appendChild($element);
833
- };
834
- const processChildNodes = ($element, node, contentNodeType) => {
835
- node.forEach((childNode) => {
836
- if (childNode.type === contentNodeType) {
837
- if (childNode.attrs.id) {
838
- $element.appendChild(this.serializeNode(childNode));
839
- }
840
- }
841
- else if (childNode.type === node.type.schema.nodes.paragraph) {
842
- $element.appendChild(this.serializeNode(childNode));
843
- }
844
- else if (childNode.type === node.type.schema.nodes.missing_figure) {
845
- $element.appendChild(this.serializeNode(childNode));
846
- }
847
- });
848
- };
849
- const createElement = (node, nodeName) => {
850
- const $element = this.createElement(nodeName);
851
- $element.setAttribute('id', normalizeID(node.attrs.id));
852
- return $element;
853
- };
854
- const appendLabels = ($element, node) => {
855
- if (this.labelTargets) {
856
- const target = this.labelTargets.get(node.attrs.id);
857
- if (target) {
858
- const $label = this.createElement('label');
859
- $label.textContent = target.label;
860
- $element.appendChild($label);
861
- }
862
- }
863
- };
864
- const appendAttributions = ($element, node) => {
865
- if (node.attrs.attribution) {
866
- const $attrib = this.createElement('attrib');
867
- $attrib.textContent = node.attrs.attribution.literal;
868
- $element.appendChild($attrib);
869
- }
870
- };
871
- const appendTable = ($element, node) => {
872
- const tableNode = this.getFirstChildOfType(schema.nodes.table, node);
873
- const colGroupNode = this.getFirstChildOfType(schema.nodes.table_colgroup, node);
874
- if (!tableNode) {
875
- return;
876
- }
877
- const $table = this.serializeNode(tableNode);
878
- const $tbody = this.createElement('tbody');
879
- while ($table.firstChild) {
880
- const $child = $table.firstChild;
881
- $table.removeChild($child);
882
- $tbody.appendChild($child);
883
- }
884
- $table.appendChild($tbody);
885
- this.normalizeTable($table);
886
- if (colGroupNode) {
887
- const $colGroup = this.serializeNode(colGroupNode);
888
- $table.insertBefore($colGroup, $table.firstChild);
889
- }
890
- $element.appendChild($table);
891
- };
892
- const createBoxElement = (node) => {
893
- const $boxedText = createElement(node, 'boxed-text');
894
- if (node.attrs.type) {
895
- $boxedText.setAttribute('content-type', node.attrs.type);
896
- }
897
- appendLabels($boxedText, node);
898
- const child = node.firstChild;
899
- if (child?.type === schema.nodes.caption_title) {
900
- this.appendCaption($boxedText, node);
901
- }
902
- processChildNodes($boxedText, node, node.type.schema.nodes.section);
903
- return $boxedText;
904
- };
905
- const abstractTypeAttrs = (category) => category && category !== 'abstract' ? { 'abstract-type': category } : {};
906
- const createAbstract = (node) => [
907
- 'abstract',
908
- abstractTypeAttrs(node.attrs.category),
909
- 0,
910
- ];
911
- const createTransAbstract = (node) => [
912
- 'trans-abstract',
913
- {
914
- [`${XML_NAMESPACE} lang`]: node.attrs.lang ?? '',
915
- ...abstractTypeAttrs(node.attrs.category),
916
- },
917
- 0,
918
- ];
919
- const isChildOfNodeType = (targetID, type, descend = false) => {
920
- const nodes = this.getChildrenOfType(type);
921
- return nodes.some((node) => {
922
- const result = findChildrenByAttr(node, (attrs) => attrs.id === targetID, descend)[0];
923
- return !!result;
924
- });
925
- };
926
- const findParentHeroImage = (targetID) => {
927
- const heroes = this.getChildrenOfType(schema.nodes.hero_image);
928
- return heroes.find((hero) => !!findChildrenByAttr(hero, (attrs) => attrs.id === targetID)[0]);
929
- };
930
- const createImage = (node) => {
931
- const graphicNode = node.content.firstChild;
932
- if (!graphicNode) {
933
- return '';
934
- }
935
- const $graphic = createGraphic(graphicNode);
936
- if (node.attrs.extLink) {
937
- const $extLink = this.appendElement($graphic, 'ext-link');
938
- $extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink);
939
- }
940
- this.appendCaption($graphic, node);
941
- this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text);
942
- this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc);
943
- return $graphic;
944
- };
945
- const createGraphic = (node) => {
946
- const $graphic = this.createElement('graphic');
947
- $graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src);
948
- const hero = findParentHeroImage(node.attrs.id);
949
- if (hero) {
950
- $graphic.setAttribute('content-type', hero.attrs.type || 'leading');
951
- }
952
- else if (!isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
953
- node.attrs.type) {
954
- $graphic.setAttribute('content-type', node.attrs.type);
955
- }
956
- return $graphic;
957
- };
958
- const createFigureElement = (node, contentNodeType) => {
959
- const $fig = createElement(node, 'fig');
960
- const figNode = this.getFirstChildOfType(schema.nodes.figure, node);
961
- const figType = figNode?.attrs.type;
962
- if (figType) {
963
- $fig.setAttribute('fig-type', figType);
964
- }
965
- appendLabels($fig, node);
966
- this.appendCaption($fig, node);
967
- this.appendChildNodeOfType($fig, node, schema.nodes.alt_text);
968
- this.appendChildNodeOfType($fig, node, schema.nodes.long_desc);
969
- this.appendChildNodeOfType($fig, node, node.type.schema.nodes.footnotes_element);
970
- processChildNodes($fig, node, contentNodeType);
971
- appendAttributions($fig, node);
972
- if (isExecutableNodeType(node.type)) {
973
- processExecutableNode(node, $fig);
974
- }
975
- moveAltTextAndLongDescToGraphics($fig);
976
- return $fig;
977
- };
978
- const moveAltTextAndLongDescToGraphics = ($element) => {
979
- const $altText = $element.querySelector('alt-text');
980
- const $longDesc = $element.querySelector('long-desc');
981
- const $graphics = $element.querySelectorAll('graphic');
982
- if ($graphics.length === 0) {
983
- return;
984
- }
985
- $graphics.forEach(($graphic) => {
986
- if ($longDesc) {
987
- $graphic.prepend($longDesc.cloneNode(true));
988
- }
989
- if ($altText) {
990
- $graphic.prepend($altText.cloneNode(true));
991
- }
992
- });
993
- $altText?.remove();
994
- $longDesc?.remove();
995
- };
996
- const createTableElement = (node) => {
997
- const nodeName = 'table-wrap';
998
- const $tableWrap = createElement(node, nodeName);
999
- appendLabels($tableWrap, node);
1000
- this.appendCaption($tableWrap, node);
1001
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text);
1002
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc);
1003
- appendTable($tableWrap, node);
1004
- this.appendChildNodeOfType($tableWrap, node, node.type.schema.nodes.table_element_footer);
1005
- if (isExecutableNodeType(node.type)) {
1006
- processExecutableNode(node, $tableWrap);
1007
- }
1008
- return $tableWrap;
1009
- };
1010
- const processExecutableNode = (node, $element) => {
1011
- const listingNode = this.getFirstChildOfType(schema.nodes.listing, node);
1012
- if (listingNode) {
1013
- const { contents, languageKey } = listingNode.attrs;
1014
- if (contents && languageKey) {
1015
- const $fig = this.createElement('fig');
1016
- $fig.setAttribute('specific-use', 'source');
1017
- $element.appendChild($fig);
1018
- const $code = this.createElement('code');
1019
- $code.setAttribute('executable', 'true');
1020
- $code.setAttribute('language', languageKey);
1021
- $code.textContent = contents;
1022
- $fig.appendChild($code);
1023
- }
1024
- }
1025
- };
1026
763
  };
1027
- this.serializeNode = (node) => this.serializer.serializeNode(node, {
1028
- document: this.document,
1029
- });
1030
764
  this.buildContributorGroup = (contributors) => {
1031
765
  if (!contributors.length) {
1032
766
  return;
@@ -1145,6 +879,10 @@ export class JATSExporter {
1145
879
  'vocab-term-identifier': url,
1146
880
  });
1147
881
  });
882
+ const bio = this.getFirstChildOfType(schema.nodes.bio, contributor);
883
+ if (bio?.childCount) {
884
+ $contrib.appendChild(this.serializeNode(bio));
885
+ }
1148
886
  return $contrib;
1149
887
  };
1150
888
  this.buildAffiliationElement = (affiliation) => {
@@ -1259,17 +997,6 @@ export class JATSExporter {
1259
997
  });
1260
998
  };
1261
999
  this.buildKeywords = () => this.getChildrenOfType(schema.nodes.keyword_group).map((group) => this.serializeNode(group));
1262
- this.changeTag = ($node, tag) => {
1263
- const $clone = this.createElement(tag);
1264
- for (const attr of $node.attributes) {
1265
- $clone.setAttributeNS(null, attr.name, attr.value);
1266
- }
1267
- while ($node.firstChild) {
1268
- $clone.appendChild($node.firstChild);
1269
- }
1270
- $node.replaceWith($clone);
1271
- return $clone;
1272
- };
1273
1000
  this.normalizeTable = ($table) => {
1274
1001
  let $tbody;
1275
1002
  Array.from($table.childNodes).forEach(($child) => {
@@ -1354,14 +1081,174 @@ export class JATSExporter {
1354
1081
  }
1355
1082
  };
1356
1083
  }
1357
- getFirstChildOfType(type, node) {
1358
- 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);
1359
1121
  }
1360
- getChildrenOfType(type, node) {
1361
- const nodes = node
1362
- ? findChildrenByType(node, type).map(({ node }) => node)
1363
- : this.nodesMap.get(type);
1364
- 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
+ }
1365
1252
  }
1366
1253
  createEquation(node, isInline = false) {
1367
1254
  if (node.attrs.format === 'tex') {
@@ -1385,10 +1272,6 @@ export class JATSExporter {
1385
1272
  return $mathml;
1386
1273
  }
1387
1274
  }
1388
- fillEmptyElements($article, selector, tagName = 'p') {
1389
- const $empty = Array.from($article.querySelectorAll(selector)).filter(($el) => !$el.innerHTML);
1390
- $empty.forEach(($element) => $element.appendChild(this.createElement(tagName)));
1391
- }
1392
1275
  addParagraphsToSections($article) {
1393
1276
  const $sections = $article.querySelectorAll('sec, abstract');
1394
1277
  const TITLE_TAGS = new Set(['title', 'label', 'sec-meta']);