@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.
- package/LICENSE +661 -0
- package/README.md +16 -0
- package/jest.config.js +23 -0
- package/package.json +59 -0
- package/schema.json +1 -0
- package/scripts/export-schema.js +16 -0
- package/src/bibliography/common.js +92 -0
- package/src/bibliography/csl_bib.js +139 -0
- package/src/citations/citeproc_sys.js +42 -0
- package/src/citations/format.js +194 -0
- package/src/common/blob.js +10 -0
- package/src/common/file.js +25 -0
- package/src/common/index.js +12 -0
- package/src/common/network.js +79 -0
- package/src/common/text.js +44 -0
- package/src/editor/e2ee/encryptor.js +228 -0
- package/src/exporter/docx/citations.js +177 -0
- package/src/exporter/docx/comments.js +165 -0
- package/src/exporter/docx/footnotes.js +240 -0
- package/src/exporter/docx/images.js +101 -0
- package/src/exporter/docx/index.js +185 -0
- package/src/exporter/docx/lists.js +260 -0
- package/src/exporter/docx/math.js +46 -0
- package/src/exporter/docx/metadata.js +289 -0
- package/src/exporter/docx/rels.js +193 -0
- package/src/exporter/docx/render.js +941 -0
- package/src/exporter/docx/richtext.js +1182 -0
- package/src/exporter/docx/tables.js +112 -0
- package/src/exporter/docx/tools.js +50 -0
- package/src/exporter/epub/index.js +142 -0
- package/src/exporter/epub/templates.js +140 -0
- package/src/exporter/epub/tools.js +96 -0
- package/src/exporter/html/citations.js +121 -0
- package/src/exporter/html/convert.js +813 -0
- package/src/exporter/html/index.js +192 -0
- package/src/exporter/html/templates.js +34 -0
- package/src/exporter/html/tools.js +50 -0
- package/src/exporter/jats/bibliography.js +183 -0
- package/src/exporter/jats/citations.js +109 -0
- package/src/exporter/jats/convert.js +871 -0
- package/src/exporter/jats/index.js +92 -0
- package/src/exporter/jats/templates.js +35 -0
- package/src/exporter/jats/text.js +72 -0
- package/src/exporter/latex/convert.js +934 -0
- package/src/exporter/latex/escape_latex.js +21 -0
- package/src/exporter/latex/index.js +74 -0
- package/src/exporter/latex/readme.js +22 -0
- package/src/exporter/native/shrink.js +132 -0
- package/src/exporter/odt/citations.js +101 -0
- package/src/exporter/odt/footnotes.js +147 -0
- package/src/exporter/odt/images.js +115 -0
- package/src/exporter/odt/index.js +156 -0
- package/src/exporter/odt/math.js +57 -0
- package/src/exporter/odt/metadata.js +251 -0
- package/src/exporter/odt/render.js +806 -0
- package/src/exporter/odt/richtext.js +865 -0
- package/src/exporter/odt/styles.js +387 -0
- package/src/exporter/odt/track.js +68 -0
- package/src/exporter/pandoc/citations.js +98 -0
- package/src/exporter/pandoc/convert.js +1017 -0
- package/src/exporter/pandoc/index.js +92 -0
- package/src/exporter/pandoc/readme.js +8 -0
- package/src/exporter/pandoc/tools.js +51 -0
- package/src/exporter/print/index.js +177 -0
- package/src/exporter/tools/doc_content.js +144 -0
- package/src/exporter/tools/file.js +9 -0
- package/src/exporter/tools/json.js +73 -0
- package/src/exporter/tools/svg.js +29 -0
- package/src/exporter/tools/xml.js +531 -0
- package/src/exporter/tools/xml_zip.js +95 -0
- package/src/exporter/tools/zip.js +90 -0
- package/src/exporter/tools/zotero_csl.js +93 -0
- package/src/importer/citations.js +129 -0
- package/src/importer/docx/citations.js +123 -0
- package/src/importer/docx/convert.js +1427 -0
- package/src/importer/docx/helpers.js +9 -0
- package/src/importer/docx/omml2mathml.js +1448 -0
- package/src/importer/docx/parse.js +735 -0
- package/src/importer/native/get_images.js +76 -0
- package/src/importer/native/update.js +29 -0
- package/src/importer/odt/citations.js +87 -0
- package/src/importer/odt/convert.js +1855 -0
- package/src/importer/pandoc/convert.js +884 -0
- package/src/importer/pandoc/helpers.js +84 -0
- package/src/importer/zip_analyzer.js +102 -0
- package/src/index.js +1 -0
- package/src/mathlive/opf_includes.js +24 -0
- package/src/schema/common/annotate.js +76 -0
- package/src/schema/common/base.js +118 -0
- package/src/schema/common/citation.js +62 -0
- package/src/schema/common/equation.js +31 -0
- package/src/schema/common/figure.js +190 -0
- package/src/schema/common/heading.js +43 -0
- package/src/schema/common/index.js +40 -0
- package/src/schema/common/list.js +95 -0
- package/src/schema/common/reference.js +100 -0
- package/src/schema/common/table.js +103 -0
- package/src/schema/common/track.js +190 -0
- package/src/schema/const.js +58 -0
- package/src/schema/convert.js +1272 -0
- package/src/schema/document/content.js +187 -0
- package/src/schema/document/index.js +117 -0
- package/src/schema/document/structure.js +452 -0
- package/src/schema/export.js +21 -0
- package/src/schema/footnotes.js +126 -0
- package/src/schema/footnotes_convert.js +31 -0
- package/src/schema/i18n.js +595 -0
- package/src/schema/index.js +5 -0
- package/src/schema/mini_json.js +61 -0
- 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
|
+
}
|