@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,813 @@
1
+ import {convertLatexToMathMl} from "mathlive"
2
+ import pretty from "pretty"
3
+
4
+ import {escapeText} from "../../common/index.js"
5
+ import {CATS} from "../../schema/i18n.js"
6
+ import {HTMLExporterCitations} from "./citations.js"
7
+ import {displayNumber} from "./tools.js"
8
+
9
+ export class HTMLExporterConvert {
10
+ constructor(
11
+ docTitle,
12
+ docSettings,
13
+ docContent,
14
+ htmlExportTemplate,
15
+ imageDB,
16
+ bibDB,
17
+ csl,
18
+ styleSheets,
19
+ {
20
+ xhtml = false,
21
+ epub = false,
22
+ relativeUrls = true, // Whether to use relative urls for images, css files, etc. Is used when bundled in HTML. Not in print.
23
+ footnoteNumbering = "decimal",
24
+ affiliationNumbering = "alpha",
25
+ idPrefix = "",
26
+ footnoteOffset = 0,
27
+ affiliationOffset = 0,
28
+ figureOffset = {}
29
+ } = {}
30
+ ) {
31
+ this.docTitle = docTitle
32
+ this.docSettings = docSettings
33
+ this.docContent = docContent
34
+ this.htmlExportTemplate = htmlExportTemplate
35
+ this.imageDB = imageDB
36
+ this.bibDB = bibDB
37
+ this.csl = csl
38
+ this.styleSheets = styleSheets
39
+ this.xhtml = xhtml
40
+ this.epub = epub
41
+ this.relativeUrls = relativeUrls
42
+ this.footnoteNumbering = footnoteNumbering
43
+ this.affiliationNumbering = affiliationNumbering
44
+
45
+ this.endSlash = this.xhtml ? "/" : ""
46
+ this.imageIds = []
47
+ this.categoryCounter = {} // counters for each type of figure (figure/table/photo)
48
+ this.affiliations = {} // affiliations of authors and editors
49
+ this.parCounter = 0
50
+ this.headingCounter = 0
51
+ this.currentSectionLevel = 0
52
+ this.listCounter = 0
53
+ this.orderedListLengths = []
54
+ this.footnotes = []
55
+ this.fnCounter = footnoteOffset
56
+ this.affCounter = affiliationOffset
57
+ this.metaData = {
58
+ title: this.docTitle,
59
+ authors: [],
60
+ abstract: false,
61
+ keywords: [],
62
+ copyright: {
63
+ licenses: []
64
+ },
65
+ toc: []
66
+ }
67
+ this.features = {
68
+ math: false,
69
+ bibliography: false
70
+ }
71
+ this.citations = {
72
+ type: "",
73
+ bibCSS: "",
74
+ bibHTML: "",
75
+ citationTexts: []
76
+ }
77
+ this.citInfos = []
78
+ this.citationCount = 0
79
+ this.extraStyleSheets = []
80
+ this.idPrefix = idPrefix
81
+ this.categoryCounter = Object.assign({}, figureOffset)
82
+ }
83
+
84
+ init() {
85
+ this.analyze(this.docContent)
86
+ return this.process()
87
+ }
88
+
89
+ async processCitInfos() {
90
+ const citationProcessor = new HTMLExporterCitations(
91
+ this.docSettings,
92
+ this.bibDB,
93
+ this.csl
94
+ )
95
+ const citations = await citationProcessor.init(this.citInfos)
96
+ this.citations = citations
97
+ }
98
+
99
+ async process() {
100
+ if (this.citInfos.length) {
101
+ await this.processCitInfos()
102
+ }
103
+
104
+ if (this.citations.bibCSS.length) {
105
+ this.extraStyleSheets.push({
106
+ filename: this.relativeUrls ? "css/bibliography.css" : null,
107
+ contents: pretty(this.citations.bibCSS, {
108
+ ocd: true
109
+ })
110
+ })
111
+ }
112
+ if (this.features.math) {
113
+ this.extraStyleSheets.push({
114
+ filename: this.relativeUrls
115
+ ? "css/mathlive.css"
116
+ : staticUrl("css/libs/mathlive/mathlive.css")
117
+ })
118
+ }
119
+ const body = this.assembleBody()
120
+ const back = this.assembleBack()
121
+ const head = this.assembleHead()
122
+ const html = this.htmlExportTemplate({
123
+ head,
124
+ body,
125
+ back,
126
+ settings: this.docSettings,
127
+ lang: this.docSettings.language.split("-")[0],
128
+ xhtml: this.xhtml,
129
+ epub: this.epub
130
+ })
131
+ return {
132
+ html,
133
+ imageIds: this.imageIds,
134
+ extraStyleSheets: this.extraStyleSheets,
135
+ metaData: this.metaData
136
+ }
137
+ }
138
+
139
+ // Find information for meta tags in header
140
+ analyze(node) {
141
+ switch (node.type) {
142
+ case "citation":
143
+ this.citInfos.push(JSON.parse(JSON.stringify(node.attrs)))
144
+ break
145
+ case "contributors_part":
146
+ if (node.attrs.metadata === "authors" && node.content) {
147
+ node.content.forEach(author => {
148
+ this.metaData.authors.push(author)
149
+ })
150
+ }
151
+ break
152
+ case "doc":
153
+ this.metaData.copyright = node.attrs.copyright
154
+ break
155
+ case "heading1":
156
+ case "heading2":
157
+ case "heading3":
158
+ case "heading4":
159
+ case "heading5":
160
+ case "heading6": {
161
+ const level = Number.parseInt(node.type.slice(-1))
162
+ this.metaData.toc.push({
163
+ level,
164
+ id: node.attrs.id,
165
+ title: (node.content || [])
166
+ .map(subNode => this.walkJson(subNode))
167
+ .join("")
168
+ })
169
+ break
170
+ }
171
+ case "equation":
172
+ case "figure_equation":
173
+ this.features.math = true
174
+ break
175
+ case "footnote":
176
+ node.attrs.footnote.forEach(child => this.analyze(child))
177
+ break
178
+ case "richtext_part":
179
+ if (
180
+ node.attrs.metadata === "abstract" &&
181
+ !node.attrs.language &&
182
+ this.metaData.abstract
183
+ ) {
184
+ this.metaData.abstract = this.walkJson(node)
185
+ }
186
+ break
187
+ case "tags_part":
188
+ if (node.attrs.metadata === "keywords" && node.content) {
189
+ node.content.forEach(tag => {
190
+ this.metaData.keywords.push(tag.attrs.tag)
191
+ })
192
+ }
193
+ break
194
+ case "title": {
195
+ const title = this.textWalkJson(node)
196
+ if (title.length) {
197
+ this.metaData.title = title
198
+ }
199
+ this.metaData.toc.push({
200
+ docTitle: true,
201
+ level: 1,
202
+ id: "title",
203
+ title: title
204
+ })
205
+ break
206
+ }
207
+
208
+ default:
209
+ break
210
+ }
211
+ if (node.content) {
212
+ node.content.forEach(child => this.analyze(child))
213
+ }
214
+ }
215
+
216
+ assembleHead() {
217
+ let head = `<title>${escapeText(this.metaData.title)}</title>`
218
+ if (this.metaData.authors.length) {
219
+ const authorString = this.metaData.authors
220
+ .map(author => {
221
+ if (author.firstname || author.lastname) {
222
+ const nameParts = []
223
+ if (author.firstname) {
224
+ nameParts.push(author.firstname)
225
+ }
226
+ if (author.lastname) {
227
+ nameParts.push(author.lastname)
228
+ }
229
+ return nameParts.join(" ")
230
+ } else if (author.institution) {
231
+ return author.institution
232
+ }
233
+ })
234
+ .join(", ")
235
+ if (authorString.length) {
236
+ head += `<meta name="author" content="${escapeText(authorString)}"${this.endSlash}>`
237
+ }
238
+ }
239
+ if (this.metaData.copyright.holder) {
240
+ head += `<link rel="schema.dcterms" href="http://purl.org/dc/terms/"${this.endSlash}>`
241
+ const year = this.metaData.copyright.year
242
+ ? this.metaData.copyright.year
243
+ : new Date().getFullYear()
244
+ head += `<meta name="dcterms.dateCopyrighted" content="${year}"${this.endSlash}>`
245
+ head += `<meta name="dcterms.rightsHolder" content="${escapeText(this.metaData.copyright.holder)}"${this.endSlash}>`
246
+ // TODO: Add this.metaData.copyright.freeToRead if present
247
+
248
+ head += this.metaData.copyright.licenses
249
+ .map(
250
+ license =>
251
+ `<link rel="license" href="${escapeText(license.url)}"${this.endSlash}>` // TODO: Add this.metaData.copyright.license.start info if present
252
+ )
253
+ .join("")
254
+ }
255
+ if (this.metaData.abstract.default) {
256
+ head += this.walkJson(this.metaData.abstract.default)
257
+ }
258
+ Object.keys(this.metaData.abstract)
259
+ .filter(language => language !== "default")
260
+ .forEach(language => {
261
+ head += this.walkJson(this.metaData.abstract[language])
262
+ })
263
+ if (this.metaData.keywords.length) {
264
+ head += `<meta name="keywords" content="${escapeText(this.metaData.keywords.join(", "))}"${this.endSlash}>`
265
+ }
266
+ head += this.styleSheets
267
+ .concat(this.extraStyleSheets)
268
+ .map(sheet => {
269
+ if (!sheet.filename && !sheet.contents) {
270
+ console.warn(
271
+ "No filename or contents for stylesheet.",
272
+ sheet
273
+ )
274
+ return ""
275
+ }
276
+ return sheet.filename
277
+ ? `<link rel="stylesheet" type="text/css" href="${sheet.filename}"${this.endSlash}>`
278
+ : `<style>${sheet.contents}</style>`
279
+ })
280
+ .join("")
281
+ return head
282
+ }
283
+
284
+ // Only allow for text output
285
+ textWalkJson(node) {
286
+ let content = ""
287
+ if (node.type === "text") {
288
+ content += escapeText(node.text).normalize("NFC")
289
+ } else if (node.content) {
290
+ node.content.forEach(child => {
291
+ content += this.textWalkJson(child)
292
+ })
293
+ }
294
+ return content
295
+ }
296
+
297
+ walkJson(node, options = {}) {
298
+ let start = "",
299
+ content = "",
300
+ end = ""
301
+ switch (node.type) {
302
+ case "doc":
303
+ break
304
+ case "title":
305
+ start += `<div class="doc-part doc-title" id="${this.idPrefix}title">`
306
+ end = "</div>" + end
307
+ break
308
+ case "heading_part":
309
+ start += `<div class="doc-part doc-heading doc-${node.attrs.id} ${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}"${node.attrs.language ? ` lang="${node.attrs.language}"` : ""}>`
310
+ end = "</div>" + end
311
+ break
312
+ case "contributor":
313
+ // Ignore - we deal with contributors_part instead.
314
+ break
315
+ case "contributors_part":
316
+ if (node.content) {
317
+ start += `<div class="doc-part doc-contributors doc-${node.attrs.id} ${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}"${node.attrs.language ? ` lang="${node.attrs.language}"` : ""}>`
318
+ end = "</div>" + end
319
+ let counter = 0
320
+ const contributorOutputs = []
321
+ node.content.forEach(childNode => {
322
+ const contributor = childNode.attrs
323
+ let output = ""
324
+ if (contributor.firstname || contributor.lastname) {
325
+ output += `<span id="${this.idPrefix}${node.attrs.id}-${counter++}" class="person">`
326
+ const nameParts = []
327
+ if (contributor.firstname) {
328
+ nameParts.push(
329
+ `<span class="firstname">${escapeText(contributor.firstname)}</span>`
330
+ )
331
+ }
332
+ if (contributor.lastname) {
333
+ nameParts.push(
334
+ `<span class="lastname">${escapeText(contributor.lastname)}</span>`
335
+ )
336
+ }
337
+ if (nameParts.length) {
338
+ output += `<span class="name">${nameParts.join(" ")}</span>`
339
+ }
340
+ if (contributor.institution) {
341
+ let affNumber
342
+ if (
343
+ this.affiliations[contributor.institution]
344
+ ) {
345
+ affNumber =
346
+ this.affiliations[
347
+ contributor.institution
348
+ ]
349
+ } else {
350
+ affNumber = ++this.affCounter
351
+ this.affiliations[contributor.institution] =
352
+ affNumber
353
+ }
354
+ const affNumberDisplay = displayNumber(
355
+ affNumber,
356
+ this.affiliationNumbering
357
+ )
358
+ output += `<a class="affiliation" href="#aff-${affNumber}"${this.epub ? ' epub:type="noteref"' : ""}>${affNumberDisplay}</a>`
359
+ }
360
+ if (contributor.id_type && contributor.id_value) {
361
+ output += `<span class="contributor-id">${escapeText(contributor.id_type)}: ${escapeText(contributor.id_value)}</span>`
362
+ }
363
+ output += "</span>"
364
+ } else if (contributor.institution) {
365
+ // There is an affiliation but no first/last name. We take this
366
+ // as a group collaboration.
367
+ output += `<span id="${this.idPrefix}${node.attrs.id}-${counter++}" class="group">`
368
+ output += `<span class="name">${escapeText(contributor.institution)}</span>`
369
+ output += "</span>"
370
+ }
371
+ contributorOutputs.push(output)
372
+ })
373
+ content += contributorOutputs.join(", ")
374
+ }
375
+ break
376
+ case "tags_part":
377
+ if (node.content) {
378
+ start += `<div class="doc-part doc-tags doc-${node.attrs.id} doc-${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}"${node.attrs.language ? ` lang="${node.attrs.language}"` : ""}>`
379
+ end = "</div>" + end
380
+ }
381
+ break
382
+ case "tag":
383
+ content += `<span class='tag'>${escapeText(node.attrs.tag)}</span>`
384
+ break
385
+ case "richtext_part":
386
+ if (node.content) {
387
+ start += `<div class="doc-part doc-richtext doc-${node.attrs.id} doc-${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}"${node.attrs.language ? ` lang="${node.attrs.language}"` : ""}>`
388
+ end = "</div>" + end
389
+ }
390
+ break
391
+ case "table_of_contents":
392
+ start += `<div class="doc-part table-of-contents"><h1>${escapeText(node.attrs.title)}</h1>`
393
+ content += this.metaData.toc
394
+ .map(
395
+ item =>
396
+ `<h${item.level}><a href="#${item.id}">${item.title}</a></h${item.level}>`
397
+ )
398
+ .join("")
399
+ end += "</div>"
400
+ break
401
+ case "separator_part":
402
+ content += `<hr class="doc-part doc-separator doc-${node.attrs.id} doc-${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}">`
403
+ break
404
+ case "table_part":
405
+ if (node.content) {
406
+ start += `<div class="doc-part doc-table doc-${node.attrs.id} doc-${node.attrs.metadata || "other"}" id="${this.idPrefix}${node.attrs.id}"${node.attrs.language ? ` lang="${node.attrs.language}"` : ""}>`
407
+ end = "</div>" + end
408
+ }
409
+ break
410
+ case "paragraph":
411
+ start += `<p id="${this.idPrefix}p-${++this.parCounter}">`
412
+ end = "</p>" + end
413
+ break
414
+ case "heading1":
415
+ case "heading2":
416
+ case "heading3":
417
+ case "heading4":
418
+ case "heading5":
419
+ case "heading6": {
420
+ const level = Number.parseInt(node.type.slice(-1))
421
+ start += `<h${level} id="${this.idPrefix}${node.attrs.id}">`
422
+ end = `</h${level}>` + end
423
+ break
424
+ }
425
+ case "code_block": {
426
+ const attrs = []
427
+ if (node.attrs.language) {
428
+ attrs.push(
429
+ `data-language="${escapeText(node.attrs.language)}"`
430
+ )
431
+ }
432
+ if (node.attrs.category) {
433
+ attrs.push(`data-category="${node.attrs.category}"`)
434
+ }
435
+ if (node.attrs.title) {
436
+ attrs.push(`data-title="${escapeText(node.attrs.title)}"`)
437
+ }
438
+ if (node.attrs.id) {
439
+ attrs.push(`data-id="${node.attrs.id}"`)
440
+ }
441
+ const attrString = attrs.length ? ` ${attrs.join(" ")}` : ""
442
+
443
+ // If there's a category, wrap in figure for proper numbering
444
+ if (node.attrs.category && node.attrs.id) {
445
+ const language = this.doc.attrs.language || "en-US"
446
+ const {CATS} = require("../../schema/i18n")
447
+ const categoryLabel =
448
+ CATS[node.attrs.category]?.[language] ||
449
+ node.attrs.category
450
+
451
+ // Count code blocks to get the number
452
+ const categories = {}
453
+ this.doc.descendants(n => {
454
+ if (
455
+ n.type === "code_block" &&
456
+ n.attrs.category &&
457
+ n.attrs.id
458
+ ) {
459
+ if (!categories[n.attrs.category]) {
460
+ categories[n.attrs.category] = 0
461
+ }
462
+ categories[n.attrs.category]++
463
+ if (n.attrs.id === node.attrs.id) {
464
+ return false
465
+ }
466
+ }
467
+ })
468
+ const number = categories[node.attrs.category] || 1
469
+ const label = node.attrs.title
470
+ ? `${categoryLabel} ${number}: ${escapeText(node.attrs.title)}`
471
+ : `${categoryLabel} ${number}`
472
+
473
+ start += `<figure class="code-block-figure" id="${this.idPrefix}${node.attrs.id}"><figcaption><span class="label">${label}</span></figcaption><pre${attrString}><code>`
474
+ end = `</code></pre></figure>` + end
475
+ } else {
476
+ start += `<code${attrString}>`
477
+ end = "</code>" + end
478
+ }
479
+ break
480
+ }
481
+ case "blockquote":
482
+ start += "<blockquote>"
483
+ end = "</blockquote>" + end
484
+ break
485
+ case "ordered_list": {
486
+ if (node.attrs.order == 1) {
487
+ start += `<ol id="${this.idPrefix}list-${++this.listCounter}">`
488
+ } else {
489
+ start += `<ol id="${this.idPrefix}list-${++this.listCounter}" start="${node.attrs.order}">`
490
+ }
491
+ end = "</ol>" + end
492
+ break
493
+ }
494
+ case "bullet_list":
495
+ start += `<ul id="${this.idPrefix}list-${++this.listCounter}">`
496
+ end = "</ul>" + end
497
+ break
498
+ case "list_item":
499
+ start += "<li>"
500
+ end = "</li>" + end
501
+ break
502
+ case "footnote": {
503
+ const footnoteNumber = ++this.fnCounter
504
+ const footnoteNumberDisplay = displayNumber(
505
+ footnoteNumber,
506
+ this.footnoteNumbering
507
+ )
508
+ content += `<a class="footnote"${this.epub ? ' epub:type="noteref"' : ""} href="#fn-${footnoteNumber}">${footnoteNumberDisplay}</a>`
509
+ options = Object.assign({}, options)
510
+ options.inFootnote = true
511
+ this.footnotes.push(
512
+ this.walkJson(
513
+ {
514
+ type: "footnotecontainer",
515
+ attrs: {
516
+ id: `fn-${footnoteNumber}`,
517
+ label: footnoteNumberDisplay // Note: it's unclear whether the footnote number is required as a label
518
+ },
519
+ content: node.attrs.footnote
520
+ },
521
+ options
522
+ )
523
+ )
524
+ break
525
+ }
526
+ case "footnotecontainer":
527
+ start += `<aside class="footnote"${this.epub ? ' epub:type="footnote"' : ""} role="doc-footnote" id="${this.idPrefix}${node.attrs.id}"><label>${node.attrs.label}</label>`
528
+ end = "</aside>" + end
529
+ break
530
+ case "text": {
531
+ let strong, em, underline, hyperlink, anchor, sup, sub, code
532
+ // Check for hyperlink, bold/strong, italic/em and underline
533
+ if (node.marks) {
534
+ strong = node.marks.find(mark => mark.type === "strong")
535
+ em = node.marks.find(mark => mark.type === "em")
536
+ underline = node.marks.find(
537
+ mark => mark.type === "underline"
538
+ )
539
+ hyperlink = node.marks.find(mark => mark.type === "link")
540
+ anchor = node.marks.find(mark => mark.type === "anchor")
541
+ sup = node.marks.find(mark => mark.type === "sup")
542
+ sub = node.marks.find(mark => mark.type === "sub")
543
+ code = node.marks.find(mark => mark.type === "code")
544
+ }
545
+ if (em) {
546
+ start += "<em>"
547
+ end = "</em>" + end
548
+ }
549
+ if (strong) {
550
+ start += "<strong>"
551
+ end = "</strong>" + end
552
+ }
553
+ if (underline) {
554
+ start += '<span class="underline">'
555
+ end = "</span>" + end
556
+ }
557
+ if (sup) {
558
+ start += "<sup>"
559
+ end = "</sup>" + end
560
+ }
561
+ if (sub) {
562
+ start += "<sub>"
563
+ end = "</sub>" + end
564
+ }
565
+ if (code) {
566
+ start += "<code>"
567
+ end = "</code>" + end
568
+ }
569
+ if (hyperlink) {
570
+ const href = hyperlink.attrs.href
571
+ const link = href.startsWith("#")
572
+ ? `#${this.idPrefix}${href.slice(1)}`
573
+ : href
574
+ start += `<a href="${link}">`
575
+ end = "</a>" + end
576
+ }
577
+ if (anchor) {
578
+ const id = anchor.attrs.id
579
+ start += `<span class="anchor" id="${this.idPrefix}${id}" data-id="${this.idPrefix}${id}">`
580
+ end = "</span>" + end
581
+ }
582
+ content += escapeText(node.text).normalize("NFC")
583
+ break
584
+ }
585
+ case "cross_reference": {
586
+ start += `<a class="reference" href="#${this.idPrefix}${node.attrs.id}">`
587
+ content += escapeText(node.attrs.title || "MISSING TARGET")
588
+ end = "</a>" + end
589
+ break
590
+ }
591
+ case "citation": {
592
+ if (!this.citations.citationTexts.length) {
593
+ // There are no citations. This may happen while analyzing.
594
+ return ""
595
+ }
596
+ const citationText =
597
+ this.citations.citationTexts[this.citationCount++]
598
+ if (
599
+ options.inFootnote ||
600
+ this.citations.citationType !== "note"
601
+ ) {
602
+ content += citationText
603
+ } else {
604
+ content += `<a class="footnote"${this.epub ? 'epub:type="noteref" ' : ""} href="#fn-${++this.fnCounter}">${this.fnCounter}</a>`
605
+ this.footnotes.push(
606
+ `<aside class="footnote"${this.epub ? 'epub:type="footnote" ' : ""} id="fn-${this.fnCounter}"><label>${this.fnCounter}</label><p id="${this.idPrefix}p-${++this.parCounter}">${citationText}</p></aside>`
607
+ )
608
+ }
609
+ break
610
+ }
611
+ case "figure": {
612
+ let imageUrl, copyright
613
+ const image =
614
+ node.content.find(node => node.type === "image")?.attrs
615
+ .image || false
616
+ if (image !== false) {
617
+ this.imageIds.push(image)
618
+ const imageDBEntry = this.imageDB.db[image],
619
+ filePathName = imageDBEntry.image
620
+ copyright = imageDBEntry.copyright
621
+ imageUrl = this.relativeUrls
622
+ ? `images/${filePathName.split("/").pop()}`
623
+ : filePathName
624
+ }
625
+ const caption = node.attrs.caption
626
+ ? node.content.find(node => node.type === "figure_caption")
627
+ ?.content || []
628
+ : []
629
+ if (
630
+ node.attrs.category === "none" &&
631
+ imageUrl &&
632
+ !caption.length &&
633
+ (!copyright || !copyright.holder)
634
+ ) {
635
+ content += `<img id="${this.idPrefix}${node.attrs.id}" class="aligned-${node.attrs.aligned} image-width-${node.attrs.width}" src="${imageUrl}"${this.endSlash}>`
636
+ } else {
637
+ start += `<figure
638
+ id="${this.idPrefix}${node.attrs.id}"
639
+ class="aligned-${node.attrs.aligned} image-width-${node.attrs.width}"
640
+ data-aligned="${node.attrs.aligned}"
641
+ data-width="${node.attrs.width}"
642
+ data-category="${node.attrs.category}"
643
+ >`
644
+ end = "</figure>" + end
645
+
646
+ const equation = node.content.find(
647
+ node => node.type === "figure_equation"
648
+ )?.attrs.equation
649
+
650
+ if (image && copyright?.holder) {
651
+ let figureFooter = `<footer class="copyright ${copyright.freeToRead ? "free-to-read" : "not-free-to-read"}"><small>`
652
+ figureFooter += "© "
653
+ const year = copyright.year
654
+ ? copyright.year
655
+ : new Date().getFullYear()
656
+ figureFooter += `<span class="copyright-year">${year}</span> `
657
+ figureFooter += `<span class="copyright-holder">${escapeText(copyright.holder)}</span> `
658
+ figureFooter += copyright.licenses
659
+ .map(
660
+ license =>
661
+ `<span class="license"><a rel="license"${license.start ? ` data-start="${license.start}"` : ""}>${escapeText(license.url)}</a></span>`
662
+ )
663
+ .join("")
664
+ figureFooter += "</small></footer>"
665
+ end = figureFooter + end
666
+ }
667
+
668
+ const category = node.attrs.category
669
+ if (caption.length || category !== "none") {
670
+ let figcaption = "<figcaption>"
671
+ if (category !== "none") {
672
+ if (!this.categoryCounter[category]) {
673
+ this.categoryCounter[category] = 0
674
+ }
675
+ const catCount = ++this.categoryCounter[category]
676
+ const catLabel = `${CATS[category][this.docSettings.language]} ${catCount}`
677
+ figcaption += `<label>${escapeText(catLabel)}</label>`
678
+ }
679
+ if (caption.length) {
680
+ figcaption += `<p>${caption.map(node => this.walkJson(node)).join("")}</p>`
681
+ }
682
+ figcaption += "</figcaption>"
683
+ if (category === "table") {
684
+ start += figcaption
685
+ } else {
686
+ end = figcaption + end
687
+ }
688
+ }
689
+
690
+ if (equation) {
691
+ start += `<div class="figure-equation" data-equation="${escapeText(equation)}"><math display="block">`
692
+ end = "</math></div>" + end
693
+ content = convertLatexToMathMl(equation)
694
+ } else {
695
+ if (imageUrl) {
696
+ content += `<img src="${imageUrl}"${this.endSlash}>`
697
+ }
698
+ }
699
+ }
700
+ break
701
+ }
702
+ case "figure_caption":
703
+ // We are already dealing with this in the figure. Prevent content from being added a second time.
704
+ return ""
705
+ case "figure_equation":
706
+ // We are already dealing with this in the figure.
707
+ break
708
+ case "image":
709
+ // We are already dealing with this in the figure.
710
+ break
711
+ case "table": {
712
+ start += `<table
713
+ id="${this.idPrefix}${node.attrs.id}"
714
+ class="table-${node.attrs.width}
715
+ table-${node.attrs.aligned}
716
+ table-${node.attrs.layout}"
717
+ data-width="${node.attrs.width}"
718
+ data-aligned="${node.attrs.aligned}"
719
+ data-layout="${node.attrs.layout}"
720
+ data-category="${node.attrs.category}"
721
+ >`
722
+ end = "</table>" + end
723
+ const category = node.attrs.category
724
+ if (category !== "none") {
725
+ if (!this.categoryCounter[category]) {
726
+ this.categoryCounter[category] = 0
727
+ }
728
+ const catCount = ++this.categoryCounter[category]
729
+ const catLabel = `${CATS[category][this.docSettings.language]} ${catCount}`
730
+ start += `<label>${escapeText(catLabel)}</label>`
731
+ }
732
+ const caption = node.attrs.caption
733
+ ? node.content[0].content || []
734
+ : []
735
+ if (caption.length) {
736
+ start += `<caption><p>${caption.map(node => this.walkJson(node)).join("")}</p></caption>`
737
+ }
738
+ start += "<tbody>"
739
+ end = "</tbody>" + end
740
+ break
741
+ }
742
+ case "table_body":
743
+ // Pass through to table.
744
+ break
745
+ case "table_caption":
746
+ // We already deal with this in 'table'.
747
+ return ""
748
+ case "table_row":
749
+ start += "<tr>"
750
+ end = "</tr>" + end
751
+ break
752
+ case "table_cell":
753
+ start += `<td${node.attrs.colspan === 1 ? "" : ` colspan="${node.attrs.colspan}"`}${node.attrs.rowspan === 1 ? "" : ` rowspan="${node.attrs.rowspan}"`}>`
754
+ end = "</td>" + end
755
+ break
756
+ case "table_header":
757
+ start += `<th${node.attrs.colspan === 1 ? "" : ` colspan="${node.attrs.colspan}"`}${node.attrs.rowspan === 1 ? "" : ` rowspan="${node.attrs.rowspan}"`}>`
758
+ end = "</th>" + end
759
+ break
760
+ case "equation":
761
+ start += '<span class="equation"><math>'
762
+ end = "</math></span>" + end
763
+ content = convertLatexToMathMl(node.attrs.equation)
764
+ break
765
+ case "hard_break":
766
+ content += `<br${this.endSlash}>`
767
+ break
768
+ default:
769
+ break
770
+ }
771
+
772
+ if (!content.length && node.content) {
773
+ node.content.forEach(child => {
774
+ content += this.walkJson(child, options)
775
+ })
776
+ }
777
+
778
+ return start + content + end
779
+ }
780
+
781
+ assembleBody() {
782
+ return `<div id="${this.idPrefix}body">${this.walkJson(this.docContent)}</div>`
783
+ }
784
+
785
+ assembleBack() {
786
+ let back = ""
787
+ if (
788
+ this.footnotes.length ||
789
+ this.citations.bibHTML.length ||
790
+ Object.keys(this.affiliations).length
791
+ ) {
792
+ back += `<div id="${this.idPrefix}back">`
793
+ if (Object.keys(this.affiliations).length) {
794
+ back += `<section id="${this.idPrefix}affiliations" class="affiliations">${Object.entries(
795
+ this.affiliations
796
+ )
797
+ .map(
798
+ ([name, id]) =>
799
+ `<aside class="affiliation" id="aff-${id}"${this.epub ? 'epub:type="footnote"' : ""}><label>${displayNumber(id, this.affiliationNumbering)}</label> <div>${escapeText(name)}</div></aside>`
800
+ )
801
+ .join("")}</section>`
802
+ }
803
+ if (this.footnotes.length) {
804
+ back += `<section class="fnlist footnotes" role="doc-footnotes" id="${this.idPrefix}footnotes">${this.footnotes.join("")}</section>`
805
+ }
806
+ if (this.citations.bibHTML.length) {
807
+ back += `<div id="${this.idPrefix}references" class="references">${this.citations.bibHTML}</div>`
808
+ }
809
+ back += "</div>"
810
+ }
811
+ return back
812
+ }
813
+ }