@fiduswriter/document 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +16 -0
  3. package/jest.config.js +23 -0
  4. package/package.json +59 -0
  5. package/schema.json +1 -0
  6. package/scripts/export-schema.js +16 -0
  7. package/src/bibliography/common.js +92 -0
  8. package/src/bibliography/csl_bib.js +139 -0
  9. package/src/citations/citeproc_sys.js +42 -0
  10. package/src/citations/format.js +194 -0
  11. package/src/common/blob.js +10 -0
  12. package/src/common/file.js +25 -0
  13. package/src/common/index.js +12 -0
  14. package/src/common/network.js +79 -0
  15. package/src/common/text.js +44 -0
  16. package/src/editor/e2ee/encryptor.js +228 -0
  17. package/src/exporter/docx/citations.js +177 -0
  18. package/src/exporter/docx/comments.js +165 -0
  19. package/src/exporter/docx/footnotes.js +240 -0
  20. package/src/exporter/docx/images.js +101 -0
  21. package/src/exporter/docx/index.js +185 -0
  22. package/src/exporter/docx/lists.js +260 -0
  23. package/src/exporter/docx/math.js +46 -0
  24. package/src/exporter/docx/metadata.js +289 -0
  25. package/src/exporter/docx/rels.js +193 -0
  26. package/src/exporter/docx/render.js +941 -0
  27. package/src/exporter/docx/richtext.js +1182 -0
  28. package/src/exporter/docx/tables.js +112 -0
  29. package/src/exporter/docx/tools.js +50 -0
  30. package/src/exporter/epub/index.js +142 -0
  31. package/src/exporter/epub/templates.js +140 -0
  32. package/src/exporter/epub/tools.js +96 -0
  33. package/src/exporter/html/citations.js +121 -0
  34. package/src/exporter/html/convert.js +813 -0
  35. package/src/exporter/html/index.js +192 -0
  36. package/src/exporter/html/templates.js +34 -0
  37. package/src/exporter/html/tools.js +50 -0
  38. package/src/exporter/jats/bibliography.js +183 -0
  39. package/src/exporter/jats/citations.js +109 -0
  40. package/src/exporter/jats/convert.js +871 -0
  41. package/src/exporter/jats/index.js +92 -0
  42. package/src/exporter/jats/templates.js +35 -0
  43. package/src/exporter/jats/text.js +72 -0
  44. package/src/exporter/latex/convert.js +934 -0
  45. package/src/exporter/latex/escape_latex.js +21 -0
  46. package/src/exporter/latex/index.js +74 -0
  47. package/src/exporter/latex/readme.js +22 -0
  48. package/src/exporter/native/shrink.js +132 -0
  49. package/src/exporter/odt/citations.js +101 -0
  50. package/src/exporter/odt/footnotes.js +147 -0
  51. package/src/exporter/odt/images.js +115 -0
  52. package/src/exporter/odt/index.js +156 -0
  53. package/src/exporter/odt/math.js +57 -0
  54. package/src/exporter/odt/metadata.js +251 -0
  55. package/src/exporter/odt/render.js +806 -0
  56. package/src/exporter/odt/richtext.js +865 -0
  57. package/src/exporter/odt/styles.js +387 -0
  58. package/src/exporter/odt/track.js +68 -0
  59. package/src/exporter/pandoc/citations.js +98 -0
  60. package/src/exporter/pandoc/convert.js +1017 -0
  61. package/src/exporter/pandoc/index.js +92 -0
  62. package/src/exporter/pandoc/readme.js +8 -0
  63. package/src/exporter/pandoc/tools.js +51 -0
  64. package/src/exporter/print/index.js +177 -0
  65. package/src/exporter/tools/doc_content.js +144 -0
  66. package/src/exporter/tools/file.js +9 -0
  67. package/src/exporter/tools/json.js +73 -0
  68. package/src/exporter/tools/svg.js +29 -0
  69. package/src/exporter/tools/xml.js +531 -0
  70. package/src/exporter/tools/xml_zip.js +95 -0
  71. package/src/exporter/tools/zip.js +90 -0
  72. package/src/exporter/tools/zotero_csl.js +93 -0
  73. package/src/importer/citations.js +129 -0
  74. package/src/importer/docx/citations.js +123 -0
  75. package/src/importer/docx/convert.js +1427 -0
  76. package/src/importer/docx/helpers.js +9 -0
  77. package/src/importer/docx/omml2mathml.js +1448 -0
  78. package/src/importer/docx/parse.js +735 -0
  79. package/src/importer/native/get_images.js +76 -0
  80. package/src/importer/native/update.js +29 -0
  81. package/src/importer/odt/citations.js +87 -0
  82. package/src/importer/odt/convert.js +1855 -0
  83. package/src/importer/pandoc/convert.js +884 -0
  84. package/src/importer/pandoc/helpers.js +84 -0
  85. package/src/importer/zip_analyzer.js +102 -0
  86. package/src/index.js +1 -0
  87. package/src/mathlive/opf_includes.js +24 -0
  88. package/src/schema/common/annotate.js +76 -0
  89. package/src/schema/common/base.js +118 -0
  90. package/src/schema/common/citation.js +62 -0
  91. package/src/schema/common/equation.js +31 -0
  92. package/src/schema/common/figure.js +190 -0
  93. package/src/schema/common/heading.js +43 -0
  94. package/src/schema/common/index.js +40 -0
  95. package/src/schema/common/list.js +95 -0
  96. package/src/schema/common/reference.js +100 -0
  97. package/src/schema/common/table.js +103 -0
  98. package/src/schema/common/track.js +190 -0
  99. package/src/schema/const.js +58 -0
  100. package/src/schema/convert.js +1272 -0
  101. package/src/schema/document/content.js +187 -0
  102. package/src/schema/document/index.js +117 -0
  103. package/src/schema/document/structure.js +452 -0
  104. package/src/schema/export.js +21 -0
  105. package/src/schema/footnotes.js +126 -0
  106. package/src/schema/footnotes_convert.js +31 -0
  107. package/src/schema/i18n.js +595 -0
  108. package/src/schema/index.js +5 -0
  109. package/src/schema/mini_json.js +61 -0
  110. package/src/schema/text.js +22 -0
