@manuscripts/transform 4.5.1 → 4.5.3
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.
- package/dist/cjs/jats/__tests__/data/section-categories.js +5 -5
- package/dist/cjs/jats/__tests__/jats-importer.test.js +19 -5
- package/dist/cjs/jats/exporter/jats-exporter.js +725 -910
- package/dist/cjs/jats/exporter/jats-versions.js +9 -9
- package/dist/cjs/jats/importer/jats-dom-parser.js +25 -24
- package/dist/cjs/jats/importer/jats-transformations.js +14 -30
- package/dist/cjs/lib/section-categories.js +31 -1
- package/dist/cjs/schema/index.js +3 -0
- package/dist/cjs/schema/migration/migration-scripts/4.5.3.js +37 -0
- package/dist/cjs/schema/migration/migration-scripts/index.js +2 -0
- package/dist/cjs/schema/nodes/abstract.js +51 -0
- package/dist/cjs/schema/nodes/abstracts.js +1 -1
- package/dist/cjs/schema/nodes/footnote.js +1 -0
- package/dist/cjs/schema/nodes/general_table_footnote.js +1 -0
- package/dist/cjs/transformer/node-names.js +1 -0
- package/dist/cjs/transformer/node-title.js +2 -0
- package/dist/cjs/version.js +1 -1
- package/dist/es/jats/__tests__/data/section-categories.js +5 -5
- package/dist/es/jats/__tests__/jats-importer.test.js +19 -5
- package/dist/es/jats/exporter/jats-exporter.js +728 -913
- package/dist/es/jats/exporter/jats-versions.js +7 -7
- package/dist/es/jats/importer/jats-dom-parser.js +25 -24
- package/dist/es/jats/importer/jats-transformations.js +14 -30
- package/dist/es/lib/section-categories.js +28 -0
- package/dist/es/schema/index.js +3 -0
- package/dist/es/schema/migration/migration-scripts/4.5.3.js +35 -0
- package/dist/es/schema/migration/migration-scripts/index.js +2 -0
- package/dist/es/schema/nodes/abstract.js +47 -0
- package/dist/es/schema/nodes/abstracts.js +1 -1
- package/dist/es/schema/nodes/footnote.js +1 -0
- package/dist/es/schema/nodes/general_table_footnote.js +1 -0
- package/dist/es/transformer/node-names.js +1 -0
- package/dist/es/transformer/node-title.js +2 -0
- package/dist/es/version.js +1 -1
- package/dist/types/jats/exporter/jats-exporter.d.ts +31 -27
- package/dist/types/jats/exporter/jats-versions.d.ts +4 -4
- package/dist/types/jats/importer/jats-dom-parser.d.ts +0 -1
- package/dist/types/lib/section-categories.d.ts +3 -0
- package/dist/types/schema/index.d.ts +1 -0
- package/dist/types/schema/migration/migration-scripts/4.5.3.d.ts +8 -0
- package/dist/types/schema/migration/migration-scripts/index.d.ts +2 -1
- package/dist/types/schema/nodes/abstract.d.ts +26 -0
- package/dist/types/schema/types.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/jats/__tests__/__snapshots__/jats-exporter.test.ts.snap +3 -3
- package/src/jats/__tests__/__snapshots__/jats-importer.test.ts.snap +86 -132
- package/src/jats/__tests__/__snapshots__/jats-roundtrip.test.ts.snap +3 -3
- package/src/jats/__tests__/data/section-categories.ts +5 -5
- package/src/jats/__tests__/jats-importer.test.ts +22 -6
- package/src/jats/exporter/jats-exporter.ts +855 -1126
- package/src/jats/exporter/jats-versions.ts +11 -11
- package/src/jats/importer/jats-dom-parser.ts +34 -34
- package/src/jats/importer/jats-transformations.ts +21 -37
- package/src/lib/section-categories.ts +36 -0
- package/src/schema/index.ts +3 -0
- package/src/schema/migration/migration-scripts/4.5.3.ts +57 -0
- package/src/schema/migration/migration-scripts/index.ts +2 -0
- package/src/schema/nodes/abstract.ts +66 -0
- package/src/schema/nodes/abstracts.ts +2 -1
- package/src/schema/nodes/footnote.ts +1 -0
- package/src/schema/nodes/general_table_footnote.ts +1 -0
- package/src/schema/types.ts +1 -0
- package/src/transformer/node-names.ts +1 -0
- package/src/transformer/node-title.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -15,25 +15,21 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import * as Citeproc from 'citeproc'
|
|
18
|
-
import {
|
|
19
|
-
DOMOutputSpec,
|
|
20
|
-
DOMParser as ProsemirrorDOMParser,
|
|
21
|
-
DOMSerializer,
|
|
22
|
-
type NodeType,
|
|
23
|
-
} from 'prosemirror-model'
|
|
18
|
+
import { DOMOutputSpec, DOMSerializer, type NodeType } from 'prosemirror-model'
|
|
24
19
|
import { findChildrenByAttr, findChildrenByType } from 'prosemirror-utils'
|
|
25
20
|
import serializeToXML from 'w3c-xmlserializer'
|
|
26
21
|
|
|
27
22
|
import { buildCiteprocCitation } from '../../lib/citeproc'
|
|
28
23
|
import { CreditRoleUrls } from '../../lib/credit-roles'
|
|
29
24
|
import { generateFootnoteLabels } from '../../lib/footnotes'
|
|
30
|
-
import {
|
|
25
|
+
import { FOOTNOTE_SECTION_CATEGORY_IDS } from '../../lib/section-categories'
|
|
31
26
|
import {
|
|
32
27
|
sanitizeXmlString,
|
|
33
28
|
XLINK_NAMESPACE,
|
|
34
29
|
XML_NAMESPACE,
|
|
35
30
|
} from '../../lib/xml'
|
|
36
31
|
import {
|
|
32
|
+
ActualManuscriptNode,
|
|
37
33
|
AffiliationNode,
|
|
38
34
|
AuthorNotesNode,
|
|
39
35
|
AwardNode,
|
|
@@ -53,13 +49,11 @@ import {
|
|
|
53
49
|
ParagraphNode,
|
|
54
50
|
QuoteImageNode,
|
|
55
51
|
schema,
|
|
56
|
-
TableElementFooterNode,
|
|
57
|
-
TableElementNode,
|
|
58
52
|
} from '../../schema'
|
|
59
|
-
import { isExecutableNodeType
|
|
53
|
+
import { isExecutableNodeType } from '../../transformer'
|
|
60
54
|
import { IDGenerator } from '../types'
|
|
61
55
|
import { initJats, jatsVariableWrapper } from './citeproc'
|
|
62
|
-
import {
|
|
56
|
+
import { selectVersionIDs, Version } from './jats-versions'
|
|
63
57
|
import { buildTargets, Target } from './labels'
|
|
64
58
|
|
|
65
59
|
interface Attrs {
|
|
@@ -74,24 +68,6 @@ type MarkSpecs = {
|
|
|
74
68
|
|
|
75
69
|
const normalizeID = (id: string) => id.replace(/:/g, '_')
|
|
76
70
|
|
|
77
|
-
const parser = ProsemirrorDOMParser.fromSchema(schema)
|
|
78
|
-
// siblings from https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/article-meta.html
|
|
79
|
-
const insertAbstractNode = (front: Element, abstractNode: Element) => {
|
|
80
|
-
const articleMeta = front.querySelector(':scope > article-meta')
|
|
81
|
-
if (!articleMeta) {
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
const insertBeforeElement = articleMeta.querySelector(
|
|
85
|
-
'kwd-group, funding-group, support-group, conference, counts, custom-meta-group'
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
if (insertBeforeElement) {
|
|
89
|
-
articleMeta.insertBefore(abstractNode, insertBeforeElement)
|
|
90
|
-
} else {
|
|
91
|
-
articleMeta.appendChild(abstractNode)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
71
|
export const createCounter = () => {
|
|
96
72
|
const counts = new Map<string, number>()
|
|
97
73
|
|
|
@@ -105,7 +81,7 @@ export const createCounter = () => {
|
|
|
105
81
|
}
|
|
106
82
|
}
|
|
107
83
|
|
|
108
|
-
const
|
|
84
|
+
const createDefaultIDGenerator = (): IDGenerator => {
|
|
109
85
|
const counter = createCounter()
|
|
110
86
|
|
|
111
87
|
return async (element: Element) => {
|
|
@@ -129,6 +105,7 @@ const chooseRefType = (type: ManuscriptNodeType): string | undefined => {
|
|
|
129
105
|
return 'table'
|
|
130
106
|
|
|
131
107
|
case schema.nodes.section:
|
|
108
|
+
case schema.nodes.abstract:
|
|
132
109
|
return 'sec'
|
|
133
110
|
|
|
134
111
|
case schema.nodes.equation:
|
|
@@ -139,10 +116,6 @@ const chooseRefType = (type: ManuscriptNodeType): string | undefined => {
|
|
|
139
116
|
return 'supplementary-material'
|
|
140
117
|
}
|
|
141
118
|
}
|
|
142
|
-
|
|
143
|
-
const sortContributors = (a: ContributorNode, b: ContributorNode) =>
|
|
144
|
-
Number(a.attrs.priority) - Number(b.attrs.priority)
|
|
145
|
-
|
|
146
119
|
export type CSLOptions = {
|
|
147
120
|
style: string
|
|
148
121
|
locale: string
|
|
@@ -157,10 +130,11 @@ export class JATSExporter {
|
|
|
157
130
|
protected serializer: DOMSerializer
|
|
158
131
|
protected labelTargets: Map<string, Target>
|
|
159
132
|
protected footnoteLabels: Map<string, string>
|
|
160
|
-
protected manuscriptNode:
|
|
133
|
+
protected manuscriptNode: ActualManuscriptNode
|
|
161
134
|
private engine: Citeproc.Engine
|
|
162
135
|
private renderedCitations: Map<string, string>
|
|
163
136
|
private nodesMap: Map<NodeType, ManuscriptNode[]> = new Map()
|
|
137
|
+
private contributorLabels: Map<string, number> = new Map()
|
|
164
138
|
|
|
165
139
|
private populateNodesMap = () => {
|
|
166
140
|
this.manuscriptNode.descendants((node) => {
|
|
@@ -189,101 +163,103 @@ export class JATSExporter {
|
|
|
189
163
|
}
|
|
190
164
|
|
|
191
165
|
public serializeToJATS = async (
|
|
192
|
-
manuscriptNode:
|
|
166
|
+
manuscriptNode: ActualManuscriptNode,
|
|
193
167
|
options: ExportOptions
|
|
194
168
|
): Promise<string> => {
|
|
195
169
|
this.manuscriptNode = manuscriptNode
|
|
196
170
|
this.populateNodesMap()
|
|
197
171
|
this.initCiteprocEngine(options)
|
|
198
172
|
this.createSerializer()
|
|
199
|
-
const
|
|
173
|
+
const versionIDs = selectVersionIDs(options.version ?? '1.2')
|
|
200
174
|
|
|
201
175
|
this.document = document.implementation.createDocument(
|
|
202
176
|
null,
|
|
203
177
|
'article',
|
|
204
178
|
document.implementation.createDocumentType(
|
|
205
179
|
'article',
|
|
206
|
-
|
|
207
|
-
|
|
180
|
+
versionIDs.publicID,
|
|
181
|
+
versionIDs.systemID
|
|
208
182
|
)
|
|
209
183
|
)
|
|
210
184
|
|
|
211
|
-
const article = this.document.documentElement
|
|
185
|
+
const $article = this.document.documentElement
|
|
212
186
|
|
|
213
|
-
article.setAttributeNS(
|
|
187
|
+
$article.setAttributeNS(
|
|
214
188
|
'http://www.w3.org/2000/xmlns/',
|
|
215
189
|
'xmlns:xlink',
|
|
216
190
|
XLINK_NAMESPACE
|
|
217
191
|
)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
192
|
+
this.labelTargets = buildTargets(
|
|
193
|
+
manuscriptNode.descendants.bind(manuscriptNode)
|
|
194
|
+
)
|
|
195
|
+
this.footnoteLabels = generateFootnoteLabels(manuscriptNode)
|
|
196
|
+
|
|
197
|
+
const $body = this.buildBody()
|
|
198
|
+
const $back = this.buildBack()
|
|
199
|
+
this.routeBackmatterSections($body, $back)
|
|
200
|
+
const $coiStatement = this.extractCoiStatement($back)
|
|
201
|
+
$back.appendChild(this.buildRefList())
|
|
202
|
+
|
|
203
|
+
const $front = this.buildFront($coiStatement)
|
|
204
|
+
$article.appendChild($front)
|
|
205
|
+
$article.setAttribute(
|
|
221
206
|
'article-type',
|
|
222
207
|
manuscriptNode.attrs.articleType || 'other'
|
|
223
208
|
)
|
|
224
|
-
article.setAttributeNS(
|
|
209
|
+
$article.setAttributeNS(
|
|
225
210
|
XML_NAMESPACE,
|
|
226
211
|
'lang',
|
|
227
212
|
manuscriptNode.attrs.primaryLanguageCode || 'en'
|
|
228
213
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.unwrapBody(body)
|
|
240
|
-
this.moveAbstracts(front, body)
|
|
241
|
-
this.removeBackContainer(body)
|
|
242
|
-
this.updateFootnoteTypes(front, back)
|
|
243
|
-
this.fillEmptyTableFooters(article)
|
|
244
|
-
this.fillEmptyFootnotes(article)
|
|
245
|
-
this.fillEmptyListItem(article)
|
|
246
|
-
this.moveAwards(front, body)
|
|
247
|
-
this.moveFloatsGroup(article)
|
|
214
|
+
$article.appendChild($body)
|
|
215
|
+
$article.appendChild($back)
|
|
216
|
+
this.addParagraphsToSections($article)
|
|
217
|
+
this.fillEmptyTableFooters($article)
|
|
218
|
+
this.fillEmptyFootnotes($article)
|
|
219
|
+
this.fillEmptyListItem($article)
|
|
220
|
+
const $floatsGroup = this.buildFloatsGroup()
|
|
221
|
+
if ($floatsGroup) {
|
|
222
|
+
$article.appendChild($floatsGroup)
|
|
223
|
+
}
|
|
248
224
|
await this.rewriteIDs()
|
|
249
225
|
return serializeToXML(this.document)
|
|
250
226
|
}
|
|
251
227
|
|
|
252
228
|
private appendChildNodeOfType = (
|
|
253
|
-
element:
|
|
229
|
+
$element: Element,
|
|
254
230
|
node: ManuscriptNode,
|
|
255
231
|
type: ManuscriptNodeType
|
|
256
232
|
) => {
|
|
257
233
|
const childNode = this.getFirstChildOfType(type, node)
|
|
258
234
|
if (childNode) {
|
|
259
|
-
element.appendChild(this.serializeNode(childNode))
|
|
235
|
+
$element.appendChild(this.serializeNode(childNode))
|
|
260
236
|
}
|
|
261
237
|
}
|
|
262
238
|
|
|
263
239
|
private appendChildrenNodeOfType = (
|
|
264
|
-
element:
|
|
240
|
+
$element: Element,
|
|
265
241
|
node: ManuscriptNode,
|
|
266
242
|
type: ManuscriptNodeType
|
|
267
243
|
) => {
|
|
268
244
|
const children = this.getChildrenOfType(type, node)
|
|
269
245
|
children.map((childNode) =>
|
|
270
|
-
element.appendChild(this.serializeNode(childNode))
|
|
246
|
+
$element.appendChild(this.serializeNode(childNode))
|
|
271
247
|
)
|
|
272
248
|
}
|
|
273
249
|
|
|
274
|
-
protected appendCaption = (element:
|
|
275
|
-
const caption = this.createElement('caption')
|
|
276
|
-
this.appendChildNodeOfType(caption, node, schema.nodes.caption_title)
|
|
250
|
+
protected appendCaption = ($element: Element, node: ManuscriptNode) => {
|
|
251
|
+
const $caption = this.createElement('caption')
|
|
252
|
+
this.appendChildNodeOfType($caption, node, schema.nodes.caption_title)
|
|
277
253
|
const captionNode = this.getChildrenOfType(schema.nodes.caption, node)[0]
|
|
278
254
|
if (captionNode && captionNode.content.size > 2) {
|
|
279
255
|
this.appendChildrenNodeOfType(
|
|
280
|
-
caption,
|
|
256
|
+
$caption,
|
|
281
257
|
captionNode,
|
|
282
258
|
schema.nodes.text_block
|
|
283
259
|
)
|
|
284
260
|
}
|
|
285
|
-
if (caption.children.length > 0) {
|
|
286
|
-
element.appendChild(caption)
|
|
261
|
+
if ($caption.children.length > 0) {
|
|
262
|
+
$element.appendChild($caption)
|
|
287
263
|
}
|
|
288
264
|
}
|
|
289
265
|
private initCiteprocEngine = (options: ExportOptions) => {
|
|
@@ -333,43 +309,36 @@ export class JATSExporter {
|
|
|
333
309
|
this.renderedCitations = new Map(output.map((i) => [i[0], i[2]]))
|
|
334
310
|
}
|
|
335
311
|
|
|
336
|
-
private nodeFromJATS = (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!JATSFragment.length) {
|
|
312
|
+
private nodeFromJATS = (fragment: string) => {
|
|
313
|
+
fragment = fragment.trim().replace(' ', ' ')
|
|
314
|
+
if (!fragment.length) {
|
|
341
315
|
return null
|
|
342
316
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
template.innerHTML = JATSFragment
|
|
347
|
-
|
|
348
|
-
return template.firstChild
|
|
317
|
+
const $template = this.createElement('template')
|
|
318
|
+
$template.innerHTML = fragment
|
|
319
|
+
return $template.firstChild
|
|
349
320
|
}
|
|
350
321
|
|
|
351
322
|
protected rewriteIDs = async (
|
|
352
|
-
generator: IDGenerator =
|
|
323
|
+
generator: IDGenerator = createDefaultIDGenerator()
|
|
353
324
|
) => {
|
|
354
325
|
const ids = new Map<string, string | null>()
|
|
355
326
|
|
|
356
|
-
for (const element of this.document.querySelectorAll('[id]')) {
|
|
357
|
-
const oldID = element.getAttribute('id')
|
|
358
|
-
const newID = await generator(element)
|
|
359
|
-
|
|
327
|
+
for (const $element of this.document.querySelectorAll('[id]')) {
|
|
328
|
+
const oldID = $element.getAttribute('id')
|
|
329
|
+
const newID = await generator($element)
|
|
360
330
|
if (newID) {
|
|
361
|
-
element.setAttribute('id', newID)
|
|
331
|
+
$element.setAttribute('id', newID)
|
|
362
332
|
} else {
|
|
363
|
-
element.removeAttribute('id')
|
|
333
|
+
$element.removeAttribute('id')
|
|
364
334
|
}
|
|
365
|
-
|
|
366
335
|
if (oldID) {
|
|
367
336
|
ids.set(oldID, newID)
|
|
368
337
|
}
|
|
369
338
|
}
|
|
370
339
|
|
|
371
|
-
for (const node of this.document.querySelectorAll('[rid]')) {
|
|
372
|
-
const rids = node.getAttribute('rid')
|
|
340
|
+
for (const $node of this.document.querySelectorAll('[rid]')) {
|
|
341
|
+
const rids = $node.getAttribute('rid')
|
|
373
342
|
|
|
374
343
|
if (rids) {
|
|
375
344
|
const newRids = rids
|
|
@@ -379,198 +348,151 @@ export class JATSExporter {
|
|
|
379
348
|
.filter(Boolean)
|
|
380
349
|
|
|
381
350
|
if (newRids.length) {
|
|
382
|
-
node.setAttribute('rid', newRids.join(' '))
|
|
351
|
+
$node.setAttribute('rid', newRids.join(' '))
|
|
383
352
|
}
|
|
384
353
|
}
|
|
385
354
|
}
|
|
386
355
|
}
|
|
387
356
|
|
|
388
|
-
protected
|
|
389
|
-
const
|
|
357
|
+
protected buildFront = ($coiStatement?: Element) => {
|
|
358
|
+
const $front = this.createElement('front')
|
|
359
|
+
const $articleMeta = this.createElement('article-meta')
|
|
360
|
+
$front.appendChild($articleMeta)
|
|
390
361
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
362
|
+
const contributors = this.getChildrenOfType<ContributorNode>(
|
|
363
|
+
schema.nodes.contributor
|
|
364
|
+
).sort((a, b) => Number(a.attrs.priority) - Number(b.attrs.priority))
|
|
365
|
+
this.contributorLabels = this.computeContributorLabels(contributors)
|
|
366
|
+
const $frontNodes = [
|
|
367
|
+
this.buildArticleID(),
|
|
368
|
+
this.buildTitleGroup(),
|
|
369
|
+
this.buildContributorGroup(contributors),
|
|
370
|
+
this.buildAuthorNotes($coiStatement),
|
|
371
|
+
this.buildSupplements(),
|
|
372
|
+
this.buildHistory(),
|
|
373
|
+
this.buildSelfUris(),
|
|
374
|
+
this.buildAbstracts(),
|
|
375
|
+
this.buildKeywords(),
|
|
376
|
+
this.buildAwards(),
|
|
377
|
+
this.buildCounts(),
|
|
378
|
+
]
|
|
396
379
|
|
|
397
|
-
|
|
380
|
+
$frontNodes.forEach(($nodes) => this.appendChildren($articleMeta, $nodes))
|
|
381
|
+
return $front
|
|
382
|
+
}
|
|
398
383
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
384
|
+
private appendChildren = (
|
|
385
|
+
$parent: Element,
|
|
386
|
+
$elements?: Node | Node[] | null
|
|
387
|
+
) => {
|
|
388
|
+
if (!$elements) {
|
|
389
|
+
return
|
|
402
390
|
}
|
|
391
|
+
const list = Array.isArray($elements) ? $elements : [$elements]
|
|
392
|
+
list.forEach(($element) => $parent.appendChild($element))
|
|
403
393
|
}
|
|
404
394
|
|
|
405
|
-
|
|
406
|
-
// https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/front.html
|
|
407
|
-
const front = this.createElement('front')
|
|
408
|
-
|
|
409
|
-
// https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/article-meta.html
|
|
410
|
-
const articleMeta = this.createElement('article-meta')
|
|
411
|
-
front.appendChild(articleMeta)
|
|
412
|
-
|
|
395
|
+
private buildArticleID = (): Element | undefined => {
|
|
413
396
|
if (this.manuscriptNode.attrs.doi) {
|
|
414
|
-
const articleID = this.createElement('article-id')
|
|
415
|
-
articleID.setAttribute('pub-id-type', 'doi')
|
|
416
|
-
|
|
417
|
-
articleID
|
|
418
|
-
articleMeta.appendChild(articleID)
|
|
397
|
+
const $articleID = this.createElement('article-id')
|
|
398
|
+
$articleID.setAttribute('pub-id-type', 'doi')
|
|
399
|
+
$articleID.textContent = this.manuscriptNode.attrs.doi
|
|
400
|
+
return $articleID
|
|
419
401
|
}
|
|
402
|
+
}
|
|
420
403
|
|
|
421
|
-
|
|
404
|
+
private buildTitleGroup = (): Element => {
|
|
405
|
+
const $titleGroup = this.createElement('title-group')
|
|
422
406
|
|
|
423
407
|
const titleNode = this.getFirstChildOfType(schema.nodes.title)
|
|
424
|
-
|
|
425
408
|
if (titleNode) {
|
|
426
|
-
|
|
427
|
-
this.setTitleContent(element, titleNode.textContent)
|
|
428
|
-
titleGroup.appendChild(element)
|
|
409
|
+
$titleGroup.appendChild(this.serializeNode(titleNode))
|
|
429
410
|
}
|
|
430
411
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
this.setTitleContent(element, subtitleNode.textContent)
|
|
435
|
-
titleGroup.appendChild(element)
|
|
436
|
-
})
|
|
412
|
+
this.getChildrenOfType(schema.nodes.subtitle).forEach((node) =>
|
|
413
|
+
$titleGroup.appendChild(this.serializeNode(node))
|
|
414
|
+
)
|
|
437
415
|
|
|
438
|
-
|
|
416
|
+
this.getChildrenOfType(schema.nodes.alt_title).forEach((node) =>
|
|
417
|
+
$titleGroup.appendChild(this.serializeNode(node))
|
|
418
|
+
)
|
|
439
419
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
element.setAttribute('alt-title-type', titleNode.attrs.type)
|
|
443
|
-
this.setTitleContent(element, titleNode.textContent)
|
|
444
|
-
titleGroup.appendChild(element)
|
|
445
|
-
})
|
|
420
|
+
return $titleGroup
|
|
421
|
+
}
|
|
446
422
|
|
|
447
|
-
|
|
448
|
-
this.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
supplementsNodes.forEach((node) => {
|
|
452
|
-
const supplementaryMaterial = this.createElement('supplementary-material')
|
|
453
|
-
supplementaryMaterial.setAttribute('id', normalizeID(node.attrs.id))
|
|
454
|
-
supplementaryMaterial.setAttributeNS(
|
|
455
|
-
XLINK_NAMESPACE,
|
|
456
|
-
'href',
|
|
457
|
-
node.attrs.href ?? ''
|
|
458
|
-
)
|
|
459
|
-
supplementaryMaterial.setAttribute('mimetype', node.attrs.mimeType ?? '')
|
|
460
|
-
supplementaryMaterial.setAttribute(
|
|
461
|
-
'mime-subtype',
|
|
462
|
-
node.attrs.mimeSubType ?? ''
|
|
463
|
-
)
|
|
464
|
-
this.appendCaption(supplementaryMaterial, node)
|
|
423
|
+
private buildSupplements = () =>
|
|
424
|
+
this.getChildrenOfType(schema.nodes.supplement).map((node) =>
|
|
425
|
+
this.serializeNode(node)
|
|
426
|
+
)
|
|
465
427
|
|
|
466
|
-
|
|
428
|
+
private buildHistory = () => {
|
|
429
|
+
const $history = this.createElement('history')
|
|
430
|
+
const dates = new Map([
|
|
431
|
+
['accepted', this.manuscriptNode.attrs.acceptanceDate],
|
|
432
|
+
['corrected', this.manuscriptNode.attrs.correctionDate],
|
|
433
|
+
['retracted', this.manuscriptNode.attrs.retractionDate],
|
|
434
|
+
['received', this.manuscriptNode.attrs.receiveDate],
|
|
435
|
+
['rev-recd', this.manuscriptNode.attrs.revisionReceiveDate],
|
|
436
|
+
['rev-request', this.manuscriptNode.attrs.revisionRequestDate],
|
|
437
|
+
])
|
|
438
|
+
|
|
439
|
+
dates.forEach((val, key) => {
|
|
440
|
+
if (val) {
|
|
441
|
+
$history.appendChild(this.buildDateElement(val, key))
|
|
442
|
+
}
|
|
467
443
|
})
|
|
468
444
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (this.manuscriptNode.attrs.acceptanceDate) {
|
|
473
|
-
const date = this.buildDateElement(
|
|
474
|
-
this.manuscriptNode.attrs.acceptanceDate,
|
|
475
|
-
'accepted'
|
|
476
|
-
)
|
|
477
|
-
history.appendChild(date)
|
|
478
|
-
}
|
|
479
|
-
if (this.manuscriptNode.attrs.correctionDate) {
|
|
480
|
-
const date = this.buildDateElement(
|
|
481
|
-
this.manuscriptNode.attrs.correctionDate,
|
|
482
|
-
'corrected'
|
|
483
|
-
)
|
|
484
|
-
history.appendChild(date)
|
|
485
|
-
}
|
|
486
|
-
if (this.manuscriptNode.attrs.retractionDate) {
|
|
487
|
-
const date = this.buildDateElement(
|
|
488
|
-
this.manuscriptNode.attrs.retractionDate,
|
|
489
|
-
'retracted'
|
|
490
|
-
)
|
|
491
|
-
history.appendChild(date)
|
|
445
|
+
if ($history.childElementCount) {
|
|
446
|
+
return $history
|
|
492
447
|
}
|
|
493
|
-
|
|
494
|
-
const date = this.buildDateElement(
|
|
495
|
-
this.manuscriptNode.attrs.receiveDate,
|
|
496
|
-
'received'
|
|
497
|
-
)
|
|
498
|
-
history.appendChild(date)
|
|
499
|
-
}
|
|
500
|
-
if (this.manuscriptNode.attrs.revisionReceiveDate) {
|
|
501
|
-
const date = this.buildDateElement(
|
|
502
|
-
this.manuscriptNode.attrs.revisionReceiveDate,
|
|
503
|
-
'rev-recd'
|
|
504
|
-
)
|
|
505
|
-
history.appendChild(date)
|
|
506
|
-
}
|
|
507
|
-
if (this.manuscriptNode.attrs.revisionRequestDate) {
|
|
508
|
-
const date = this.buildDateElement(
|
|
509
|
-
this.manuscriptNode.attrs.revisionRequestDate,
|
|
510
|
-
'rev-request'
|
|
511
|
-
)
|
|
512
|
-
history.appendChild(date)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (history.childElementCount) {
|
|
516
|
-
articleMeta.appendChild(history)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
this.buildKeywords(articleMeta)
|
|
520
|
-
|
|
521
|
-
let countingElements = []
|
|
522
|
-
const figureCount = this.getChildrenOfType(schema.nodes.figure).length
|
|
523
|
-
countingElements.push(this.buildCountingElement('fig-count', figureCount))
|
|
524
|
-
|
|
525
|
-
const tableCount = this.getChildrenOfType(schema.nodes.table).length
|
|
526
|
-
countingElements.push(this.buildCountingElement('table-count', tableCount))
|
|
527
|
-
|
|
528
|
-
const equationCount = this.getChildrenOfType(
|
|
529
|
-
schema.nodes.equation_element
|
|
530
|
-
).length
|
|
531
|
-
countingElements.push(
|
|
532
|
-
this.buildCountingElement('equation-count', equationCount)
|
|
533
|
-
)
|
|
448
|
+
}
|
|
534
449
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
450
|
+
private buildCounts = () => {
|
|
451
|
+
const $counts = this.createElement('counts')
|
|
452
|
+
const counts = new Map([
|
|
453
|
+
['fig-count', this.getChildrenOfType(schema.nodes.figure).length],
|
|
454
|
+
['table-count', this.getChildrenOfType(schema.nodes.table).length],
|
|
455
|
+
[
|
|
456
|
+
'equation-count',
|
|
457
|
+
this.getChildrenOfType(schema.nodes.equation_element).length,
|
|
458
|
+
],
|
|
459
|
+
[
|
|
460
|
+
'ref-count',
|
|
461
|
+
this.getChildrenOfType(schema.nodes.bibliography_item).length,
|
|
462
|
+
],
|
|
463
|
+
['word-count', this.manuscriptNode.textContent.split(/\s+/).length],
|
|
464
|
+
])
|
|
541
465
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
466
|
+
counts.forEach((val, key) => {
|
|
467
|
+
if (val) {
|
|
468
|
+
$counts.appendChild(
|
|
469
|
+
this.createElement(key, undefined, { count: String(val) })
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
})
|
|
545
473
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const counts = this.createElement('counts')
|
|
549
|
-
counts.append(...countingElements)
|
|
550
|
-
articleMeta.append(counts)
|
|
474
|
+
if ($counts.childElementCount) {
|
|
475
|
+
return $counts
|
|
551
476
|
}
|
|
477
|
+
}
|
|
552
478
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const insertBeforeElements = articleMeta.querySelector(
|
|
560
|
-
'related-article, related-object, abstract, trans-abstract, kwd-group, funding-group, support-group, conference, counts, custom-meta-group'
|
|
561
|
-
)
|
|
562
|
-
insertBeforeElements
|
|
563
|
-
? articleMeta.insertBefore($selfUri, insertBeforeElements)
|
|
564
|
-
: articleMeta.appendChild($selfUri)
|
|
565
|
-
})
|
|
566
|
-
|
|
567
|
-
return front
|
|
479
|
+
private buildAwards = () => {
|
|
480
|
+
const awards = this.getFirstChildOfType(schema.nodes.awards)
|
|
481
|
+
if (!awards || !awards.childCount) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
return this.serializeNode(awards)
|
|
568
485
|
}
|
|
569
486
|
|
|
487
|
+
private buildSelfUris = () =>
|
|
488
|
+
this.getChildrenOfType(schema.nodes.attachment).map((attachment) =>
|
|
489
|
+
this.serializeNode(attachment)
|
|
490
|
+
)
|
|
491
|
+
|
|
570
492
|
protected buildDateElement = (timestamp: number, type: string) => {
|
|
571
|
-
const
|
|
493
|
+
const $date = this.createElement('date')
|
|
572
494
|
|
|
573
|
-
|
|
495
|
+
$date.setAttribute('date-type', type)
|
|
574
496
|
|
|
575
497
|
const date = new Date(timestamp * 1000) // s => ms
|
|
576
498
|
const lookup = {
|
|
@@ -580,160 +502,192 @@ export class JATSExporter {
|
|
|
580
502
|
}
|
|
581
503
|
|
|
582
504
|
for (const [key, value] of Object.entries(lookup).reverse()) {
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
dateElement.appendChild(el)
|
|
505
|
+
const $datePart = this.createElement(key, value)
|
|
506
|
+
$date.appendChild($datePart)
|
|
586
507
|
}
|
|
587
508
|
|
|
588
|
-
return
|
|
589
|
-
}
|
|
590
|
-
protected buildCountingElement = (
|
|
591
|
-
tagName: string,
|
|
592
|
-
count: number | undefined
|
|
593
|
-
) => {
|
|
594
|
-
if (count) {
|
|
595
|
-
const wordCount = this.createElement(tagName)
|
|
596
|
-
wordCount.setAttribute('count', String(count))
|
|
597
|
-
return wordCount
|
|
598
|
-
}
|
|
509
|
+
return $date
|
|
599
510
|
}
|
|
511
|
+
|
|
600
512
|
protected buildBody = () => {
|
|
601
|
-
const body = this.
|
|
602
|
-
this.
|
|
603
|
-
|
|
604
|
-
body.
|
|
513
|
+
const body = this.getFirstChildOfType(schema.nodes.body)
|
|
514
|
+
const $body = this.createElement('body')
|
|
515
|
+
body?.forEach((node) => {
|
|
516
|
+
$body.appendChild(this.serializeNode(node))
|
|
605
517
|
})
|
|
606
|
-
|
|
518
|
+
return $body
|
|
519
|
+
}
|
|
607
520
|
|
|
608
|
-
|
|
521
|
+
protected buildBack = () => {
|
|
522
|
+
const backmatter = this.getFirstChildOfType(schema.nodes.backmatter)
|
|
523
|
+
const $back = this.createElement('back')
|
|
524
|
+
backmatter?.forEach((node) => {
|
|
525
|
+
$back.appendChild(this.serializeNode(node))
|
|
526
|
+
})
|
|
527
|
+
return $back
|
|
609
528
|
}
|
|
610
529
|
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
530
|
+
private findSection = (category: string, ...$roots: Element[]) => {
|
|
531
|
+
for (const $root of $roots) {
|
|
532
|
+
const $match = $root.querySelector(`sec[sec-type="${category}"]`)
|
|
533
|
+
if ($match) {
|
|
534
|
+
return $match
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
614
538
|
|
|
615
|
-
|
|
616
|
-
|
|
539
|
+
private findSections = (category: string, ...$roots: Element[]) =>
|
|
540
|
+
$roots.flatMap(($root) =>
|
|
541
|
+
Array.from($root.querySelectorAll<Element>(`sec[sec-type="${category}"]`))
|
|
542
|
+
)
|
|
617
543
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
544
|
+
private routeBackmatterSections = ($body: Element, $back: Element) => {
|
|
545
|
+
const $footnoteSections = FOOTNOTE_SECTION_CATEGORY_IDS.flatMap(
|
|
546
|
+
(category) =>
|
|
547
|
+
this.findSections(category, $body, $back).map(($section) => ({
|
|
548
|
+
$section,
|
|
549
|
+
category,
|
|
550
|
+
}))
|
|
551
|
+
)
|
|
552
|
+
let $fnGroup: Element | undefined
|
|
553
|
+
if ($footnoteSections.length) {
|
|
554
|
+
$fnGroup = this.createElement('fn-group')
|
|
555
|
+
for (const { $section, category } of $footnoteSections) {
|
|
556
|
+
$fnGroup.appendChild(this.sectionToFootnote($section, category))
|
|
557
|
+
}
|
|
558
|
+
}
|
|
622
559
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (!previousParent.childElementCount) {
|
|
632
|
-
previousParent.remove()
|
|
633
|
-
}
|
|
560
|
+
const $appendices = this.findSections('appendices', $body, $back)
|
|
561
|
+
let $appGroup: Element | undefined
|
|
562
|
+
if ($appendices.length) {
|
|
563
|
+
$appGroup = this.createElement('app-group')
|
|
564
|
+
for (const $section of $appendices) {
|
|
565
|
+
const $app = this.createElement('app')
|
|
566
|
+
$app.appendChild($section)
|
|
567
|
+
$appGroup.appendChild($app)
|
|
634
568
|
}
|
|
635
569
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
570
|
+
|
|
571
|
+
const $ackSection = this.findSection('acknowledgements', $body, $back)
|
|
572
|
+
let $ack: Element | undefined
|
|
573
|
+
if ($ackSection) {
|
|
574
|
+
$ack = this.createElement('ack')
|
|
575
|
+
$ack.append(...$ackSection.childNodes)
|
|
576
|
+
$ackSection.remove()
|
|
640
577
|
}
|
|
641
578
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
579
|
+
const $availabilitySection = this.findSection('availability', $body, $back)
|
|
580
|
+
const $ethicsSection = this.findSection('ethics-statement', $body, $back)
|
|
581
|
+
|
|
582
|
+
const $prepend = [
|
|
583
|
+
$appGroup,
|
|
584
|
+
$ack,
|
|
585
|
+
$availabilitySection,
|
|
586
|
+
$ethicsSection,
|
|
587
|
+
$fnGroup,
|
|
588
|
+
].filter((el): el is Element => el != null)
|
|
589
|
+
|
|
590
|
+
if ($prepend.length) {
|
|
591
|
+
$back.prepend(...$prepend)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private extractCoiStatement = ($back: Element) => {
|
|
596
|
+
const $coiStatement = $back.querySelector<Element>(
|
|
597
|
+
'fn[fn-type="coi-statement"]'
|
|
598
|
+
)
|
|
599
|
+
if (!$coiStatement) {
|
|
600
|
+
return undefined
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const $fnGroup = $coiStatement.parentElement
|
|
604
|
+
$coiStatement.remove()
|
|
605
|
+
|
|
606
|
+
if ($fnGroup && !$fnGroup.hasChildNodes()) {
|
|
607
|
+
$fnGroup.remove()
|
|
654
608
|
}
|
|
655
609
|
|
|
656
|
-
return
|
|
610
|
+
return $coiStatement
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private buildRefList = () => {
|
|
614
|
+
const $refList = this.createElement('ref-list')
|
|
615
|
+
const [, bibliography] = this.engine.makeBibliography()
|
|
616
|
+
const parser = new DOMParser()
|
|
617
|
+
bibliography.forEach((item) => {
|
|
618
|
+
const fragment = `<template xmlns:xlink="${XLINK_NAMESPACE}">${sanitizeXmlString(
|
|
619
|
+
item
|
|
620
|
+
)}</template>`
|
|
621
|
+
const $ref = parser
|
|
622
|
+
.parseFromString(fragment, 'text/xml')
|
|
623
|
+
.querySelector('ref')
|
|
624
|
+
if ($ref) {
|
|
625
|
+
$refList.appendChild($ref)
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
return $refList
|
|
657
629
|
}
|
|
658
630
|
|
|
659
|
-
//@TODO: part of the export cleanup: check if we can use this elsewhere, maybe we can use strategy pattern for each element to have its own creator.
|
|
660
631
|
private createElement = (
|
|
661
632
|
tag: string,
|
|
662
633
|
content?: string,
|
|
663
634
|
attrs?: Record<string, string | undefined>
|
|
664
635
|
) => {
|
|
665
|
-
const
|
|
636
|
+
const $element = this.document.createElement(tag)
|
|
666
637
|
if (content) {
|
|
667
|
-
|
|
638
|
+
$element.textContent = content
|
|
668
639
|
}
|
|
669
640
|
if (attrs) {
|
|
670
641
|
Object.entries(attrs).forEach(([k, v]) => {
|
|
671
642
|
if (v) {
|
|
672
|
-
|
|
643
|
+
$element.setAttribute(k, v)
|
|
673
644
|
}
|
|
674
645
|
})
|
|
675
646
|
}
|
|
676
|
-
return
|
|
647
|
+
return $element
|
|
677
648
|
}
|
|
678
649
|
|
|
679
650
|
private appendElement = (
|
|
680
|
-
parent:
|
|
651
|
+
$parent: Element,
|
|
681
652
|
tag: string,
|
|
682
653
|
content?: string,
|
|
683
654
|
attrs?: Record<string, string | undefined>
|
|
684
655
|
) => {
|
|
685
|
-
const
|
|
686
|
-
parent.appendChild(
|
|
687
|
-
return
|
|
656
|
+
const $element = this.createElement(tag, content, attrs)
|
|
657
|
+
$parent.appendChild($element)
|
|
658
|
+
return $element
|
|
688
659
|
}
|
|
689
660
|
|
|
690
661
|
protected createSerializer = () => {
|
|
691
662
|
const nodes: NodeSpecs = {
|
|
692
|
-
trans_abstract: (node) =>
|
|
693
|
-
|
|
694
|
-
id: normalizeID(node.attrs.id),
|
|
695
|
-
}
|
|
696
|
-
if (node.attrs.lang) {
|
|
697
|
-
attrs[`${XML_NAMESPACE} lang`] = node.attrs.lang
|
|
698
|
-
}
|
|
699
|
-
if (node.attrs.category) {
|
|
700
|
-
attrs['sec-type'] = node.attrs.category
|
|
701
|
-
}
|
|
702
|
-
return ['trans-abstract', attrs, 0]
|
|
703
|
-
},
|
|
704
|
-
trans_graphical_abstract: (node) => {
|
|
705
|
-
const attrs: { [key: string]: string } = {
|
|
706
|
-
id: normalizeID(node.attrs.id),
|
|
707
|
-
}
|
|
708
|
-
if (node.attrs.lang) {
|
|
709
|
-
attrs[`${XML_NAMESPACE} lang`] = node.attrs.lang
|
|
710
|
-
}
|
|
711
|
-
if (node.attrs.category) {
|
|
712
|
-
attrs['sec-type'] = node.attrs.category
|
|
713
|
-
}
|
|
714
|
-
return ['trans-abstract', attrs, 0]
|
|
715
|
-
},
|
|
663
|
+
trans_abstract: (node) => createTransAbstract(node),
|
|
664
|
+
trans_graphical_abstract: (node) => createTransAbstract(node),
|
|
716
665
|
hero_image: () => '',
|
|
717
666
|
headshot_grid: () => ['p', { 'content-type': 'headshots' }, 0],
|
|
718
667
|
headshot_element: (node) => createImage(node),
|
|
719
668
|
headshot_image: () => '',
|
|
720
669
|
alt_text: (node) => {
|
|
721
670
|
if (node.textContent) {
|
|
722
|
-
const altText = this.createElement('alt-text')
|
|
723
|
-
altText.textContent = node.textContent
|
|
724
|
-
return altText
|
|
671
|
+
const $altText = this.createElement('alt-text')
|
|
672
|
+
$altText.textContent = node.textContent
|
|
673
|
+
return $altText
|
|
725
674
|
}
|
|
726
675
|
return ''
|
|
727
676
|
},
|
|
728
677
|
long_desc: (node) => {
|
|
729
678
|
if (node.textContent) {
|
|
730
|
-
const longDesc = this.createElement('long-desc')
|
|
731
|
-
longDesc.textContent = node.textContent
|
|
732
|
-
return longDesc
|
|
679
|
+
const $longDesc = this.createElement('long-desc')
|
|
680
|
+
$longDesc.textContent = node.textContent
|
|
681
|
+
return $longDesc
|
|
733
682
|
}
|
|
734
683
|
return ''
|
|
735
684
|
},
|
|
736
|
-
attachment: () =>
|
|
685
|
+
attachment: (node) => {
|
|
686
|
+
const $selfUri = this.createElement('self-uri')
|
|
687
|
+
$selfUri.setAttribute('content-type', node.attrs.type)
|
|
688
|
+
$selfUri.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.href)
|
|
689
|
+
return $selfUri
|
|
690
|
+
},
|
|
737
691
|
attachments: () => '',
|
|
738
692
|
image_element: (node) => createImage(node),
|
|
739
693
|
embed: (node) => {
|
|
@@ -741,52 +695,56 @@ export class JATSExporter {
|
|
|
741
695
|
if (!href) {
|
|
742
696
|
return ''
|
|
743
697
|
}
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
698
|
+
const $media = this.createElement('media')
|
|
699
|
+
$media.setAttribute('id', normalizeID(id))
|
|
700
|
+
$media.setAttributeNS(XLINK_NAMESPACE, 'show', 'embed')
|
|
701
|
+
$media.setAttributeNS(XLINK_NAMESPACE, 'href', href)
|
|
748
702
|
if (mimetype) {
|
|
749
|
-
|
|
703
|
+
$media.setAttribute('mimetype', node.attrs.mimetype)
|
|
750
704
|
}
|
|
751
705
|
if (mimeSubtype) {
|
|
752
|
-
|
|
706
|
+
$media.setAttribute('mime-subtype', node.attrs.mimeSubtype)
|
|
753
707
|
}
|
|
754
|
-
appendLabels(
|
|
755
|
-
this.appendChildNodeOfType(
|
|
756
|
-
this.appendChildNodeOfType(
|
|
757
|
-
this.appendCaption(
|
|
758
|
-
return
|
|
708
|
+
appendLabels($media, node)
|
|
709
|
+
this.appendChildNodeOfType($media, node, schema.nodes.alt_text)
|
|
710
|
+
this.appendChildNodeOfType($media, node, schema.nodes.long_desc)
|
|
711
|
+
this.appendCaption($media, node)
|
|
712
|
+
return $media
|
|
759
713
|
},
|
|
760
714
|
awards: () => ['funding-group', 0],
|
|
761
715
|
award: (node) => {
|
|
762
716
|
const awardGroup = node as AwardNode
|
|
763
|
-
const
|
|
764
|
-
|
|
717
|
+
const $awardGroup = this.createElement('award-group')
|
|
718
|
+
$awardGroup.setAttribute('id', normalizeID(awardGroup.attrs.id))
|
|
765
719
|
appendChildIfPresent(
|
|
766
|
-
|
|
720
|
+
$awardGroup,
|
|
767
721
|
'funding-source',
|
|
768
722
|
awardGroup.attrs.source
|
|
769
723
|
)
|
|
770
724
|
awardGroup.attrs.code
|
|
771
725
|
?.split(';')
|
|
772
726
|
.forEach((code) =>
|
|
773
|
-
appendChildIfPresent(
|
|
727
|
+
appendChildIfPresent($awardGroup, 'award-id', code)
|
|
774
728
|
)
|
|
775
729
|
appendChildIfPresent(
|
|
776
|
-
|
|
730
|
+
$awardGroup,
|
|
777
731
|
'principal-award-recipient',
|
|
778
732
|
awardGroup.attrs.recipient
|
|
779
733
|
)
|
|
780
734
|
|
|
781
|
-
return
|
|
735
|
+
return $awardGroup
|
|
782
736
|
},
|
|
783
737
|
box_element: (node) => createBoxElement(node),
|
|
784
738
|
author_notes: () => '',
|
|
785
739
|
corresp: () => '',
|
|
786
|
-
title: () => '',
|
|
787
|
-
alt_title: () =>
|
|
740
|
+
title: () => ['article-title', 0],
|
|
741
|
+
alt_title: (node) => [
|
|
742
|
+
'alt-title',
|
|
743
|
+
{ 'alt-title-type': node.attrs.type },
|
|
744
|
+
0,
|
|
745
|
+
],
|
|
788
746
|
alt_titles: () => '',
|
|
789
|
-
subtitle: () => '',
|
|
747
|
+
subtitle: () => ['subtitle', 0],
|
|
790
748
|
subtitles: () => '',
|
|
791
749
|
text_block: (node) => nodes.paragraph(node),
|
|
792
750
|
affiliations: () => '',
|
|
@@ -795,17 +753,45 @@ export class JATSExporter {
|
|
|
795
753
|
node.childCount == 0
|
|
796
754
|
? ['table-wrap-foot', ['fn-group', ['fn', ['p']]]]
|
|
797
755
|
: ['table-wrap-foot', 0],
|
|
798
|
-
contributor: () =>
|
|
799
|
-
|
|
756
|
+
contributor: (node) =>
|
|
757
|
+
this.buildContributorElement(node as ContributorNode),
|
|
758
|
+
affiliation: (node) =>
|
|
759
|
+
this.buildAffiliationElement(node as AffiliationNode),
|
|
800
760
|
attribution: () => ['attrib', 0],
|
|
801
761
|
bibliography_element: () => '',
|
|
802
762
|
bibliography_item: () => '',
|
|
803
763
|
comments: () => '',
|
|
804
|
-
keyword_group: () =>
|
|
764
|
+
keyword_group: (node) => {
|
|
765
|
+
const attrs: { [key: string]: string } = {}
|
|
766
|
+
if (node.attrs.type) {
|
|
767
|
+
attrs['kwd-group-type'] = node.attrs.type
|
|
768
|
+
}
|
|
769
|
+
return ['kwd-group', attrs, 0]
|
|
770
|
+
},
|
|
805
771
|
body: () => ['body', 0],
|
|
806
772
|
abstracts: () => ['abstract', 0],
|
|
807
773
|
backmatter: () => ['backmatter', 0],
|
|
808
|
-
supplement: () =>
|
|
774
|
+
supplement: (node) => {
|
|
775
|
+
const $supplementaryMaterial = this.createElement(
|
|
776
|
+
'supplementary-material'
|
|
777
|
+
)
|
|
778
|
+
$supplementaryMaterial.setAttribute('id', normalizeID(node.attrs.id))
|
|
779
|
+
$supplementaryMaterial.setAttributeNS(
|
|
780
|
+
XLINK_NAMESPACE,
|
|
781
|
+
'href',
|
|
782
|
+
node.attrs.href ?? ''
|
|
783
|
+
)
|
|
784
|
+
$supplementaryMaterial.setAttribute(
|
|
785
|
+
'mimetype',
|
|
786
|
+
node.attrs.mimeType ?? ''
|
|
787
|
+
)
|
|
788
|
+
$supplementaryMaterial.setAttribute(
|
|
789
|
+
'mime-subtype',
|
|
790
|
+
node.attrs.mimeSubType ?? ''
|
|
791
|
+
)
|
|
792
|
+
this.appendCaption($supplementaryMaterial, node)
|
|
793
|
+
return $supplementaryMaterial
|
|
794
|
+
},
|
|
809
795
|
supplements: () => '',
|
|
810
796
|
bibliography_section: () => '',
|
|
811
797
|
blockquote_element: () => ['disp-quote', { 'content-type': 'quote' }, 0],
|
|
@@ -830,14 +816,14 @@ export class JATSExporter {
|
|
|
830
816
|
return ''
|
|
831
817
|
}
|
|
832
818
|
|
|
833
|
-
const xref = this.createElement('xref')
|
|
834
|
-
xref.setAttribute('ref-type', 'bibr')
|
|
835
|
-
xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
819
|
+
const $xref = this.createElement('xref')
|
|
820
|
+
$xref.setAttribute('ref-type', 'bibr')
|
|
821
|
+
$xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
836
822
|
const fragment = this.renderedCitations.get(node.attrs.id)
|
|
837
823
|
if (fragment) {
|
|
838
|
-
xref.innerHTML = fragment
|
|
824
|
+
$xref.innerHTML = fragment
|
|
839
825
|
}
|
|
840
|
-
return xref
|
|
826
|
+
return $xref
|
|
841
827
|
},
|
|
842
828
|
cross_reference: (node) => {
|
|
843
829
|
const cross = node as CrossReferenceNode
|
|
@@ -857,43 +843,44 @@ export class JATSExporter {
|
|
|
857
843
|
return text ?? ''
|
|
858
844
|
}
|
|
859
845
|
|
|
860
|
-
const xref = this.createElement('xref')
|
|
846
|
+
const $xref = this.createElement('xref')
|
|
861
847
|
|
|
862
848
|
const type = chooseRefType(target.type)
|
|
863
849
|
if (type) {
|
|
864
|
-
xref.setAttribute('ref-type', type)
|
|
850
|
+
$xref.setAttribute('ref-type', type)
|
|
865
851
|
}
|
|
866
852
|
|
|
867
|
-
xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
868
|
-
xref.textContent = text ?? ''
|
|
853
|
+
$xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
854
|
+
$xref.textContent = text ?? ''
|
|
869
855
|
|
|
870
|
-
return xref
|
|
856
|
+
return $xref
|
|
871
857
|
},
|
|
872
858
|
doc: () => '',
|
|
873
859
|
equation: (node) => {
|
|
874
860
|
return node.attrs.contents ? this.createEquation(node) : ''
|
|
875
861
|
},
|
|
876
862
|
general_table_footnote: (node) => {
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
863
|
+
const $fragment = this.document.createDocumentFragment()
|
|
864
|
+
node.forEach((child) => {
|
|
865
|
+
$fragment.appendChild(this.serializeNode(child))
|
|
866
|
+
})
|
|
867
|
+
return $fragment
|
|
881
868
|
},
|
|
882
869
|
inline_equation: (node) => {
|
|
883
870
|
if (!node.attrs.contents) {
|
|
884
871
|
return ''
|
|
885
872
|
}
|
|
886
|
-
const
|
|
873
|
+
const $inlineFormula = this.createElement('inline-formula')
|
|
887
874
|
const equation = this.createEquation(node, true)
|
|
888
|
-
|
|
889
|
-
return
|
|
875
|
+
$inlineFormula.append(equation)
|
|
876
|
+
return $inlineFormula
|
|
890
877
|
},
|
|
891
878
|
equation_element: (node) => {
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
appendLabels(
|
|
895
|
-
processChildNodes(
|
|
896
|
-
return
|
|
879
|
+
const $dispFormula = this.createElement('disp-formula')
|
|
880
|
+
$dispFormula.setAttribute('id', normalizeID(node.attrs.id))
|
|
881
|
+
appendLabels($dispFormula, node)
|
|
882
|
+
processChildNodes($dispFormula, node, schema.nodes.equation)
|
|
883
|
+
return $dispFormula
|
|
897
884
|
},
|
|
898
885
|
figure: (node) => createGraphic(node),
|
|
899
886
|
figure_element: (node) =>
|
|
@@ -914,26 +901,44 @@ export class JATSExporter {
|
|
|
914
901
|
? ['fn-group', { id: normalizeID(node.attrs.id) }, ['fn', ['p']]]
|
|
915
902
|
: ['fn-group', { id: normalizeID(node.attrs.id) }, 0],
|
|
916
903
|
footnotes_section: (node) => {
|
|
917
|
-
const
|
|
904
|
+
const $fnGroup = this.createElement('fn-group', undefined, {
|
|
918
905
|
id: normalizeID(node.attrs.id),
|
|
919
|
-
|
|
906
|
+
})
|
|
907
|
+
const titleNode = this.getFirstChildOfType(
|
|
908
|
+
schema.nodes.section_title,
|
|
909
|
+
node
|
|
910
|
+
)
|
|
911
|
+
if (titleNode) {
|
|
912
|
+
$fnGroup.appendChild(this.serializeNode(titleNode))
|
|
920
913
|
}
|
|
921
|
-
|
|
922
|
-
|
|
914
|
+
const footnotesNode = this.getFirstChildOfType(
|
|
915
|
+
schema.nodes.footnotes_element,
|
|
916
|
+
node
|
|
917
|
+
)
|
|
918
|
+
if (footnotesNode?.childCount) {
|
|
919
|
+
footnotesNode.forEach((footnote) => {
|
|
920
|
+
$fnGroup.appendChild(this.serializeNode(footnote))
|
|
921
|
+
})
|
|
922
|
+
} else {
|
|
923
|
+
const $fn = this.createElement('fn')
|
|
924
|
+
$fn.appendChild(this.createElement('p'))
|
|
925
|
+
$fnGroup.appendChild($fn)
|
|
926
|
+
}
|
|
927
|
+
return $fnGroup
|
|
923
928
|
},
|
|
924
929
|
hard_break: () => '',
|
|
925
930
|
highlight_marker: () => '',
|
|
926
931
|
inline_footnote: (node) => {
|
|
927
932
|
const rids: string[] = node.attrs.rids
|
|
928
|
-
const xref = this.createElement('xref')
|
|
929
|
-
xref.setAttribute('ref-type', 'fn')
|
|
930
|
-
xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
931
|
-
xref.textContent = rids
|
|
933
|
+
const $xref = this.createElement('xref')
|
|
934
|
+
$xref.setAttribute('ref-type', 'fn')
|
|
935
|
+
$xref.setAttribute('rid', normalizeID(rids.join(' ')))
|
|
936
|
+
$xref.textContent = rids
|
|
932
937
|
.map((rid) => this.footnoteLabels.get(rid))
|
|
933
938
|
.join(', ')
|
|
934
|
-
return xref
|
|
939
|
+
return $xref
|
|
935
940
|
},
|
|
936
|
-
keyword: () => '',
|
|
941
|
+
keyword: () => ['kwd', 0],
|
|
937
942
|
keywords_element: () => '',
|
|
938
943
|
keywords: () => '',
|
|
939
944
|
link: (node) => {
|
|
@@ -947,38 +952,38 @@ export class JATSExporter {
|
|
|
947
952
|
return text
|
|
948
953
|
}
|
|
949
954
|
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
955
|
+
const $extLink = this.createElement('ext-link')
|
|
956
|
+
$extLink.setAttribute('ext-link-type', 'uri')
|
|
957
|
+
$extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.href)
|
|
958
|
+
$extLink.textContent = text
|
|
954
959
|
|
|
955
960
|
if (node.attrs.title) {
|
|
956
|
-
|
|
961
|
+
$extLink.setAttributeNS(
|
|
957
962
|
XLINK_NAMESPACE,
|
|
958
963
|
'xlink:title',
|
|
959
964
|
node.attrs.title
|
|
960
965
|
)
|
|
961
966
|
}
|
|
962
967
|
|
|
963
|
-
return
|
|
968
|
+
return $extLink
|
|
964
969
|
},
|
|
965
970
|
list_item: () => ['list-item', 0],
|
|
966
971
|
listing: (node) => {
|
|
967
|
-
const code = this.createElement('code')
|
|
968
|
-
code.setAttribute('id', normalizeID(node.attrs.id))
|
|
969
|
-
code.setAttribute('language', node.attrs.languageKey)
|
|
970
|
-
code.textContent = node.attrs.contents
|
|
972
|
+
const $code = this.createElement('code')
|
|
973
|
+
$code.setAttribute('id', normalizeID(node.attrs.id))
|
|
974
|
+
$code.setAttribute('language', node.attrs.languageKey)
|
|
975
|
+
$code.textContent = node.attrs.contents
|
|
971
976
|
|
|
972
|
-
return code
|
|
977
|
+
return $code
|
|
973
978
|
},
|
|
974
979
|
listing_element: (node) =>
|
|
975
980
|
createFigureElement(node, node.type.schema.nodes.listing),
|
|
976
981
|
manuscript: (node) => ['article', { id: normalizeID(node.attrs.id) }, 0],
|
|
977
982
|
missing_figure: () => {
|
|
978
|
-
const graphic = this.createElement('graphic')
|
|
979
|
-
graphic.setAttribute('specific-use', 'MISSING')
|
|
980
|
-
graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', '')
|
|
981
|
-
return graphic
|
|
983
|
+
const $graphic = this.createElement('graphic')
|
|
984
|
+
$graphic.setAttribute('specific-use', 'MISSING')
|
|
985
|
+
$graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', '')
|
|
986
|
+
return $graphic
|
|
982
987
|
},
|
|
983
988
|
paragraph: (node) => {
|
|
984
989
|
if (!node.childCount) {
|
|
@@ -1017,15 +1022,7 @@ export class JATSExporter {
|
|
|
1017
1022
|
}
|
|
1018
1023
|
return ''
|
|
1019
1024
|
},
|
|
1020
|
-
graphical_abstract_section: (node) =>
|
|
1021
|
-
const attrs: { [key: string]: string } = {
|
|
1022
|
-
id: normalizeID(node.attrs.id),
|
|
1023
|
-
}
|
|
1024
|
-
if (node.attrs.category) {
|
|
1025
|
-
attrs['sec-type'] = node.attrs.category
|
|
1026
|
-
}
|
|
1027
|
-
return ['sec', attrs, 0]
|
|
1028
|
-
},
|
|
1025
|
+
graphical_abstract_section: (node) => createAbstract(node),
|
|
1029
1026
|
section: (node) => {
|
|
1030
1027
|
const attrs: { [key: string]: string } = {
|
|
1031
1028
|
id: normalizeID(node.attrs.id),
|
|
@@ -1037,17 +1034,18 @@ export class JATSExporter {
|
|
|
1037
1034
|
|
|
1038
1035
|
return ['sec', attrs, 0]
|
|
1039
1036
|
},
|
|
1037
|
+
abstract: (node) => createAbstract(node),
|
|
1040
1038
|
section_label: () => ['label', 0],
|
|
1041
1039
|
section_title: () => ['title', 0],
|
|
1042
1040
|
section_title_plain: () => ['title', 0],
|
|
1043
1041
|
table: (node) => ['table', { id: normalizeID(node.attrs.id) }, 0],
|
|
1044
1042
|
table_element: (node) => {
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1043
|
+
const $tableWrap = createTableElement(node)
|
|
1044
|
+
$tableWrap.setAttribute('position', 'anchor')
|
|
1047
1045
|
if (node.attrs.type) {
|
|
1048
|
-
|
|
1046
|
+
$tableWrap.setAttribute('content-type', node.attrs.type)
|
|
1049
1047
|
}
|
|
1050
|
-
return
|
|
1048
|
+
return $tableWrap
|
|
1051
1049
|
},
|
|
1052
1050
|
table_cell: (node) => [
|
|
1053
1051
|
'td',
|
|
@@ -1097,60 +1095,60 @@ export class JATSExporter {
|
|
|
1097
1095
|
|
|
1098
1096
|
this.serializer = new DOMSerializer(nodes, marks)
|
|
1099
1097
|
const appendChildIfPresent = (
|
|
1100
|
-
parent: Element,
|
|
1098
|
+
$parent: Element,
|
|
1101
1099
|
tagName: string,
|
|
1102
1100
|
textContent: string
|
|
1103
1101
|
) => {
|
|
1104
1102
|
if (!textContent) {
|
|
1105
1103
|
return
|
|
1106
1104
|
}
|
|
1107
|
-
const element = this.createElement(tagName)
|
|
1108
|
-
element.textContent = textContent
|
|
1109
|
-
parent.appendChild(element)
|
|
1105
|
+
const $element = this.createElement(tagName)
|
|
1106
|
+
$element.textContent = textContent
|
|
1107
|
+
$parent.appendChild($element)
|
|
1110
1108
|
}
|
|
1111
1109
|
const processChildNodes = (
|
|
1112
|
-
element:
|
|
1110
|
+
$element: Element,
|
|
1113
1111
|
node: ManuscriptNode,
|
|
1114
1112
|
contentNodeType: ManuscriptNodeType
|
|
1115
1113
|
) => {
|
|
1116
1114
|
node.forEach((childNode) => {
|
|
1117
1115
|
if (childNode.type === contentNodeType) {
|
|
1118
1116
|
if (childNode.attrs.id) {
|
|
1119
|
-
element.appendChild(this.serializeNode(childNode))
|
|
1117
|
+
$element.appendChild(this.serializeNode(childNode))
|
|
1120
1118
|
}
|
|
1121
1119
|
} else if (childNode.type === node.type.schema.nodes.paragraph) {
|
|
1122
|
-
element.appendChild(this.serializeNode(childNode))
|
|
1120
|
+
$element.appendChild(this.serializeNode(childNode))
|
|
1123
1121
|
} else if (childNode.type === node.type.schema.nodes.missing_figure) {
|
|
1124
|
-
element.appendChild(this.serializeNode(childNode))
|
|
1122
|
+
$element.appendChild(this.serializeNode(childNode))
|
|
1125
1123
|
}
|
|
1126
1124
|
})
|
|
1127
1125
|
}
|
|
1128
1126
|
const createElement = (node: ManuscriptNode, nodeName: string) => {
|
|
1129
|
-
const element = this.createElement(nodeName)
|
|
1130
|
-
element.setAttribute('id', normalizeID(node.attrs.id))
|
|
1131
|
-
return element
|
|
1127
|
+
const $element = this.createElement(nodeName)
|
|
1128
|
+
$element.setAttribute('id', normalizeID(node.attrs.id))
|
|
1129
|
+
return $element
|
|
1132
1130
|
}
|
|
1133
1131
|
|
|
1134
|
-
const appendLabels = (element:
|
|
1132
|
+
const appendLabels = ($element: Element, node: ManuscriptNode) => {
|
|
1135
1133
|
if (this.labelTargets) {
|
|
1136
1134
|
const target = this.labelTargets.get(node.attrs.id)
|
|
1137
1135
|
|
|
1138
1136
|
if (target) {
|
|
1139
|
-
const label = this.createElement('label')
|
|
1140
|
-
label.textContent = target.label
|
|
1141
|
-
element.appendChild(label)
|
|
1137
|
+
const $label = this.createElement('label')
|
|
1138
|
+
$label.textContent = target.label
|
|
1139
|
+
$element.appendChild($label)
|
|
1142
1140
|
}
|
|
1143
1141
|
}
|
|
1144
1142
|
}
|
|
1145
|
-
const appendAttributions = (element:
|
|
1143
|
+
const appendAttributions = ($element: Element, node: ManuscriptNode) => {
|
|
1146
1144
|
if (node.attrs.attribution) {
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
element.appendChild(
|
|
1145
|
+
const $attrib = this.createElement('attrib')
|
|
1146
|
+
$attrib.textContent = node.attrs.attribution.literal
|
|
1147
|
+
$element.appendChild($attrib)
|
|
1150
1148
|
}
|
|
1151
1149
|
}
|
|
1152
1150
|
|
|
1153
|
-
const appendTable = (element:
|
|
1151
|
+
const appendTable = ($element: Element, node: ManuscriptNode) => {
|
|
1154
1152
|
const tableNode = this.getFirstChildOfType(schema.nodes.table, node)
|
|
1155
1153
|
const colGroupNode = this.getFirstChildOfType(
|
|
1156
1154
|
schema.nodes.table_colgroup,
|
|
@@ -1159,42 +1157,61 @@ export class JATSExporter {
|
|
|
1159
1157
|
if (!tableNode) {
|
|
1160
1158
|
return
|
|
1161
1159
|
}
|
|
1162
|
-
const table = this.serializeNode(tableNode)
|
|
1163
|
-
const
|
|
1160
|
+
const $table = this.serializeNode(tableNode)
|
|
1161
|
+
const $tbody = this.createElement('tbody')
|
|
1164
1162
|
|
|
1165
|
-
while (table.firstChild) {
|
|
1166
|
-
const child = table.firstChild
|
|
1167
|
-
table.removeChild(child)
|
|
1168
|
-
|
|
1163
|
+
while ($table.firstChild) {
|
|
1164
|
+
const $child = $table.firstChild
|
|
1165
|
+
$table.removeChild($child)
|
|
1166
|
+
$tbody.appendChild($child)
|
|
1169
1167
|
}
|
|
1170
|
-
table.appendChild(
|
|
1168
|
+
$table.appendChild($tbody)
|
|
1169
|
+
this.normalizeTable($table)
|
|
1171
1170
|
if (colGroupNode) {
|
|
1172
|
-
const colGroup = this.serializeNode(colGroupNode)
|
|
1173
|
-
table.insertBefore(colGroup, table.firstChild)
|
|
1171
|
+
const $colGroup = this.serializeNode(colGroupNode)
|
|
1172
|
+
$table.insertBefore($colGroup, $table.firstChild)
|
|
1174
1173
|
}
|
|
1175
1174
|
|
|
1176
|
-
element.appendChild(table)
|
|
1175
|
+
$element.appendChild($table)
|
|
1177
1176
|
}
|
|
1178
1177
|
const createBoxElement = (node: ManuscriptNode) => {
|
|
1179
|
-
const
|
|
1178
|
+
const $boxedText = createElement(node, 'boxed-text')
|
|
1180
1179
|
if (node.attrs.type) {
|
|
1181
|
-
|
|
1180
|
+
$boxedText.setAttribute('content-type', node.attrs.type)
|
|
1182
1181
|
}
|
|
1183
|
-
appendLabels(
|
|
1182
|
+
appendLabels($boxedText, node)
|
|
1184
1183
|
const child = node.firstChild
|
|
1185
1184
|
if (child?.type === schema.nodes.caption_title) {
|
|
1186
|
-
this.appendCaption(
|
|
1185
|
+
this.appendCaption($boxedText, node)
|
|
1187
1186
|
}
|
|
1188
1187
|
|
|
1189
|
-
processChildNodes(
|
|
1190
|
-
return
|
|
1188
|
+
processChildNodes($boxedText, node, node.type.schema.nodes.section)
|
|
1189
|
+
return $boxedText
|
|
1191
1190
|
}
|
|
1192
1191
|
|
|
1192
|
+
const abstractTypeAttrs = (category: string) =>
|
|
1193
|
+
category && category !== 'abstract' ? { 'abstract-type': category } : {}
|
|
1194
|
+
|
|
1195
|
+
const createAbstract = (node: ManuscriptNode): DOMOutputSpec => [
|
|
1196
|
+
'abstract',
|
|
1197
|
+
abstractTypeAttrs(node.attrs.category),
|
|
1198
|
+
0,
|
|
1199
|
+
]
|
|
1200
|
+
|
|
1201
|
+
const createTransAbstract = (node: ManuscriptNode): DOMOutputSpec => [
|
|
1202
|
+
'trans-abstract',
|
|
1203
|
+
{
|
|
1204
|
+
[`${XML_NAMESPACE} lang`]: node.attrs.lang ?? '',
|
|
1205
|
+
...abstractTypeAttrs(node.attrs.category),
|
|
1206
|
+
},
|
|
1207
|
+
0,
|
|
1208
|
+
]
|
|
1209
|
+
|
|
1193
1210
|
const isChildOfNodeType = (
|
|
1194
1211
|
targetID: string,
|
|
1195
1212
|
type: NodeType,
|
|
1196
1213
|
descend = false
|
|
1197
|
-
)
|
|
1214
|
+
) => {
|
|
1198
1215
|
const nodes = this.getChildrenOfType(type)
|
|
1199
1216
|
return nodes.some((node) => {
|
|
1200
1217
|
const result = findChildrenByAttr(
|
|
@@ -1219,116 +1236,116 @@ export class JATSExporter {
|
|
|
1219
1236
|
if (!graphicNode) {
|
|
1220
1237
|
return ''
|
|
1221
1238
|
}
|
|
1222
|
-
const
|
|
1239
|
+
const $graphic = createGraphic(graphicNode)
|
|
1223
1240
|
if (node.attrs.extLink) {
|
|
1224
|
-
const extLink = this.appendElement(
|
|
1225
|
-
extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink)
|
|
1241
|
+
const $extLink = this.appendElement($graphic, 'ext-link')
|
|
1242
|
+
$extLink.setAttributeNS(XLINK_NAMESPACE, 'href', node.attrs.extLink)
|
|
1226
1243
|
}
|
|
1227
|
-
this.appendCaption(
|
|
1228
|
-
this.appendChildNodeOfType(
|
|
1229
|
-
this.appendChildNodeOfType(
|
|
1230
|
-
return
|
|
1244
|
+
this.appendCaption($graphic, node)
|
|
1245
|
+
this.appendChildNodeOfType($graphic, node, schema.nodes.alt_text)
|
|
1246
|
+
this.appendChildNodeOfType($graphic, node, schema.nodes.long_desc)
|
|
1247
|
+
return $graphic
|
|
1231
1248
|
}
|
|
1232
1249
|
|
|
1233
1250
|
const createGraphic = (node: ManuscriptNode) => {
|
|
1234
|
-
const graphic = this.createElement('graphic')
|
|
1235
|
-
graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src)
|
|
1251
|
+
const $graphic = this.createElement('graphic')
|
|
1252
|
+
$graphic.setAttributeNS(XLINK_NAMESPACE, 'xlink:href', node.attrs.src)
|
|
1236
1253
|
|
|
1237
1254
|
const hero = findParentHeroImage(node.attrs.id)
|
|
1238
1255
|
if (hero) {
|
|
1239
|
-
graphic.setAttribute('content-type', hero.attrs.type || 'leading')
|
|
1256
|
+
$graphic.setAttribute('content-type', hero.attrs.type || 'leading')
|
|
1240
1257
|
} else if (
|
|
1241
1258
|
!isChildOfNodeType(node.attrs.id, schema.nodes.figure_element) &&
|
|
1242
1259
|
node.attrs.type
|
|
1243
1260
|
) {
|
|
1244
|
-
graphic.setAttribute('content-type', node.attrs.type)
|
|
1261
|
+
$graphic.setAttribute('content-type', node.attrs.type)
|
|
1245
1262
|
}
|
|
1246
|
-
return graphic
|
|
1263
|
+
return $graphic
|
|
1247
1264
|
}
|
|
1248
1265
|
const createFigureElement = (
|
|
1249
1266
|
node: ManuscriptNode,
|
|
1250
1267
|
contentNodeType: ManuscriptNodeType
|
|
1251
1268
|
) => {
|
|
1252
|
-
const
|
|
1269
|
+
const $fig = createElement(node, 'fig')
|
|
1253
1270
|
const figNode = this.getFirstChildOfType(schema.nodes.figure, node)
|
|
1254
1271
|
const figType = figNode?.attrs.type
|
|
1255
1272
|
if (figType) {
|
|
1256
|
-
|
|
1273
|
+
$fig.setAttribute('fig-type', figType)
|
|
1257
1274
|
}
|
|
1258
|
-
appendLabels(
|
|
1259
|
-
this.appendCaption(
|
|
1260
|
-
this.appendChildNodeOfType(
|
|
1261
|
-
this.appendChildNodeOfType(
|
|
1275
|
+
appendLabels($fig, node)
|
|
1276
|
+
this.appendCaption($fig, node)
|
|
1277
|
+
this.appendChildNodeOfType($fig, node, schema.nodes.alt_text)
|
|
1278
|
+
this.appendChildNodeOfType($fig, node, schema.nodes.long_desc)
|
|
1262
1279
|
this.appendChildNodeOfType(
|
|
1263
|
-
|
|
1280
|
+
$fig,
|
|
1264
1281
|
node,
|
|
1265
1282
|
node.type.schema.nodes.footnotes_element
|
|
1266
1283
|
)
|
|
1267
|
-
processChildNodes(
|
|
1268
|
-
appendAttributions(
|
|
1284
|
+
processChildNodes($fig, node, contentNodeType)
|
|
1285
|
+
appendAttributions($fig, node)
|
|
1269
1286
|
if (isExecutableNodeType(node.type)) {
|
|
1270
|
-
processExecutableNode(node,
|
|
1287
|
+
processExecutableNode(node, $fig)
|
|
1271
1288
|
}
|
|
1272
|
-
moveAltTextAndLongDescToGraphics(
|
|
1273
|
-
return
|
|
1289
|
+
moveAltTextAndLongDescToGraphics($fig)
|
|
1290
|
+
return $fig
|
|
1274
1291
|
}
|
|
1275
1292
|
|
|
1276
|
-
const moveAltTextAndLongDescToGraphics = (element: Element) => {
|
|
1277
|
-
const altText = element.querySelector('alt-text')
|
|
1278
|
-
const longDesc = element.querySelector('long-desc')
|
|
1279
|
-
const graphics = element.querySelectorAll('graphic')
|
|
1293
|
+
const moveAltTextAndLongDescToGraphics = ($element: Element) => {
|
|
1294
|
+
const $altText = $element.querySelector('alt-text')
|
|
1295
|
+
const $longDesc = $element.querySelector('long-desc')
|
|
1296
|
+
const $graphics = $element.querySelectorAll('graphic')
|
|
1280
1297
|
|
|
1281
|
-
if (graphics.length === 0) {
|
|
1298
|
+
if ($graphics.length === 0) {
|
|
1282
1299
|
return
|
|
1283
1300
|
}
|
|
1284
1301
|
|
|
1285
|
-
graphics.forEach((graphic) => {
|
|
1286
|
-
if (longDesc) {
|
|
1287
|
-
graphic.prepend(longDesc.cloneNode(true))
|
|
1302
|
+
$graphics.forEach(($graphic) => {
|
|
1303
|
+
if ($longDesc) {
|
|
1304
|
+
$graphic.prepend($longDesc.cloneNode(true))
|
|
1288
1305
|
}
|
|
1289
|
-
if (altText) {
|
|
1290
|
-
graphic.prepend(altText.cloneNode(true))
|
|
1306
|
+
if ($altText) {
|
|
1307
|
+
$graphic.prepend($altText.cloneNode(true))
|
|
1291
1308
|
}
|
|
1292
1309
|
})
|
|
1293
1310
|
|
|
1294
|
-
altText?.remove()
|
|
1295
|
-
longDesc?.remove()
|
|
1311
|
+
$altText?.remove()
|
|
1312
|
+
$longDesc?.remove()
|
|
1296
1313
|
}
|
|
1297
1314
|
|
|
1298
1315
|
const createTableElement = (node: ManuscriptNode) => {
|
|
1299
1316
|
const nodeName = 'table-wrap'
|
|
1300
|
-
const
|
|
1301
|
-
appendLabels(
|
|
1302
|
-
this.appendCaption(
|
|
1303
|
-
this.appendChildNodeOfType(
|
|
1304
|
-
this.appendChildNodeOfType(
|
|
1305
|
-
appendTable(
|
|
1317
|
+
const $tableWrap = createElement(node, nodeName)
|
|
1318
|
+
appendLabels($tableWrap, node)
|
|
1319
|
+
this.appendCaption($tableWrap, node)
|
|
1320
|
+
this.appendChildNodeOfType($tableWrap, node, schema.nodes.alt_text)
|
|
1321
|
+
this.appendChildNodeOfType($tableWrap, node, schema.nodes.long_desc)
|
|
1322
|
+
appendTable($tableWrap, node)
|
|
1306
1323
|
this.appendChildNodeOfType(
|
|
1307
|
-
|
|
1324
|
+
$tableWrap,
|
|
1308
1325
|
node,
|
|
1309
1326
|
node.type.schema.nodes.table_element_footer
|
|
1310
1327
|
)
|
|
1311
1328
|
if (isExecutableNodeType(node.type)) {
|
|
1312
|
-
processExecutableNode(node,
|
|
1329
|
+
processExecutableNode(node, $tableWrap)
|
|
1313
1330
|
}
|
|
1314
|
-
return
|
|
1331
|
+
return $tableWrap
|
|
1315
1332
|
}
|
|
1316
|
-
const processExecutableNode = (node: ManuscriptNode, element: Element) => {
|
|
1333
|
+
const processExecutableNode = (node: ManuscriptNode, $element: Element) => {
|
|
1317
1334
|
const listingNode = this.getFirstChildOfType(schema.nodes.listing, node)
|
|
1318
1335
|
|
|
1319
1336
|
if (listingNode) {
|
|
1320
1337
|
const { contents, languageKey } = listingNode.attrs
|
|
1321
1338
|
|
|
1322
1339
|
if (contents && languageKey) {
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
element.appendChild(
|
|
1326
|
-
|
|
1327
|
-
const code = this.createElement('code')
|
|
1328
|
-
code.setAttribute('executable', 'true')
|
|
1329
|
-
code.setAttribute('language', languageKey)
|
|
1330
|
-
code.textContent = contents
|
|
1331
|
-
|
|
1340
|
+
const $fig = this.createElement('fig')
|
|
1341
|
+
$fig.setAttribute('specific-use', 'source')
|
|
1342
|
+
$element.appendChild($fig)
|
|
1343
|
+
|
|
1344
|
+
const $code = this.createElement('code')
|
|
1345
|
+
$code.setAttribute('executable', 'true')
|
|
1346
|
+
$code.setAttribute('language', languageKey)
|
|
1347
|
+
$code.textContent = contents
|
|
1348
|
+
$fig.appendChild($code)
|
|
1332
1349
|
}
|
|
1333
1350
|
}
|
|
1334
1351
|
}
|
|
@@ -1336,22 +1353,22 @@ export class JATSExporter {
|
|
|
1336
1353
|
|
|
1337
1354
|
private createEquation(node: ManuscriptNode, isInline = false) {
|
|
1338
1355
|
if (node.attrs.format === 'tex') {
|
|
1339
|
-
const texMath = this.createElement('tex-math')
|
|
1340
|
-
texMath.setAttribute('notation', 'LaTeX')
|
|
1341
|
-
texMath.setAttribute('version', 'MathJax')
|
|
1356
|
+
const $texMath = this.createElement('tex-math')
|
|
1357
|
+
$texMath.setAttribute('notation', 'LaTeX')
|
|
1358
|
+
$texMath.setAttribute('version', 'MathJax')
|
|
1342
1359
|
if (node.attrs.contents.includes('<![CDATA[')) {
|
|
1343
|
-
texMath.innerHTML = node.attrs.contents
|
|
1360
|
+
$texMath.innerHTML = node.attrs.contents
|
|
1344
1361
|
} else {
|
|
1345
|
-
texMath.innerHTML = `<![CDATA[ ${node.attrs.contents} ]]>`
|
|
1362
|
+
$texMath.innerHTML = `<![CDATA[ ${node.attrs.contents} ]]>`
|
|
1346
1363
|
}
|
|
1347
|
-
return texMath
|
|
1364
|
+
return $texMath
|
|
1348
1365
|
} else {
|
|
1349
1366
|
const math = this.nodeFromJATS(node.attrs.contents)
|
|
1350
|
-
const mathml = math as Element
|
|
1367
|
+
const $mathml = math as Element
|
|
1351
1368
|
if (!isInline) {
|
|
1352
|
-
mathml.setAttribute('id', normalizeID(node.attrs.id))
|
|
1369
|
+
$mathml.setAttribute('id', normalizeID(node.attrs.id))
|
|
1353
1370
|
}
|
|
1354
|
-
return mathml
|
|
1371
|
+
return $mathml
|
|
1355
1372
|
}
|
|
1356
1373
|
}
|
|
1357
1374
|
|
|
@@ -1360,213 +1377,227 @@ export class JATSExporter {
|
|
|
1360
1377
|
document: this.document,
|
|
1361
1378
|
})
|
|
1362
1379
|
|
|
1363
|
-
private
|
|
1364
|
-
const contributors = this.getChildrenOfType<ContributorNode>(
|
|
1365
|
-
schema.nodes.contributor
|
|
1366
|
-
).sort(sortContributors)
|
|
1367
|
-
|
|
1380
|
+
private buildContributorGroup = (contributors: ContributorNode[]) => {
|
|
1368
1381
|
if (!contributors.length) {
|
|
1369
1382
|
return
|
|
1370
1383
|
}
|
|
1371
1384
|
|
|
1372
|
-
const
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
if (!
|
|
1376
|
-
|
|
1377
|
-
labels.set(id, label)
|
|
1385
|
+
const affiliationIDs = contributors.flatMap((n) => n.attrs.affiliationIDs)
|
|
1386
|
+
const affiliationOrder = new Map<string, number>()
|
|
1387
|
+
affiliationIDs.forEach((id, index) => {
|
|
1388
|
+
if (!affiliationOrder.has(id)) {
|
|
1389
|
+
affiliationOrder.set(id, index)
|
|
1378
1390
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1391
|
+
})
|
|
1392
|
+
|
|
1393
|
+
const affiliations = this.getChildrenOfType<AffiliationNode>(
|
|
1394
|
+
schema.nodes.affiliation
|
|
1395
|
+
)
|
|
1396
|
+
.filter((a) => affiliationIDs.includes(a.attrs.id))
|
|
1397
|
+
.sort(
|
|
1398
|
+
(a, b) =>
|
|
1399
|
+
(affiliationOrder.get(a.attrs.id) ?? Infinity) -
|
|
1400
|
+
(affiliationOrder.get(b.attrs.id) ?? Infinity)
|
|
1401
|
+
)
|
|
1383
1402
|
|
|
1384
1403
|
const $contribGroup = this.createElement('contrib-group')
|
|
1385
1404
|
$contribGroup.setAttribute('content-type', 'authors')
|
|
1386
|
-
articleMeta.appendChild($contribGroup)
|
|
1387
|
-
|
|
1388
1405
|
contributors.forEach((contributor) => {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1406
|
+
$contribGroup.appendChild(this.serializeNode(contributor))
|
|
1407
|
+
})
|
|
1408
|
+
affiliations.forEach((affiliation) => {
|
|
1409
|
+
$contribGroup.appendChild(this.serializeNode(affiliation))
|
|
1410
|
+
})
|
|
1411
|
+
return $contribGroup
|
|
1412
|
+
}
|
|
1392
1413
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1414
|
+
private buildAuthorNotes = ($coiStatement?: Element) => {
|
|
1415
|
+
const authorNotes = this.getFirstChildOfType<AuthorNotesNode>(
|
|
1416
|
+
schema.nodes.author_notes
|
|
1417
|
+
)
|
|
1418
|
+
const $authorNotes = this.createElement('author-notes')
|
|
1419
|
+
if (authorNotes) {
|
|
1420
|
+
this.appendAuthorNotes($authorNotes, authorNotes)
|
|
1421
|
+
}
|
|
1422
|
+
if ($coiStatement) {
|
|
1423
|
+
$authorNotes.appendChild($coiStatement)
|
|
1424
|
+
}
|
|
1425
|
+
return $authorNotes.hasChildNodes() ? $authorNotes : undefined
|
|
1426
|
+
}
|
|
1396
1427
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1428
|
+
private computeContributorLabels = (contributors: ContributorNode[]) => {
|
|
1429
|
+
const labels = new Map<string, number>()
|
|
1430
|
+
const assign = (id: string) => {
|
|
1431
|
+
if (!labels.has(id)) {
|
|
1432
|
+
labels.set(id, labels.size + 1)
|
|
1399
1433
|
}
|
|
1434
|
+
}
|
|
1435
|
+
contributors.forEach((contributor) => {
|
|
1436
|
+
contributor.attrs.affiliationIDs?.forEach(assign)
|
|
1437
|
+
contributor.attrs.footnoteIDs?.forEach(assign)
|
|
1438
|
+
contributor.attrs.correspIDs?.forEach(assign)
|
|
1439
|
+
})
|
|
1440
|
+
return labels
|
|
1441
|
+
}
|
|
1400
1442
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
contribIdAttrs['authenticated'] = 'true'
|
|
1407
|
-
}
|
|
1408
|
-
this.appendElement(
|
|
1409
|
-
$contrib,
|
|
1410
|
-
'contrib-id',
|
|
1411
|
-
contributor.attrs.ORCID,
|
|
1412
|
-
contribIdAttrs
|
|
1413
|
-
)
|
|
1414
|
-
}
|
|
1443
|
+
private createLabelSup = (id: string) => {
|
|
1444
|
+
const $sup = this.createElement('sup')
|
|
1445
|
+
$sup.textContent = String(this.contributorLabels.get(id))
|
|
1446
|
+
return $sup
|
|
1447
|
+
}
|
|
1415
1448
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1449
|
+
private buildContributorElement = (contributor: ContributorNode) => {
|
|
1450
|
+
const $contrib = this.createElement('contrib')
|
|
1451
|
+
$contrib.setAttribute('contrib-type', 'author')
|
|
1452
|
+
$contrib.setAttribute('id', normalizeID(contributor.attrs.id))
|
|
1418
1453
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1454
|
+
if (contributor.attrs.isCorresponding) {
|
|
1455
|
+
$contrib.setAttribute('corresp', 'yes')
|
|
1456
|
+
}
|
|
1422
1457
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1458
|
+
if (contributor.attrs.role) {
|
|
1459
|
+
this.appendElement($contrib, 'role', contributor.attrs.role)
|
|
1460
|
+
}
|
|
1426
1461
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1462
|
+
if (contributor.attrs.ORCID) {
|
|
1463
|
+
const contribIDAttrs: Record<string, string> = {
|
|
1464
|
+
'contrib-id-type': 'orcid',
|
|
1429
1465
|
}
|
|
1466
|
+
if (contributor.attrs.isAuthenticated) {
|
|
1467
|
+
contribIDAttrs['authenticated'] = 'true'
|
|
1468
|
+
}
|
|
1469
|
+
this.appendElement(
|
|
1470
|
+
$contrib,
|
|
1471
|
+
'contrib-id',
|
|
1472
|
+
contributor.attrs.ORCID,
|
|
1473
|
+
contribIDAttrs
|
|
1474
|
+
)
|
|
1475
|
+
}
|
|
1430
1476
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
'ref-type': 'aff',
|
|
1434
|
-
rid: normalizeID(rid),
|
|
1435
|
-
})
|
|
1436
|
-
$xref.appendChild(createLabel(rid))
|
|
1437
|
-
})
|
|
1477
|
+
const $name = this.buildContributorName(contributor)
|
|
1478
|
+
$contrib.appendChild($name)
|
|
1438
1479
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1480
|
+
if (contributor.attrs.suffix) {
|
|
1481
|
+
this.appendElement($name, 'suffix', contributor.attrs.suffix)
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
contributor.attrs.degrees?.forEach((degree) => {
|
|
1485
|
+
this.appendElement($contrib, 'degrees', degree)
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
if (contributor.attrs.email) {
|
|
1489
|
+
this.appendElement($contrib, 'email', contributor.attrs.email)
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
contributor.attrs.affiliationIDs?.forEach((rid) => {
|
|
1493
|
+
const $xref = this.appendElement($contrib, 'xref', '', {
|
|
1494
|
+
'ref-type': 'aff',
|
|
1495
|
+
rid: normalizeID(rid),
|
|
1445
1496
|
})
|
|
1497
|
+
$xref.appendChild(this.createLabelSup(rid))
|
|
1498
|
+
})
|
|
1446
1499
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
})
|
|
1452
|
-
$xref.appendChild(createLabel(rid))
|
|
1500
|
+
contributor.attrs.footnoteIDs?.forEach((rid) => {
|
|
1501
|
+
const $xref = this.appendElement($contrib, 'xref', '', {
|
|
1502
|
+
'ref-type': 'fn',
|
|
1503
|
+
rid: normalizeID(rid),
|
|
1453
1504
|
})
|
|
1505
|
+
$xref.appendChild(this.createLabelSup(rid))
|
|
1506
|
+
})
|
|
1454
1507
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
}
|
|
1460
|
-
this.appendElement($contrib, 'role', credit.vocabTerm, {
|
|
1461
|
-
'vocab-identifier': 'http://credit.niso.org/',
|
|
1462
|
-
vocab: 'CRediT',
|
|
1463
|
-
'vocab-term': credit.vocabTerm,
|
|
1464
|
-
'vocab-term-identifier': url,
|
|
1465
|
-
})
|
|
1508
|
+
contributor.attrs.correspIDs?.forEach((rid) => {
|
|
1509
|
+
const $xref = this.appendElement($contrib, 'xref', '', {
|
|
1510
|
+
'ref-type': 'corresp',
|
|
1511
|
+
rid: normalizeID(rid),
|
|
1466
1512
|
})
|
|
1467
|
-
$
|
|
1513
|
+
$xref.appendChild(this.createLabelSup(rid))
|
|
1468
1514
|
})
|
|
1469
1515
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1516
|
+
contributor.attrs.creditRoles?.forEach((credit) => {
|
|
1517
|
+
const url = CreditRoleUrls.get(credit.vocabTerm)
|
|
1518
|
+
if (!url) {
|
|
1519
|
+
return
|
|
1520
|
+
}
|
|
1521
|
+
this.appendElement($contrib, 'role', credit.vocabTerm, {
|
|
1522
|
+
'vocab-identifier': 'http://credit.niso.org/',
|
|
1523
|
+
vocab: 'CRediT',
|
|
1524
|
+
'vocab-term': credit.vocabTerm,
|
|
1525
|
+
'vocab-term-identifier': url,
|
|
1526
|
+
})
|
|
1527
|
+
})
|
|
1473
1528
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
.sort(sortAffiliations)
|
|
1477
|
-
.forEach((affiliation) => {
|
|
1478
|
-
const $content = []
|
|
1479
|
-
|
|
1480
|
-
if (affiliation.attrs.department) {
|
|
1481
|
-
const $institution = this.createElement(
|
|
1482
|
-
'institution',
|
|
1483
|
-
affiliation.attrs.department,
|
|
1484
|
-
{
|
|
1485
|
-
'content-type': 'dept',
|
|
1486
|
-
}
|
|
1487
|
-
)
|
|
1488
|
-
$content.push($institution)
|
|
1489
|
-
}
|
|
1529
|
+
return $contrib
|
|
1530
|
+
}
|
|
1490
1531
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
'institution',
|
|
1494
|
-
affiliation.attrs.institution
|
|
1495
|
-
)
|
|
1496
|
-
$content.push($institution)
|
|
1497
|
-
}
|
|
1532
|
+
private buildAffiliationElement = (affiliation: AffiliationNode) => {
|
|
1533
|
+
const $content: Element[] = []
|
|
1498
1534
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1535
|
+
if (affiliation.attrs.department) {
|
|
1536
|
+
$content.push(
|
|
1537
|
+
this.createElement('institution', affiliation.attrs.department, {
|
|
1538
|
+
'content-type': 'dept',
|
|
1539
|
+
})
|
|
1540
|
+
)
|
|
1541
|
+
}
|
|
1506
1542
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
$content.push($addrLine)
|
|
1513
|
-
}
|
|
1543
|
+
if (affiliation.attrs.institution) {
|
|
1544
|
+
$content.push(
|
|
1545
|
+
this.createElement('institution', affiliation.attrs.institution)
|
|
1546
|
+
)
|
|
1547
|
+
}
|
|
1514
1548
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
$content.push($addrLine)
|
|
1521
|
-
}
|
|
1549
|
+
if (affiliation.attrs.addressLine1) {
|
|
1550
|
+
$content.push(
|
|
1551
|
+
this.createElement('addr-line', affiliation.attrs.addressLine1)
|
|
1552
|
+
)
|
|
1553
|
+
}
|
|
1522
1554
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1555
|
+
if (affiliation.attrs.addressLine2) {
|
|
1556
|
+
$content.push(
|
|
1557
|
+
this.createElement('addr-line', affiliation.attrs.addressLine2)
|
|
1558
|
+
)
|
|
1559
|
+
}
|
|
1527
1560
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1561
|
+
if (affiliation.attrs.addressLine3) {
|
|
1562
|
+
$content.push(
|
|
1563
|
+
this.createElement('addr-line', affiliation.attrs.addressLine3)
|
|
1564
|
+
)
|
|
1565
|
+
}
|
|
1532
1566
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
affiliation.attrs.country
|
|
1537
|
-
)
|
|
1538
|
-
$content.push($country)
|
|
1539
|
-
}
|
|
1567
|
+
if (affiliation.attrs.city) {
|
|
1568
|
+
$content.push(this.createElement('city', affiliation.attrs.city))
|
|
1569
|
+
}
|
|
1540
1570
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
affiliation.attrs.postCode
|
|
1545
|
-
)
|
|
1546
|
-
$content.push($postalCode)
|
|
1547
|
-
}
|
|
1571
|
+
if (affiliation.attrs.county) {
|
|
1572
|
+
$content.push(this.createElement('state', affiliation.attrs.county))
|
|
1573
|
+
}
|
|
1548
1574
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1575
|
+
if (affiliation.attrs.country) {
|
|
1576
|
+
$content.push(this.createElement('country', affiliation.attrs.country))
|
|
1577
|
+
}
|
|
1552
1578
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1579
|
+
if (affiliation.attrs.postCode) {
|
|
1580
|
+
$content.push(
|
|
1581
|
+
this.createElement('postal-code', affiliation.attrs.postCode)
|
|
1582
|
+
)
|
|
1583
|
+
}
|
|
1557
1584
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
$aff.appendChild(this.document.createTextNode(', '))
|
|
1561
|
-
}
|
|
1562
|
-
$aff.appendChild(node)
|
|
1563
|
-
})
|
|
1564
|
-
})
|
|
1585
|
+
const $aff = this.createElement('aff')
|
|
1586
|
+
$aff.setAttribute('id', normalizeID(affiliation.attrs.id))
|
|
1565
1587
|
|
|
1566
|
-
const
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1588
|
+
const label = this.contributorLabels.get(affiliation.attrs.id)
|
|
1589
|
+
if (label) {
|
|
1590
|
+
this.appendElement($aff, 'label', String(label))
|
|
1569
1591
|
}
|
|
1592
|
+
|
|
1593
|
+
$content.forEach((node, i) => {
|
|
1594
|
+
if (i > 0) {
|
|
1595
|
+
$aff.appendChild(this.document.createTextNode(', '))
|
|
1596
|
+
}
|
|
1597
|
+
$aff.appendChild(node)
|
|
1598
|
+
})
|
|
1599
|
+
|
|
1600
|
+
return $aff
|
|
1570
1601
|
}
|
|
1571
1602
|
|
|
1572
1603
|
private buildContributorName = (contributor: ContributorNode) => {
|
|
@@ -1575,58 +1606,21 @@ export class JATSExporter {
|
|
|
1575
1606
|
return this.createElement('string-name', given || family)
|
|
1576
1607
|
}
|
|
1577
1608
|
|
|
1578
|
-
const name = this.createElement('name')
|
|
1609
|
+
const $name = this.createElement('name')
|
|
1579
1610
|
|
|
1580
1611
|
if (contributor.attrs.family) {
|
|
1581
|
-
this.appendElement(name, 'surname', contributor.attrs.family)
|
|
1612
|
+
this.appendElement($name, 'surname', contributor.attrs.family)
|
|
1582
1613
|
}
|
|
1583
1614
|
|
|
1584
1615
|
if (contributor.attrs.given) {
|
|
1585
|
-
this.appendElement(name, 'given-names', contributor.attrs.given)
|
|
1616
|
+
this.appendElement($name, 'given-names', contributor.attrs.given)
|
|
1586
1617
|
}
|
|
1587
1618
|
|
|
1588
1619
|
if (contributor.attrs.prefix) {
|
|
1589
|
-
this.appendElement(name, 'prefix', contributor.attrs.prefix)
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
return name
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
private createAuthorNotesElement = (contributors: ContributorNode[]) => {
|
|
1596
|
-
const authorNotes = this.getFirstChildOfType<AuthorNotesNode>(
|
|
1597
|
-
schema.nodes.author_notes
|
|
1598
|
-
)
|
|
1599
|
-
if (!authorNotes || !authorNotes.childCount) {
|
|
1600
|
-
return
|
|
1620
|
+
this.appendElement($name, 'prefix', contributor.attrs.prefix)
|
|
1601
1621
|
}
|
|
1602
1622
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const used = contributors.flatMap((c) => c.attrs.correspIDs)
|
|
1606
|
-
|
|
1607
|
-
authorNotes.descendants((node) => {
|
|
1608
|
-
switch (node.type) {
|
|
1609
|
-
case schema.nodes.paragraph: {
|
|
1610
|
-
const $p = this.writeParagraph(node as ParagraphNode)
|
|
1611
|
-
$authorNotes.append($p)
|
|
1612
|
-
break
|
|
1613
|
-
}
|
|
1614
|
-
case schema.nodes.footnote: {
|
|
1615
|
-
const $fn = this.writeFootnote(node as FootnoteNode)
|
|
1616
|
-
$authorNotes.append($fn)
|
|
1617
|
-
break
|
|
1618
|
-
}
|
|
1619
|
-
case schema.nodes.corresp: {
|
|
1620
|
-
if (used.includes(node.attrs.id)) {
|
|
1621
|
-
const $corresp = this.writeCorresp(node as CorrespNode)
|
|
1622
|
-
$authorNotes.append($corresp)
|
|
1623
|
-
}
|
|
1624
|
-
break
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
return false
|
|
1628
|
-
})
|
|
1629
|
-
return $authorNotes.hasChildNodes() ? $authorNotes : undefined
|
|
1623
|
+
return $name
|
|
1630
1624
|
}
|
|
1631
1625
|
|
|
1632
1626
|
private writeCorresp = (corresp: CorrespNode) => {
|
|
@@ -1663,460 +1657,195 @@ export class JATSExporter {
|
|
|
1663
1657
|
return $fn
|
|
1664
1658
|
}
|
|
1665
1659
|
|
|
1666
|
-
private
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
articleMeta.appendChild(kwdGroup)
|
|
1681
|
-
})
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
private fixBody = (body: Element) => {
|
|
1685
|
-
this.manuscriptNode.descendants((node) => {
|
|
1686
|
-
if (node.attrs.id) {
|
|
1687
|
-
if (
|
|
1688
|
-
isNodeType<TableElementFooterNode>(node, 'general_table_footnote')
|
|
1689
|
-
) {
|
|
1690
|
-
const generalTableFootnote = body.querySelector(
|
|
1691
|
-
`#${normalizeID(node.attrs.id)}`
|
|
1692
|
-
)
|
|
1693
|
-
if (generalTableFootnote) {
|
|
1694
|
-
Array.from(generalTableFootnote.childNodes).forEach((cn) => {
|
|
1695
|
-
generalTableFootnote.before(cn)
|
|
1696
|
-
})
|
|
1697
|
-
generalTableFootnote.remove()
|
|
1698
|
-
}
|
|
1660
|
+
private appendAuthorNotes = (
|
|
1661
|
+
$authorNotes: Element,
|
|
1662
|
+
authorNotes: AuthorNotesNode
|
|
1663
|
+
) => {
|
|
1664
|
+
const correspIDs = new Set(
|
|
1665
|
+
this.getChildrenOfType<ContributorNode>(schema.nodes.contributor).flatMap(
|
|
1666
|
+
(contributor) => contributor.attrs.correspIDs
|
|
1667
|
+
)
|
|
1668
|
+
)
|
|
1669
|
+
authorNotes.forEach((node) => {
|
|
1670
|
+
switch (node.type) {
|
|
1671
|
+
case schema.nodes.paragraph: {
|
|
1672
|
+
$authorNotes.append(this.writeParagraph(node as ParagraphNode))
|
|
1673
|
+
break
|
|
1699
1674
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
for (const childNode of tableElement.childNodes) {
|
|
1708
|
-
switch (childNode.nodeName) {
|
|
1709
|
-
case 'caption': {
|
|
1710
|
-
const label = tableElement.querySelector('label')
|
|
1711
|
-
|
|
1712
|
-
tableElement.insertBefore(
|
|
1713
|
-
childNode,
|
|
1714
|
-
label ? label.nextSibling : tableElement.firstChild
|
|
1715
|
-
)
|
|
1716
|
-
break
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
case 'table': {
|
|
1720
|
-
this.fixTable(childNode)
|
|
1721
|
-
break
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1675
|
+
case schema.nodes.footnote: {
|
|
1676
|
+
$authorNotes.append(this.writeFootnote(node as FootnoteNode))
|
|
1677
|
+
break
|
|
1678
|
+
}
|
|
1679
|
+
case schema.nodes.corresp: {
|
|
1680
|
+
if (correspIDs.has(node.attrs.id)) {
|
|
1681
|
+
$authorNotes.append(this.writeCorresp(node as CorrespNode))
|
|
1725
1682
|
}
|
|
1683
|
+
break
|
|
1726
1684
|
}
|
|
1727
1685
|
}
|
|
1728
1686
|
})
|
|
1729
1687
|
}
|
|
1730
1688
|
|
|
1731
|
-
private
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1689
|
+
private buildKeywords = (): Node[] =>
|
|
1690
|
+
this.getChildrenOfType(schema.nodes.keyword_group).map((group) =>
|
|
1691
|
+
this.serializeNode(group)
|
|
1692
|
+
)
|
|
1693
|
+
|
|
1694
|
+
private changeTag = ($node: Element, tag: string) => {
|
|
1695
|
+
const $clone = this.createElement(tag)
|
|
1696
|
+
for (const attr of $node.attributes) {
|
|
1697
|
+
$clone.setAttributeNS(null, attr.name, attr.value)
|
|
1735
1698
|
}
|
|
1736
|
-
while (node.firstChild) {
|
|
1737
|
-
clone.appendChild(node.firstChild)
|
|
1699
|
+
while ($node.firstChild) {
|
|
1700
|
+
$clone.appendChild($node.firstChild)
|
|
1738
1701
|
}
|
|
1739
|
-
node.replaceWith(clone)
|
|
1740
|
-
return clone
|
|
1702
|
+
$node.replaceWith($clone)
|
|
1703
|
+
return $clone
|
|
1741
1704
|
}
|
|
1742
1705
|
|
|
1743
|
-
private
|
|
1744
|
-
let tbody: Element | undefined
|
|
1706
|
+
private normalizeTable = ($table: Node) => {
|
|
1707
|
+
let $tbody: Element | undefined
|
|
1745
1708
|
|
|
1746
|
-
Array.from(table.childNodes).forEach((child) => {
|
|
1747
|
-
if (
|
|
1748
|
-
|
|
1709
|
+
Array.from($table.childNodes).forEach(($child) => {
|
|
1710
|
+
if (
|
|
1711
|
+
$child instanceof Element &&
|
|
1712
|
+
$child.tagName.toLowerCase() === 'tbody'
|
|
1713
|
+
) {
|
|
1714
|
+
$tbody = $child
|
|
1749
1715
|
}
|
|
1750
1716
|
})
|
|
1751
1717
|
|
|
1752
|
-
if (
|
|
1718
|
+
if (!$tbody) {
|
|
1753
1719
|
return
|
|
1754
1720
|
}
|
|
1755
1721
|
|
|
1756
|
-
const tbodyRows = Array.from(tbody.childNodes)
|
|
1757
|
-
const thead = this.createElement('thead')
|
|
1722
|
+
const tbodyRows = Array.from($tbody.childNodes)
|
|
1723
|
+
const $thead = this.createElement('thead')
|
|
1758
1724
|
|
|
1759
|
-
tbodyRows.forEach((row, i) => {
|
|
1760
|
-
const isRow =
|
|
1725
|
+
tbodyRows.forEach(($row, i) => {
|
|
1726
|
+
const isRow =
|
|
1727
|
+
$row instanceof Element && $row.tagName.toLowerCase() === 'tr'
|
|
1761
1728
|
if (isRow) {
|
|
1762
1729
|
// we assume that <th scope="col | colgroup"> always belongs to <thead>
|
|
1763
|
-
const headerCell = (row as Element).querySelector(
|
|
1730
|
+
const $headerCell = ($row as Element).querySelector(
|
|
1764
1731
|
'th[scope="col"], th[scope="colgroup"]'
|
|
1765
1732
|
)
|
|
1766
|
-
if (i === 0 || headerCell) {
|
|
1767
|
-
tbody?.removeChild(row)
|
|
1768
|
-
const tableCells = (row as Element).querySelectorAll('td')
|
|
1769
|
-
for (const td of tableCells) {
|
|
1733
|
+
if (i === 0 || $headerCell) {
|
|
1734
|
+
$tbody?.removeChild($row)
|
|
1735
|
+
const $tableCells = ($row as Element).querySelectorAll('td')
|
|
1736
|
+
for (const $td of $tableCells) {
|
|
1770
1737
|
// for backwards compatibility since older docs use tds for header cells
|
|
1771
|
-
this.changeTag(td, 'th')
|
|
1738
|
+
this.changeTag($td, 'th')
|
|
1772
1739
|
}
|
|
1773
|
-
thead.appendChild(row)
|
|
1740
|
+
$thead.appendChild($row)
|
|
1774
1741
|
}
|
|
1775
1742
|
}
|
|
1776
1743
|
})
|
|
1777
1744
|
|
|
1778
|
-
if (thead.hasChildNodes()) {
|
|
1779
|
-
table.insertBefore(thead, tbody as Element)
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
private unwrapBody = (body: HTMLElement) => {
|
|
1784
|
-
const container = body.querySelector(':scope > sec[sec-type="body"]')
|
|
1785
|
-
if (!container) {
|
|
1786
|
-
return
|
|
1787
|
-
}
|
|
1788
|
-
const sections = container.querySelectorAll(':scope > sec')
|
|
1789
|
-
sections.forEach((section) => {
|
|
1790
|
-
body.appendChild(section.cloneNode(true))
|
|
1791
|
-
})
|
|
1792
|
-
body.removeChild(container)
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
private removeBackContainer = (body: HTMLElement) => {
|
|
1796
|
-
const container = body.querySelector(':scope > sec[sec-type="backmatter"]')
|
|
1797
|
-
if (!container) {
|
|
1798
|
-
return
|
|
1799
|
-
}
|
|
1800
|
-
body.removeChild(container)
|
|
1801
|
-
}
|
|
1802
|
-
private moveAwards = (front: HTMLElement, body: HTMLElement) => {
|
|
1803
|
-
const awardGroups = body.querySelectorAll(':scope > award-group')
|
|
1804
|
-
if (!awardGroups.length) {
|
|
1805
|
-
return
|
|
1806
|
-
}
|
|
1807
|
-
const fundingGroup = this.createElement('funding-group')
|
|
1808
|
-
awardGroups.forEach((award) => {
|
|
1809
|
-
fundingGroup.appendChild(award)
|
|
1810
|
-
})
|
|
1811
|
-
const articleMeta = front.querySelector(':scope > article-meta')
|
|
1812
|
-
|
|
1813
|
-
if (articleMeta) {
|
|
1814
|
-
const insertBeforeElement = articleMeta.querySelector(
|
|
1815
|
-
':scope > support-group, :scope > conference, :scope > counts, :scope > custom-meta-group'
|
|
1816
|
-
)
|
|
1817
|
-
insertBeforeElement
|
|
1818
|
-
? articleMeta.insertBefore(fundingGroup, insertBeforeElement)
|
|
1819
|
-
: articleMeta.appendChild(fundingGroup)
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
private moveAbstracts = (front: HTMLElement, body: HTMLElement) => {
|
|
1824
|
-
const abstractSections = this.getAbstractSections(body)
|
|
1825
|
-
|
|
1826
|
-
for (const abstractSection of abstractSections) {
|
|
1827
|
-
const node =
|
|
1828
|
-
abstractSection.nodeName === 'trans-abstract'
|
|
1829
|
-
? this.createTransAbstractNode(abstractSection)
|
|
1830
|
-
: this.createAbstractNode(abstractSection)
|
|
1831
|
-
abstractSection.remove()
|
|
1832
|
-
insertAbstractNode(front, node)
|
|
1745
|
+
if ($thead.hasChildNodes()) {
|
|
1746
|
+
$table.insertBefore($thead, $tbody as Element)
|
|
1833
1747
|
}
|
|
1834
1748
|
}
|
|
1835
1749
|
|
|
1836
|
-
private
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
private getAbstractSections(body: HTMLElement) {
|
|
1848
|
-
{
|
|
1849
|
-
const abstractsNode = this.getFirstChildOfType(schema.nodes.abstracts)
|
|
1850
|
-
const abstractCategories = this.getAbstractCategories(abstractsNode)
|
|
1851
|
-
const sections = Array.from(
|
|
1852
|
-
body.querySelectorAll(':scope > sec, :scope > trans-abstract')
|
|
1853
|
-
)
|
|
1854
|
-
return sections.filter((section) =>
|
|
1855
|
-
this.isAbstractSection(section, abstractCategories)
|
|
1856
|
-
)
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
private isAbstractSection(
|
|
1861
|
-
section: Element,
|
|
1862
|
-
abstractCategories: string[]
|
|
1863
|
-
): boolean {
|
|
1864
|
-
const sectionType = section.getAttribute('sec-type')
|
|
1865
|
-
return sectionType ? abstractCategories.includes(sectionType) : false
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
private createTransAbstractNode(transAbstract: Element): Element {
|
|
1869
|
-
const transAbstractNode = this.createElement('trans-abstract')
|
|
1870
|
-
transAbstractNode.setAttributeNS(
|
|
1871
|
-
XML_NAMESPACE,
|
|
1872
|
-
'lang',
|
|
1873
|
-
transAbstract.getAttributeNS(XML_NAMESPACE, 'lang') ?? ''
|
|
1874
|
-
)
|
|
1875
|
-
this.setAbstractType(transAbstractNode, transAbstract)
|
|
1876
|
-
transAbstractNode.append(...transAbstract.childNodes)
|
|
1877
|
-
return transAbstractNode
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
private createAbstractNode(abstractSection: Element): Element {
|
|
1881
|
-
const abstractNode = this.createElement('abstract')
|
|
1882
|
-
for (const node of abstractSection.childNodes) {
|
|
1883
|
-
if (node.nodeName !== 'title') {
|
|
1884
|
-
abstractNode.appendChild(node.cloneNode(true))
|
|
1750
|
+
private buildAbstracts = () => {
|
|
1751
|
+
const $abstracts: Element[] = []
|
|
1752
|
+
const abstractsNode = this.getFirstChildOfType(schema.nodes.abstracts)
|
|
1753
|
+
abstractsNode?.forEach((child) => {
|
|
1754
|
+
const $abstract = this.serializeNode(child) as Element
|
|
1755
|
+
if ($abstract.nodeName === 'abstract') {
|
|
1756
|
+
$abstract
|
|
1757
|
+
.querySelectorAll(':scope > title')
|
|
1758
|
+
.forEach((title) => title.remove())
|
|
1885
1759
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
return
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
private setAbstractType(abstractNode: Element, abstractSection: Element) {
|
|
1892
|
-
const sectionType = abstractSection.getAttribute('sec-type')
|
|
1893
|
-
if (sectionType && sectionType !== 'abstract') {
|
|
1894
|
-
const abstractType = sectionType.replace('abstract-', '')
|
|
1895
|
-
abstractNode.setAttribute('abstract-type', abstractType)
|
|
1896
|
-
}
|
|
1760
|
+
$abstracts.push($abstract)
|
|
1761
|
+
})
|
|
1762
|
+
return $abstracts
|
|
1897
1763
|
}
|
|
1898
1764
|
|
|
1899
|
-
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
)
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
const section = body.querySelector('sec[sec-type="acknowledgements"]')
|
|
1914
|
-
|
|
1915
|
-
if (section) {
|
|
1916
|
-
const ack = this.createElement('ack')
|
|
1917
|
-
|
|
1918
|
-
while (section.firstChild) {
|
|
1919
|
-
ack.appendChild(section.firstChild)
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
if (section.parentNode) {
|
|
1923
|
-
section.parentNode.removeChild(section)
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
back.insertBefore(ack, back.firstChild)
|
|
1927
|
-
}
|
|
1928
|
-
const appendicesSections = body.querySelectorAll(
|
|
1929
|
-
'sec[sec-type="appendices"]'
|
|
1930
|
-
)
|
|
1931
|
-
|
|
1932
|
-
if (appendicesSections) {
|
|
1933
|
-
const appGroup = this.createElement('app-group')
|
|
1934
|
-
appendicesSections.forEach((section) => {
|
|
1935
|
-
if (section.parentNode) {
|
|
1936
|
-
section.parentNode.removeChild(section)
|
|
1937
|
-
}
|
|
1938
|
-
const app = this.createElement('app')
|
|
1939
|
-
app.appendChild(section)
|
|
1940
|
-
appGroup.appendChild(app)
|
|
1941
|
-
})
|
|
1942
|
-
back.insertBefore(appGroup, back.firstChild)
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
const footNotes = []
|
|
1946
|
-
|
|
1947
|
-
const footnoteCategories = [
|
|
1948
|
-
'con',
|
|
1949
|
-
'conflict',
|
|
1950
|
-
'deceased',
|
|
1951
|
-
'equal',
|
|
1952
|
-
'present-address',
|
|
1953
|
-
'presented-at',
|
|
1954
|
-
'previously-at',
|
|
1955
|
-
'supplementary-material',
|
|
1956
|
-
'supported-by',
|
|
1957
|
-
'financial-disclosure',
|
|
1958
|
-
'coi-statement',
|
|
1959
|
-
]
|
|
1960
|
-
|
|
1961
|
-
const sections = body.querySelectorAll('sec')
|
|
1962
|
-
for (const currentSection of sections) {
|
|
1963
|
-
const currentSectionType = currentSection.getAttribute('sec-type')
|
|
1964
|
-
if (
|
|
1965
|
-
currentSectionType &&
|
|
1966
|
-
footnoteCategories.indexOf(currentSectionType) >= 0
|
|
1967
|
-
) {
|
|
1968
|
-
footNotes.push(
|
|
1969
|
-
this.sectionToFootnote(currentSection, currentSectionType)
|
|
1970
|
-
)
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
if (footNotes.length > 0) {
|
|
1975
|
-
const fnGroup = this.createElement('fn-group')
|
|
1976
|
-
fnGroup.append(...footNotes)
|
|
1977
|
-
back.append(fnGroup)
|
|
1765
|
+
sectionToFootnote = ($section: Element, fnType: string) => {
|
|
1766
|
+
const $fn = this.createElement('fn')
|
|
1767
|
+
$fn.setAttribute('fn-type', fnType)
|
|
1768
|
+
const $title = $section.querySelector(':scope > title')
|
|
1769
|
+
if ($title) {
|
|
1770
|
+
const $label = this.createElement('label')
|
|
1771
|
+
$label.textContent = $title.textContent
|
|
1772
|
+
$section.removeChild($title)
|
|
1773
|
+
$fn.append($label)
|
|
1774
|
+
}
|
|
1775
|
+
$fn.append(...$section.children)
|
|
1776
|
+
if ($section.parentNode) {
|
|
1777
|
+
$section.parentNode.removeChild($section)
|
|
1978
1778
|
}
|
|
1779
|
+
return $fn
|
|
1979
1780
|
}
|
|
1980
|
-
|
|
1981
|
-
sectionToFootnote = (section: Element, fnType: string) => {
|
|
1982
|
-
const footNote = this.createElement('fn')
|
|
1983
|
-
footNote.setAttribute('fn-type', fnType)
|
|
1984
|
-
const title = section.querySelector('title')
|
|
1985
|
-
if (title) {
|
|
1986
|
-
const footNoteTitle = this.createElement('label')
|
|
1987
|
-
footNoteTitle.textContent = title.textContent
|
|
1988
|
-
section.removeChild(title)
|
|
1989
|
-
footNote.append(footNoteTitle)
|
|
1990
|
-
}
|
|
1991
|
-
footNote.append(...section.children)
|
|
1992
|
-
if (section.parentNode) {
|
|
1993
|
-
section.parentNode.removeChild(section)
|
|
1994
|
-
}
|
|
1995
|
-
return footNote
|
|
1996
|
-
}
|
|
1997
|
-
private moveFloatsGroup = (article: HTMLElement) => {
|
|
1781
|
+
private buildFloatsGroup = () => {
|
|
1998
1782
|
const heroImage = this.getFirstChildOfType(schema.nodes.hero_image)
|
|
1999
1783
|
if (!heroImage) {
|
|
2000
1784
|
return
|
|
2001
1785
|
}
|
|
2002
|
-
const floatsGroup = this.createElement('floats-group')
|
|
2003
|
-
let
|
|
1786
|
+
const $floatsGroup = this.createElement('floats-group')
|
|
1787
|
+
let $graphic: Element | null = null
|
|
2004
1788
|
heroImage.descendants((node) => {
|
|
2005
1789
|
if (node.type === schema.nodes.figure) {
|
|
2006
|
-
|
|
2007
|
-
floatsGroup.appendChild(
|
|
1790
|
+
$graphic = this.serializeNode(node) as Element
|
|
1791
|
+
$floatsGroup.appendChild($graphic)
|
|
2008
1792
|
} else {
|
|
2009
|
-
const serializedNode = this.serializeNode(node)
|
|
2010
|
-
|
|
1793
|
+
const $serializedNode = this.serializeNode(node)
|
|
1794
|
+
$graphic?.appendChild($serializedNode)
|
|
2011
1795
|
}
|
|
2012
1796
|
return false
|
|
2013
1797
|
})
|
|
2014
1798
|
|
|
2015
|
-
if (floatsGroup.children.length > 0) {
|
|
2016
|
-
|
|
1799
|
+
if ($floatsGroup.children.length > 0) {
|
|
1800
|
+
return $floatsGroup
|
|
2017
1801
|
}
|
|
2018
1802
|
}
|
|
2019
1803
|
|
|
2020
|
-
private moveCoiStatementToAuthorNotes(back: HTMLElement, front: HTMLElement) {
|
|
2021
|
-
const fnGroups = back.querySelectorAll('fn-group')
|
|
2022
|
-
fnGroups.forEach((fnGroup) => {
|
|
2023
|
-
if (fnGroup) {
|
|
2024
|
-
const coiStatement = fnGroup.querySelector(
|
|
2025
|
-
'fn[fn-type="coi-statement"]'
|
|
2026
|
-
)
|
|
2027
|
-
if (coiStatement) {
|
|
2028
|
-
const authorNotes = this.createElement('author-notes')
|
|
2029
|
-
authorNotes.append(coiStatement)
|
|
2030
|
-
const articleMeta = front.querySelector('article-meta')
|
|
2031
|
-
if (articleMeta) {
|
|
2032
|
-
const authorNoteEl = articleMeta.querySelector('author-notes')
|
|
2033
|
-
if (authorNoteEl) {
|
|
2034
|
-
authorNoteEl.append(...authorNotes.childNodes)
|
|
2035
|
-
} else {
|
|
2036
|
-
const appendableSelectors = [
|
|
2037
|
-
'contrib-group',
|
|
2038
|
-
'title-group',
|
|
2039
|
-
'article-id',
|
|
2040
|
-
]
|
|
2041
|
-
const appendable = [
|
|
2042
|
-
...(articleMeta as HTMLElement).querySelectorAll(
|
|
2043
|
-
appendableSelectors.join(', ')
|
|
2044
|
-
),
|
|
2045
|
-
]
|
|
2046
|
-
for (let i = 0; i < appendableSelectors.length; i++) {
|
|
2047
|
-
const sel = appendableSelectors[i]
|
|
2048
|
-
const match = appendable.find((el) => el.matches(sel))
|
|
2049
|
-
if (match) {
|
|
2050
|
-
articleMeta.insertBefore(authorNotes, match.nextSibling)
|
|
2051
|
-
break
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
if (!fnGroup.hasChildNodes()) {
|
|
2057
|
-
fnGroup.remove()
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
})
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
private updateFootnoteTypes(front: HTMLElement, body: HTMLElement) {
|
|
2065
|
-
const footnotes: Element[] = [...front.querySelectorAll('fn').values()]
|
|
2066
|
-
footnotes.push(...body.querySelectorAll('fn'))
|
|
2067
|
-
footnotes.forEach((fn) => {
|
|
2068
|
-
const fnType = fn.getAttribute('fn-type')
|
|
2069
|
-
if (fnType) {
|
|
2070
|
-
fn.setAttribute('fn-type', fnType)
|
|
2071
|
-
}
|
|
2072
|
-
})
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
1804
|
private fillEmptyElements(
|
|
2076
|
-
|
|
1805
|
+
$article: Element,
|
|
2077
1806
|
selector: string,
|
|
2078
1807
|
tagName = 'p'
|
|
2079
1808
|
) {
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
)
|
|
2083
|
-
|
|
2084
|
-
element.appendChild(this.createElement(tagName))
|
|
1809
|
+
const $empty = Array.from($article.querySelectorAll(selector)).filter(
|
|
1810
|
+
($el) => !$el.innerHTML
|
|
1811
|
+
)
|
|
1812
|
+
$empty.forEach(($element) =>
|
|
1813
|
+
$element.appendChild(this.createElement(tagName))
|
|
2085
1814
|
)
|
|
2086
1815
|
}
|
|
2087
|
-
private addParagraphsToSections(
|
|
2088
|
-
const sections =
|
|
1816
|
+
private addParagraphsToSections($article: Element) {
|
|
1817
|
+
const $sections = $article.querySelectorAll('sec, abstract')
|
|
2089
1818
|
const TITLE_TAGS = new Set(['title', 'label', 'sec-meta'])
|
|
2090
|
-
for (const section of sections) {
|
|
2091
|
-
const hasContent = Array.from(section.children).some(
|
|
2092
|
-
(child) => !TITLE_TAGS.has(child.tagName)
|
|
1819
|
+
for (const $section of $sections) {
|
|
1820
|
+
const hasContent = Array.from($section.children).some(
|
|
1821
|
+
($child) => !TITLE_TAGS.has($child.tagName)
|
|
2093
1822
|
)
|
|
2094
1823
|
if (hasContent) {
|
|
2095
1824
|
continue
|
|
2096
1825
|
}
|
|
2097
|
-
const p = this.createElement('p')
|
|
2098
|
-
const
|
|
2099
|
-
section.querySelector(':scope > title') ??
|
|
2100
|
-
section.querySelector(':scope > label') ??
|
|
2101
|
-
section.querySelector(':scope > sec-meta')
|
|
2102
|
-
|
|
2103
|
-
if (
|
|
2104
|
-
|
|
1826
|
+
const $p = this.createElement('p')
|
|
1827
|
+
const $insertAfter =
|
|
1828
|
+
$section.querySelector(':scope > title') ??
|
|
1829
|
+
$section.querySelector(':scope > label') ??
|
|
1830
|
+
$section.querySelector(':scope > sec-meta')
|
|
1831
|
+
|
|
1832
|
+
if ($insertAfter) {
|
|
1833
|
+
$insertAfter.insertAdjacentElement('afterend', $p)
|
|
2105
1834
|
} else {
|
|
2106
|
-
section.prepend(p)
|
|
1835
|
+
$section.prepend($p)
|
|
2107
1836
|
}
|
|
2108
1837
|
}
|
|
2109
1838
|
}
|
|
2110
1839
|
|
|
2111
|
-
private fillEmptyFootnotes(
|
|
2112
|
-
this.fillEmptyElements(
|
|
1840
|
+
private fillEmptyFootnotes($article: Element) {
|
|
1841
|
+
this.fillEmptyElements($article, 'fn')
|
|
2113
1842
|
}
|
|
2114
1843
|
|
|
2115
|
-
private fillEmptyTableFooters(
|
|
2116
|
-
this.fillEmptyElements(
|
|
1844
|
+
private fillEmptyTableFooters($article: Element) {
|
|
1845
|
+
this.fillEmptyElements($article, 'table-wrap-foot')
|
|
2117
1846
|
}
|
|
2118
1847
|
|
|
2119
|
-
private fillEmptyListItem(
|
|
2120
|
-
this.fillEmptyElements(
|
|
1848
|
+
private fillEmptyListItem($article: Element) {
|
|
1849
|
+
this.fillEmptyElements($article, 'list-item')
|
|
2121
1850
|
}
|
|
2122
1851
|
}
|