@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,531 @@
1
+ import {XMLBuilder, XMLParser} from "fast-xml-parser"
2
+
3
+ const fastXMLParserOptions = {
4
+ attributeNamePrefix: "",
5
+ ignoreAttributes: false,
6
+ allowBooleanAttributes: true,
7
+ preserveOrder: true,
8
+ cdataPropName: "__cdata",
9
+ commentPropName: "#comment",
10
+ processEntities: true,
11
+ suppressUnpairedNode: false,
12
+ suppressEmptyNode: true,
13
+ trimValues: false
14
+ }
15
+
16
+ const isLeaf = tagName => ["#text", "__cdata", "#comment"].includes(tagName)
17
+
18
+ class XMLElement {
19
+ constructor(node, parentElement = null) {
20
+ this.node = node
21
+ this.parentElement = parentElement
22
+
23
+ // Recursively wrap child elements if they exist
24
+ const tagName = this.tagName
25
+ if (tagName && this.node[tagName] && !isLeaf(tagName)) {
26
+ this.node[tagName] = this.node[tagName].map(child => {
27
+ // Only wrap objects (not text nodes)
28
+ return typeof child === "object"
29
+ ? new XMLElement(child, this)
30
+ : child
31
+ })
32
+ }
33
+ }
34
+
35
+ get tagName() {
36
+ // Get the tag name dynamically (the first key that isn't ":@")
37
+ return Object.keys(this.node).find(key => key !== ":@")
38
+ }
39
+
40
+ get children() {
41
+ // Return child elements if they exist, or an empty array if none
42
+ return this.node[this.tagName] || []
43
+ }
44
+
45
+ get attributes() {
46
+ // Return attributes stored under the ":@" key, or an empty object if not present
47
+ return this.node[":@"] || {}
48
+ }
49
+
50
+ set attributes(attrs) {
51
+ // Update the attributes object
52
+ this.node[":@"] = attrs
53
+ }
54
+
55
+ get innerXML() {
56
+ // Serialize the children back to XML
57
+ return this.children
58
+ .map(child => {
59
+ return child.toString()
60
+ })
61
+ .join("")
62
+ }
63
+
64
+ set innerXML(xmlString) {
65
+ this.node[this.tagName].forEach(child => {
66
+ child.setParent(null)
67
+ })
68
+ // Clear existing children
69
+ this.node[this.tagName] = []
70
+
71
+ // Parse the new XML string
72
+ const parser = new XMLParser(fastXMLParserOptions)
73
+ const xml = parser.parse(
74
+ `<${this.tagName}>${xmlString}</${this.tagName}>`
75
+ )
76
+ // Append new children
77
+ xml[0][this.tagName].forEach(child => {
78
+ this.appendChild(child)
79
+ })
80
+ }
81
+
82
+ get textContent() {
83
+ if (isLeaf(this.tagName)) {
84
+ if (this.tagName === "#text") {
85
+ return this.node[this.tagName]
86
+ }
87
+ return ""
88
+ } else {
89
+ // Serialize the children back to text
90
+ return this.children.map(child => child.textContent).join("")
91
+ }
92
+ }
93
+
94
+ set textContent(value) {
95
+ // For leaf nodes, directly set the text content
96
+ if (this.tagName === "#text") {
97
+ this.node["#text"] = value
98
+ return
99
+ }
100
+
101
+ // For element nodes, clear children and add a text node
102
+ if (this.node[this.tagName]) {
103
+ // Clear existing children
104
+ this.node[this.tagName] = []
105
+
106
+ // Only add text content if it's not empty
107
+ if (value) {
108
+ const textNode = {
109
+ "#text": value
110
+ }
111
+ this.node[this.tagName].push(new XMLElement(textNode, this))
112
+ }
113
+ }
114
+ }
115
+
116
+ get firstChild() {
117
+ return this.children[0]
118
+ }
119
+
120
+ get lastChild() {
121
+ return this.children[this.children.length - 1]
122
+ }
123
+
124
+ get firstElementChild() {
125
+ return this.children.filter(child => !isLeaf(child.tagName))[0]
126
+ }
127
+
128
+ get lastElementChild() {
129
+ const elements = this.children.filter(child => !isLeaf(child.tagName))
130
+ if (elements.length === 0) {
131
+ return null
132
+ }
133
+ return elements[elements.length - 1]
134
+ }
135
+
136
+ get nextSibling() {
137
+ if (this.parentElement) {
138
+ const siblings = this.parentElement.children
139
+ const index = siblings.indexOf(this)
140
+ if (index < siblings.length - 1) {
141
+ return siblings[index + 1]
142
+ }
143
+ }
144
+ return null
145
+ }
146
+
147
+ get previousSibling() {
148
+ if (this.parentElement) {
149
+ const siblings = this.parentElement.children
150
+ const index = siblings.indexOf(this)
151
+ if (index > 0) {
152
+ return siblings[index - 1]
153
+ }
154
+ }
155
+ return null
156
+ }
157
+
158
+ setParent(element) {
159
+ this.parentElement = element
160
+ return this
161
+ }
162
+
163
+ hasAttribute(name) {
164
+ return name in this.attributes
165
+ }
166
+
167
+ getAttribute(name) {
168
+ return this.attributes[name]
169
+ }
170
+
171
+ setAttribute(name, value) {
172
+ if (isLeaf(this.tagName)) {
173
+ return false
174
+ }
175
+ this.attributes[name] = value
176
+ }
177
+
178
+ cloneNode(deep = false, parentElement = null) {
179
+ if (isLeaf(this.tagName)) {
180
+ return new XMLElement({...this.node}, parentElement)
181
+ }
182
+ const clonedNode = {
183
+ ":@": {...this.node[":@"]}
184
+ }
185
+ clonedNode[this.tagName] = []
186
+ const clone = new XMLElement(clonedNode, parentElement)
187
+ if (deep) {
188
+ clonedNode[this.tagName] = this.children.map(child =>
189
+ child.cloneNode(deep, clone)
190
+ )
191
+ }
192
+ return clone
193
+ }
194
+
195
+ appendChild(newChild) {
196
+ if (isLeaf(this.tagName)) {
197
+ return false
198
+ }
199
+ if (!this.node[this.tagName]) {
200
+ this.node[this.tagName] = []
201
+ }
202
+ let newChildElement
203
+ // Wrap newChild in XMLElement if it's not already
204
+ if (newChild instanceof XMLElement) {
205
+ newChild.parentElement?.removeChild(newChild)
206
+ newChildElement = newChild.setParent(this)
207
+ } else {
208
+ newChildElement = new XMLElement(newChild, this)
209
+ }
210
+ // Append newChild to the list of children under the tagName
211
+ this.node[this.tagName].push(newChildElement)
212
+ }
213
+
214
+ prependChild(newChild) {
215
+ if (isLeaf(this.tagName)) {
216
+ return false
217
+ }
218
+ if (!this.node[this.tagName]) {
219
+ this.node[this.tagName] = []
220
+ }
221
+ let newChildElement
222
+ // Wrap newChild in XMLElement if it's not already
223
+ if (newChild instanceof XMLElement) {
224
+ newChild.parentElement?.removeChild(newChild)
225
+ newChildElement = newChild.setParent(this)
226
+ } else {
227
+ newChildElement = new XMLElement(newChild, this)
228
+ }
229
+ // Prepend newChild to the list of children under the tagName
230
+ this.node[this.tagName].unshift(newChildElement)
231
+ }
232
+
233
+ appendXML(xmlString) {
234
+ if (isLeaf(this.tagName)) {
235
+ return false
236
+ }
237
+ const parser = new XMLParser(fastXMLParserOptions)
238
+ const xml = parser.parse(
239
+ `<${this.tagName}>${xmlString}</${this.tagName}>`
240
+ )
241
+ xml[0][this.tagName].forEach(child => {
242
+ this.appendChild(child)
243
+ })
244
+ }
245
+
246
+ prependXML(xmlString) {
247
+ if (isLeaf(this.tagName)) {
248
+ return false
249
+ }
250
+ const parser = new XMLParser(fastXMLParserOptions)
251
+ const xml = parser.parse(
252
+ `<${this.tagName}>${xmlString}</${this.tagName}>`
253
+ )
254
+ xml[0][this.tagName].reverse().forEach(child => {
255
+ this.prependChild(child)
256
+ })
257
+ }
258
+
259
+ insertXMLAt(xmlString, index) {
260
+ if (isLeaf(this.tagName)) {
261
+ return false
262
+ }
263
+ const parser = new XMLParser(fastXMLParserOptions)
264
+ const xml = parser.parse(
265
+ `<${this.tagName}>${xmlString}</${this.tagName}>`
266
+ )
267
+ xml[0][this.tagName].forEach((child, i) => {
268
+ const newChild = new XMLElement(child, this)
269
+ this.node[this.tagName].splice(index + i, 0, newChild)
270
+ })
271
+ }
272
+
273
+ splitAtChildElement(
274
+ childElement,
275
+ appendToCurrentNode = "",
276
+ insertBetweenNodes = "",
277
+ insertAfterSplit = ""
278
+ ) {
279
+ if (!this.children.includes(childElement)) {
280
+ return false
281
+ }
282
+
283
+ // Get the index of the child element
284
+ const splitIndex = this.children.indexOf(childElement)
285
+
286
+ // Store the original content
287
+ const beforeContent = this.children.slice(0, splitIndex)
288
+ const afterContent = this.children.slice(splitIndex + 1)
289
+
290
+ // Clear current node's content
291
+ this.node[this.tagName] = []
292
+
293
+ // Add back content before split point plus any appendToCurrentNode
294
+ beforeContent.forEach(child => this.appendChild(child))
295
+ if (appendToCurrentNode) {
296
+ this.appendXML(appendToCurrentNode)
297
+ }
298
+
299
+ const nextSibling = this.nextSibling
300
+
301
+ // Insert between content if provided
302
+ if (insertBetweenNodes) {
303
+ const parentElement = this.parentElement
304
+ if (parentElement) {
305
+ const currentIndex = parentElement.children.indexOf(this)
306
+ parentElement.insertXMLAt(insertBetweenNodes, currentIndex + 1)
307
+ }
308
+ }
309
+
310
+ // Create and insert the after content
311
+ if (afterContent.length || insertAfterSplit) {
312
+ const parentElement = this.parentElement
313
+ if (parentElement) {
314
+ const insertIndex = nextSibling
315
+ ? parentElement.children.indexOf(nextSibling)
316
+ : parentElement.children.length
317
+
318
+ // Parse insertAfterSplit to get the node type and attributes
319
+ if (insertAfterSplit) {
320
+ const parser = new XMLParser(fastXMLParserOptions)
321
+ const tempXml = parser.parse(insertAfterSplit)[0]
322
+ const newTagName = Object.keys(tempXml).find(
323
+ key => key !== ":@"
324
+ )
325
+ const newAttributes = tempXml[":@"] || {}
326
+
327
+ // Create new element with the parsed tag name and attributes
328
+ const newElement = new XMLElement(
329
+ {
330
+ [newTagName]: [],
331
+ ":@": newAttributes
332
+ },
333
+ parentElement
334
+ )
335
+
336
+ // Add the content from insertAfterSplit first
337
+ if (tempXml[newTagName]) {
338
+ tempXml[newTagName].forEach(child =>
339
+ newElement.appendChild(child)
340
+ )
341
+ }
342
+
343
+ // Then add the existing after content
344
+ afterContent.forEach(child => newElement.appendChild(child))
345
+
346
+ parentElement.node[parentElement.tagName].splice(
347
+ insertIndex,
348
+ 0,
349
+ newElement
350
+ )
351
+ } else {
352
+ // Fallback to original tag name if no insertAfterSplit provided
353
+ const tagName = this.tagName
354
+ const newElement = new XMLElement(
355
+ {[tagName]: []},
356
+ parentElement
357
+ )
358
+ afterContent.forEach(child => newElement.appendChild(child))
359
+ parentElement.node[parentElement.tagName].splice(
360
+ insertIndex,
361
+ 0,
362
+ newElement
363
+ )
364
+ }
365
+ }
366
+ }
367
+
368
+ return true
369
+ }
370
+
371
+ removeChild(child) {
372
+ if (isLeaf(this.tagName)) {
373
+ return false
374
+ }
375
+ if (this.node[this.tagName]) {
376
+ const index = this.node[this.tagName].indexOf(child)
377
+ if (index > -1) {
378
+ this.node[this.tagName].splice(index, 1)
379
+ child.setParent(null)
380
+ }
381
+ }
382
+ }
383
+
384
+ insertBefore(newChild, referenceChild) {
385
+ if (isLeaf(this.tagName)) {
386
+ return false
387
+ }
388
+ if (this.node[this.tagName]) {
389
+ const index = this.node[this.tagName].indexOf(referenceChild)
390
+ if (index > -1) {
391
+ let newChildElement
392
+ // Wrap newChild in XMLElement if it's not already
393
+ if (newChild instanceof XMLElement) {
394
+ newChild.parentElement?.removeChild(newChild)
395
+ newChildElement = newChild.setParent(this)
396
+ } else {
397
+ newChildElement = new XMLElement(newChild, this)
398
+ }
399
+ this.node[this.tagName].splice(index, 0, newChildElement)
400
+ } else {
401
+ // If referenceChild is not found, fallback to append
402
+ this.appendChild(newChild)
403
+ }
404
+ }
405
+ }
406
+
407
+ query(tagName, attributes = {}) {
408
+ return this.queryAll(tagName, attributes, 1)[0]
409
+ }
410
+
411
+ queryAll(tagName, attributes = {}, limit = false) {
412
+ const result = []
413
+ const tags = typeof tagName === "string" ? [tagName] : tagName
414
+
415
+ function traverse(dom) {
416
+ const currentTagName = Object.keys(dom.node).find(
417
+ key => key !== ":@"
418
+ )
419
+ if (
420
+ tags.includes(currentTagName) &&
421
+ Object.keys(attributes).every(attr => {
422
+ if (!dom.hasAttribute(attr)) {
423
+ return false
424
+ }
425
+ const attributeValue = attributes[attr]
426
+ if (attributeValue === null) {
427
+ return true
428
+ }
429
+
430
+ if (Array.isArray(attributeValue)) {
431
+ return attributeValue.includes(dom.getAttribute(attr))
432
+ }
433
+
434
+ return dom.getAttribute(attr) === attributeValue
435
+ })
436
+ ) {
437
+ result.push(dom)
438
+ }
439
+ if (limit && result.length >= limit) {
440
+ return true
441
+ }
442
+ if (
443
+ currentTagName &&
444
+ dom.node[currentTagName] &&
445
+ !isLeaf(currentTagName)
446
+ ) {
447
+ for (const childDOM of dom.node[currentTagName]) {
448
+ if (traverse(childDOM)) {
449
+ return true
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ traverse(this)
456
+ return result
457
+ }
458
+
459
+ closest(tagName) {
460
+ let currentNode = this
461
+ while (currentNode) {
462
+ if (currentNode.tagName === tagName) {
463
+ return currentNode
464
+ }
465
+ currentNode = currentNode.parentElement
466
+ }
467
+ return null
468
+ }
469
+
470
+ // Serialize back to original structure in a non-destructive way
471
+ toObject() {
472
+ const tagName = this.tagName
473
+ const node = {...this.node}
474
+ if (this.node[":@"]) {
475
+ node[":@"] = {...this.node[":@"]}
476
+ }
477
+ if (tagName && this.node[tagName]) {
478
+ if (Array.isArray(this.node[tagName])) {
479
+ node[tagName] = this.node[tagName].map(child => {
480
+ return child instanceof XMLElement
481
+ ? child.toObject()
482
+ : child
483
+ })
484
+ } else {
485
+ node[tagName] =
486
+ this.node[tagName] instanceof XMLElement
487
+ ? this.node[tagName].toObject()
488
+ : this.node[tagName]
489
+ }
490
+ }
491
+
492
+ if (tagName === "#document") {
493
+ return node["#document"]
494
+ }
495
+
496
+ return node
497
+ }
498
+
499
+ toString() {
500
+ if (isLeaf(this.tagName)) {
501
+ if (this.tagName === "#text") {
502
+ return this.node[this.tagName]
503
+ } else if (this.tagName === "__cdata") {
504
+ return `<![CDATA[${this.node[this.tagName]}]]>`
505
+ } else if (this.tagName === "#comment") {
506
+ return `<!--${this.node[this.tagName]}-->`
507
+ }
508
+ }
509
+ const builder = new XMLBuilder(fastXMLParserOptions)
510
+ const object = this.toObject()
511
+ return builder.build(Array.isArray(object) ? object : [object])
512
+ }
513
+
514
+ get outerXML() {
515
+ return this.toString()
516
+ }
517
+ }
518
+
519
+ // Helper function to wrap the entire XML structure recursively
520
+ export const xmlDOM = xmlString => {
521
+ const parser = new XMLParser(fastXMLParserOptions)
522
+ // Parse the XML string into an object
523
+ const xmlStructure = parser.parse(xmlString)
524
+
525
+ const node =
526
+ xmlStructure.length === 1
527
+ ? xmlStructure[0]
528
+ : {"#document": xmlStructure}
529
+ // Recursively wrap each node in XMLElement
530
+ return new XMLElement(node)
531
+ }
@@ -0,0 +1,95 @@
1
+ import {xmlDOM} from "./xml.js"
2
+
3
+ import {get} from "../../common/index.js"
4
+ // Handle a zip file containing XML files. Make sure files are only opened once,
5
+ // and provide a mechanism to save the file.
6
+
7
+ export class XmlZip {
8
+ constructor(url, mimeType) {
9
+ this.url = url
10
+ this.mimeType = mimeType
11
+ this.docs = {}
12
+ this.extraFiles = {}
13
+ this.rawFile = false
14
+ }
15
+
16
+ init() {
17
+ return import("jszip")
18
+ .then(({default: JSZip}) => {
19
+ this.zip = new JSZip()
20
+ return this.downloadZip()
21
+ })
22
+ .then(() => this.loadZip())
23
+ }
24
+
25
+ downloadZip() {
26
+ return get(this.url)
27
+ .then(response => response.blob())
28
+ .then(blob => (this.rawFile = blob))
29
+ }
30
+
31
+ loadZip() {
32
+ return this.zip.loadAsync(this.rawFile)
33
+ }
34
+
35
+ // Open file at filePath from zip file and parse it as XML.
36
+ getXml(filePath, defaultContents) {
37
+ if (this.docs[filePath]) {
38
+ // file has been loaded already.
39
+ return Promise.resolve(this.docs[filePath])
40
+ } else if (this.zip.files[filePath]) {
41
+ return this.zip
42
+ .file(filePath)
43
+ .async("string")
44
+ .then(string => {
45
+ this.docs[filePath] = xmlDOM(string)
46
+ return Promise.resolve(this.docs[filePath])
47
+ })
48
+ } else if (defaultContents) {
49
+ return Promise.resolve(defaultContents).then(string => {
50
+ this.docs[filePath] = xmlDOM(string)
51
+ return Promise.resolve(this.docs[filePath])
52
+ })
53
+ } else {
54
+ // File couldn't be found and there was no default value.
55
+ return Promise.reject(new Error("File not found"))
56
+ }
57
+ }
58
+
59
+ // Add an xml file at filepath without checking for previous version
60
+ addXmlFile(filePath, xmlContents) {
61
+ this.docs[filePath] = xmlContents
62
+ }
63
+
64
+ // Add extra file to be saved in zip later.
65
+ addExtraFile(filePath, fileContents) {
66
+ this.extraFiles[filePath] = fileContents
67
+ }
68
+
69
+ // Put all currently open XML files into zip.
70
+ allXMLToZip() {
71
+ for (const fileName in this.docs) {
72
+ this.xmlToZip(fileName)
73
+ }
74
+ }
75
+
76
+ // Put all extra files into zip.
77
+ allExtraToZip() {
78
+ for (const fileName in this.extraFiles) {
79
+ this.zip.file(fileName, this.extraFiles[fileName])
80
+ }
81
+ }
82
+
83
+ // Put the xml identified by filePath into zip.
84
+ xmlToZip(filePath) {
85
+ const string = this.docs[filePath].toString()
86
+ this.zip.file(filePath, string)
87
+ }
88
+
89
+ prepareBlob() {
90
+ this.allXMLToZip()
91
+ this.allExtraToZip()
92
+
93
+ return this.zip.generateAsync({type: "blob", mimeType: this.mimeType})
94
+ }
95
+ }
@@ -0,0 +1,90 @@
1
+ import {convertDataURIToBlob, get} from "../../common/index.js"
2
+
3
+ /** Creates a zip file.
4
+ * @function zipFileCreator
5
+ * @param {list} textFiles A list of files in plain text format.
6
+ * @param {list} binaryFiles A list fo files that have to be downloaded from the internet before being included.
7
+ * @param {list} includeZips A list of zip files to be merged into the output zip file.
8
+ * @param {string} [mimeType=application/zip] The mimetype of the file that is to be created.
9
+ */
10
+
11
+ export class ZipFileCreator {
12
+ constructor(
13
+ textFiles = [],
14
+ binaryFiles = [],
15
+ zipFiles = [],
16
+ mimeType = "application/zip",
17
+ date = new Date()
18
+ ) {
19
+ this.textFiles = textFiles
20
+ this.binaryFiles = binaryFiles
21
+ this.zipFiles = zipFiles
22
+ this.mimeType = mimeType
23
+ this.date = date
24
+ }
25
+
26
+ init() {
27
+ return import("jszip").then(({default: JSZip}) => {
28
+ JSZip.defaults.date = this.date
29
+ this.zipFs = new JSZip()
30
+ if (this.mimeType !== "application/zip") {
31
+ this.zipFs.file("mimetype", this.mimeType, {
32
+ compression: "STORE"
33
+ })
34
+ }
35
+
36
+ return this.includeZips()
37
+ })
38
+ }
39
+
40
+ includeZips() {
41
+ const getZipBlobs = this.zipFiles.map(zipFile => {
42
+ return get(zipFile.url)
43
+ .then(response => response.blob())
44
+ .then(blob => (zipFile.blob = blob))
45
+ })
46
+ return Promise.all(getZipBlobs)
47
+ .then(() => {
48
+ return this.zipFiles.map(zipFile => {
49
+ const zipDir =
50
+ zipFile.directory === ""
51
+ ? this.zipFs
52
+ : this.zipFs.folder(zipFile.directory)
53
+ return zipDir.loadAsync(zipFile.blob)
54
+ })
55
+ })
56
+ .then(() => this.createZip())
57
+ }
58
+
59
+ createZip() {
60
+ this.textFiles.forEach(textFile => {
61
+ this.zipFs.file(textFile.filename, textFile.contents, {
62
+ compression: "DEFLATE"
63
+ })
64
+ })
65
+ const blobPromises = this.binaryFiles.map(binaryFile =>
66
+ get(binaryFile.url)
67
+ .then(response => response.blob())
68
+ .then(blob =>
69
+ Promise.resolve({blob, filename: binaryFile.filename})
70
+ )
71
+ )
72
+ return Promise.all(blobPromises).then(promises => {
73
+ promises.forEach(promise =>
74
+ this.zipFs.file(promise.filename, promise.blob, {
75
+ binary: true,
76
+ compression: "DEFLATE"
77
+ })
78
+ )
79
+ return this.zipFs.generateAsync({
80
+ type: "blob",
81
+ mimeType: this.mimeType
82
+ })
83
+ })
84
+ }
85
+
86
+ // Legacy - remove in 3.12. Can be sued directly from function in common/blob.js
87
+ convertDataURIToBlob(dataURI) {
88
+ return convertDataURIToBlob(dataURI)
89
+ }
90
+ }