@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,387 @@
1
+ const GRAPHIC_STYLES = {
2
+ Formula: `
3
+ <style:style style:name="Formula" style:family="graphic">
4
+ <style:graphic-properties text:anchor-type="as-char" svg:y="0in" fo:margin-left="0.0791in" fo:margin-right="0.0791in" style:vertical-pos="middle" style:vertical-rel="text"/>
5
+ </style:style>`,
6
+ Graphics: `
7
+ <style:style style:name="Graphics" style:family="graphic">
8
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph"/>
9
+ </style:style>`,
10
+ Frame: `
11
+ <style:style style:name="Frame" style:family="graphic">
12
+ <style:graphic-properties text:anchor-type="paragraph" svg:x="0in" svg:y="0in" style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph"/>
13
+ </style:style>`
14
+ }
15
+
16
+ const PAR_STYLES = {
17
+ Bibliography_20_Heading: `<style:style style:name="Bibliography_20_Heading" style:display-name="Bibliography Heading" style:family="paragraph" style:parent-style-name="Heading" style:class="index">
18
+ <style:paragraph-properties fo:margin-left="0in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/>
19
+ <style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="16pt" style:font-weight-asian="bold" style:font-size-complex="16pt" style:font-weight-complex="bold"/>
20
+ </style:style>`,
21
+ Caption: `<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
22
+ <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0" />
23
+ <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic" />
24
+ </style:style>`,
25
+ Figure: '<style:style style:name="Figure" style:family="paragraph" style:parent-style-name="Caption" style:class="extra" />',
26
+ Standard:
27
+ '<style:style style:name="Standard" style:family="paragraph" style:class="text" />'
28
+ }
29
+
30
+ export class ODTExporterStyles {
31
+ constructor(xml) {
32
+ this.xml = xml
33
+
34
+ this.contentXml = false
35
+ this.stylesXml = false
36
+ this.boldStyleId = false
37
+ this.italicStyleId = false
38
+ this.boldItalicStyleId = false
39
+ this.inlineStyleIds = {}
40
+ this.tableStyleIds = {}
41
+ this.graphicStyleIds = {}
42
+ this.bulletListStyleId = [false, false]
43
+ this.inlineStyleCounter = 0
44
+ this.tableStyleCounter = 0
45
+ this.blockStyleCounter = 0
46
+ this.listStyleCounter = 0
47
+ this.graphicStyleCounter = 0
48
+ }
49
+
50
+ init() {
51
+ return this.xml
52
+ .getXml("styles.xml")
53
+ .then(stylesXml => {
54
+ this.stylesXml = stylesXml
55
+ return this.xml.getXml("content.xml")
56
+ })
57
+ .then(contentXml => {
58
+ this.contentXml = contentXml
59
+ this.getStyleCounters()
60
+ return Promise.resolve()
61
+ })
62
+ }
63
+
64
+ getStyleCounters() {
65
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
66
+ const styles = autoStylesEl.queryAll("style:style")
67
+ styles.forEach(style => {
68
+ const styleNumber = Number.parseInt(
69
+ style.getAttribute("style:name").replace(/\D/g, "")
70
+ )
71
+ const styleFamily = style.getAttribute("style:family")
72
+ if (styleFamily === "text") {
73
+ if (styleNumber > this.inlineStyleCounter) {
74
+ this.inlineStyleCounter = styleNumber
75
+ }
76
+ } else if (styleFamily === "table") {
77
+ if (styleNumber > this.tableStyleCounter) {
78
+ this.tableStyleCounter = styleNumber
79
+ }
80
+ } else if (styleFamily === "paragraph") {
81
+ if (styleNumber > this.blockStyleCounter) {
82
+ this.blockStyleCounter = styleNumber
83
+ }
84
+ } else if (styleFamily === "graphic") {
85
+ if (styleNumber > this.graphicStyleCounter) {
86
+ this.graphicStyleCounter = styleNumber
87
+ }
88
+ }
89
+ })
90
+ const listStyles = autoStylesEl.queryAll("text:list-style")
91
+ listStyles.forEach(style => {
92
+ const styleNumber = Number.parseInt(
93
+ style.getAttribute("style:name").replace(/\D/g, "")
94
+ )
95
+ if (styleNumber > this.listStyleCounter) {
96
+ this.listStyleCounter = styleNumber
97
+ }
98
+ })
99
+ }
100
+
101
+ /*
102
+ attributes is a string that consists of these characters (in this order).
103
+ Only one of super/sub possible.
104
+ e = italic/em
105
+ s = bold/strong
106
+ u = underline
107
+ c = small caps
108
+ p = super
109
+ b = sub
110
+ t = code (monospace)
111
+ */
112
+ getInlineStyleId(attributes) {
113
+ if (this.inlineStyleIds[attributes]) {
114
+ return this.inlineStyleIds[attributes]
115
+ }
116
+
117
+ let styleProperties = ""
118
+ if (attributes.includes("e")) {
119
+ styleProperties +=
120
+ ' fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"'
121
+ }
122
+ if (attributes.includes("s")) {
123
+ styleProperties +=
124
+ ' fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"'
125
+ }
126
+ if (attributes.includes("u")) {
127
+ styleProperties +=
128
+ ' style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"'
129
+ }
130
+ if (attributes.includes("c")) {
131
+ styleProperties += ' fo:font-variant="small-caps"'
132
+ }
133
+ if (attributes.includes("p")) {
134
+ styleProperties += ' style:text-position="super 58%"'
135
+ } else if (attributes.includes("b")) {
136
+ styleProperties += ' style:text-position="sub 58%"'
137
+ }
138
+ if (attributes.includes("t")) {
139
+ styleProperties +=
140
+ ' style:font-name="Courier New" fo:font-family="Courier New"'
141
+ }
142
+ const styleCounter = ++this.inlineStyleCounter
143
+ this.inlineStyleIds[attributes] = styleCounter
144
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
145
+ autoStylesEl.appendXML(`
146
+ <style:style style:name="T${styleCounter}" style:family="text">
147
+ <style:text-properties${styleProperties}/>
148
+ </style:style>
149
+ `)
150
+ return styleCounter
151
+ }
152
+
153
+ /*
154
+ aligned: left/center/right
155
+ width: '75'/'50'/'25' = percentage width - 100% doesn't need any style
156
+ */
157
+ getTableStyleId(aligned, width) {
158
+ if (this.tableStyleIds[aligned + width]) {
159
+ return this.tableStyleIds[aligned + width]
160
+ }
161
+ const styleCounter = ++this.tableStyleCounter
162
+ this.tableStyleIds[aligned + width] = styleCounter
163
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
164
+ autoStylesEl.appendXML(`
165
+ <style:style style:name="Table${styleCounter}" style:family="table">
166
+ <style:table-properties style:rel-width="${width}%" table:align="${aligned}"/>
167
+ </style:style>
168
+ `)
169
+ return styleCounter
170
+ }
171
+
172
+ checkParStyle(styleName) {
173
+ const stylesParStyle = this.stylesXml.query("style:style", {
174
+ "style:name": styleName
175
+ })
176
+ const contentParStyle = this.contentXml.query("style:style", {
177
+ "style:name": styleName
178
+ })
179
+ if (!stylesParStyle && !contentParStyle) {
180
+ const stylesEl = this.stylesXml.query("office:styles")
181
+ const displayName = styleName.split("_20_").join(" ")
182
+ stylesEl.appendXML(
183
+ PAR_STYLES[styleName] ||
184
+ `<style:style style:name="${styleName}" style:display-name="${displayName}" style:family="paragraph" style:parent-style-name="Standard" style:class="text" />`
185
+ )
186
+ }
187
+ }
188
+
189
+ checkGraphicStyle(styleName) {
190
+ const stylesParStyle = this.stylesXml.query("style:style", {
191
+ "style:name": styleName
192
+ })
193
+ const contentParStyle = this.contentXml.query("style:style", {
194
+ "style:name": styleName
195
+ })
196
+ if (!stylesParStyle && !contentParStyle) {
197
+ const stylesEl = this.stylesXml.query("office:styles")
198
+ stylesEl.appendXML(GRAPHIC_STYLES[styleName])
199
+ }
200
+ }
201
+
202
+ checkSectionStyle(styleName) {
203
+ const stylesSection = this.stylesXml.query("style:style", {
204
+ "style:name": styleName,
205
+ "style:family": "section"
206
+ })
207
+ const contentSection = this.contentXml.query("style:style", {
208
+ "style:name": styleName,
209
+ "style:family": "section"
210
+ })
211
+ if (!stylesSection && !contentSection) {
212
+ const stylesEl = this.stylesXml.query("office:styles")
213
+ // Add a basic section style if it doesn't exist
214
+ stylesEl.appendXML(
215
+ `<style:style style:name="${styleName}" style:family="section">
216
+ <style:section-properties text:dont-balance-text-columns="false" fo:background-color="transparent">
217
+ <style:columns fo:column-count="1" fo:column-gap="0in"/>
218
+ </style:section-properties>
219
+ </style:style>`
220
+ )
221
+ }
222
+ }
223
+
224
+ /*
225
+ styleName: Frame/Formula/Graphics
226
+ aligned: left/center/right (not used for Formula)
227
+ */
228
+ getGraphicStyleId(styleName, aligned = "") {
229
+ if (this.graphicStyleIds[styleName + aligned]) {
230
+ return this.graphicStyleIds[styleName + aligned]
231
+ }
232
+ this.checkGraphicStyle(styleName)
233
+
234
+ const styleCounter = ++this.graphicStyleCounter
235
+ this.graphicStyleIds[styleName + aligned] = styleCounter
236
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
237
+ autoStylesEl.appendXML(`
238
+ <style:style style:name="fr${styleCounter}" style:family="graphic" style:parent-style-name="${styleName}">
239
+ ${
240
+ styleName === "Formula"
241
+ ? '<style:graphic-properties style:vertical-pos="from-top" style:horizontal-pos="from-left" style:horizontal-rel="paragraph-content" draw:ole-draw-aspect="1" />'
242
+ : `<style:graphic-properties fo:margin-left="0in" fo:margin-right="0in" fo:margin-top="0in" fo:margin-bottom="0in" ${aligned === "center" ? 'style:wrap="none"' : 'style:wrap="dynamic" style:number-wrapped-paragraphs="no-limit"'} style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="${aligned}" style:horizontal-rel="paragraph" fo:padding="0in" fo:border="none" loext:rel-width-rel="paragraph" />`
243
+ } style:number-wrapped-paragraphs="no-limit"
244
+ </style:style>`)
245
+ return styleCounter
246
+ }
247
+
248
+ addReferenceStyle(bibInfo) {
249
+ // The style called "Bibliography_20_1" will override any previous style
250
+ // of the same name.
251
+ const stylesParStyle = this.stylesXml.query("style:style", {
252
+ "style:name": "Bibliography_20_1"
253
+ })
254
+ if (stylesParStyle) {
255
+ stylesParStyle.parentElement.removeChild(stylesParStyle)
256
+ }
257
+ const contentParStyle = this.contentXml.query("style:style", {
258
+ "style:name": "Bibliography_20_1"
259
+ })
260
+ if (contentParStyle) {
261
+ contentParStyle.parentElement.removeChild(contentParStyle)
262
+ }
263
+
264
+ this.checkParStyle("Index")
265
+
266
+ const lineHeight = `${0.1665 * bibInfo.linespacing}in`
267
+ const marginBottom = `${0.1667 * bibInfo.entryspacing}in`
268
+ let marginLeft = "0in",
269
+ textIndent = "0in",
270
+ tabStops = "<style:tab-stops/>"
271
+
272
+ if (bibInfo.hangingindent) {
273
+ marginLeft = "0.5in"
274
+ textIndent = "-0.5in"
275
+ } else if (bibInfo["second-field-align"]) {
276
+ // We calculate 0.55em as roughly equivalent to one letter width.
277
+ const firstFieldWidth = `${(bibInfo.maxoffset + 1) * 0.55}em`
278
+ if (bibInfo["second-field-align"] === "margin") {
279
+ textIndent = `-${firstFieldWidth}`
280
+ tabStops =
281
+ '<style:tab-stops><style:tab-stop style:position="0in"/></style:tab-stops>'
282
+ } else {
283
+ textIndent = `-${firstFieldWidth}`
284
+ marginLeft = `${firstFieldWidth}`
285
+ tabStops = `<style:tab-stops><style:tab-stop style:position="${firstFieldWidth}"/></style:tab-stops>`
286
+ }
287
+ }
288
+ const styleDef = `
289
+ <style:style style:name="Bibliography_20_1" style:display-name="Bibliography 1" style:family="paragraph" style:parent-style-name="Index" style:class="index">
290
+ <style:paragraph-properties fo:margin-left="${marginLeft}" fo:margin-right="0in" fo:margin-top="0in" fo:margin-bottom="${marginBottom}" loext:contextual-spacing="false" fo:text-indent="${textIndent}" style:line-height-at-least="${lineHeight}" style:auto-text-indent="false">
291
+ ${tabStops}
292
+ </style:paragraph-properties>
293
+ </style:style>`
294
+ const stylesEl = this.stylesXml.query("office:styles")
295
+ stylesEl.appendXML(styleDef)
296
+ }
297
+
298
+ getBulletListStyleId() {
299
+ if (this.bulletListStyleId[0]) {
300
+ return this.bulletListStyleId
301
+ }
302
+ this.bulletListStyleId[0] = ++this.listStyleCounter
303
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
304
+ autoStylesEl.appendXML(`
305
+ <text:list-style style:name="L${this.bulletListStyleId[0]}">
306
+ </text:list-style>
307
+ `)
308
+ const listStyleEl =
309
+ autoStylesEl.children[autoStylesEl.children.length - 1]
310
+ // ODT files seem to contain ten levels of lists (1-10)
311
+ for (let level = 1; level < 11; level++) {
312
+ listStyleEl.appendXML(`
313
+ <text:list-level-style-bullet text:level="${level}" text:style-name="Bullet_20_Symbols" text:bullet-char="•">
314
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
315
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="${(level + 1) * 0.25}in" fo:text-indent="-0.25in" fo:margin-left="${(level + 1) * 0.25}in" />
316
+ </style:list-level-properties>
317
+ </text:list-level-style-bullet>
318
+ `)
319
+ }
320
+ this.bulletListStyleId[1] = this.addListParStyle(
321
+ this.bulletListStyleId[0]
322
+ )
323
+ return this.bulletListStyleId
324
+ }
325
+
326
+ getOrderedListStyleId(start) {
327
+ const orderedListStyleId = ++this.listStyleCounter
328
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
329
+ autoStylesEl.appendXML(`
330
+ <text:list-style style:name="L${orderedListStyleId}">
331
+ </text:list-style>
332
+ `)
333
+ const listStyleEl =
334
+ autoStylesEl.children[autoStylesEl.children.length - 1]
335
+ // ODT files seem to contain ten levels of lists (1-10)
336
+ for (let level = 1; level < 11; level++) {
337
+ listStyleEl.appendXML(`
338
+ <text:list-level-style-number text:level="${level}" text:style-name="Numbering_20_Symbols" style:num-suffix="." style:num-format="1"${start > 1 ? ` text:start-value="${start}"` : ""}>
339
+ <style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
340
+ <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="${(level + 1) * 0.25}in" fo:text-indent="-0.25in" fo:margin-left="${(level + 1) * 0.25}in" />
341
+ </style:list-level-properties>
342
+ </text:list-level-style-number>
343
+ `)
344
+ }
345
+ return [orderedListStyleId, this.addListParStyle(orderedListStyleId)]
346
+ }
347
+
348
+ // Add a paragraph style for either paragraph in bullet or numeric list
349
+ addListParStyle(_listId) {
350
+ const parStyleId = ++this.blockStyleCounter
351
+ const autoStylesEl = this.contentXml.query("office:automatic-styles")
352
+ autoStylesEl.appendXML(
353
+ `<style:style style:name="P${parStyleId}" style:family="paragraph" style:parent-style-name="Standard" text:list-style-name="L1" />`
354
+ )
355
+ return parStyleId
356
+ }
357
+
358
+ addPageBreakStyle() {
359
+ const stylesEl = this.stylesXml.query("office:styles")
360
+ stylesEl.queryAll("style:style").forEach(style => {
361
+ if (style.getAttribute("style:name") === "PageBreak") {
362
+ return
363
+ }
364
+ })
365
+ stylesEl.appendXML(
366
+ '<style:style style:name="PageBreak" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"><style:paragraph-properties fo:break-before="page"/></style:style>'
367
+ )
368
+ }
369
+
370
+ setLanguage(langCode) {
371
+ const langCodes = langCode.split("-"),
372
+ [language] = langCodes
373
+
374
+ let [, country] = langCodes
375
+
376
+ if (!country) {
377
+ country = "none"
378
+ }
379
+ const stylesEl = this.stylesXml.query("office:styles")
380
+ stylesEl.queryAll("style:default-style").forEach(el => {
381
+ el.queryAll("style:text-properties").forEach(el => {
382
+ el.setAttribute("fo:language", language)
383
+ el.setAttribute("fo:country", country)
384
+ })
385
+ })
386
+ }
387
+ }
@@ -0,0 +1,68 @@
1
+ import {escapeText} from "../../common/index.js"
2
+
3
+ export class ODTExporterTracks {
4
+ constructor(xml) {
5
+ this.xml = xml
6
+
7
+ this.contentXml = false
8
+ this.trackChangesSection = false
9
+ this.counter = 0
10
+ }
11
+
12
+ init() {
13
+ return this.xml.getXml("content.xml").then(contentXml => {
14
+ this.contentXml = contentXml
15
+ })
16
+ }
17
+
18
+ checkTrackedChangesSection() {
19
+ if (this.trackChangesSection) {
20
+ return
21
+ }
22
+ const trackChangesSection = this.contentXml.query(
23
+ "text:tracked-changes"
24
+ )
25
+ if (trackChangesSection) {
26
+ this.trackChangesSection = trackChangesSection
27
+ } else {
28
+ const textElement = this.contentXml.query("office:text")
29
+ if (!textElement) {
30
+ throw new Error("No text element found in content.xml")
31
+ }
32
+ textElement.prependXML(
33
+ "<text:tracked-changes></text:tracked-changes>"
34
+ )
35
+ this.trackChangesSection = textElement.firstElementChild
36
+ }
37
+ }
38
+
39
+ addChange(trackInfo, deletionString = "") {
40
+ if (!this.trackChangesSection) {
41
+ this.checkTrackedChangesSection()
42
+ }
43
+ const trackId = `ct${Date.now() + this.counter++}`
44
+ const changeXml = `
45
+ <text:changed-region xml:id="${trackId}" text:id="${trackId}">
46
+ ${
47
+ trackInfo.type === "deletion"
48
+ ? `<text:deletion>
49
+ <office:change-info>
50
+ <dc:creator>${escapeText(trackInfo.username)}</dc:creator>
51
+ <dc:date>${new Date(trackInfo.date * 60000).toISOString().slice(0, 19)}</dc:date>
52
+ </office:change-info>
53
+ ${deletionString}
54
+ </text:deletion>`
55
+ : trackInfo.type === "insertion"
56
+ ? `<text:insertion>
57
+ <office:change-info>
58
+ <dc:creator>${escapeText(trackInfo.username)}</dc:creator>
59
+ <dc:date>${new Date(trackInfo.date * 60000).toISOString().slice(0, 19)}</dc:date>
60
+ </office:change-info>
61
+ </text:insertion>`
62
+ : ""
63
+ }
64
+ </text:changed-region>`
65
+ this.trackChangesSection.appendXML(changeXml)
66
+ return trackId
67
+ }
68
+ }
@@ -0,0 +1,98 @@
1
+ import {DOMParser, DOMSerializer} from "prosemirror-model"
2
+
3
+ import {cslBibSchema} from "../../bibliography/csl_bib.js"
4
+ import {FormatCitations} from "../../citations/format.js"
5
+ import {fnSchema} from "../../schema/footnotes.js"
6
+ import {descendantNodes} from "../tools/doc_content.js"
7
+
8
+ export class PandocExporterCitations {
9
+ constructor(exporter, bibDB, csl, docContent, origCitInfos = []) {
10
+ this.exporter = exporter
11
+ this.bibDB = bibDB
12
+ this.csl = csl
13
+ this.docContent = docContent
14
+ // If citInfos were found in a previous run, they are stored here
15
+ // (for example: first citations in main document, then in footnotes)
16
+ this.origCitInfos = origCitInfos
17
+ this.citInfos = []
18
+ this.citationTexts = []
19
+ this.pmCits = []
20
+ this.citFm = false
21
+ this.pmBib = false
22
+ }
23
+
24
+ init() {
25
+ return this.formatCitations()
26
+ }
27
+
28
+ // Citations are highly interdependent -- so we need to format them all
29
+ // together before laying out the document.
30
+ formatCitations() {
31
+ if (this.origCitInfos.length) {
32
+ // Initial citInfos are taken from a previous run to include in
33
+ // bibliography, and they are removed before spitting out the
34
+ // citation entries for the given document.
35
+ // That way the bibliography should contain information from both.
36
+ this.citInfos = this.citInfos.concat(this.origCitInfos)
37
+ }
38
+ descendantNodes(this.docContent).forEach(node => {
39
+ if (node.type === "citation") {
40
+ this.citInfos.push(JSON.parse(JSON.stringify(node.attrs)))
41
+ }
42
+ })
43
+ this.citFm = new FormatCitations(
44
+ this.csl,
45
+ this.citInfos,
46
+ this.exporter.doc.settings.citationstyle,
47
+ "",
48
+ this.bibDB,
49
+ false,
50
+ this.exporter.doc.settings.language
51
+ )
52
+ return this.citFm.init().then(() => {
53
+ this.citationTexts = this.citFm.citationTexts
54
+ if (this.origCitInfos.length) {
55
+ // Remove all citation texts originating from original starting citInfos
56
+ this.citationTexts.splice(0, this.origCitInfos.length)
57
+ }
58
+ this.convertCitations()
59
+ return Promise.resolve()
60
+ })
61
+ }
62
+
63
+ convertCitations() {
64
+ // There could be some formatting in the citations, so we parse them through the PM schema for final formatting.
65
+ // We need to put the citations each in a paragraph so that it works with
66
+ // the fiduswriter schema and so that the converter doesn't mash them together.
67
+ if (this.citationTexts.length) {
68
+ let citationsHTML = ""
69
+ this.citationTexts.forEach(ct => {
70
+ citationsHTML += `<p>${ct}</p>`
71
+ })
72
+
73
+ // We create a standard footnote container DOM node,
74
+ // add the citations into it, and parse it back.
75
+ const fnNode = fnSchema.nodeFromJSON({type: "footnotecontainer"})
76
+ const serializer = DOMSerializer.fromSchema(fnSchema)
77
+ const dom = serializer.serializeNode(fnNode)
78
+ dom.innerHTML = citationsHTML
79
+ this.pmCits = DOMParser.fromSchema(fnSchema)
80
+ .parse(dom, {topNode: fnNode})
81
+ .toJSON().content
82
+ } else {
83
+ this.pmCits = []
84
+ }
85
+
86
+ // Now we do the same for the bibliography.
87
+ const cslBib = this.citFm.bibliography
88
+ if (cslBib && cslBib[1].length > 0) {
89
+ const bibNode = cslBibSchema.nodeFromJSON({type: "cslbib"})
90
+ const serializer = DOMSerializer.fromSchema(cslBibSchema)
91
+ const dom = serializer.serializeNode(bibNode)
92
+ dom.innerHTML = cslBib[1].join("")
93
+ this.pmBib = DOMParser.fromSchema(cslBibSchema)
94
+ .parse(dom, {topNode: bibNode})
95
+ .toJSON()
96
+ }
97
+ }
98
+ }