@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,156 @@
1
+ import download from "downloadjs"
2
+
3
+ import {shortFileTitle} from "../../common/index.js"
4
+
5
+ import {fixTables, removeHidden, textContent} from "../tools/doc_content.js"
6
+ import {createSlug} from "../tools/file.js"
7
+ import {XmlZip} from "../tools/xml_zip.js"
8
+
9
+ import {ODTExporterCitations} from "./citations.js"
10
+ import {ODTExporterFootnotes} from "./footnotes.js"
11
+ import {ODTExporterImages} from "./images.js"
12
+ import {ODTExporterMath} from "./math.js"
13
+ import {ODTExporterMetadata} from "./metadata.js"
14
+ import {ODTExporterRender} from "./render.js"
15
+ import {ODTExporterRichtext} from "./richtext.js"
16
+ import {ODTExporterStyles} from "./styles.js"
17
+ import {ODTExporterTracks} from "./track.js"
18
+
19
+ /*
20
+ Exporter to Open Document Text (LibreOffice)
21
+ */
22
+
23
+ /*
24
+ TODO:
25
+ * - Export tracked changes of block changes and inline format changes
26
+ * (this feature is lacking in ODT files created with LibreOffice 7.6.7.2)
27
+ */
28
+
29
+ export class ODTExporter {
30
+ constructor(doc, templateUrl, bibDB, imageDB, csl) {
31
+ this.doc = doc
32
+ this.templateUrl = templateUrl
33
+ this.bibDB = bibDB
34
+ this.imageDB = imageDB
35
+ this.csl = csl
36
+
37
+ this.pmCits = false
38
+ this.docContent = fixTables(removeHidden(this.doc.content))
39
+ this.docTitle = shortFileTitle(this.doc.title, this.doc.path)
40
+ this.mimeType = "application/vnd.oasis.opendocument.text"
41
+ }
42
+
43
+ init() {
44
+ const xml = new XmlZip(this.templateUrl, this.mimeType)
45
+ const styles = new ODTExporterStyles(xml)
46
+ const math = new ODTExporterMath(xml)
47
+ const tracks = new ODTExporterTracks(xml)
48
+
49
+ const metadata = new ODTExporterMetadata(
50
+ xml,
51
+ styles,
52
+ this.getBaseMetadata(),
53
+ this.csl
54
+ )
55
+ const citations = new ODTExporterCitations(
56
+ this.docContent,
57
+ this.doc.settings,
58
+ styles,
59
+ this.bibDB,
60
+ this.csl
61
+ )
62
+ const footnotes = new ODTExporterFootnotes(
63
+ this.docContent,
64
+ this.doc.settings,
65
+ xml,
66
+ citations,
67
+ styles,
68
+ this.bibDB,
69
+ this.imageDB,
70
+ this.csl
71
+ )
72
+
73
+ const images = new ODTExporterImages(this.docContent, xml, this.imageDB)
74
+
75
+ const richtext = new ODTExporterRichtext(
76
+ this.doc.comments,
77
+ this.doc.settings,
78
+ styles,
79
+ tracks,
80
+ footnotes,
81
+ citations,
82
+ math,
83
+ images
84
+ )
85
+
86
+ const render = new ODTExporterRender(xml)
87
+ return xml
88
+ .init()
89
+ .then(() => styles.init())
90
+ .then(() => tracks.init())
91
+ .then(() => math.init())
92
+ .then(() => metadata.init())
93
+ .then(() => citations.init())
94
+ .then(() => render.init())
95
+ .then(() => images.init())
96
+ .then(() => footnotes.init())
97
+ .then(() => {
98
+ const pmBib = footnotes.pmBib || citations.pmBib
99
+ render.render(
100
+ this.docContent,
101
+ pmBib,
102
+ this.doc.settings,
103
+ richtext,
104
+ citations
105
+ )
106
+ return xml.prepareBlob()
107
+ })
108
+ .then(blob => this.download(blob))
109
+ }
110
+
111
+ getBaseMetadata() {
112
+ const contributors = this.docContent.content.reduce(
113
+ (contributors, part) => {
114
+ if (
115
+ part.type === "contributors_part" &&
116
+ part.attrs.metadata &&
117
+ part.content
118
+ ) {
119
+ return contributors.concat(
120
+ part.content.map(node => ({
121
+ ...node.attrs,
122
+ role: part.attrs.metadata
123
+ }))
124
+ )
125
+ } else {
126
+ return contributors
127
+ }
128
+ },
129
+ []
130
+ )
131
+ return {
132
+ authors: contributors.filter(c => c.role === "authors"),
133
+ contributors,
134
+ keywords: this.docContent.content.reduce((keywords, part) => {
135
+ if (
136
+ part.type === "tags_part" &&
137
+ part.attrs.metadata === "keywords" &&
138
+ part.content
139
+ ) {
140
+ return keywords.concat(
141
+ part.content.map(keywordNode => keywordNode.attrs.tag)
142
+ )
143
+ } else {
144
+ return keywords
145
+ }
146
+ }, []),
147
+ title: textContent(this.docContent.content[0]),
148
+ language: this.doc.settings.language,
149
+ citationStyle: this.doc.settings.citationstyle
150
+ }
151
+ }
152
+
153
+ download(blob) {
154
+ return download(blob, createSlug(this.docTitle) + ".odt", this.mimeType)
155
+ }
156
+ }
@@ -0,0 +1,57 @@
1
+ export class ODTExporterMath {
2
+ constructor(xml) {
3
+ this.xml = xml
4
+ this.objectCounter = 1
5
+ this.manifestXml = false
6
+ this.domParser = new DOMParser()
7
+ }
8
+
9
+ init() {
10
+ return this.xml
11
+ .getXml("META-INF/manifest.xml")
12
+ .then(manifestXml => {
13
+ this.manifestXml = manifestXml
14
+ this.checkObjectCounter()
15
+ return Promise.resolve()
16
+ })
17
+ .then(() => import("mathlive"))
18
+ .then(MathLive => (this.mathLive = MathLive))
19
+ }
20
+
21
+ checkObjectCounter() {
22
+ const manifestEl = this.manifestXml.query("manifest:manifest")
23
+ const fileEntries = manifestEl.queryAll("manifest:file-entry")
24
+
25
+ fileEntries.forEach(fileEntry => {
26
+ const fullPath = fileEntry.getAttribute("manifest:full-path")
27
+ const dir = fullPath.split("/")[0]
28
+ const dirParts = dir.split(" ")
29
+ if (dirParts.length === 2 && dirParts[0] === "Object") {
30
+ const objectNumber = Number.parseInt(dirParts[1])
31
+ if (objectNumber >= this.objectCounter) {
32
+ this.objectCounter = objectNumber + 1
33
+ }
34
+ }
35
+ })
36
+ }
37
+
38
+ latexToMathML(latex) {
39
+ return this.mathLive.convertLatexToMathMl(latex)
40
+ }
41
+
42
+ addMath(latex) {
43
+ const objectNumber = this.objectCounter++
44
+ this.xml.addExtraFile(
45
+ `Object ${objectNumber}/content.xml`,
46
+ `<math xmlns="http://www.w3.org/1998/Math/MathML">${this.latexToMathML(
47
+ latex
48
+ )}</math>`
49
+ )
50
+ const manifestEl = this.manifestXml.query("manifest:manifest")
51
+ const stringOne = `<manifest:file-entry manifest:full-path="Object ${objectNumber}/content.xml" manifest:media-type="text/xml"/>`
52
+ manifestEl.appendXML(stringOne)
53
+ const stringTwo = `<manifest:file-entry manifest:full-path="Object ${objectNumber}/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.formula"/>`
54
+ manifestEl.appendXML(stringTwo)
55
+ return objectNumber
56
+ }
57
+ }
@@ -0,0 +1,251 @@
1
+ import {escapeText} from "../../common/index.js"
2
+
3
+ export class ODTExporterMetadata {
4
+ constructor(xml, styles, metadata, csl = null) {
5
+ this.xml = xml
6
+ this.styles = styles
7
+ this.metadata = metadata
8
+ this.csl = csl
9
+ this.metaXml = false
10
+ }
11
+
12
+ init() {
13
+ return this.xml.getXml("meta.xml").then(metaXml => {
14
+ this.metaXml = metaXml
15
+ this.addMetadata()
16
+ this.addContributorMetadata()
17
+ return this.addZoteroPrefs()
18
+ })
19
+ }
20
+
21
+ async hasBibliography() {
22
+ if (!this.csl || !this.metadata.citationStyle) {
23
+ return "0"
24
+ }
25
+ try {
26
+ const style = await this.csl.getStyle(this.metadata.citationStyle)
27
+ // Check if the style has a bibliography section
28
+ const hasBib = style.children.some(
29
+ section => section.name === "bibliography"
30
+ )
31
+ return hasBib ? "1" : "0"
32
+ } catch (_error) {
33
+ return "0"
34
+ }
35
+ }
36
+
37
+ addMetadata() {
38
+ const metaEl = this.metaXml.query("office:meta")
39
+
40
+ // Title
41
+ const titleEl = this.metaXml.query("dc:title")
42
+ if (titleEl) {
43
+ titleEl.innerXML = escapeText(this.metadata.title)
44
+ } else {
45
+ metaEl.appendXML(
46
+ `<dc:title>${escapeText(this.metadata.title)}</dc:title>`
47
+ )
48
+ }
49
+
50
+ // Authors
51
+ const authors = this.metadata.authors.map(author => {
52
+ const nameParts = []
53
+ if (author.firstname) {
54
+ nameParts.push(author.firstname)
55
+ }
56
+ if (author.lastname) {
57
+ nameParts.push(author.lastname)
58
+ }
59
+ if (!nameParts.length && author.institution) {
60
+ // We have an institution but no names. Use institution as name.
61
+ nameParts.push(author.institution)
62
+ }
63
+ return nameParts.join(" ")
64
+ })
65
+
66
+ const initialAuthor = authors.length
67
+ ? escapeText(authors[0])
68
+ : gettext("Unknown")
69
+ // TODO: We likely want to differentiate between first and last author.
70
+ const lastAuthor = initialAuthor
71
+
72
+ const lastAuthorEl = this.metaXml.query("dc:creator")
73
+ if (lastAuthorEl) {
74
+ lastAuthorEl.innerXML = lastAuthor
75
+ } else {
76
+ metaEl.appendXML(`<dc:creator>${lastAuthor}</dc:creator>`)
77
+ }
78
+ const initialAuthorEl = this.metaXml.query("meta:initial-creator")
79
+ if (initialAuthorEl) {
80
+ initialAuthorEl.innerXML = initialAuthor
81
+ } else {
82
+ metaEl.appendXML(
83
+ `<meta:initial-creator>${initialAuthor}</meta:initial-creator>`
84
+ )
85
+ }
86
+
87
+ // Keywords
88
+ // Remove all existing keywords
89
+ const keywordEls = this.metaXml.queryAll("meta:keywords")
90
+ keywordEls.forEach(keywordEl =>
91
+ keywordEl.parentElement.removeChild(keywordEl)
92
+ )
93
+ // Add new keywords
94
+ const keywords = this.metadata.keywords
95
+ keywords.forEach(keyword =>
96
+ metaEl.appendXML(
97
+ `<meta:keyword>${escapeText(keyword)}</meta:keyword>`
98
+ )
99
+ )
100
+
101
+ // language
102
+ // LibreOffice seems to ignore the value set in metadata and instead uses
103
+ // the one set in default styles. So we set both.
104
+ this.styles.setLanguage(this.metadata.language)
105
+ const languageEl = this.metaXml.query("dc:language")
106
+ if (languageEl) {
107
+ languageEl.innerXML = this.metadata.language
108
+ } else {
109
+ metaEl.appendXML(
110
+ `<dc:language>${this.metadata.language}</dc:language>`
111
+ )
112
+ }
113
+ // time
114
+ const date = new Date()
115
+ const dateString = date.toISOString().split(".")[0]
116
+ const createdEl = metaEl.query("meta:creation-date")
117
+ createdEl.innerXML = dateString
118
+ const dateEl = this.metaXml.query("dc:date")
119
+ if (dateEl) {
120
+ dateEl.innerXML = `${dateString}.000000000`
121
+ } else {
122
+ metaEl.appendXML(`<dc:date>${dateString}.000000000</dc:date>`)
123
+ }
124
+ }
125
+
126
+ async addZoteroPrefs() {
127
+ // Add citation style property to meta.xml
128
+ if (!this.metadata.citationStyle) {
129
+ return Promise.resolve()
130
+ }
131
+
132
+ const metaEl = this.metaXml.query("office:meta")
133
+
134
+ // Remove any existing ZOTERO_PREF_ properties
135
+ const existingZoteroProps = this.metaXml
136
+ .queryAll("meta:user-defined")
137
+ .filter(
138
+ prop =>
139
+ prop.getAttribute("meta:name") &&
140
+ prop.getAttribute("meta:name").startsWith("ZOTERO_PREF_")
141
+ )
142
+ existingZoteroProps.forEach(prop =>
143
+ prop.parentElement.removeChild(prop)
144
+ )
145
+
146
+ // Determine if the citation style has a bibliography
147
+ const hasBib = await this.hasBibliography()
148
+
149
+ // Create the data content
150
+ const citationStyleUrl = `http://www.zotero.org/styles/${escapeText(this.metadata.citationStyle)}`
151
+ const dataContent = escapeText(
152
+ `<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="ReferenceMark"/><pref name="automaticJournalAbbreviations" value="true"/></prefs></data>`
153
+ )
154
+
155
+ // Split content into chunks of 378 characters (ODT limit)
156
+ const chunkSize = 378
157
+ const chunks = []
158
+ for (let i = 0; i < dataContent.length; i += chunkSize) {
159
+ chunks.push(dataContent.substring(i, i + chunkSize))
160
+ }
161
+
162
+ // Create meta:user-defined elements for each chunk
163
+ chunks.forEach((chunk, index) => {
164
+ const propName = `ZOTERO_PREF_${index + 1}`
165
+ const userDefinedEl = `<meta:user-defined meta:name="${propName}">${chunk}</meta:user-defined>`
166
+ metaEl.appendXML(userDefinedEl)
167
+ })
168
+
169
+ return Promise.resolve()
170
+ }
171
+
172
+ addContributorMetadata() {
173
+ if (!this.metadata.contributors || !this.metadata.contributors.length) {
174
+ return
175
+ }
176
+
177
+ const metaEl = this.metaXml.query("office:meta")
178
+
179
+ // Remove any existing fidus_contributor_ properties
180
+ const existingContributorProps = this.metaXml
181
+ .queryAll("meta:user-defined")
182
+ .filter(
183
+ prop =>
184
+ prop.getAttribute("meta:name") &&
185
+ prop
186
+ .getAttribute("meta:name")
187
+ .startsWith("fidus_contributor_")
188
+ )
189
+ existingContributorProps.forEach(prop =>
190
+ prop.parentElement.removeChild(prop)
191
+ )
192
+
193
+ const contributors = this.metadata.contributors
194
+
195
+ // Add contributor count
196
+ metaEl.appendXML(
197
+ `<meta:user-defined meta:name="fidus_contributor_count" meta:value-type="float">${contributors.length}</meta:user-defined>`
198
+ )
199
+
200
+ // Add metadata for each contributor
201
+ contributors.forEach((contributor, index) => {
202
+ const num = index + 1
203
+ const nameParts = []
204
+ if (contributor.firstname) {
205
+ nameParts.push(contributor.firstname)
206
+ }
207
+ if (contributor.lastname) {
208
+ nameParts.push(contributor.lastname)
209
+ }
210
+ const fullName =
211
+ nameParts.join(" ") || contributor.institution || ""
212
+
213
+ const fields = [
214
+ {
215
+ name: `fidus_contributor_${num}_role`,
216
+ value: contributor.role || ""
217
+ },
218
+ {name: `fidus_contributor_${num}_name`, value: fullName},
219
+ {
220
+ name: `fidus_contributor_${num}_firstname`,
221
+ value: contributor.firstname || ""
222
+ },
223
+ {
224
+ name: `fidus_contributor_${num}_lastname`,
225
+ value: contributor.lastname || ""
226
+ },
227
+ {
228
+ name: `fidus_contributor_${num}_institution`,
229
+ value: contributor.institution || ""
230
+ },
231
+ {
232
+ name: `fidus_contributor_${num}_email`,
233
+ value: contributor.email || ""
234
+ },
235
+ {
236
+ name: `fidus_contributor_${num}_id_type`,
237
+ value: contributor.id_type || ""
238
+ },
239
+ {
240
+ name: `fidus_contributor_${num}_id_value`,
241
+ value: contributor.id_value || ""
242
+ }
243
+ ]
244
+
245
+ fields.forEach(field => {
246
+ const userDefinedEl = `<meta:user-defined meta:name="${field.name}">${escapeText(field.value)}</meta:user-defined>`
247
+ metaEl.appendXML(userDefinedEl)
248
+ })
249
+ })
250
+ }
251
+ }