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