@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,260 @@
|
|
|
1
|
+
import {descendantNodes} from "../tools/doc_content.js"
|
|
2
|
+
|
|
3
|
+
const DEFAULT_LISTPARAGRAPH_XML = `
|
|
4
|
+
<w:style w:type="paragraph" w:styleId="ListParagraph">
|
|
5
|
+
<w:name w:val="List Paragraph"/>
|
|
6
|
+
<w:basedOn w:val="Normal"/>
|
|
7
|
+
<w:uiPriority w:val="34"/>
|
|
8
|
+
<w:qFormat/>
|
|
9
|
+
<w:rsid w:val="006E68A6"/>
|
|
10
|
+
<w:pPr>
|
|
11
|
+
<w:ind w:left="720"/>
|
|
12
|
+
<w:contextualSpacing/>
|
|
13
|
+
</w:pPr>
|
|
14
|
+
</w:style>
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
const DEFAULT_NUMBERING_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
18
|
+
<w:numbering xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se wp14">
|
|
19
|
+
</w:numbering>`
|
|
20
|
+
|
|
21
|
+
export class DOCXExporterLists {
|
|
22
|
+
constructor(docContent, xml, rels) {
|
|
23
|
+
this.docContent = docContent
|
|
24
|
+
this.xml = xml
|
|
25
|
+
this.rels = rels
|
|
26
|
+
this.useBulletList = false
|
|
27
|
+
this.usedNumberedList = []
|
|
28
|
+
this.styleXML = false
|
|
29
|
+
this.numberingXML = false
|
|
30
|
+
this.abstractNumIdCounter = 0
|
|
31
|
+
this.numIdCounter = 0
|
|
32
|
+
// We only need one bulletType for all bullet lists, but a new
|
|
33
|
+
// numberedType for each numbered list so that the numbering starts in 1
|
|
34
|
+
// each time.
|
|
35
|
+
this.bulletType = false
|
|
36
|
+
this.numberFormat = "decimal"
|
|
37
|
+
this.numberedTypes = []
|
|
38
|
+
this.styleFilePath = "word/styles.xml"
|
|
39
|
+
this.numberingFilePath = "word/numbering.xml"
|
|
40
|
+
this.ctFilePath = "[Content_Types].xml"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
init() {
|
|
44
|
+
this.findLists()
|
|
45
|
+
if (this.usedNumberedList.length > 0 || this.useBulletList) {
|
|
46
|
+
const p = []
|
|
47
|
+
|
|
48
|
+
p.push(
|
|
49
|
+
new Promise(resolve => {
|
|
50
|
+
this.initCt().then(() => resolve())
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
p.push(
|
|
55
|
+
new Promise(resolve => {
|
|
56
|
+
this.addNumberingXml().then(() => resolve())
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
p.push(
|
|
61
|
+
new Promise(resolve => {
|
|
62
|
+
this.addListParagraphStyle().then(() => resolve())
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
return Promise.all(p)
|
|
66
|
+
} else {
|
|
67
|
+
return Promise.resolve()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
initCt() {
|
|
72
|
+
return this.xml.getXml(this.ctFilePath).then(ctXML => {
|
|
73
|
+
this.ctXML = ctXML
|
|
74
|
+
this.addRelsToCt()
|
|
75
|
+
return Promise.resolve()
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
addRelsToCt() {
|
|
80
|
+
const override = this.ctXML.query("Override", {
|
|
81
|
+
PartName: `/${this.numberingFilePath}`
|
|
82
|
+
})
|
|
83
|
+
if (!override) {
|
|
84
|
+
const types = this.ctXML.query("Types")
|
|
85
|
+
types.appendXML(
|
|
86
|
+
`<Override PartName="/${this.numberingFilePath}" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>`
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
findLists() {
|
|
92
|
+
descendantNodes(this.docContent).forEach(node => {
|
|
93
|
+
if (node.type === "bullet_list") {
|
|
94
|
+
this.useBulletList = true
|
|
95
|
+
} else if (node.type === "ordered_list") {
|
|
96
|
+
this.usedNumberedList.push(node.attrs.order)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
addNumberingXml() {
|
|
102
|
+
return this.xml
|
|
103
|
+
.getXml(this.numberingFilePath, DEFAULT_NUMBERING_XML)
|
|
104
|
+
.then(numberingXML => {
|
|
105
|
+
this.numberingXML = numberingXML
|
|
106
|
+
this.rels.addNumberingRel()
|
|
107
|
+
this.addUsedListTypes()
|
|
108
|
+
return Promise.resolve()
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
addListParagraphStyle() {
|
|
113
|
+
return this.xml.getXml(this.styleFilePath).then(styleXML => {
|
|
114
|
+
this.styleXML = styleXML
|
|
115
|
+
if (
|
|
116
|
+
!this.styleXML.query("w:style", {"w:styleId": "ListParagraph"})
|
|
117
|
+
) {
|
|
118
|
+
const stylesEl = this.styleXML.query("w:styles")
|
|
119
|
+
stylesEl.appendXML(DEFAULT_LISTPARAGRAPH_XML)
|
|
120
|
+
}
|
|
121
|
+
return Promise.resolve()
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
addUsedListTypes() {
|
|
126
|
+
const allAbstractNum = this.numberingXML.queryAll("w:abstractNum")
|
|
127
|
+
allAbstractNum.forEach(abstractNum => {
|
|
128
|
+
// We check the format for the lowest level list and use the first
|
|
129
|
+
// one we find for 'bullet' or 'not bullet'
|
|
130
|
+
// This means that if a list is defined using anything else than
|
|
131
|
+
// bullets, it will be accepted as the format of
|
|
132
|
+
// the numeric list.
|
|
133
|
+
const levelZeroFormat = abstractNum
|
|
134
|
+
.query("w:lvl", {"w:ilvl": "0"})
|
|
135
|
+
.query("w:numFmt")
|
|
136
|
+
.getAttribute("w:val")
|
|
137
|
+
const abstractNumId = Number.parseInt(
|
|
138
|
+
abstractNum.getAttribute("w:abstractNumId")
|
|
139
|
+
)
|
|
140
|
+
if (levelZeroFormat === "bullet" && !this.bulletAbstractType) {
|
|
141
|
+
const numEl = this.numberingXML.query("w:abstractNumId", {
|
|
142
|
+
"w:val": abstractNumId
|
|
143
|
+
}).parentElement
|
|
144
|
+
const numId = Number.parseInt(numEl.getAttribute("w:numId"))
|
|
145
|
+
this.bulletType = numId
|
|
146
|
+
} else if (levelZeroFormat !== "bullet" && !this.numberFormat) {
|
|
147
|
+
this.numberFormat = levelZeroFormat
|
|
148
|
+
}
|
|
149
|
+
if (this.abstractNumIdCounter < abstractNumId) {
|
|
150
|
+
this.abstractNumIdCounter = abstractNumId
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
const allNum = this.numberingXML.queryAll("w:num")
|
|
154
|
+
allNum.forEach(numEl => {
|
|
155
|
+
const numId = Number.parseInt(numEl.getAttribute("w:val"))
|
|
156
|
+
if (this.numIdCounter < numId) {
|
|
157
|
+
this.numIdCounter = numId
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (!this.bulletType && this.useBulletList) {
|
|
162
|
+
this.addBulletNumType(
|
|
163
|
+
++this.numIdCounter,
|
|
164
|
+
++this.abstractNumIdCounter
|
|
165
|
+
)
|
|
166
|
+
this.bulletType = this.numIdCounter
|
|
167
|
+
}
|
|
168
|
+
if (this.usedNumberedList.length > 0) {
|
|
169
|
+
this.abstractNumIdCounter++
|
|
170
|
+
|
|
171
|
+
this.numberedAbstractType = this.abstractNumIdCounter
|
|
172
|
+
}
|
|
173
|
+
for (let i = 0; i < this.usedNumberedList.length; i++) {
|
|
174
|
+
const numId = ++this.numIdCounter
|
|
175
|
+
this.addNumberedNumType(numId, this.usedNumberedList[i])
|
|
176
|
+
this.numberedTypes.push(numId)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getBulletType() {
|
|
181
|
+
return this.bulletType
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getNumberedType() {
|
|
185
|
+
return this.numberedTypes.shift()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
addBulletNumType(numId, abstractNumId) {
|
|
189
|
+
const numberingEl = this.numberingXML.query("w:numbering")
|
|
190
|
+
numberingEl.appendXML(`
|
|
191
|
+
<w:abstractNum w:abstractNumId="${abstractNumId}" w15:restartNumberingAfterBreak="0">
|
|
192
|
+
<w:nsid w:val="3620195A" />
|
|
193
|
+
<w:multiLevelType w:val="hybridMultilevel" />
|
|
194
|
+
<w:tmpl w:val="A74C9E6A" />
|
|
195
|
+
</w:abstractNum>
|
|
196
|
+
<w:num w:numId="${numId}">
|
|
197
|
+
<w:abstractNumId w:val="${abstractNumId}" />
|
|
198
|
+
</w:num>
|
|
199
|
+
`)
|
|
200
|
+
const newAbstractNum = this.numberingXML.query("w:abstractNum", {
|
|
201
|
+
"w:abstractNumId": String(abstractNumId)
|
|
202
|
+
})
|
|
203
|
+
// Definition seem to always define 9 levels (0-8).
|
|
204
|
+
for (let level = 0; level < 9; level++) {
|
|
205
|
+
newAbstractNum.appendXML(`
|
|
206
|
+
<w:lvl w:ilvl="${level}" w:tplc="04090001" w:tentative="1">
|
|
207
|
+
<w:start w:val="1" />
|
|
208
|
+
<w:numFmt w:val="bullet" />
|
|
209
|
+
<w:lvlText w:val="•" />
|
|
210
|
+
<w:lvlJc w:val="left" />
|
|
211
|
+
<w:pPr>
|
|
212
|
+
<w:ind w:left="${(level + 1) * 720}" w:hanging="360" />
|
|
213
|
+
</w:pPr>
|
|
214
|
+
<w:rPr>
|
|
215
|
+
<w:rFonts w:ascii="Symbol" w:hAnsi="Symbol" />
|
|
216
|
+
</w:rPr>
|
|
217
|
+
</w:lvl>
|
|
218
|
+
`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
addNumberedNumType(numId, start) {
|
|
223
|
+
this.abstractNumIdCounter++
|
|
224
|
+
this.addNumberedAbstractNumType(this.abstractNumIdCounter, start)
|
|
225
|
+
const numberingEl = this.numberingXML.query("w:numbering")
|
|
226
|
+
numberingEl.appendXML(`
|
|
227
|
+
<w:num w:numId="${numId}">
|
|
228
|
+
<w:abstractNumId w:val="${this.abstractNumIdCounter}" />
|
|
229
|
+
</w:num>
|
|
230
|
+
`)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
addNumberedAbstractNumType(abstractNumId, start) {
|
|
234
|
+
const numberingEl = this.numberingXML.query("w:numbering")
|
|
235
|
+
numberingEl.appendXML(`
|
|
236
|
+
<w:abstractNum w:abstractNumId="${abstractNumId}" w15:restartNumberingAfterBreak="0">
|
|
237
|
+
<w:nsid w:val="7F6635F3" />
|
|
238
|
+
<w:multiLevelType w:val="hybridMultilevel" />
|
|
239
|
+
<w:tmpl w:val="BFFEF214" />
|
|
240
|
+
</w:abstractNum>
|
|
241
|
+
`)
|
|
242
|
+
const newAbstractNum = this.numberingXML.query("w:abstractNum", {
|
|
243
|
+
"w:abstractNumId": String(abstractNumId)
|
|
244
|
+
})
|
|
245
|
+
// Definition seem to always define 9 levels (0-8).
|
|
246
|
+
for (let level = 0; level < 9; level++) {
|
|
247
|
+
newAbstractNum.appendXML(`
|
|
248
|
+
<w:lvl w:ilvl="${level}" w:tplc="0409000F">
|
|
249
|
+
<w:start w:val="${start}" />
|
|
250
|
+
<w:numFmt w:val="${this.numberFormat}" />
|
|
251
|
+
<w:lvlText w:val="%${level + 1}." />
|
|
252
|
+
<w:lvlJc w:val="left" />
|
|
253
|
+
<w:pPr>
|
|
254
|
+
<w:ind w:left="${(level + 1) * 720}" w:hanging="360" />
|
|
255
|
+
</w:pPr>
|
|
256
|
+
</w:lvl>
|
|
257
|
+
`)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {mml2omml} from "mathml2omml"
|
|
2
|
+
|
|
3
|
+
// Not entirely sure if we need this font here. This is included whenever Word
|
|
4
|
+
// itself adds a formula, but our ooml doesn't refer to the font, so it may be pointless.
|
|
5
|
+
const CAMBRIA_MATH_FONT_DECLARATION = `
|
|
6
|
+
<w:font w:name="Cambria Math">
|
|
7
|
+
<w:panose1 w:val="02040503050406030204" />
|
|
8
|
+
<w:charset w:val="00" />
|
|
9
|
+
<w:family w:val="roman" />
|
|
10
|
+
<w:pitch w:val="variable" />
|
|
11
|
+
<w:sig w:usb0="E00002FF" w:usb1="420024FF" w:usb2="00000000" w:usb3="00000000" w:csb0="0000019F" w:csb1="00000000" />
|
|
12
|
+
</w:font>`
|
|
13
|
+
|
|
14
|
+
export class DOCXExporterMath {
|
|
15
|
+
constructor(xml) {
|
|
16
|
+
this.xml = xml
|
|
17
|
+
this.fontTableXML = false
|
|
18
|
+
this.addedCambriaMath = false
|
|
19
|
+
this.domParser = new DOMParser()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
init() {
|
|
23
|
+
return this.xml
|
|
24
|
+
.getXml("word/fontTable.xml")
|
|
25
|
+
.then(fontTablesXML => {
|
|
26
|
+
this.fontTablesXML = fontTablesXML
|
|
27
|
+
return import("mathlive")
|
|
28
|
+
})
|
|
29
|
+
.then(MathLive => (this.mathLive = MathLive))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
latexToMathML(latex) {
|
|
33
|
+
return this.mathLive.convertLatexToMathMl(latex)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getOmml(latex) {
|
|
37
|
+
if (!this.addedCambriaMath) {
|
|
38
|
+
const fontsEl = this.fontTablesXML.query("w:fonts")
|
|
39
|
+
fontsEl.appendXML(CAMBRIA_MATH_FONT_DECLARATION)
|
|
40
|
+
this.addedCambriaMath = true
|
|
41
|
+
}
|
|
42
|
+
const mathmlString = `<math xmlns="http://www.w3.org/1998/Math/MathML"><semantics>${this.latexToMathML(latex)}</semantics></math>`
|
|
43
|
+
const ommlString = mml2omml(mathmlString)
|
|
44
|
+
return ommlString
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import {escapeText} from "../../common/index.js"
|
|
2
|
+
|
|
3
|
+
export class DOCXExporterMetadata {
|
|
4
|
+
constructor(xml, metadata, csl = null) {
|
|
5
|
+
this.xml = xml
|
|
6
|
+
this.metadata = metadata
|
|
7
|
+
this.csl = csl
|
|
8
|
+
this.coreXML = false
|
|
9
|
+
this.customXML = false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
init() {
|
|
13
|
+
return this.xml.getXml("docProps/core.xml").then(coreXML => {
|
|
14
|
+
this.coreXML = coreXML
|
|
15
|
+
this.addMetadata()
|
|
16
|
+
return this.addCustomProperties()
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async hasBibliography() {
|
|
21
|
+
if (!this.csl || !this.metadata.citationStyle) {
|
|
22
|
+
return "0"
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const style = await this.csl.getStyle(this.metadata.citationStyle)
|
|
26
|
+
// Check if the style has a bibliography section
|
|
27
|
+
const hasBib = style.children.some(
|
|
28
|
+
section => section.name === "bibliography"
|
|
29
|
+
)
|
|
30
|
+
return hasBib ? "1" : "0"
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
return "0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addMetadata() {
|
|
37
|
+
const corePropertiesEl = this.coreXML.query("cp:coreProperties")
|
|
38
|
+
|
|
39
|
+
// Title
|
|
40
|
+
let titleEl = this.coreXML.query("dc:title")
|
|
41
|
+
if (!titleEl) {
|
|
42
|
+
corePropertiesEl.appendXML("<dc:title></dc:title>")
|
|
43
|
+
titleEl = corePropertiesEl.lastElementChild
|
|
44
|
+
}
|
|
45
|
+
titleEl.innerXML = escapeText(this.metadata.title)
|
|
46
|
+
// Authors
|
|
47
|
+
|
|
48
|
+
const authors = this.metadata.authors.map(author => {
|
|
49
|
+
const nameParts = []
|
|
50
|
+
if (author.firstname) {
|
|
51
|
+
nameParts.push(author.firstname)
|
|
52
|
+
}
|
|
53
|
+
if (author.lastname) {
|
|
54
|
+
nameParts.push(author.lastname)
|
|
55
|
+
}
|
|
56
|
+
if (!nameParts.length && author.institution) {
|
|
57
|
+
// We have an institution but no names. Use institution as name.
|
|
58
|
+
nameParts.push(author.institution)
|
|
59
|
+
}
|
|
60
|
+
return nameParts.join(" ")
|
|
61
|
+
})
|
|
62
|
+
const lastAuthor = authors.length
|
|
63
|
+
? escapeText(authors[0])
|
|
64
|
+
: gettext("Unknown")
|
|
65
|
+
const allAuthors = authors.length
|
|
66
|
+
? escapeText(authors.join(";"))
|
|
67
|
+
: gettext("Unknown")
|
|
68
|
+
let allAuthorsEl = this.coreXML.query("dc:creator")
|
|
69
|
+
|
|
70
|
+
if (!allAuthorsEl) {
|
|
71
|
+
corePropertiesEl.appendXML("<dc:creator></dc:creator>")
|
|
72
|
+
allAuthorsEl = corePropertiesEl.lastElementChild
|
|
73
|
+
}
|
|
74
|
+
allAuthorsEl.innerXML = allAuthors
|
|
75
|
+
let lastAuthorEl = this.coreXML.query("dc:lastModifiedBy")
|
|
76
|
+
if (!lastAuthorEl) {
|
|
77
|
+
corePropertiesEl.appendXML(
|
|
78
|
+
"<dc:lastModifiedBy></dc:lastModifiedBy>"
|
|
79
|
+
)
|
|
80
|
+
lastAuthorEl = corePropertiesEl.lastElementChild
|
|
81
|
+
}
|
|
82
|
+
lastAuthorEl.innerXML = lastAuthor
|
|
83
|
+
// Keywords
|
|
84
|
+
if (this.metadata.keywords.length) {
|
|
85
|
+
// It is not really clear how keywords should be separated in DOCX files,
|
|
86
|
+
// so we use ", ".
|
|
87
|
+
const keywordsString = escapeText(this.metadata.keywords.join(", "))
|
|
88
|
+
|
|
89
|
+
let keywordsEl = this.coreXML.query("cp:keywords")
|
|
90
|
+
if (!keywordsEl) {
|
|
91
|
+
corePropertiesEl.appendXML("<cp:keywords></cp:keywords>")
|
|
92
|
+
keywordsEl = corePropertiesEl.lastElementChild
|
|
93
|
+
}
|
|
94
|
+
keywordsEl.innerXML = keywordsString
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// time
|
|
98
|
+
const date = new Date()
|
|
99
|
+
const dateString = date.toISOString().split(".")[0] + "Z"
|
|
100
|
+
const createdEl = this.coreXML.query("dcterms:created")
|
|
101
|
+
createdEl.innerXML = dateString
|
|
102
|
+
let modifiedEl = this.coreXML.query("dcterms:modified")
|
|
103
|
+
if (!modifiedEl) {
|
|
104
|
+
corePropertiesEl.appendXML(
|
|
105
|
+
'<dcterms:modified xsi:type="dcterms:W3CDTF"></dcterms:modified>'
|
|
106
|
+
)
|
|
107
|
+
modifiedEl = corePropertiesEl.lastElementChild
|
|
108
|
+
}
|
|
109
|
+
modifiedEl.innerXML = dateString
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async addCustomProperties() {
|
|
113
|
+
// Create or update docProps/custom.xml with citation style information
|
|
114
|
+
const customXmlContent = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
115
|
+
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
|
|
116
|
+
</Properties>`
|
|
117
|
+
|
|
118
|
+
const customXML = await this.xml.getXml(
|
|
119
|
+
"docProps/custom.xml",
|
|
120
|
+
Promise.resolve(customXmlContent)
|
|
121
|
+
)
|
|
122
|
+
this.customXML = customXML
|
|
123
|
+
|
|
124
|
+
// Add citation style property
|
|
125
|
+
if (this.metadata.citationStyle) {
|
|
126
|
+
const propertiesEl = this.customXML.query("Properties")
|
|
127
|
+
|
|
128
|
+
// Remove any existing ZOTERO_PREF_ properties
|
|
129
|
+
const existingZoteroProps = this.customXML
|
|
130
|
+
.queryAll("property")
|
|
131
|
+
.filter(
|
|
132
|
+
prop =>
|
|
133
|
+
prop.getAttribute("name") &&
|
|
134
|
+
prop.getAttribute("name").startsWith("ZOTERO_PREF_")
|
|
135
|
+
)
|
|
136
|
+
existingZoteroProps.forEach(prop =>
|
|
137
|
+
prop.parentElement.removeChild(prop)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// Find the highest pid to determine the next one
|
|
141
|
+
const existingProperties = this.customXML.queryAll("property")
|
|
142
|
+
let maxPid = 0
|
|
143
|
+
existingProperties.forEach(prop => {
|
|
144
|
+
const pid = parseInt(prop.getAttribute("pid"))
|
|
145
|
+
if (pid > maxPid) {
|
|
146
|
+
maxPid = pid
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Determine if the citation style has a bibliography
|
|
151
|
+
const hasBib = await this.hasBibliography()
|
|
152
|
+
|
|
153
|
+
// Create the data content
|
|
154
|
+
const citationStyleUrl = `http://www.zotero.org/styles/${escapeText(this.metadata.citationStyle)}`
|
|
155
|
+
const dataContent = `<data data-version="3" zotero-version="8.0.2"><session id=""/><style id="${citationStyleUrl}" locale="${escapeText(this.metadata.language || "en-US")}" hasBibliography="${hasBib}" bibliographyStyleHasBeenSet="1"/><prefs><pref name="fieldType" value="Field"/></prefs></data>`
|
|
156
|
+
|
|
157
|
+
// Split content into chunks of 255 characters (DOCX limit)
|
|
158
|
+
const chunkSize = 255
|
|
159
|
+
const chunks = []
|
|
160
|
+
for (let i = 0; i < dataContent.length; i += chunkSize) {
|
|
161
|
+
chunks.push(dataContent.substring(i, i + chunkSize))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Create properties for each chunk
|
|
165
|
+
chunks.forEach((chunk, index) => {
|
|
166
|
+
const propName = `ZOTERO_PREF_${index + 1}`
|
|
167
|
+
const propertyXML = `<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="${maxPid + index + 1}" name="${propName}">
|
|
168
|
+
<vt:lpwstr></vt:lpwstr>
|
|
169
|
+
</property>`
|
|
170
|
+
propertiesEl.appendXML(propertyXML)
|
|
171
|
+
// Set the text content after appending (textContent escapes XML characters)
|
|
172
|
+
const lpwstrEl =
|
|
173
|
+
propertiesEl.lastElementChild.query("vt:lpwstr")
|
|
174
|
+
if (lpwstrEl) {
|
|
175
|
+
lpwstrEl.textContent = chunk
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add structured contributor metadata properties
|
|
181
|
+
await this.addContributorProperties()
|
|
182
|
+
|
|
183
|
+
return Promise.resolve()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async addContributorProperties() {
|
|
187
|
+
if (!this.metadata.contributors || !this.metadata.contributors.length) {
|
|
188
|
+
return Promise.resolve()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const propertiesEl = this.customXML.query("Properties")
|
|
192
|
+
|
|
193
|
+
// Remove any existing fidus_contributor_ properties
|
|
194
|
+
const existingContributorProps = this.customXML
|
|
195
|
+
.queryAll("property")
|
|
196
|
+
.filter(
|
|
197
|
+
prop =>
|
|
198
|
+
prop.getAttribute("name") &&
|
|
199
|
+
prop.getAttribute("name").startsWith("fidus_contributor_")
|
|
200
|
+
)
|
|
201
|
+
existingContributorProps.forEach(prop =>
|
|
202
|
+
prop.parentElement.removeChild(prop)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
// Find the highest pid
|
|
206
|
+
const existingProperties = this.customXML.queryAll("property")
|
|
207
|
+
let maxPid = 0
|
|
208
|
+
existingProperties.forEach(prop => {
|
|
209
|
+
const pid = parseInt(prop.getAttribute("pid"))
|
|
210
|
+
if (pid > maxPid) {
|
|
211
|
+
maxPid = pid
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const contributors = this.metadata.contributors
|
|
216
|
+
|
|
217
|
+
// Add contributor count
|
|
218
|
+
maxPid++
|
|
219
|
+
const countXML = `<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="${maxPid}" name="fidus_contributor_count">
|
|
220
|
+
<vt:i4></vt:i4>
|
|
221
|
+
</property>`
|
|
222
|
+
propertiesEl.appendXML(countXML)
|
|
223
|
+
const countEl = propertiesEl.lastElementChild.query("vt:i4")
|
|
224
|
+
if (countEl) {
|
|
225
|
+
countEl.textContent = String(contributors.length)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Add property for each contributor
|
|
229
|
+
contributors.forEach((contributor, index) => {
|
|
230
|
+
const num = index + 1
|
|
231
|
+
const nameParts = []
|
|
232
|
+
if (contributor.firstname) {
|
|
233
|
+
nameParts.push(contributor.firstname)
|
|
234
|
+
}
|
|
235
|
+
if (contributor.lastname) {
|
|
236
|
+
nameParts.push(contributor.lastname)
|
|
237
|
+
}
|
|
238
|
+
const fullName =
|
|
239
|
+
nameParts.join(" ") || contributor.institution || ""
|
|
240
|
+
|
|
241
|
+
const fields = [
|
|
242
|
+
{
|
|
243
|
+
name: `fidus_contributor_${num}_role`,
|
|
244
|
+
value: contributor.role || ""
|
|
245
|
+
},
|
|
246
|
+
{name: `fidus_contributor_${num}_name`, value: fullName},
|
|
247
|
+
{
|
|
248
|
+
name: `fidus_contributor_${num}_firstname`,
|
|
249
|
+
value: contributor.firstname || ""
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: `fidus_contributor_${num}_lastname`,
|
|
253
|
+
value: contributor.lastname || ""
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: `fidus_contributor_${num}_institution`,
|
|
257
|
+
value: contributor.institution || ""
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: `fidus_contributor_${num}_email`,
|
|
261
|
+
value: contributor.email || ""
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: `fidus_contributor_${num}_id_type`,
|
|
265
|
+
value: contributor.id_type || ""
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: `fidus_contributor_${num}_id_value`,
|
|
269
|
+
value: contributor.id_value || ""
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
fields.forEach(field => {
|
|
274
|
+
maxPid++
|
|
275
|
+
const propertyXML = `<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="${maxPid}" name="${field.name}">
|
|
276
|
+
<vt:lpwstr></vt:lpwstr>
|
|
277
|
+
</property>`
|
|
278
|
+
propertiesEl.appendXML(propertyXML)
|
|
279
|
+
const lpwstrEl =
|
|
280
|
+
propertiesEl.lastElementChild.query("vt:lpwstr")
|
|
281
|
+
if (lpwstrEl) {
|
|
282
|
+
lpwstrEl.textContent = field.value
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return Promise.resolve()
|
|
288
|
+
}
|
|
289
|
+
}
|