@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,165 @@
|
|
|
1
|
+
import {escapeText} from "../../common/index.js"
|
|
2
|
+
import {descendantNodes} from "../tools/doc_content.js"
|
|
3
|
+
|
|
4
|
+
const DEFAULT_COMMENTS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
5
|
+
<w:comments xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 wp14 w15">
|
|
6
|
+
</w:comments>`
|
|
7
|
+
|
|
8
|
+
const DEFAULT_COMMENTS_EXTENDED_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
9
|
+
<w15:commentsEx xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w15">
|
|
10
|
+
</w15:commentsEx>`
|
|
11
|
+
|
|
12
|
+
export class DOCXExporterComments {
|
|
13
|
+
constructor(docContent, commentsDB, xml, rels, richtext) {
|
|
14
|
+
this.docContent = docContent
|
|
15
|
+
this.commentsDB = commentsDB
|
|
16
|
+
this.xml = xml
|
|
17
|
+
this.rels = rels
|
|
18
|
+
this.richtext = richtext
|
|
19
|
+
|
|
20
|
+
this.usedComments = []
|
|
21
|
+
this.commentsXML = false
|
|
22
|
+
this.commentsExtendedXML = false
|
|
23
|
+
this.commentsFilePath = "word/comments.xml"
|
|
24
|
+
this.commentsExtendedFilePath = "word/commentsExtended.xml"
|
|
25
|
+
this.commentIdCounter = -1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init() {
|
|
29
|
+
let useExtended = false
|
|
30
|
+
descendantNodes(this.docContent).forEach(node => {
|
|
31
|
+
if (node.marks) {
|
|
32
|
+
const comments = node.marks.filter(
|
|
33
|
+
mark => mark.type === "comment"
|
|
34
|
+
)
|
|
35
|
+
comments.forEach(comment => {
|
|
36
|
+
if (
|
|
37
|
+
!this.usedComments.includes(comment.attrs.id) &&
|
|
38
|
+
this.commentsDB[comment.attrs.id]
|
|
39
|
+
) {
|
|
40
|
+
this.usedComments.push(comment.attrs.id)
|
|
41
|
+
if (
|
|
42
|
+
this.commentsDB[comment.attrs.id].resolved ||
|
|
43
|
+
this.commentsDB[comment.attrs.id].answers?.length
|
|
44
|
+
) {
|
|
45
|
+
useExtended = true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
if (!this.usedComments.length) {
|
|
52
|
+
return Promise.resolve()
|
|
53
|
+
}
|
|
54
|
+
this.rels.addCommentsRel()
|
|
55
|
+
const addCommentXMLs = [
|
|
56
|
+
this.xml
|
|
57
|
+
.getXml(this.commentsFilePath, DEFAULT_COMMENTS_XML)
|
|
58
|
+
.then(commentsXML => (this.commentsXML = commentsXML))
|
|
59
|
+
]
|
|
60
|
+
if (useExtended) {
|
|
61
|
+
this.rels.addCommentsExtendedRel()
|
|
62
|
+
addCommentXMLs.push(
|
|
63
|
+
this.xml
|
|
64
|
+
.getXml(
|
|
65
|
+
this.commentsExtendedFilePath,
|
|
66
|
+
DEFAULT_COMMENTS_EXTENDED_XML
|
|
67
|
+
)
|
|
68
|
+
.then(
|
|
69
|
+
commentsExtendedXML =>
|
|
70
|
+
(this.commentsExtendedXML = commentsExtendedXML)
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
return Promise.all(addCommentXMLs).then(() => {
|
|
75
|
+
this.commentsXML.queryAll("w:comment").forEach(el => {
|
|
76
|
+
const id = Number.parseInt(el.getAttribute("w:id"))
|
|
77
|
+
if (id > this.commentIdCounter) {
|
|
78
|
+
this.commentIdCounter = id
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
return this.exportComments()
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
addComment(id) {
|
|
86
|
+
const commentId = ++this.commentIdCounter
|
|
87
|
+
this.richtext.comments[id] = commentId
|
|
88
|
+
const commentDBEntry = this.commentsDB[id]
|
|
89
|
+
const comments = this.commentsXML.query("w:comments")
|
|
90
|
+
let string = `<w:comment w:id="${commentId}" w:author="${escapeText(commentDBEntry.username)}" w:date="${new Date(commentDBEntry.date).toISOString().split(".")[0]}Z" w:initials="${escapeText(
|
|
91
|
+
commentDBEntry.username
|
|
92
|
+
.split(" ")
|
|
93
|
+
.map(n => n[0])
|
|
94
|
+
.join("")
|
|
95
|
+
.toUpperCase()
|
|
96
|
+
)}">`
|
|
97
|
+
let parentParagraphId = ""
|
|
98
|
+
string += commentDBEntry.comment
|
|
99
|
+
.map((node, index) => {
|
|
100
|
+
const options = {section: "CommentText"}
|
|
101
|
+
if (
|
|
102
|
+
(commentDBEntry.resolved ||
|
|
103
|
+
commentDBEntry.answers?.length) &&
|
|
104
|
+
index === commentDBEntry.comment.length - 1
|
|
105
|
+
) {
|
|
106
|
+
// If comment has been resolved or there are answers, we need to add an id to the last paragraph
|
|
107
|
+
// of the comment and add an entry into commentsExtended.xml.
|
|
108
|
+
parentParagraphId = (++this.richtext.paragraphIdCounter)
|
|
109
|
+
.toString(16)
|
|
110
|
+
.padStart(8, "0")
|
|
111
|
+
options.paragraphId = parentParagraphId
|
|
112
|
+
const extendedString = `<w15:commentEx w15:paraId="${parentParagraphId}" w15:done="${commentDBEntry.resolved ? "1" : "0"}"/>`
|
|
113
|
+
const extendedComments =
|
|
114
|
+
this.commentsExtendedXML.query("w15:commentsEx")
|
|
115
|
+
extendedComments.appendXML(extendedString)
|
|
116
|
+
}
|
|
117
|
+
if (!index) {
|
|
118
|
+
options.commentReference = true
|
|
119
|
+
}
|
|
120
|
+
return this.richtext.transformRichtext(node, options)
|
|
121
|
+
})
|
|
122
|
+
.join("")
|
|
123
|
+
string += "</w:comment>"
|
|
124
|
+
commentDBEntry.answers?.forEach(answer => {
|
|
125
|
+
const answerId = ++this.commentIdCounter
|
|
126
|
+
string += `<w:comment w:id="${answerId}" w:author="${escapeText(answer.username)}" w:date="${new Date(answer.date).toISOString().split(".")[0]}Z" w:initials="${escapeText(
|
|
127
|
+
answer.username
|
|
128
|
+
.split(" ")
|
|
129
|
+
.map(n => n[0])
|
|
130
|
+
.join("")
|
|
131
|
+
.toUpperCase()
|
|
132
|
+
)}">`
|
|
133
|
+
string += answer.answer
|
|
134
|
+
.map((node, index) => {
|
|
135
|
+
const options = {section: "CommentText"}
|
|
136
|
+
if (index === answer.answer.length - 1) {
|
|
137
|
+
// We need to add an id to the last paragraph of the comment and add an entry
|
|
138
|
+
// into commentsExtended.xml pointing to the last paragraph of the parent comment.
|
|
139
|
+
const paragraphId = (++this.richtext.paragraphIdCounter)
|
|
140
|
+
.toString(16)
|
|
141
|
+
.padStart(8, "0")
|
|
142
|
+
options.paragraphId = paragraphId
|
|
143
|
+
const extendedString = `<w15:commentEx w15:paraId="${paragraphId}" w15:done="${commentDBEntry.resolved ? "1" : "0"}" w15:paraIdParent="${parentParagraphId}"/>`
|
|
144
|
+
const extendedComments =
|
|
145
|
+
this.commentsExtendedXML.query("w15:commentsEx")
|
|
146
|
+
extendedComments.appendXML(extendedString)
|
|
147
|
+
}
|
|
148
|
+
if (!index) {
|
|
149
|
+
options.commentReference = true
|
|
150
|
+
}
|
|
151
|
+
return this.richtext.transformRichtext(node, options)
|
|
152
|
+
})
|
|
153
|
+
.join("")
|
|
154
|
+
string += "</w:comment>"
|
|
155
|
+
})
|
|
156
|
+
comments.appendXML(string)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
exportComments() {
|
|
160
|
+
this.usedComments.forEach(comment => {
|
|
161
|
+
this.addComment(comment)
|
|
162
|
+
})
|
|
163
|
+
return Promise.resolve()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {descendantNodes} from "../tools/doc_content.js"
|
|
2
|
+
import {DOCXExporterCitations} from "./citations.js"
|
|
3
|
+
import {DOCXExporterImages} from "./images.js"
|
|
4
|
+
import {DOCXExporterLists} from "./lists.js"
|
|
5
|
+
import {DOCXExporterRels} from "./rels.js"
|
|
6
|
+
import {DOCXExporterRichtext} from "./richtext.js"
|
|
7
|
+
|
|
8
|
+
const DEFAULT_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
9
|
+
<w:footnotes xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14">
|
|
10
|
+
<w:footnote w:id="0" w:type="separator">
|
|
11
|
+
<w:p>
|
|
12
|
+
<w:r>
|
|
13
|
+
<w:separator />
|
|
14
|
+
</w:r>
|
|
15
|
+
</w:p>
|
|
16
|
+
</w:footnote>
|
|
17
|
+
<w:footnote w:id="1" w:type="continuationSeparator">
|
|
18
|
+
<w:p>
|
|
19
|
+
<w:r>
|
|
20
|
+
<w:continuationSeparator />
|
|
21
|
+
</w:r>
|
|
22
|
+
</w:p>
|
|
23
|
+
</w:footnote>
|
|
24
|
+
</w:footnotes>`
|
|
25
|
+
|
|
26
|
+
const DEFAULT_SETTINGS_XML = `<w:footnotePr>
|
|
27
|
+
<w:numFmt w:val="decimal"/>
|
|
28
|
+
<w:footnote w:id="0"/>
|
|
29
|
+
<w:footnote w:id="1"/>
|
|
30
|
+
</w:footnotePr>`
|
|
31
|
+
|
|
32
|
+
const DEFAULT_STYLE_FOOTNOTE = `<w:style w:type="paragraph" w:styleId="Footnote">
|
|
33
|
+
<w:name w:val="Footnote Text" />
|
|
34
|
+
<w:basedOn w:val="Normal" />
|
|
35
|
+
<w:pPr>
|
|
36
|
+
<w:suppressLineNumbers />
|
|
37
|
+
<w:ind w:left="339" w:hanging="339" />
|
|
38
|
+
</w:pPr>
|
|
39
|
+
<w:rPr>
|
|
40
|
+
<w:sz w:val="20" />
|
|
41
|
+
<w:szCs w:val="20" />
|
|
42
|
+
</w:rPr>
|
|
43
|
+
</w:style>`
|
|
44
|
+
|
|
45
|
+
const DEFAULT_STYLE_FOOTNOTE_ANCHOR = `
|
|
46
|
+
<w:style w:type="character" w:styleId="FootnoteAnchor">
|
|
47
|
+
<w:name w:val="Footnote Anchor" />
|
|
48
|
+
<w:rPr>
|
|
49
|
+
<w:vertAlign w:val="superscript" />
|
|
50
|
+
</w:rPr>
|
|
51
|
+
</w:style>
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
export class DOCXExporterFootnotes {
|
|
55
|
+
constructor(
|
|
56
|
+
doc,
|
|
57
|
+
docContent,
|
|
58
|
+
settings,
|
|
59
|
+
imageDB,
|
|
60
|
+
bibDB,
|
|
61
|
+
xml,
|
|
62
|
+
citations,
|
|
63
|
+
csl,
|
|
64
|
+
lists,
|
|
65
|
+
math,
|
|
66
|
+
tables,
|
|
67
|
+
rels
|
|
68
|
+
) {
|
|
69
|
+
this.doc = doc
|
|
70
|
+
this.docContent = docContent
|
|
71
|
+
this.settings = settings
|
|
72
|
+
this.imageDB = imageDB
|
|
73
|
+
this.bibDB = bibDB
|
|
74
|
+
this.xml = xml
|
|
75
|
+
this.citations = citations
|
|
76
|
+
this.csl = csl
|
|
77
|
+
this.lists = lists
|
|
78
|
+
this.math = math
|
|
79
|
+
this.tables = tables
|
|
80
|
+
this.rels = rels
|
|
81
|
+
|
|
82
|
+
this.pmBib = false
|
|
83
|
+
this.fnPmJSON = false
|
|
84
|
+
this.images = false
|
|
85
|
+
this.augmentedCitations = false
|
|
86
|
+
this.footnotes = [] // footnotes
|
|
87
|
+
this.fnXML = false
|
|
88
|
+
this.ctXML = false
|
|
89
|
+
this.styleXML = false
|
|
90
|
+
this.filePath = "word/footnotes.xml"
|
|
91
|
+
this.ctFilePath = "[Content_Types].xml"
|
|
92
|
+
this.settingsFilePath = "word/settings.xml"
|
|
93
|
+
this.styleFilePath = "word/styles.xml"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
init() {
|
|
97
|
+
this.findFootnotes()
|
|
98
|
+
if (
|
|
99
|
+
this.footnotes.length ||
|
|
100
|
+
(this.citations.citFm.citationType === "note" &&
|
|
101
|
+
this.citations.citInfos.length)
|
|
102
|
+
) {
|
|
103
|
+
this.convertFootnotes()
|
|
104
|
+
this.fnRels = new DOCXExporterRels(this.xml, "footnotes")
|
|
105
|
+
// Include the citinfos from the main body document so that they will be
|
|
106
|
+
// used for calculating the bibliography as well
|
|
107
|
+
this.augmentedCitations = new DOCXExporterCitations(
|
|
108
|
+
this.fnPmJSON,
|
|
109
|
+
this.settings,
|
|
110
|
+
this.bibDB,
|
|
111
|
+
this.csl,
|
|
112
|
+
this.xml,
|
|
113
|
+
this.citations.citInfos
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
this.images = new DOCXExporterImages(
|
|
117
|
+
this.fnPmJSON,
|
|
118
|
+
this.imageDB,
|
|
119
|
+
this.xml,
|
|
120
|
+
this.fnRels
|
|
121
|
+
)
|
|
122
|
+
this.lists = new DOCXExporterLists(
|
|
123
|
+
this.fnPmJSON,
|
|
124
|
+
this.xml,
|
|
125
|
+
this.fnRels
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return this.augmentedCitations
|
|
129
|
+
.init()
|
|
130
|
+
.then(() => {
|
|
131
|
+
// Replace the main bibliography with the new one that
|
|
132
|
+
// includes both citations in main document
|
|
133
|
+
// and in the footnotes.
|
|
134
|
+
this.pmBib = this.augmentedCitations.pmBib
|
|
135
|
+
return this.fnRels.init()
|
|
136
|
+
})
|
|
137
|
+
.then(() => this.images.init())
|
|
138
|
+
.then(() => this.lists.init())
|
|
139
|
+
.then(() => this.initCt())
|
|
140
|
+
.then(() => this.setSettings())
|
|
141
|
+
.then(() => this.addStyles())
|
|
142
|
+
.then(() => this.createXml())
|
|
143
|
+
} else {
|
|
144
|
+
// No footnotes were found.
|
|
145
|
+
return Promise.resolve()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
initCt() {
|
|
150
|
+
return this.xml.getXml(this.ctFilePath).then(ctXML => {
|
|
151
|
+
this.ctXML = ctXML
|
|
152
|
+
this.addRelsToCt()
|
|
153
|
+
return Promise.resolve()
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
addRelsToCt() {
|
|
158
|
+
const override = this.ctXML.query("Override", {
|
|
159
|
+
PartName: `/${this.filePath}`
|
|
160
|
+
})
|
|
161
|
+
if (!override) {
|
|
162
|
+
const types = this.ctXML.query("Types")
|
|
163
|
+
types.appendXML(
|
|
164
|
+
`<Override PartName="/${this.filePath}" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"/>`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
addStyles() {
|
|
170
|
+
return this.xml.getXml(this.styleFilePath).then(styleXML => {
|
|
171
|
+
this.styleXML = styleXML
|
|
172
|
+
this.addStyle("Footnote", DEFAULT_STYLE_FOOTNOTE)
|
|
173
|
+
this.addStyle("FootnoteAnchor", DEFAULT_STYLE_FOOTNOTE_ANCHOR)
|
|
174
|
+
return Promise.resolve()
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
addStyle(styleName, xml) {
|
|
179
|
+
if (!this.styleXML.query("w:style", {"w:styleId": styleName})) {
|
|
180
|
+
const stylesEl = this.styleXML.query("w:styles")
|
|
181
|
+
stylesEl.appendXML(xml)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
findFootnotes() {
|
|
186
|
+
descendantNodes(this.docContent).forEach(node => {
|
|
187
|
+
if (node.type === "footnote") {
|
|
188
|
+
this.footnotes.push(node.attrs.footnote)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
convertFootnotes() {
|
|
194
|
+
const fnContent = []
|
|
195
|
+
this.footnotes.forEach(footnote => {
|
|
196
|
+
fnContent.push({
|
|
197
|
+
type: "footnotecontainer",
|
|
198
|
+
content: footnote
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
this.fnPmJSON = {
|
|
202
|
+
type: "doc",
|
|
203
|
+
content: fnContent
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
createXml() {
|
|
208
|
+
this.richtext = new DOCXExporterRichtext(
|
|
209
|
+
this.doc,
|
|
210
|
+
this.lists,
|
|
211
|
+
this,
|
|
212
|
+
this.settings,
|
|
213
|
+
this.math,
|
|
214
|
+
this.tables,
|
|
215
|
+
this.fnRels,
|
|
216
|
+
this.augmentedCitations,
|
|
217
|
+
this.images
|
|
218
|
+
)
|
|
219
|
+
this.fnXML = this.richtext.transformRichtext(this.fnPmJSON)
|
|
220
|
+
// TODO: add max dimensions
|
|
221
|
+
this.rels.addFootnoteRel()
|
|
222
|
+
return this.xml.getXml(this.filePath, DEFAULT_XML).then(xml => {
|
|
223
|
+
const footnotesEl = xml.query("w:footnotes")
|
|
224
|
+
footnotesEl.appendXML(this.fnXML)
|
|
225
|
+
this.xml = xml
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
setSettings() {
|
|
230
|
+
return this.xml.getXml(this.settingsFilePath).then(settingsXML => {
|
|
231
|
+
const footnotePr = settingsXML.query("w:footnotePr")
|
|
232
|
+
if (!footnotePr) {
|
|
233
|
+
const settingsEl = settingsXML.query("w:settings")
|
|
234
|
+
settingsEl.appendXML(DEFAULT_SETTINGS_XML)
|
|
235
|
+
}
|
|
236
|
+
this.settingsXML = settingsXML
|
|
237
|
+
return Promise.resolve()
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {get} from "../../common/index.js"
|
|
2
|
+
import {descendantNodes} from "../tools/doc_content.js"
|
|
3
|
+
import {svg2png} from "../tools/svg.js"
|
|
4
|
+
|
|
5
|
+
export class DOCXExporterImages {
|
|
6
|
+
constructor(docContent, imageDB, xml, rels) {
|
|
7
|
+
this.docContent = docContent
|
|
8
|
+
this.imageDB = imageDB
|
|
9
|
+
this.xml = xml
|
|
10
|
+
this.rels = rels
|
|
11
|
+
|
|
12
|
+
this.images = {}
|
|
13
|
+
this.ctXML = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
init() {
|
|
17
|
+
return this.xml.getXml("[Content_Types].xml").then(ctXML => {
|
|
18
|
+
this.ctXML = ctXML
|
|
19
|
+
return this.exportImages()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// add an image to the list of files
|
|
24
|
+
addImage(imgFileName, image) {
|
|
25
|
+
const rId = this.rels.addImageRel(imgFileName)
|
|
26
|
+
this.addContentType(imgFileName.split(".").pop())
|
|
27
|
+
this.xml.addExtraFile(`word/media/${imgFileName}`, image)
|
|
28
|
+
return rId
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// add a global contenttype declaration for an image type (if needed)
|
|
32
|
+
addContentType(fileEnding) {
|
|
33
|
+
const types = this.ctXML.query("Types")
|
|
34
|
+
const contentDec = types.query("Default", {Extension: fileEnding})
|
|
35
|
+
if (!contentDec) {
|
|
36
|
+
const string = `<Default ContentType="image/${fileEnding}" Extension="${fileEnding}"/>`
|
|
37
|
+
types.appendXML(string)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Find all images used in file and add these to the export zip.
|
|
42
|
+
// TODO: This will likely fail on image types docx doesn't support such as SVG.
|
|
43
|
+
// Try out and fix.
|
|
44
|
+
exportImages() {
|
|
45
|
+
const usedImgs = []
|
|
46
|
+
descendantNodes(this.docContent).forEach(node => {
|
|
47
|
+
if (node.type === "image" && node.attrs.image !== false) {
|
|
48
|
+
if (!usedImgs.includes(node.attrs.image)) {
|
|
49
|
+
usedImgs.push(node.attrs.image)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
return new Promise(resolveExportImages => {
|
|
54
|
+
const p = []
|
|
55
|
+
usedImgs.forEach(image => {
|
|
56
|
+
const imgDBEntry = this.imageDB.db[image]
|
|
57
|
+
p.push(
|
|
58
|
+
get(imgDBEntry.image)
|
|
59
|
+
.then(response => response.blob())
|
|
60
|
+
.then(blob => {
|
|
61
|
+
if (blob.type === "image/svg+xml") {
|
|
62
|
+
// DOCX doesn't support SVG. Convert to PNG.
|
|
63
|
+
return svg2png(blob).then(
|
|
64
|
+
({blob: pngBlob, width, height}) => {
|
|
65
|
+
const wImgId = this.addImage(
|
|
66
|
+
imgDBEntry.image
|
|
67
|
+
.split("/")
|
|
68
|
+
.pop()
|
|
69
|
+
.replace(/.svg$/g, ".png"),
|
|
70
|
+
pngBlob
|
|
71
|
+
)
|
|
72
|
+
this.images[image] = {
|
|
73
|
+
id: wImgId,
|
|
74
|
+
width,
|
|
75
|
+
height,
|
|
76
|
+
title: imgDBEntry.title
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
} else {
|
|
81
|
+
const wImgId = this.addImage(
|
|
82
|
+
imgDBEntry.image.split("/").pop(),
|
|
83
|
+
blob
|
|
84
|
+
)
|
|
85
|
+
this.images[image] = {
|
|
86
|
+
id: wImgId,
|
|
87
|
+
width: imgDBEntry.width,
|
|
88
|
+
height: imgDBEntry.height,
|
|
89
|
+
title: imgDBEntry.title
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
Promise.all(p).then(() => {
|
|
97
|
+
resolveExportImages()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import download from "downloadjs"
|
|
2
|
+
|
|
3
|
+
import {shortFileTitle} from "../../common/index.js"
|
|
4
|
+
import {fixTables, removeHidden, textContent} from "../tools/doc_content.js"
|
|
5
|
+
import {createSlug} from "../tools/file.js"
|
|
6
|
+
import {XmlZip} from "../tools/xml_zip.js"
|
|
7
|
+
import {DOCXExporterCitations} from "./citations.js"
|
|
8
|
+
import {DOCXExporterComments} from "./comments.js"
|
|
9
|
+
import {DOCXExporterFootnotes} from "./footnotes.js"
|
|
10
|
+
import {DOCXExporterImages} from "./images.js"
|
|
11
|
+
import {DOCXExporterLists} from "./lists.js"
|
|
12
|
+
import {DOCXExporterMath} from "./math.js"
|
|
13
|
+
import {DOCXExporterMetadata} from "./metadata.js"
|
|
14
|
+
import {DOCXExporterRels} from "./rels.js"
|
|
15
|
+
import {DOCXExporterRender} from "./render.js"
|
|
16
|
+
import {DOCXExporterRichtext} from "./richtext.js"
|
|
17
|
+
import {DOCXExporterTables} from "./tables.js"
|
|
18
|
+
import {moveFootnoteComments} from "./tools.js"
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
Exporter to Office Open XML docx (Microsoft Word)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
TODO:
|
|
26
|
+
* - Remove comments
|
|
27
|
+
* - Export document language
|
|
28
|
+
* - Templating of tag/contributor output
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export class DOCXExporter {
|
|
32
|
+
constructor(doc, templateUrl, bibDB, imageDB, csl) {
|
|
33
|
+
this.doc = doc
|
|
34
|
+
this.templateUrl = templateUrl
|
|
35
|
+
this.bibDB = bibDB
|
|
36
|
+
this.imageDB = imageDB
|
|
37
|
+
this.csl = csl
|
|
38
|
+
|
|
39
|
+
this.docTitle = shortFileTitle(this.doc.title, this.doc.path)
|
|
40
|
+
this.mimeType =
|
|
41
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
42
|
+
this.docContent = moveFootnoteComments(
|
|
43
|
+
fixTables(removeHidden(this.doc.content))
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
init() {
|
|
48
|
+
const xml = new XmlZip(this.templateUrl, this.mimeType)
|
|
49
|
+
|
|
50
|
+
const tables = new DOCXExporterTables(xml)
|
|
51
|
+
const math = new DOCXExporterMath(xml)
|
|
52
|
+
const render = new DOCXExporterRender(xml)
|
|
53
|
+
const rels = new DOCXExporterRels(xml, "document")
|
|
54
|
+
const metadata = new DOCXExporterMetadata(
|
|
55
|
+
xml,
|
|
56
|
+
this.getBaseMetadata(),
|
|
57
|
+
this.csl
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const images = new DOCXExporterImages(
|
|
61
|
+
this.docContent,
|
|
62
|
+
this.imageDB,
|
|
63
|
+
xml,
|
|
64
|
+
rels
|
|
65
|
+
)
|
|
66
|
+
const lists = new DOCXExporterLists(this.docContent, xml, rels)
|
|
67
|
+
const citations = new DOCXExporterCitations(
|
|
68
|
+
this.docContent,
|
|
69
|
+
this.doc.settings,
|
|
70
|
+
this.bibDB,
|
|
71
|
+
this.csl,
|
|
72
|
+
xml
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const footnotes = new DOCXExporterFootnotes(
|
|
76
|
+
this.doc,
|
|
77
|
+
this.docContent,
|
|
78
|
+
this.doc.settings,
|
|
79
|
+
this.imageDB,
|
|
80
|
+
this.bibDB,
|
|
81
|
+
xml,
|
|
82
|
+
citations,
|
|
83
|
+
this.csl,
|
|
84
|
+
lists,
|
|
85
|
+
math,
|
|
86
|
+
tables,
|
|
87
|
+
rels
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const richtext = new DOCXExporterRichtext(
|
|
91
|
+
this.doc,
|
|
92
|
+
this.doc.settings,
|
|
93
|
+
lists,
|
|
94
|
+
footnotes,
|
|
95
|
+
math,
|
|
96
|
+
tables,
|
|
97
|
+
rels,
|
|
98
|
+
citations,
|
|
99
|
+
images
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const comments = new DOCXExporterComments(
|
|
103
|
+
this.docContent,
|
|
104
|
+
this.doc.comments,
|
|
105
|
+
xml,
|
|
106
|
+
rels,
|
|
107
|
+
richtext
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return xml
|
|
111
|
+
.init()
|
|
112
|
+
.then(() => citations.init())
|
|
113
|
+
.then(() => metadata.init())
|
|
114
|
+
.then(() => tables.init())
|
|
115
|
+
.then(() => math.init())
|
|
116
|
+
.then(() => render.init())
|
|
117
|
+
.then(() => rels.init())
|
|
118
|
+
.then(() => images.init())
|
|
119
|
+
.then(() => comments.init())
|
|
120
|
+
.then(() => lists.init())
|
|
121
|
+
.then(() => footnotes.init())
|
|
122
|
+
.then(() => {
|
|
123
|
+
const pmBib = footnotes.pmBib || citations.pmBib
|
|
124
|
+
render.render(
|
|
125
|
+
this.docContent,
|
|
126
|
+
pmBib,
|
|
127
|
+
this.doc.settings,
|
|
128
|
+
richtext,
|
|
129
|
+
citations
|
|
130
|
+
)
|
|
131
|
+
return xml.prepareBlob()
|
|
132
|
+
})
|
|
133
|
+
.then(blob => this.download(blob))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
download(blob) {
|
|
137
|
+
return download(
|
|
138
|
+
blob,
|
|
139
|
+
createSlug(this.docTitle) + ".docx",
|
|
140
|
+
this.mimeType
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getBaseMetadata() {
|
|
145
|
+
const contributors = this.docContent.content.reduce(
|
|
146
|
+
(contributors, part) => {
|
|
147
|
+
if (
|
|
148
|
+
part.type === "contributors_part" &&
|
|
149
|
+
part.attrs.metadata &&
|
|
150
|
+
part.content
|
|
151
|
+
) {
|
|
152
|
+
return contributors.concat(
|
|
153
|
+
part.content.map(node => ({
|
|
154
|
+
...node.attrs,
|
|
155
|
+
role: part.attrs.metadata
|
|
156
|
+
}))
|
|
157
|
+
)
|
|
158
|
+
} else {
|
|
159
|
+
return contributors
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[]
|
|
163
|
+
)
|
|
164
|
+
return {
|
|
165
|
+
authors: contributors.filter(c => c.role === "authors"),
|
|
166
|
+
contributors,
|
|
167
|
+
keywords: this.docContent.content.reduce((keywords, part) => {
|
|
168
|
+
if (
|
|
169
|
+
part.type === "tags_part" &&
|
|
170
|
+
part.attrs.metadata === "keywords" &&
|
|
171
|
+
part.content
|
|
172
|
+
) {
|
|
173
|
+
return keywords.concat(
|
|
174
|
+
part.content.map(keywordNode => keywordNode.attrs.tag)
|
|
175
|
+
)
|
|
176
|
+
} else {
|
|
177
|
+
return keywords
|
|
178
|
+
}
|
|
179
|
+
}, []),
|
|
180
|
+
title: textContent(this.docContent.content[0]),
|
|
181
|
+
language: this.doc.settings.language,
|
|
182
|
+
citationStyle: this.doc.settings.citationstyle
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|