@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,8 +15,8 @@
15
15
  */
16
16
 
17
17
  import * as Citeproc from 'citeproc'
18
- import { DOMOutputSpec, DOMSerializer, type NodeType } from 'prosemirror-model'
19
- import { findChildrenByAttr, findChildrenByType } from 'prosemirror-utils'
18
+ import { DOMOutputSpec, DOMSerializer } from 'prosemirror-model'
19
+ import { findChildrenByAttr } from 'prosemirror-utils'
20
20
 
21
21
  import { buildCiteprocCitation } from '../../lib/citeproc'
22
22
  import { CreditRoleUrls } from '../../lib/credit-roles'
@@ -40,7 +40,6 @@ import {
40
40
  FootnoteNode,
41
41
  isBibliographyItemNode,
42
42
  isCitationNode,
43
- isNodeOfType,
44
43
  ManuscriptMark,
45
44
  ManuscriptNode,
46
45
  ManuscriptNodeType,
@@ -49,12 +48,15 @@ import {
49
48
  ParagraphNode,
50
49
  QuoteImageNode,
51
50
  schema,
51
+ BioNode,
52
52
  } from '../../schema'
53
53
  import { isExecutableNodeType } from '../../transformer'
54
54
  import { IDGenerator } from '../types'
55
55
  import { initJats, jatsVariableWrapper } from './citeproc'
56
56
  import { selectVersionIDs, Version } from './jats-versions'
57
57
  import { buildTargets, Target } from './labels'
58
+ import { JATSExporterBase } from './JATSExporterBase'
59
+ import { chooseRefType, createDefaultIDGenerator, normalizeID } from './lib'
58
60
 
59
61
  interface Attrs {
60
62
  [key: string]: string
@@ -66,56 +68,6 @@ type MarkSpecs = {
66
68
  [key in Marks]: (mark: ManuscriptMark, inline: boolean) => DOMOutputSpec
67
69
  }
68
70
 
69
- const normalizeID = (id: string) => id.replace(/:/g, '_')
70
-
71
- export const createCounter = () => {
72
- const counts = new Map<string, number>()
73
-
74
- return {
75
- increment: (field: string) => {
76
- const value = counts.get(field)
77
- const newValue = value === undefined ? 1 : value + 1
78
- counts.set(field, newValue)
79
- return newValue
80
- },
81
- }
82
- }
83
-
84
- const createDefaultIDGenerator = (): IDGenerator => {
85
- const counter = createCounter()
86
-
87
- return async (element: Element) => {
88
- const value = String(counter.increment(element.nodeName))
89
-
90
- return `${element.localName}-${value}`
91
- }
92
- }
93
-
94
- const chooseRefType = (type: ManuscriptNodeType): string | undefined => {
95
- switch (type) {
96
- case schema.nodes.figure:
97
- case schema.nodes.figure_element:
98
- return 'fig'
99
-
100
- case schema.nodes.footnote:
101
- return 'fn'
102
-
103
- case schema.nodes.table:
104
- case schema.nodes.table_element:
105
- return 'table'
106
-
107
- case schema.nodes.section:
108
- case schema.nodes.abstract:
109
- return 'sec'
110
-
111
- case schema.nodes.equation:
112
- case schema.nodes.equation_element:
113
- return 'disp-formula'
114
-
115
- case schema.nodes.supplement:
116
- return 'supplementary-material'
117
- }
118
- }
119
71
  export type CSLOptions = {
120
72
  style: string
121
73
  locale: string
@@ -125,42 +77,14 @@ export type ExportOptions = {
125
77
  csl: CSLOptions
126
78
  }
127
79
 
128
- export class JATSExporter {
129
- protected document: Document
130
- protected serializer: DOMSerializer
80
+ export class JATSExporter extends JATSExporterBase {
131
81
  protected labelTargets: Map<string, Target>
132
82
  protected footnoteLabels: Map<string, string>
133
83
  protected manuscriptNode: ActualManuscriptNode
134
84
  private engine: Citeproc.Engine
135
85
  private renderedCitations: Map<string, string>
136
- private nodesMap: Map<NodeType, ManuscriptNode[]> = new Map()
137
- private contributorLabels: Map<string, number> = new Map()
138
-
139
- private populateNodesMap = () => {
140
- this.manuscriptNode.descendants((node) => {
141
- const type = node.type
142
- const nodes = this.nodesMap.get(type) ?? []
143
- nodes.push(node)
144
- this.nodesMap.set(type, nodes)
145
- })
146
- }
147
-
148
- protected getFirstChildOfType<T extends ManuscriptNode>(
149
- type: NodeType,
150
- node?: ManuscriptNode
151
- ): T | undefined {
152
- return this.getChildrenOfType<T>(type, node)[0]
153
- }
154
86
 
155
- protected getChildrenOfType<T extends ManuscriptNode>(
156
- type: NodeType,
157
- node?: ManuscriptNode
158
- ): T[] {
159
- const nodes = node
160
- ? findChildrenByType(node, type).map(({ node }) => node)
161
- : this.nodesMap.get(type)
162
- return (nodes ?? []).filter((n): n is T => isNodeOfType<T>(n, type))
163
- }
87
+ private contributorLabels: Map<string, number> = new Map()
164
88
 
165
89
  public export = async (
166
90
  manuscriptNode: ActualManuscriptNode,
@@ -183,7 +107,6 @@ export class JATSExporter {
183
107
  )
184
108
 
185
109
  const $article = this.document.documentElement
186
-
187
110
  $article.setAttributeNS(
188
111
  'http://www.w3.org/2000/xmlns/',
189
112
  'xmlns:xlink',
@@ -628,43 +551,13 @@ export class JATSExporter {
628
551
  return $refList
629
552
  }
630
553
 
631
- private createElement = (
632
- tag: string,
633
- content?: string,
634
- attrs?: Record<string, string | undefined>
635
- ) => {
636
- const $element = this.document.createElement(tag)
637
- if (content) {
638
- $element.textContent = content
639
- }
640
- if (attrs) {
641
- Object.entries(attrs).forEach(([k, v]) => {
642
- if (v) {
643
- $element.setAttribute(k, v)
644
- }
645
- })
646
- }
647
- return $element
648
- }
649
-
650
- private appendElement = (
651
- $parent: Element,
652
- tag: string,
653
- content?: string,
654
- attrs?: Record<string, string | undefined>
655
- ) => {
656
- const $element = this.createElement(tag, content, attrs)
657
- $parent.appendChild($element)
658
- return $element
659
- }
660
-
661
554
  protected createSerializer = () => {
662
555
  const nodes: NodeSpecs = {
663
- trans_abstract: (node) => createTransAbstract(node),
664
- trans_graphical_abstract: (node) => createTransAbstract(node),
556
+ trans_abstract: (node) => this.createTransAbstract(node),
557
+ trans_graphical_abstract: (node) => this.createTransAbstract(node),
665
558
  hero_image: () => '',
666
559
  headshot_grid: () => ['p', { 'content-type': 'headshots' }, 0],
667
- headshot_element: (node) => createImage(node),
560
+ headshot_element: (node) => this.createImage(node),
668
561
  headshot_image: () => '',
669
562
  alt_text: (node) => {
670
563
  if (node.textContent) {
@@ -689,7 +582,7 @@ export class JATSExporter {
689
582
  return $selfUri
690
583
  },
691
584
  attachments: () => '',
692
- image_element: (node) => createImage(node),
585
+ image_element: (node) => this.createImage(node),
693
586
  embed: (node) => {
694
587
  const { id, href, mimetype, mimeSubtype, extLinks } = node.attrs
695
588
  if (!href) {
@@ -705,7 +598,7 @@ export class JATSExporter {
705
598
  if (mimeSubtype) {
706
599
  $media.setAttribute('mime-subtype', node.attrs.mimeSubtype)
707
600
  }
708
- appendLabels($media, node)
601
+ this.appendLabels($media, node)
709
602
  this.appendChildNodeOfType($media, node, schema.nodes.alt_text)
710
603
  this.appendChildNodeOfType($media, node, schema.nodes.long_desc)
711
604
  this.appendCaption($media, node)
@@ -714,9 +607,17 @@ export class JATSExporter {
714
607
  if (extLink.href) {
715
608
  const extLinkElement = this.createElement('ext-link')
716
609
  extLinkElement.setAttribute('ext-link-type', extLink.type)
717
- extLinkElement.setAttributeNS(XLINK_NAMESPACE, 'href', extLink.href)
610
+ extLinkElement.setAttributeNS(
611
+ XLINK_NAMESPACE,
612
+ 'href',
613
+ extLink.href
614
+ )
718
615
  if (extLink.lang) {
719
- extLinkElement.setAttributeNS(XML_NAMESPACE, 'lang', extLink.lang)
616
+ extLinkElement.setAttributeNS(
617
+ XML_NAMESPACE,
618
+ 'lang',
619
+ extLink.lang
620
+ )
720
621
  }
721
622
  if (extLink.label) {
722
623
  extLinkElement.textContent = extLink.label
@@ -732,7 +633,7 @@ export class JATSExporter {
732
633
  const awardGroup = node as AwardNode
733
634
  const $awardGroup = this.createElement('award-group')
734
635
  $awardGroup.setAttribute('id', normalizeID(awardGroup.attrs.id))
735
- appendChildIfPresent(
636
+ this.appendChildIfPresent(
736
637
  $awardGroup,
737
638
  'funding-source',
738
639
  awardGroup.attrs.source
@@ -740,9 +641,9 @@ export class JATSExporter {
740
641
  awardGroup.attrs.code
741
642
  ?.split(';')
742
643
  .forEach((code) =>
743
- appendChildIfPresent($awardGroup, 'award-id', code)
644
+ this.appendChildIfPresent($awardGroup, 'award-id', code)
744
645
  )
745
- appendChildIfPresent(
646
+ this.appendChildIfPresent(
746
647
  $awardGroup,
747
648
  'principal-award-recipient',
748
649
  awardGroup.attrs.recipient
@@ -750,7 +651,7 @@ export class JATSExporter {
750
651
 
751
652
  return $awardGroup
752
653
  },
753
- box_element: (node) => createBoxElement(node),
654
+ box_element: (node) => this.createBoxElement(node),
754
655
  author_notes: () => '',
755
656
  corresp: () => '',
756
657
  title: () => ['article-title', 0],
@@ -771,6 +672,7 @@ export class JATSExporter {
771
672
  : ['table-wrap-foot', 0],
772
673
  contributor: (node) =>
773
674
  this.buildContributorElement(node as ContributorNode),
675
+ bio: () => ['bio', 0],
774
676
  affiliation: (node) =>
775
677
  this.buildAffiliationElement(node as AffiliationNode),
776
678
  attribution: () => ['attrib', 0],
@@ -805,7 +707,7 @@ export class JATSExporter {
805
707
  'mime-subtype',
806
708
  node.attrs.mimeSubType ?? ''
807
709
  )
808
- appendLabels($supplementaryMaterial, node)
710
+ this.appendLabels($supplementaryMaterial, node)
809
711
  this.appendCaption($supplementaryMaterial, node)
810
712
  return $supplementaryMaterial
811
713
  },
@@ -851,8 +753,7 @@ export class JATSExporter {
851
753
 
852
754
  const rid = rids[0]
853
755
  const labelTarget = this.labelTargets.get(rid)
854
- const isSupplement =
855
- labelTarget?.type === schema.nodes.supplement.name
756
+ const isSupplement = labelTarget?.type === schema.nodes.supplement.name
856
757
  const text =
857
758
  cross.attrs.label ||
858
759
  (isSupplement
@@ -904,13 +805,13 @@ export class JATSExporter {
904
805
  equation_element: (node) => {
905
806
  const $dispFormula = this.createElement('disp-formula')
906
807
  $dispFormula.setAttribute('id', normalizeID(node.attrs.id))
907
- appendLabels($dispFormula, node)
908
- processChildNodes($dispFormula, node, schema.nodes.equation)
808
+ this.appendLabels($dispFormula, node)
809
+ this.processChildNodes($dispFormula, node, schema.nodes.equation)
909
810
  return $dispFormula
910
811
  },
911
- figure: (node) => createGraphic(node),
812
+ figure: (node) => this.createGraphic(node),
912
813
  figure_element: (node) =>
913
- createFigureElement(node, node.type.schema.nodes.figure),
814
+ this.createFigureElement(node, node.type.schema.nodes.figure),
914
815
  footnote: (node) => {
915
816
  const attrs: Attrs = {}
916
817
 
@@ -1003,7 +904,7 @@ export class JATSExporter {
1003
904
  return $code
1004
905
  },
1005
906
  listing_element: (node) =>
1006
- createFigureElement(node, node.type.schema.nodes.listing),
907
+ this.createFigureElement(node, node.type.schema.nodes.listing),
1007
908
  manuscript: (node) => ['article', { id: normalizeID(node.attrs.id) }, 0],
1008
909
  missing_figure: () => {
1009
910
  const $graphic = this.createElement('graphic')
@@ -1044,11 +945,11 @@ export class JATSExporter {
1044
945
  quote_image: (node) => {
1045
946
  const img = node as QuoteImageNode
1046
947
  if (img.attrs.src) {
1047
- return createGraphic(node)
948
+ return this.createGraphic(node)
1048
949
  }
1049
950
  return ''
1050
951
  },
1051
- graphical_abstract_section: (node) => createAbstract(node),
952
+ graphical_abstract_section: (node) => this.createAbstract(node),
1052
953
  section: (node) => {
1053
954
  const attrs: { [key: string]: string } = {
1054
955
  id: normalizeID(node.attrs.id),
@@ -1060,13 +961,13 @@ export class JATSExporter {
1060
961
 
1061
962
  return ['sec', attrs, 0]
1062
963
  },
1063
- abstract: (node) => createAbstract(node),
964
+ abstract: (node) => this.createAbstract(node),
1064
965
  section_label: () => ['label', 0],
1065
966
  section_title: () => ['title', 0],
1066
967
  section_title_plain: () => ['title', 0],
1067
968
  table: (node) => ['table', { id: normalizeID(node.attrs.id) }, 0],
1068
969
  table_element: (node) => {
1069
- const $tableWrap = createTableElement(node)
970
+ const $tableWrap = this.createTableElement(node)
1070
971
  $tableWrap.setAttribute('position', 'anchor')
1071
972
  if (node.attrs.type) {
1072
973
  $tableWrap.setAttribute('content-type', node.attrs.type)
@@ -1120,134 +1021,93 @@ export class JATSExporter {
1120
1021
  }
1121
1022
 
1122
1023
  this.serializer = new DOMSerializer(nodes, marks)
1123
- const appendChildIfPresent = (
1124
- $parent: Element,
1125
- tagName: string,
1126
- textContent: string
1127
- ) => {
1128
- if (!textContent) {
1129
- return
1024
+ }
1025
+
1026
+ appendLabels($element: Element, node: ManuscriptNode) {
1027
+ if (this.labelTargets) {
1028
+ const target = this.labelTargets.get(node.attrs.id)
1029
+
1030
+ if (target) {
1031
+ const $label = this.createElement('label')
1032
+ $label.textContent = target.label
1033
+ $element.appendChild($label)
1130
1034
  }
1131
- const $element = this.createElement(tagName)
1132
- $element.textContent = textContent
1133
- $parent.appendChild($element)
1134
- }
1135
- const processChildNodes = (
1136
- $element: Element,
1137
- node: ManuscriptNode,
1138
- contentNodeType: ManuscriptNodeType
1139
- ) => {
1140
- node.forEach((childNode) => {
1141
- if (childNode.type === contentNodeType) {
1142
- if (childNode.attrs.id) {
1143
- $element.appendChild(this.serializeNode(childNode))
1144
- }
1145
- } else if (childNode.type === node.type.schema.nodes.paragraph) {
1146
- $element.appendChild(this.serializeNode(childNode))
1147
- } else if (childNode.type === node.type.schema.nodes.missing_figure) {
1148
- $element.appendChild(this.serializeNode(childNode))
1149
- }
1150
- })
1151
1035
  }
1152
- const createElement = (node: ManuscriptNode, nodeName: string) => {
1153
- const $element = this.createElement(nodeName)
1154
- $element.setAttribute('id', normalizeID(node.attrs.id))
1155
- return $element
1036
+ }
1037
+ appendAttributions($element: Element, node: ManuscriptNode) {
1038
+ if (node.attrs.attribution) {
1039
+ const $attrib = this.createElement('attrib')
1040
+ $attrib.textContent = node.attrs.attribution.literal
1041
+ $element.appendChild($attrib)
1156
1042
  }
1043
+ }
1157
1044
 
1158
- const appendLabels = ($element: Element, node: ManuscriptNode) => {
1159
- if (this.labelTargets) {
1160
- const target = this.labelTargets.get(node.attrs.id)
1045
+ appendTable($element: Element, node: ManuscriptNode) {
1046
+ const tableNode = this.getFirstChildOfType(schema.nodes.table, node)
1047
+ const colGroupNode = this.getFirstChildOfType(
1048
+ schema.nodes.table_colgroup,
1049
+ node
1050
+ )
1051
+ if (!tableNode) {
1052
+ return
1053
+ }
1054
+ const $table = this.serializeNode(tableNode)
1055
+ const $tbody = this.createElement('tbody')
1161
1056
 
1162
- if (target) {
1163
- const $label = this.createElement('label')
1164
- $label.textContent = target.label
1165
- $element.appendChild($label)
1166
- }
1167
- }
1057
+ while ($table.firstChild) {
1058
+ const $child = $table.firstChild
1059
+ $table.removeChild($child)
1060
+ $tbody.appendChild($child)
1168
1061
  }
1169
- const appendAttributions = ($element: Element, node: ManuscriptNode) => {
1170
- if (node.attrs.attribution) {
1171
- const $attrib = this.createElement('attrib')
1172
- $attrib.textContent = node.attrs.attribution.literal
1173
- $element.appendChild($attrib)
1174
- }
1062
+ $table.appendChild($tbody)
1063
+ this.normalizeTable($table)
1064
+ if (colGroupNode) {
1065
+ const $colGroup = this.serializeNode(colGroupNode)
1066
+ $table.insertBefore($colGroup, $table.firstChild)
1175
1067
  }
1176
1068
 
1177
- const appendTable = ($element: Element, node: ManuscriptNode) => {
1178
- const tableNode = this.getFirstChildOfType(schema.nodes.table, node)
1179
- const colGroupNode = this.getFirstChildOfType(
1180
- schema.nodes.table_colgroup,
1181
- node
1182
- )
1183
- if (!tableNode) {
1184
- return
1185
- }
1186
- const $table = this.serializeNode(tableNode)
1187
- const $tbody = this.createElement('tbody')
1188
-
1189
- while ($table.firstChild) {
1190
- const $child = $table.firstChild
1191
- $table.removeChild($child)
1192
- $tbody.appendChild($child)
1193
- }
1194
- $table.appendChild($tbody)
1195
- this.normalizeTable($table)
1196
- if (colGroupNode) {
1197
- const $colGroup = this.serializeNode(colGroupNode)
1198
- $table.insertBefore($colGroup, $table.firstChild)
1199
- }
1069
+ $element.appendChild($table)
1070
+ }
1200
1071
 
1201
- $element.appendChild($table)
1072
+ createBoxElement(node: ManuscriptNode) {
1073
+ const $boxedText = this.createElementWithID(node, 'boxed-text')
1074
+ if (node.attrs.type) {
1075
+ $boxedText.setAttribute('content-type', node.attrs.type)
1202
1076
  }
1203
- const createBoxElement = (node: ManuscriptNode) => {
1204
- const $boxedText = createElement(node, 'boxed-text')
1205
- if (node.attrs.type) {
1206
- $boxedText.setAttribute('content-type', node.attrs.type)
1207
- }
1208
- appendLabels($boxedText, node)
1209
- const child = node.firstChild
1210
- if (child?.type === schema.nodes.caption_title) {
1211
- this.appendCaption($boxedText, node)
1212
- }
1213
-
1214
- processChildNodes($boxedText, node, node.type.schema.nodes.section)
1215
- return $boxedText
1077
+ this.appendLabels($boxedText, node)
1078
+ const child = node.firstChild
1079
+ if (child?.type === schema.nodes.caption_title) {
1080
+ this.appendCaption($boxedText, node)
1216
1081
  }
1217
1082
 
1218
- const abstractTypeAttrs = (category: string) =>
1219
- category && category !== 'abstract' ? { 'abstract-type': category } : {}
1083
+ this.processChildNodes($boxedText, node, node.type.schema.nodes.section)
1084
+ return $boxedText
1085
+ }
1220
1086
 
1221
- const createAbstract = (node: ManuscriptNode): DOMOutputSpec => [
1222
- 'abstract',
1223
- abstractTypeAttrs(node.attrs.category),
1224
- 0,
1225
- ]
1087
+ abstractTypeAttrs(category: string) {
1088
+ return category && category !== 'abstract'
1089
+ ? { 'abstract-type': category }
1090
+ : {}
1091
+ }
1092
+
1093
+ createAbstract(node: ManuscriptNode): DOMOutputSpec {
1094
+ return ['abstract', this.abstractTypeAttrs(node.attrs.category), 0]
1095
+ }
1226
1096
 
1227
- const createTransAbstract = (node: ManuscriptNode): DOMOutputSpec => [
1097
+ createTransAbstract(node: ManuscriptNode): DOMOutputSpec {
1098
+ return [
1228
1099
  'trans-abstract',
1229
1100
  {
1230
1101
  [`${XML_NAMESPACE} lang`]: node.attrs.lang ?? '',
1231
- ...abstractTypeAttrs(node.attrs.category),
1102
+ ...this.abstractTypeAttrs(node.attrs.category),
1232
1103
  },
1233
1104
  0,
1234
1105
  ]
1106
+ }
1235
1107
 
1236
- const isChildOfNodeType = (
1237
- targetID: string,
1238
- type: NodeType,
1239
- descend = false
1240
- ) => {
1241
- const nodes = this.getChildrenOfType(type)
1242
- return nodes.some((node) => {
1243
- const result = findChildrenByAttr(
1244
- node,
1245
- (attrs) => attrs.id === targetID,
1246
- descend
1247
- )[0]
1248
- return !!result
1249
- })
1250
- }
1108
+ private createGraphic(node: ManuscriptNode) {
1109
+ const $graphic = this.createElement('graphic')
1110
+ $graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src)
1251
1111
 
1252
1112
  const findParentHeroImage = (targetID: string) => {
1253
1113
  const heroes = this.getChildrenOfType(schema.nodes.hero_image)
@@ -1257,122 +1117,118 @@ export class JATSExporter {
1257
1117
  )
1258
1118
  }
1259
1119
 
1260
- const createImage = (node: ManuscriptNode) => {
1261
- const graphicNode = node.content.firstChild
1262
- if (!graphicNode) {
1263
- return ''
1264
- }
1265
- const $graphic = createGraphic(graphicNode)
1266
- if (node.attrs.extLink) {
1267
- const $extLink = this.appendElement($graphic, 'ext-link')
1268
- $extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink)
1269
- }
1270
- this.appendCaption($graphic, node)
1271
- this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text)
1272
- this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc)
1273
- return $graphic
1274
- }
1275
-
1276
- const createGraphic = (node: ManuscriptNode) => {
1277
- const $graphic = this.createElement('graphic')
1278
- $graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src)
1279
-
1280
- const hero = findParentHeroImage(node.attrs.id)
1281
- if (hero) {
1282
- $graphic.setAttribute('content-type', hero.attrs.type || 'leading')
1283
- } else if (
1284
- !isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
1285
- node.attrs.type
1286
- ) {
1287
- $graphic.setAttribute('content-type', node.attrs.type)
1288
- }
1289
- return $graphic
1290
- }
1291
- const createFigureElement = (
1292
- node: ManuscriptNode,
1293
- contentNodeType: ManuscriptNodeType
1294
- ) => {
1295
- const $fig = createElement(node, 'fig')
1296
- const figNode = this.getFirstChildOfType(schema.nodes.figure, node)
1297
- const figType = figNode?.attrs.type
1298
- if (figType) {
1299
- $fig.setAttribute('fig-type', figType)
1300
- }
1301
- appendLabels($fig, node)
1302
- this.appendCaption($fig, node)
1303
- this.appendChildNodeOfType($fig, node, schema.nodes.alt_text)
1304
- this.appendChildNodeOfType($fig, node, schema.nodes.long_desc)
1305
- this.appendChildNodeOfType(
1306
- $fig,
1307
- node,
1308
- node.type.schema.nodes.footnotes_element
1309
- )
1310
- processChildNodes($fig, node, contentNodeType)
1311
- appendAttributions($fig, node)
1312
- if (isExecutableNodeType(node.type)) {
1313
- processExecutableNode(node, $fig)
1314
- }
1315
- moveAltTextAndLongDescToGraphics($fig)
1316
- return $fig
1120
+ const hero = findParentHeroImage(node.attrs.id)
1121
+ if (hero) {
1122
+ $graphic.setAttribute('content-type', hero.attrs.type || 'leading')
1123
+ } else if (
1124
+ !this.isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
1125
+ node.attrs.type
1126
+ ) {
1127
+ $graphic.setAttribute('content-type', node.attrs.type)
1317
1128
  }
1129
+ return $graphic
1130
+ }
1318
1131
 
1319
- const moveAltTextAndLongDescToGraphics = ($element: Element) => {
1320
- const $altText = $element.querySelector('alt-text')
1321
- const $longDesc = $element.querySelector('long-desc')
1322
- const $graphics = $element.querySelectorAll('graphic')
1132
+ createImage(node: ManuscriptNode) {
1133
+ const graphicNode = node.content.firstChild
1134
+ if (!graphicNode) {
1135
+ return ''
1136
+ }
1137
+ const $graphic = this.createGraphic(graphicNode)
1138
+ if (node.attrs.extLink) {
1139
+ const $extLink = this.appendElement($graphic, 'ext-link')
1140
+ $extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink)
1141
+ }
1142
+ this.appendCaption($graphic, node)
1143
+ this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text)
1144
+ this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc)
1145
+ return $graphic
1146
+ }
1323
1147
 
1324
- if ($graphics.length === 0) {
1325
- return
1326
- }
1148
+ createFigureElement(
1149
+ node: ManuscriptNode,
1150
+ contentNodeType: ManuscriptNodeType
1151
+ ) {
1152
+ const $fig = this.createElementWithID(node, 'fig')
1153
+ const figNode = this.getFirstChildOfType(schema.nodes.figure, node)
1154
+ const figType = figNode?.attrs.type
1155
+ if (figType) {
1156
+ $fig.setAttribute('fig-type', figType)
1157
+ }
1158
+ this.appendLabels($fig, node)
1159
+ this.appendCaption($fig, node)
1160
+ this.appendChildNodeOfType($fig, node, schema.nodes.alt_text)
1161
+ this.appendChildNodeOfType($fig, node, schema.nodes.long_desc)
1162
+ this.appendChildNodeOfType(
1163
+ $fig,
1164
+ node,
1165
+ node.type.schema.nodes.footnotes_element
1166
+ )
1167
+ this.processChildNodes($fig, node, contentNodeType)
1168
+ this.appendAttributions($fig, node)
1169
+ if (isExecutableNodeType(node.type)) {
1170
+ this.processExecutableNode(node, $fig)
1171
+ }
1172
+ this.moveAltTextAndLongDescToGraphics($fig)
1173
+ return $fig
1174
+ }
1327
1175
 
1328
- $graphics.forEach(($graphic) => {
1329
- if ($longDesc) {
1330
- $graphic.prepend($longDesc.cloneNode(true))
1331
- }
1332
- if ($altText) {
1333
- $graphic.prepend($altText.cloneNode(true))
1334
- }
1335
- })
1176
+ moveAltTextAndLongDescToGraphics($element: Element) {
1177
+ const $altText = $element.querySelector('alt-text')
1178
+ const $longDesc = $element.querySelector('long-desc')
1179
+ const $graphics = $element.querySelectorAll('graphic')
1336
1180
 
1337
- $altText?.remove()
1338
- $longDesc?.remove()
1339
- }
1340
-
1341
- const createTableElement = (node: ManuscriptNode) => {
1342
- const nodeName = 'table-wrap'
1343
- const $tableWrap = createElement(node, nodeName)
1344
- appendLabels($tableWrap, node)
1345
- this.appendCaption($tableWrap, node)
1346
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text)
1347
- this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc)
1348
- appendTable($tableWrap, node)
1349
- this.appendChildNodeOfType(
1350
- $tableWrap,
1351
- node,
1352
- node.type.schema.nodes.table_element_footer
1353
- )
1354
- if (isExecutableNodeType(node.type)) {
1355
- processExecutableNode(node, $tableWrap)
1181
+ if ($graphics.length === 0) {
1182
+ return
1183
+ }
1184
+
1185
+ $graphics.forEach(($graphic) => {
1186
+ if ($longDesc) {
1187
+ $graphic.prepend($longDesc.cloneNode(true))
1356
1188
  }
1357
- return $tableWrap
1189
+ if ($altText) {
1190
+ $graphic.prepend($altText.cloneNode(true))
1191
+ }
1192
+ })
1193
+
1194
+ $altText?.remove()
1195
+ $longDesc?.remove()
1196
+ }
1197
+
1198
+ createTableElement(node: ManuscriptNode) {
1199
+ const nodeName = 'table-wrap'
1200
+ const $tableWrap = this.createElementWithID(node, nodeName)
1201
+ this.appendLabels($tableWrap, node)
1202
+ this.appendCaption($tableWrap, node)
1203
+ this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text)
1204
+ this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc)
1205
+ this.appendTable($tableWrap, node)
1206
+ this.appendChildNodeOfType(
1207
+ $tableWrap,
1208
+ node,
1209
+ node.type.schema.nodes.table_element_footer
1210
+ )
1211
+ if (isExecutableNodeType(node.type)) {
1212
+ this.processExecutableNode(node, $tableWrap)
1358
1213
  }
1359
- const processExecutableNode = (node: ManuscriptNode, $element: Element) => {
1360
- const listingNode = this.getFirstChildOfType(schema.nodes.listing, node)
1214
+ return $tableWrap
1215
+ }
1216
+ processExecutableNode(node: ManuscriptNode, $element: Element) {
1217
+ const listingNode = this.getFirstChildOfType(schema.nodes.listing, node)
1361
1218
 
1362
- if (listingNode) {
1363
- const { contents, languageKey } = listingNode.attrs
1219
+ if (listingNode) {
1220
+ const { contents, languageKey } = listingNode.attrs
1364
1221
 
1365
- if (contents && languageKey) {
1366
- const $fig = this.createElement('fig')
1367
- $fig.setAttribute('specific-use', 'source')
1368
- $element.appendChild($fig)
1222
+ if (contents && languageKey) {
1223
+ const $fig = this.createElement('fig')
1224
+ $fig.setAttribute('specific-use', 'source')
1225
+ $element.appendChild($fig)
1369
1226
 
1370
- const $code = this.createElement('code')
1371
- $code.setAttribute('executable', 'true')
1372
- $code.setAttribute('language', languageKey)
1373
- $code.textContent = contents
1374
- $fig.appendChild($code)
1375
- }
1227
+ const $code = this.createElement('code')
1228
+ $code.setAttribute('executable', 'true')
1229
+ $code.setAttribute('language', languageKey)
1230
+ $code.textContent = contents
1231
+ $fig.appendChild($code)
1376
1232
  }
1377
1233
  }
1378
1234
  }
@@ -1398,11 +1254,6 @@ export class JATSExporter {
1398
1254
  }
1399
1255
  }
1400
1256
 
1401
- protected serializeNode = (node: ManuscriptNode) =>
1402
- this.serializer.serializeNode(node, {
1403
- document: this.document,
1404
- })
1405
-
1406
1257
  private buildContributorGroup = (contributors: ContributorNode[]) => {
1407
1258
  if (!contributors.length) {
1408
1259
  return
@@ -1552,6 +1403,11 @@ export class JATSExporter {
1552
1403
  })
1553
1404
  })
1554
1405
 
1406
+ const bio = this.getFirstChildOfType<BioNode>(schema.nodes.bio, contributor)
1407
+ if (bio?.childCount) {
1408
+ $contrib.appendChild(this.serializeNode(bio))
1409
+ }
1410
+
1555
1411
  return $contrib
1556
1412
  }
1557
1413
 
@@ -1717,18 +1573,6 @@ export class JATSExporter {
1717
1573
  this.serializeNode(group)
1718
1574
  )
1719
1575
 
1720
- private changeTag = ($node: Element, tag: string) => {
1721
- const $clone = this.createElement(tag)
1722
- for (const attr of $node.attributes) {
1723
- $clone.setAttributeNS(null, attr.name, attr.value)
1724
- }
1725
- while ($node.firstChild) {
1726
- $clone.appendChild($node.firstChild)
1727
- }
1728
- $node.replaceWith($clone)
1729
- return $clone
1730
- }
1731
-
1732
1576
  private normalizeTable = ($table: Node) => {
1733
1577
  let $tbody: Element | undefined
1734
1578
 
@@ -1827,18 +1671,6 @@ export class JATSExporter {
1827
1671
  }
1828
1672
  }
1829
1673
 
1830
- private fillEmptyElements(
1831
- $article: Element,
1832
- selector: string,
1833
- tagName = 'p'
1834
- ) {
1835
- const $empty = Array.from($article.querySelectorAll(selector)).filter(
1836
- ($el) => !$el.innerHTML
1837
- )
1838
- $empty.forEach(($element) =>
1839
- $element.appendChild(this.createElement(tagName))
1840
- )
1841
- }
1842
1674
  private addParagraphsToSections($article: Element) {
1843
1675
  const $sections = $article.querySelectorAll('sec, abstract')
1844
1676
  const TITLE_TAGS = new Set(['title', 'label', 'sec-meta'])