@@ -0,0 +1,871 @@
1
+ import {convertLatexToMathMl} from "mathlive"
2
+
3
+ import {escapeText} from "../../common/index.js"
4
+ import {CATS} from "../../schema/i18n.js"
5
+
6
+ import {removeHidden} from "../tools/doc_content.js"
7
+
8
+ import {JATSExporterCitations} from "./citations.js"
9
+ import {convertText} from "./text.js"
10
+
11
+ export class JATSExporterConverter {
12
+ constructor(type, doc, csl, imageDB, bibDB) {
13
+ this.type = type
14
+ this.doc = doc
15
+ this.csl = csl
16
+ this.imageDB = imageDB
17
+ this.bibDB = bibDB
18
+ this.imageIds = []
19
+ this.categoryCounter = {} // counters for each type of figure (figure/table/photo)
20
+ this.affiliations = {} // affiliations of authors and editors
21
+ this.affCounter = 0
22
+ this.parCounter = 0
23
+ this.headingCounter = 0
24
+ this.currentSectionLevel = 0
25
+ this.listCounter = 0
26
+ this.orderedListLengths = []
27
+ this.footnotes = []
28
+ this.fnCounter = 0
29
+ this.frontMatter = {
30
+ title: {},
31
+ subtitle: {},
32
+ contributors: [],
33
+ abstract: {},
34
+ keywords: [],
35
+ tags: [],
36
+ copyright: {
37
+ licenses: []
38
+ }
39
+ }
40
+ this.citInfos = []
41
+ this.citationCount = 0
42
+ this.citations = new JATSExporterCitations(
43
+ this.doc,
44
+ this.bibDB,
45
+ this.csl
46
+ )
47
+ }
48
+
49
+ init() {
50
+ const docContent = removeHidden(this.doc.content)
51
+ this.preWalkJson(docContent)
52
+ this.findAllCitations(docContent)
53
+ return this.citations.init(this.citInfos).then(() => {
54
+ const front =
55
+ this.type === "article"
56
+ ? this.assembleArticleFront()
57
+ : this.assembleBookPartFront()
58
+ const body = this.assembleBody(docContent)
59
+ const back = this.assembleBack()
60
+ return {
61
+ front,
62
+ body,
63
+ back,
64
+ imageIds: this.imageIds
65
+ }
66
+ })
67
+ }
68
+
69
+ // Remove items from the body that should be in the front.
70
+ preWalkJson(node, parentNode = false) {
71
+ switch (node.type) {
72
+ case "doc":
73
+ this.frontMatter.copyright = node.attrs.copyright
74
+ break
75
+ case "title":
76
+ this.frontMatter.title["default"] = node
77
+ parentNode.content = parentNode.content.filter(
78
+ child => child !== node
79
+ )
80
+ break
81
+ case "heading_part":
82
+ if (
83
+ ["title", "subtitle"].includes(node.attrs.metadata) &&
84
+ !this.frontMatter[node.attrs.metadata][
85
+ node.attrs.language || "default"
86
+ ] &&
87
+ node.content &&
88
+ node.content.length
89
+ ) {
90
+ // We only take the first instance of title/subtitle per language
91
+ this.frontMatter[node.attrs.metadata][
92
+ node.attrs.language || "default"
93
+ ] = {
94
+ type: node.attrs.language
95
+ ? `trans_${node.attrs.metadata}`
96
+ : node.attrs.metadata,
97
+ attrs: {
98
+ id: node.content[0].attrs.id,
99
+ language: node.attrs.language
100
+ },
101
+ content: node.content[0].content
102
+ }
103
+ parentNode.content = parentNode.content.filter(
104
+ child => child !== node
105
+ )
106
+ }
107
+ break
108
+ case "richtext_part":
109
+ if (
110
+ node.attrs.metadata === "abstract" &&
111
+ !this.frontMatter.abstract[node.attrs.language || "default"]
112
+ ) {
113
+ // We only take the first instance of abstract per language
114
+ this.frontMatter.abstract[
115
+ node.attrs.language || "default"
116
+ ] = {
117
+ type: node.attrs.language
118
+ ? "trans_abstract"
119
+ : "abstract",
120
+ attrs: {
121
+ id: node.attrs.id,
122
+ language: node.attrs.language
123
+ },
124
+ content: node.content
125
+ }
126
+ parentNode.content = parentNode.content.filter(
127
+ child => child !== node
128
+ )
129
+ }
130
+ break
131
+ case "tags_part":
132
+ if (node.attrs.metadata === "keywords" && node.content) {
133
+ this.frontMatter.keywords.push({
134
+ type: "keywords",
135
+ attrs: {
136
+ language: node.attrs.language
137
+ },
138
+ content: node.content
139
+ })
140
+ } else {
141
+ this.frontMatter.tags.push(node)
142
+ }
143
+ parentNode.content = parentNode.content.filter(
144
+ child => child !== node
145
+ )
146
+ break
147
+ case "contributors_part":
148
+ this.frontMatter.contributors.push(node)
149
+ parentNode.content = parentNode.content.filter(
150
+ child => child !== node
151
+ )
152
+ break
153
+ default:
154
+ break
155
+ }
156
+ if (node.content) {
157
+ node.content.forEach(child => this.preWalkJson(child, node))
158
+ }
159
+ }
160
+
161
+ findAllCitations(docContent) {
162
+ // We need to look for citations in the same order they will be found in front + body
163
+ // to get the formatting right.
164
+ if (this.frontMatter.subtitle.default) {
165
+ this.findCitations(this.frontMatter.subtitle.default)
166
+ }
167
+ Object.keys(this.frontMatter.title)
168
+ .filter(language => language !== "default")
169
+ .forEach(language => {
170
+ this.findCitations(this.frontMatter.title[language])
171
+ if (this.frontMatter.subtitle[language]) {
172
+ this.findCitations(this.frontMatter.subtitle[language])
173
+ }
174
+ })
175
+ if (this.frontMatter.abstract.default) {
176
+ this.findCitations(this.frontMatter.abstract.default)
177
+ }
178
+ Object.keys(this.frontMatter.abstract)
179
+ .filter(language => language !== "default")
180
+ .forEach(language => {
181
+ this.findCitations(this.frontMatter.abstract[language])
182
+ })
183
+ this.findCitations(docContent)
184
+ }
185
+
186
+ findCitations(node) {
187
+ switch (node.type) {
188
+ case "citation":
189
+ this.citInfos.push(JSON.parse(JSON.stringify(node.attrs)))
190
+ break
191
+ case "footnote":
192
+ node.attrs.footnote.forEach(child => this.findCitations(child))
193
+ break
194
+ default:
195
+ break
196
+ }
197
+ if (node.content) {
198
+ node.content.forEach(child => this.findCitations(child))
199
+ }
200
+ }
201
+
202
+ assembleArticleFront() {
203
+ let front = "<front>"
204
+ front +=
205
+ "<journal-meta><journal-id></journal-id><issn></issn></journal-meta>" // Required by DTD
206
+ front += "<article-meta>"
207
+ if (this.frontMatter.tags.length) {
208
+ front += `<article-categories>${this.frontMatter.tags.map(node => this.walkJson(node)).join("")}</article-categories>`
209
+ }
210
+ Object.keys(this.frontMatter.subtitle)
211
+ .filter(language => language !== "default")
212
+ .forEach(language => {
213
+ // Making sure there is a title for each subtitle
214
+ if (!this.frontMatter.title[language]) {
215
+ this.frontMatter.title[language] = {
216
+ type: "trans_title",
217
+ attrs: {language}
218
+ }
219
+ }
220
+ })
221
+ front += "<title-group>"
222
+ front += this.walkJson(this.frontMatter.title.default)
223
+ if (this.frontMatter.subtitle.default) {
224
+ front += this.walkJson(this.frontMatter.subtitle.default)
225
+ }
226
+ Object.keys(this.frontMatter.title)
227
+ .filter(language => language !== "default")
228
+ .forEach(language => {
229
+ front += `<trans-title-group @xml:lang="${language}">`
230
+ front += this.walkJson(this.frontMatter.title[language])
231
+ if (this.frontMatter.subtitle[language]) {
232
+ front += this.walkJson(this.frontMatter.subtitle[language])
233
+ }
234
+ front += "</trans-title-group>"
235
+ })
236
+ front += "</title-group>"
237
+ this.frontMatter.contributors.forEach(contributors => {
238
+ front += this.walkJson(contributors)
239
+ })
240
+ Object.entries(this.affiliations).forEach(
241
+ ([institution, index]) =>
242
+ (front += `<aff id="aff${index}"><institution>${escapeText(institution)}</institution></aff>`)
243
+ )
244
+ // https://validator.jats4r.org/ requires a <permissions> element here, but is OK with it being empty.
245
+ if (this.frontMatter.copyright.holder) {
246
+ front += "<permissions>"
247
+ const year = this.frontMatter.copyright.year
248
+ ? this.frontMatter.copyright.year
249
+ : new Date().getFullYear()
250
+ front += `<copyright-year>${year}</copyright-year>`
251
+ front += `<copyright-holder>${escapeText(this.frontMatter.copyright.holder)}</copyright-holder>`
252
+ if (this.frontMatter.copyright.freeToRead) {
253
+ front += "<ali:free_to_read/>"
254
+ }
255
+ front += this.frontMatter.copyright.licenses
256
+ .map(
257
+ license =>
258
+ `<license><ali:license_ref${license.start ? ` start_date="${license.start}"` : ""}>${escapeText(license.url)}</ali:license_ref></license>`
259
+ )
260
+ .join("")
261
+ front += "</permissions>"
262
+ } else {
263
+ front += "<permissions/>"
264
+ }
265
+ if (this.frontMatter.abstract.default) {
266
+ front += this.walkJson(this.frontMatter.abstract.default)
267
+ front += this.closeSections(0)
268
+ }
269
+ Object.keys(this.frontMatter.abstract)
270
+ .filter(language => language !== "default")
271
+ .forEach(language => {
272
+ front += this.walkJson(this.frontMatter.abstract[language])
273
+ front += this.closeSections(0)
274
+ })
275
+ this.frontMatter.keywords.forEach(keywords => {
276
+ front += this.walkJson(keywords)
277
+ })
278
+ front += "</article-meta></front>"
279
+ return front
280
+ }
281
+
282
+ assembleBookPartFront() {
283
+ let front = "<front-matter><book-part-meta>"
284
+ if (this.frontMatter.tags.length) {
285
+ front += `<subj-group>${this.frontMatter.tags.map(node => this.walkJson(node)).join("")}</subj-group>`
286
+ }
287
+ Object.keys(this.frontMatter.subtitle)
288
+ .filter(language => language !== "default")
289
+ .forEach(language => {
290
+ // Making sure there is a title for each subtitle
291
+ if (!this.frontMatter.title[language]) {
292
+ this.frontMatter.title[language] = {
293
+ type: "trans_title",
294
+ attrs: {language}
295
+ }
296
+ }
297
+ })
298
+ front += "<title-group>"
299
+ front += this.walkJson(this.frontMatter.title.default)
300
+ if (this.frontMatter.subtitle.default) {
301
+ front += this.walkJson(this.frontMatter.subtitle.default)
302
+ }
303
+ Object.keys(this.frontMatter.title)
304
+ .filter(language => language !== "default")
305
+ .forEach(language => {
306
+ front += `<trans-title-group @xml:lang="${language}">`
307
+ front += this.walkJson(this.frontMatter.title[language])
308
+ if (this.frontMatter.subtitle[language]) {
309
+ front += this.walkJson(this.frontMatter.subtitle[language])
310
+ }
311
+ front += "</trans-title-group>"
312
+ })
313
+ front += "</title-group>"
314
+ this.frontMatter.contributors.forEach(contributors => {
315
+ front += this.walkJson(contributors)
316
+ })
317
+ Object.entries(this.affiliations).forEach(
318
+ ([institution, index]) =>
319
+ (front += `<aff id="aff${index}"><institution>${escapeText(institution)}</institution></aff>`)
320
+ )
321
+ // https://validator.jats4r.org/ requires a <permissions> element here, but is OK with it being empty.
322
+ if (this.frontMatter.copyright.holder) {
323
+ front += "<permissions>"
324
+ const year = this.frontMatter.copyright.year
325
+ ? this.frontMatter.copyright.year
326
+ : new Date().getFullYear()
327
+ front += `<copyright-year>${year}</copyright-year>`
328
+ front += `<copyright-holder>${escapeText(this.frontMatter.copyright.holder)}</copyright-holder>`
329
+ if (this.frontMatter.copyright.freeToRead) {
330
+ front += "<ali:free_to_read/>"
331
+ }
332
+ front += this.frontMatter.copyright.licenses
333
+ .map(
334
+ license =>
335
+ `<license><ali:license_ref${license.start ? ` start_date="${license.start}"` : ""}>${escapeText(license.url)}</ali:license_ref></license>`
336
+ )
337
+ .join("")
338
+ front += "</permissions>"
339
+ } else {
340
+ front += "<permissions/>"
341
+ }
342
+ if (this.frontMatter.abstract.default) {
343
+ front += this.walkJson(this.frontMatter.abstract.default)
344
+ front += this.closeSections(0)
345
+ }
346
+ Object.keys(this.frontMatter.abstract)
347
+ .filter(language => language !== "default")
348
+ .forEach(language => {
349
+ front += this.walkJson(this.frontMatter.abstract[language])
350
+ front += this.closeSections(0)
351
+ })
352
+ this.frontMatter.keywords.forEach(keywords => {
353
+ front += this.walkJson(keywords)
354
+ })
355
+ front += "</book-part-meta></front-matter>"
356
+ return front
357
+ }
358
+
359
+ walkJson(node, options = {}) {
360
+ let start = "",
361
+ content = "",
362
+ end = ""
363
+ switch (node.type) {
364
+ case "doc":
365
+ break
366
+ case "title":
367
+ if (this.type === "article") {
368
+ start += "<article-title>"
369
+ end = "</article-title>" + end
370
+ } else {
371
+ start += "<title>"
372
+ end = "</title>" + end
373
+ }
374
+ options = Object.assign({}, options)
375
+ options.breakAllowed = true
376
+ break
377
+ case "trans_title":
378
+ start += "<trans-title>"
379
+ end = "</trans-title>" + end
380
+ options = Object.assign({}, options)
381
+ options.breakAllowed = true
382
+ break
383
+ case "subtitle":
384
+ if (node.content) {
385
+ start += "<subtitle>"
386
+ end = "</subtitle>" + end
387
+ options = Object.assign({}, options)
388
+ options.breakAllowed = true
389
+ }
390
+ break
391
+ case "trans_subtitle":
392
+ if (node.content) {
393
+ start += "<trans-subtitle>"
394
+ end = "</trans-subtitle>" + end
395
+ options = Object.assign({}, options)
396
+ options.breakAllowed = true
397
+ }
398
+ break
399
+ case "heading_part":
400
+ // Ignore - we deal with the heading inside
401
+ break
402
+ case "contributor":
403
+ // Ignore - we deal with contributors_part instead.
404
+ break
405
+ case "contributors_part":
406
+ if (node.content) {
407
+ const contributorTypes = {
408
+ authors: "author",
409
+ editors: "editor",
410
+ translators: "translator",
411
+ reviewers: "reviewer",
412
+ contributors: "contributor"
413
+ }
414
+ const contributorType =
415
+ contributorTypes[node.attrs.metadata] || "other"
416
+ start += `<contrib-group content-type="${contributorType}">`
417
+ end = "</contrib-group>" + end
418
+ const contributorTypeId = node.attrs.id
419
+ let counter = 1
420
+ node.content.forEach(childNode => {
421
+ const contributor = childNode.attrs
422
+ if (contributor.firstname || contributor.lastname) {
423
+ content += `<contrib id="${contributorTypeId}-${counter++}" contrib-type="person">`
424
+ content += "<name>"
425
+ if (contributor.lastname) {
426
+ content += `<surname>${escapeText(contributor.lastname)}</surname>`
427
+ }
428
+ if (contributor.firstname) {
429
+ content += `<given-names>${escapeText(contributor.firstname)}</given-names>`
430
+ }
431
+ content += "</name>"
432
+ if (contributor.institution) {
433
+ let affNumber
434
+ if (
435
+ this.affiliations[contributor.institution]
436
+ ) {
437
+ affNumber =
438
+ this.affiliations[
439
+ contributor.institution
440
+ ]
441
+ } else {
442
+ affNumber = ++this.affCounter
443
+ this.affiliations[contributor.institution] =
444
+ affNumber
445
+ }
446
+ content += `<xref ref-type="aff" rid="aff${affNumber}" />`
447
+ }
448
+ if (contributor.id_type && contributor.id_value) {
449
+ const idType = escapeText(
450
+ contributor.id_type.toLowerCase()
451
+ )
452
+ content += `<contrib-id contrib-id-type="${idType}">${escapeText(contributor.id_value)}</contrib-id>`
453
+ }
454
+ content += "</contrib>"
455
+ } else if (contributor.institution) {
456
+ // There is an affiliation but no first/last name. We take this
457
+ // as a group collaboration.
458
+ content += `<contrib id="${contributorTypeId}-${counter++}" contrib-type="group">`
459
+ content += `<collab><named-content content-type="name">${escapeText(contributor.institution)}</named-content></collab>`
460
+ if (contributor.id_type && contributor.id_value) {
461
+ const idType = escapeText(
462
+ contributor.id_type.toLowerCase()
463
+ )
464
+ content += `<contrib-id contrib-id-type="${idType}">${escapeText(contributor.id_value)}</contrib-id>`
465
+ }
466
+ content += "</contrib>"
467
+ }
468
+ })
469
+ }
470
+ break
471
+ case "tags_part":
472
+ if (node.content) {
473
+ start += `<subj-group subj-group-type="${node.attrs.id}"${node.attrs.language ? ` xml:lang="${node.attrs.language}"` : ""}>`
474
+ end = "</subj-group>" + end
475
+ }
476
+ break
477
+ case "keywords":
478
+ if (node.content) {
479
+ start += `<kwd-group${node.attrs.language ? ` xml:lang="${node.attrs.language}"` : ""}>`
480
+ end = "</kwd-group>" + end
481
+ options = Object.assign({}, options)
482
+ options.inKeywords = true
483
+ }
484
+ break
485
+ case "tag":
486
+ if (options.inKeywords) {
487
+ content += `<kwd>${node.attrs.tag}</kwd>`
488
+ } else {
489
+ content += `<subject>${node.attrs.tag}</subject>`
490
+ }
491
+ break
492
+ case "abstract":
493
+ if (node.content) {
494
+ start += "<abstract>"
495
+ end = "</abstract>" + end
496
+ }
497
+ break
498
+ case "trans_abstract":
499
+ if (node.content) {
500
+ start += `<trans-abstract xml:lang="${node.attrs.language}">`
501
+ end = "</trans-abstract>" + end
502
+ }
503
+ break
504
+ case "richtext_part":
505
+ if (node.attrs.metadata) {
506
+ options = Object.assign({}, options)
507
+ options.partMetadata = node.attrs.metadata
508
+ }
509
+ break
510
+ case "table_of_contents":
511
+ // TODO: Not sure what to use here.
512
+ break
513
+ case "separator_part":
514
+ case "table_part":
515
+ // part separators as in page breaks should usually already be handled
516
+ // by JATS renderer and table parts will simply show the table inside of them.
517
+ break
518
+ case "paragraph":
519
+ start += `<p id="p-${++this.parCounter}">`
520
+ end = "</p>" + end
521
+ break
522
+ case "heading1":
523
+ case "heading2":
524
+ case "heading3":
525
+ case "heading4":
526
+ case "heading5":
527
+ case "heading6": {
528
+ if (options.ignoreHeading) {
529
+ break
530
+ } else if (options.inFootnote) {
531
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
532
+ start += `<p id="p-${++this.parCounter}">`
533
+ end = "</p>" + end
534
+ break
535
+ }
536
+ const metadata = options.partMetadata
537
+ if (metadata) {
538
+ // the metadata should only be applied once within a part.
539
+ delete options.partMetadata
540
+ }
541
+ const level = Number.parseInt(node.type.slice(-1))
542
+ if (this.currentSectionLevel > level - 1) {
543
+ start += this.closeSections(level - 1)
544
+ }
545
+ while (this.currentSectionLevel < level) {
546
+ this.currentSectionLevel++
547
+ if (this.currentSectionLevel === level) {
548
+ start += `<sec id="${node.attrs.id}"${metadata ? ` sec-type="${metadata}"` : ""}>`
549
+ } else {
550
+ start += `<sec id="h-${++this.headingCounter}">`
551
+ }
552
+ }
553
+ start += "<title>"
554
+ end = "</title>" + end
555
+ options = Object.assign({}, options)
556
+ options.breakAllowed = true
557
+ break
558
+ }
559
+ case "code_block":
560
+ if (options.inFootnote) {
561
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
562
+ start += `<p id="p-${++this.parCounter}">`
563
+ end = "</p>" + end
564
+ break
565
+ }
566
+ start += "<code>"
567
+ end = "</code>" + end
568
+ break
569
+ case "blockquote":
570
+ start += "<disp-quote>"
571
+ end = "</disp-quote>" + end
572
+ break
573
+ case "ordered_list": {
574
+ if (options.inFootnote) {
575
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
576
+ break
577
+ }
578
+ const continuedListEndNumber = node.attrs.order - 1
579
+ let lastListIndex
580
+ // TODO: deal with lists that have an order number other than 1 that do not continue previous lists. Currently not possible in JATS
581
+ if (continuedListEndNumber) {
582
+ lastListIndex = this.orderedListLengths.lastIndexOf(
583
+ continuedListEndNumber
584
+ )
585
+ // const lastListReverseIndex = this.orderedListLengths.slice().reverse().findIndex(length => length === continuedListEndNumber)
586
+ // if (lastListReverseIndex !== undefined) {
587
+ // lastListIndex = this.orderedListLengths.length-lastListReverseIndex
588
+ // }
589
+ }
590
+ if (lastListIndex > -1) {
591
+ start += `<list list-type="order" id="list-${++this.listCounter}" continued-from="list-${lastListIndex}">`
592
+ } else {
593
+ start += `<list list-type="order" id="list-${++this.listCounter}">`
594
+ }
595
+ options = Object.assign({}, options)
596
+ options.inOrderedList = this.listCounter
597
+ this.orderedListLengths[options.inOrderedList] =
598
+ continuedListEndNumber
599
+ end = "</list>" + end
600
+ break
601
+ }
602
+ case "bullet_list":
603
+ if (options.inFootnote) {
604
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
605
+ break
606
+ }
607
+ start += `<list list-type="bullet" id="list-${++this.listCounter}">`
608
+ end = "</list>" + end
609
+ options = Object.assign({}, options)
610
+ delete options.inOrderedList
611
+ break
612
+ case "list_item":
613
+ if (options.inFootnote) {
614
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
615
+ break
616
+ }
617
+ if (options.inOrderedList !== undefined) {
618
+ this.orderedListLengths[options.inOrderedList] += 1
619
+ }
620
+ start += "<list-item>"
621
+ end = "</list-item>" + end
622
+ break
623
+ case "footnote":
624
+ content += `<xref ref-type="fn" rid="fn-${++this.fnCounter}">${this.fnCounter}</xref>`
625
+ options = Object.assign({}, options)
626
+ options.inFootnote = true
627
+ this.footnotes.push(
628
+ this.walkJson(
629
+ {
630
+ type: "footnotecontainer",
631
+ attrs: {
632
+ id: `fn-${this.fnCounter}`,
633
+ label: this.fnCounter // Note: it's unclear whether the footnote number is required as a label
634
+ },
635
+ content: node.attrs.footnote
636
+ },
637
+ options
638
+ )
639
+ )
640
+ break
641
+ case "footnotecontainer":
642
+ start += `<fn id="${node.attrs.id}"><label>${node.attrs.label}</label>`
643
+ end = "</fn>" + end
644
+ break
645
+ case "text": {
646
+ content += convertText(node)
647
+ break
648
+ }
649
+ case "cross_reference": {
650
+ start += `<xref rid="${node.attrs.id}">`
651
+ content += escapeText(node.attrs.title || "MISSING TARGET")
652
+ end = "</xref>" + end
653
+ break
654
+ }
655
+ case "citation": {
656
+ const citationText =
657
+ this.citations.citationTexts[this.citationCount++]
658
+ if (
659
+ options.inFootnote ||
660
+ this.citations.citFm.citationType !== "note"
661
+ ) {
662
+ content += citationText
663
+ } else {
664
+ content += `<xref ref-type="fn" rid="fn-${++this.fnCounter}">${this.fnCounter}</xref>`
665
+ this.footnotes.push(
666
+ `<fn id="fn-${this.fnCounter}"><label>${this.fnCounter}</label><p id="p-${++this.parCounter}">${citationText}</p></fn>`
667
+ )
668
+ }
669
+ break
670
+ }
671
+ case "figure": {
672
+ // Note: width and alignment are not stored due to lack of corresponding attributes in JATS.
673
+ if (options.inFootnote) {
674
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
675
+ break
676
+ }
677
+ let imageFilename, copyright
678
+ const image =
679
+ node.content.find(node => node.type === "image")?.attrs
680
+ .image || false
681
+ if (image !== false) {
682
+ this.imageIds.push(image)
683
+ const imageDBEntry = this.imageDB.db[image],
684
+ filePathName = imageDBEntry.image
685
+ copyright = imageDBEntry.copyright
686
+ imageFilename = filePathName.split("/").pop()
687
+ }
688
+ const caption = node.attrs.caption
689
+ ? node.content.find(node => node.type === "figure_caption")
690
+ ?.content || []
691
+ : []
692
+ if (
693
+ node.attrs.category === "none" &&
694
+ imageFilename &&
695
+ !caption.length &&
696
+ (!copyright || !copyright.holder)
697
+ ) {
698
+ content += `<graphic id="${node.attrs.id}" position="anchor" xlink:href="${imageFilename}">`
699
+ content += `<alt-text>${escapeText(caption.map(node => node.text || "").join("") || imageFilename)}</alt-text>`
700
+ content += "</graphic>"
701
+ } else {
702
+ start += `<fig id="${node.attrs.id}">`
703
+ end = "</fig>" + end
704
+
705
+ const category = node.attrs.category
706
+ if (category !== "none") {
707
+ if (!this.categoryCounter[category]) {
708
+ this.categoryCounter[category] = 0
709
+ }
710
+ const catCount = ++this.categoryCounter[category]
711
+ const catLabel = `${CATS[category][this.doc.settings.language]} ${catCount}`
712
+ start += `<label>${escapeText(catLabel)}</label>`
713
+ }
714
+ if (caption.length) {
715
+ start += `<caption><p>${caption.map(node => this.walkJson(node)).join("")}</p></caption>`
716
+ }
717
+ const equation = node.content.find(
718
+ node => node.type === "figure_equation"
719
+ )?.attrs.equation
720
+ if (equation) {
721
+ start += "<disp-formula>"
722
+ end = "</disp-formula>" + end
723
+ const equationML = convertLatexToMathMl(equation)
724
+ content = `
725
+ <alternatives>
726
+ <tex-math><![CDATA[${equation}]]></tex-math>
727
+ <mml:math>${equationML}</mml:math>
728
+ </alternatives>
729
+ `
730
+ } else {
731
+ if (copyright?.holder) {
732
+ start += "<permissions>"
733
+ const year = copyright.year
734
+ ? copyright.year
735
+ : new Date().getFullYear()
736
+ start += `<copyright-year>${year}</copyright-year>`
737
+ start += `<copyright-holder>${escapeText(copyright.holder)}</copyright-holder>`
738
+ if (copyright.freeToRead) {
739
+ start += "<ali:free_to_read/>"
740
+ }
741
+ start += copyright.licenses
742
+ .map(
743
+ license =>
744
+ `<license><ali:license_ref${license.start ? ` start_date="${license.start}"` : ""}>${escapeText(license.url)}</ali:license_ref></license>`
745
+ )
746
+ .join("")
747
+ start += "</permissions>"
748
+ }
749
+ if (imageFilename) {
750
+ content += `<graphic position="anchor" xlink:href="${imageFilename}">`
751
+ content += `<alt-text>${escapeText(caption.map(node => node.text || "").join("") || imageFilename)}</alt-text>`
752
+ content += "</graphic>"
753
+ }
754
+ }
755
+ }
756
+ break
757
+ }
758
+ case "figure_caption":
759
+ // We are already dealing with this in the figure. Prevent content from being added a second time.
760
+ return ""
761
+ case "figure_equation":
762
+ // We are already dealing with this in the figure.
763
+ break
764
+ case "image":
765
+ // We are already dealing with this in the figure.
766
+ break
767
+ case "table": {
768
+ // Note: We ignore right/left/center aligned and table layout
769
+ if (options.inFootnote) {
770
+ // only allows <p> block level elements https://jats.nlm.nih.gov/archiving/tag-library/1.2/element/fn.html
771
+ break
772
+ }
773
+ start += `<table-wrap id="${node.attrs.id}">`
774
+ end = "</table-wrap>" + end
775
+ const category = node.attrs.category
776
+ if (category !== "none") {
777
+ if (!this.categoryCounter[category]) {
778
+ this.categoryCounter[category] = 0
779
+ }
780
+ const catCount = ++this.categoryCounter[category]
781
+ const catLabel = `${CATS[category][this.doc.settings.language]} ${catCount}`
782
+ start += `<label>${escapeText(catLabel)}</label>`
783
+ }
784
+ const caption = node.attrs.caption
785
+ ? node.content[0].content || []
786
+ : []
787
+ if (caption.length) {
788
+ start += `<caption><p>${caption.map(node => this.walkJson(node)).join("")}</p></caption>`
789
+ }
790
+ start += `<table width="${node.attrs.width}%"><tbody>`
791
+ end = "</tbody></table>" + end
792
+ break
793
+ }
794
+ case "table_body":
795
+ // Pass through to table.
796
+ break
797
+ case "table_caption":
798
+ // We already deal with this in 'table'.
799
+ return ""
800
+ case "table_row":
801
+ start += "<tr>"
802
+ end = "</tr>" + end
803
+ break
804
+ case "table_cell":
805
+ start += `<td${node.attrs.colspan === 1 ? "" : ` colspan="${node.attrs.colspan}"`}${node.attrs.rowspan === 1 ? "" : ` rowspan="${node.attrs.rowspan}"`}>`
806
+ end = "</td>" + end
807
+ break
808
+ case "table_header":
809
+ start += `<th${node.attrs.colspan === 1 ? "" : ` colspan="${node.attrs.colspan}"`}${node.attrs.rowspan === 1 ? "" : ` rowspan="${node.attrs.rowspan}"`}>`
810
+ end = "</th>" + end
811
+ break
812
+ case "equation": {
813
+ start += "<inline-formula>"
814
+ end = "</inline-formula>" + end
815
+ const equationML = convertLatexToMathMl(node.attrs.equation)
816
+ content = `
817
+ <alternatives>
818
+ <tex-math><![CDATA[${node.attrs.equation}]]></tex-math>
819
+ <mml:math>${equationML}</mml:math>
820
+ </alternatives>
821
+ `
822
+ break
823
+ }
824
+ case "hard_break":
825
+ // Forbidden inside of most elements. We only render it if explicitly allowed.
826
+ // https://jats.nlm.nih.gov/publishing/tag-library/1.3/element/break.html
827
+ if (options.breakAllowed) {
828
+ content += "<break />"
829
+ } else {
830
+ content += " "
831
+ }
832
+ break
833
+ default:
834
+ break
835
+ }
836
+
837
+ if (!content.length && node.content) {
838
+ node.content.forEach(child => {
839
+ content += this.walkJson(child, options)
840
+ })
841
+ }
842
+
843
+ return start + content + end
844
+ }
845
+
846
+ closeSections(targetLevel) {
847
+ let returnValue = ""
848
+ while (this.currentSectionLevel > targetLevel) {
849
+ returnValue += "</sec>"
850
+ this.currentSectionLevel--
851
+ }
852
+
853
+ return returnValue
854
+ }
855
+
856
+ assembleBody(docContent) {
857
+ return `<body id="body">${this.walkJson(docContent) + this.closeSections(0)}</body>`
858
+ }
859
+
860
+ assembleBack() {
861
+ let back = "<back>"
862
+ if (this.footnotes.length) {
863
+ back += `<fn-group>${this.footnotes.join("")}</fn-group>`
864
+ }
865
+ if (this.citations.jatsBib.length) {
866
+ back += `<ref-list>${this.citations.jatsBib}</ref-list>`
867
+ }
868
+ back += "</back>"
869
+ return back
870
+ }
871
+ }