@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,865 @@
|
|
|
1
|
+
import {escapeText} from "../../common/index.js"
|
|
2
|
+
import {CATS} from "../../schema/i18n.js"
|
|
3
|
+
import {createZoteroCitation} from "../tools/zotero_csl.js"
|
|
4
|
+
|
|
5
|
+
const TEXT_TYPES = {
|
|
6
|
+
heading1: {tag: "text:h", attrs: _options => 'text:outline-level="1"'},
|
|
7
|
+
heading2: {tag: "text:h", attrs: _options => 'text:outline-level="2"'},
|
|
8
|
+
heading3: {tag: "text:h", attrs: _options => 'text:outline-level="3"'},
|
|
9
|
+
heading4: {tag: "text:h", attrs: _options => 'text:outline-level="4"'},
|
|
10
|
+
heading5: {tag: "text:h", attrs: _options => 'text:outline-level="5"'},
|
|
11
|
+
heading6: {tag: "text:h", attrs: _options => 'text:outline-level="6"'},
|
|
12
|
+
paragraph: {
|
|
13
|
+
tag: "text:p",
|
|
14
|
+
attrs: options => `text:style-name="${options.section}"`
|
|
15
|
+
},
|
|
16
|
+
code_block: {
|
|
17
|
+
tag: "text:p",
|
|
18
|
+
attrs: _options => 'text:style-name="Preformatted_20_Text"'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const INLINE_TYPES = [
|
|
23
|
+
"citation",
|
|
24
|
+
"cross_reference",
|
|
25
|
+
"cslbib",
|
|
26
|
+
"cslblock",
|
|
27
|
+
"cslindent",
|
|
28
|
+
"cslinline",
|
|
29
|
+
"cslleftmargin",
|
|
30
|
+
"cslrightinline",
|
|
31
|
+
"equation",
|
|
32
|
+
"footnote",
|
|
33
|
+
"hard_break",
|
|
34
|
+
"image",
|
|
35
|
+
"text"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create Zotero reference mark name for ODT.
|
|
40
|
+
* @param {Array} references - Array of {id, prefix?, locator?} from citation node
|
|
41
|
+
* @param {Object} bibDB - Bibliography database
|
|
42
|
+
* @param {string} formattedCitation - Pre-formatted citation text from citeproc
|
|
43
|
+
* @param {string} citationId - Optional citation ID (generated if not provided)
|
|
44
|
+
* @returns {string} Reference mark name with JSON encoded
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// Generate a random ID for Zotero bibliography section + Zotero citations
|
|
48
|
+
// Format: RND + random alphanumeric string (10 characters)
|
|
49
|
+
function generateZoteroId() {
|
|
50
|
+
const chars =
|
|
51
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
52
|
+
const length = 10
|
|
53
|
+
let result = "RND"
|
|
54
|
+
for (let i = 0; i < length; i++) {
|
|
55
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createZoteroCitationMark(
|
|
61
|
+
references,
|
|
62
|
+
bibDB,
|
|
63
|
+
formattedCitation,
|
|
64
|
+
citationId = null
|
|
65
|
+
) {
|
|
66
|
+
const zoteroCitation = createZoteroCitation(
|
|
67
|
+
references,
|
|
68
|
+
bibDB,
|
|
69
|
+
formattedCitation,
|
|
70
|
+
citationId
|
|
71
|
+
)
|
|
72
|
+
if (!zoteroCitation) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const jsonStr = JSON.stringify(zoteroCitation)
|
|
77
|
+
// In ODT, quotes must be encoded as " in attribute values
|
|
78
|
+
const encodedJson = jsonStr.replace(/"/g, """)
|
|
79
|
+
const zoteroId = generateZoteroId()
|
|
80
|
+
return `ZOTERO_ITEM CSL_CITATION ${encodedJson} ${zoteroId}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class ODTExporterRichtext {
|
|
84
|
+
constructor(
|
|
85
|
+
comments,
|
|
86
|
+
settings,
|
|
87
|
+
styles,
|
|
88
|
+
tracks,
|
|
89
|
+
footnotes,
|
|
90
|
+
citations,
|
|
91
|
+
math,
|
|
92
|
+
images
|
|
93
|
+
) {
|
|
94
|
+
this.comments = comments
|
|
95
|
+
this.styles = styles
|
|
96
|
+
this.tracks = tracks
|
|
97
|
+
this.footnotes = footnotes
|
|
98
|
+
this.citations = citations
|
|
99
|
+
this.settings = settings
|
|
100
|
+
this.math = math
|
|
101
|
+
this.images = images
|
|
102
|
+
|
|
103
|
+
this.imgCounter = 1
|
|
104
|
+
this.fnCounter = 0 // real footnotes
|
|
105
|
+
this.fnAlikeCounter = 0 // real footnotes and citations as footnotes
|
|
106
|
+
this.categoryCounter = {} // counters for each type of table/figure category (figure/table/photo)
|
|
107
|
+
this.fnCategoryCounter = {} // counters for each type of table/figure category (figure/table/photo)
|
|
108
|
+
this.zIndex = 0
|
|
109
|
+
this.citationCounter = 0 // Track which citation we're processing
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
run(node, options = {}, parent = null, siblingIndex = 0) {
|
|
113
|
+
options.comments = this.findComments(node) // Data related to comments. We need to mark the first and last occurence of comment
|
|
114
|
+
return this.transformRichtext(node, options, parent, siblingIndex)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
findComments(node, comments = {}) {
|
|
118
|
+
if (node.marks) {
|
|
119
|
+
node.marks
|
|
120
|
+
.filter(mark => mark.type === "comment")
|
|
121
|
+
.forEach(comment => {
|
|
122
|
+
if (!comments[comment.attrs.id]) {
|
|
123
|
+
comments[comment.attrs.id] = {
|
|
124
|
+
start: node,
|
|
125
|
+
end: node,
|
|
126
|
+
content: this.comments[comment.attrs.id]
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
comments[comment.attrs.id]["end"] = node
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
if (node.content) {
|
|
134
|
+
for (let i = 0; i < node.content.length; i++) {
|
|
135
|
+
this.findComments(node.content[i], comments)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return comments
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
transformRichtext(node, options = {}, parent = null, siblingIndex = 0) {
|
|
142
|
+
let start = "",
|
|
143
|
+
content = "",
|
|
144
|
+
end = ""
|
|
145
|
+
const siblings = parent?.content || []
|
|
146
|
+
const previousSibling = siblings[siblingIndex - 1]
|
|
147
|
+
const nextSibling = siblings[siblingIndex + 1]
|
|
148
|
+
|
|
149
|
+
const inlineNode = INLINE_TYPES.includes(node.type)
|
|
150
|
+
|
|
151
|
+
let blockDelete, blockInsert
|
|
152
|
+
|
|
153
|
+
if (!inlineNode && node.attrs?.track) {
|
|
154
|
+
blockDelete = node.attrs.track.find(
|
|
155
|
+
mark => mark.type === "deletion"
|
|
156
|
+
)
|
|
157
|
+
if (blockDelete) {
|
|
158
|
+
options = Object.assign({}, options)
|
|
159
|
+
options.blockDelete = blockDelete
|
|
160
|
+
}
|
|
161
|
+
blockInsert = node.attrs.track.find(
|
|
162
|
+
mark => mark.type === "insertion"
|
|
163
|
+
)
|
|
164
|
+
if (blockInsert) {
|
|
165
|
+
options = Object.assign({}, options)
|
|
166
|
+
options.blockInsert = blockInsert
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (node.marks) {
|
|
171
|
+
node.marks
|
|
172
|
+
.filter(mark => mark.type === "comment")
|
|
173
|
+
.forEach(comment => {
|
|
174
|
+
const commentData = options.comments[comment.attrs.id]
|
|
175
|
+
if (!commentData || !commentData.content) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
if (commentData.start === node) {
|
|
179
|
+
start += `<office:annotation office:name="comment_${options.tag}_${comment.attrs.id}" loext:resolved="${commentData.content.resolved}">
|
|
180
|
+
<dc:creator>${escapeText(commentData.content.username)}</dc:creator>
|
|
181
|
+
<dc:date>${new Date(commentData.content.date).toISOString().slice(0, -1)}000000</dc:date>
|
|
182
|
+
${commentData.content.comment.map(node => this.transformRichtext(node, options)).join("")}
|
|
183
|
+
</office:annotation>`
|
|
184
|
+
}
|
|
185
|
+
if (commentData.end === node) {
|
|
186
|
+
end =
|
|
187
|
+
`<office:annotation-end office:name="comment_${options.tag}_${comment.attrs.id}"/>` +
|
|
188
|
+
(commentData.content.answers || [])
|
|
189
|
+
.map(
|
|
190
|
+
answer =>
|
|
191
|
+
`<office:annotation loext:resolved="${commentData.content.resolved}">
|
|
192
|
+
<dc:creator>${escapeText(answer.username)}</dc:creator>
|
|
193
|
+
<dc:date>${new Date(answer.date).toISOString().slice(0, -1)}000000</dc:date>
|
|
194
|
+
${answer.answer.map(node => this.transformRichtext(node, options)).join("")}
|
|
195
|
+
</office:annotation>`
|
|
196
|
+
)
|
|
197
|
+
.join("") +
|
|
198
|
+
end
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
switch (node.type) {
|
|
204
|
+
case "bibliography_heading":
|
|
205
|
+
this.styles.checkParStyle("Bibliography_20_Heading")
|
|
206
|
+
start += '<text:p text:style-name="Bibliography_20_Heading">'
|
|
207
|
+
end = "</text:p>" + end
|
|
208
|
+
break
|
|
209
|
+
case "code_block":
|
|
210
|
+
case "heading1":
|
|
211
|
+
case "heading2":
|
|
212
|
+
case "heading3":
|
|
213
|
+
case "heading4":
|
|
214
|
+
case "heading5":
|
|
215
|
+
case "heading6":
|
|
216
|
+
case "paragraph": {
|
|
217
|
+
// Handles all types of text blocks.
|
|
218
|
+
if (node.type === "code_block") {
|
|
219
|
+
this.styles.checkParStyle("Preformatted_20_Text")
|
|
220
|
+
} else if (node.type === "paragraph") {
|
|
221
|
+
if (!options.section) {
|
|
222
|
+
options.section = "Text_20_body"
|
|
223
|
+
}
|
|
224
|
+
this.styles.checkParStyle(options.section)
|
|
225
|
+
}
|
|
226
|
+
const nextBlockDelete = nextSibling?.attrs?.track?.find(
|
|
227
|
+
mark => mark.type === "deletion"
|
|
228
|
+
)
|
|
229
|
+
const nextBlockInsert = nextSibling?.attrs?.track?.find(
|
|
230
|
+
mark => mark.type === "insertion"
|
|
231
|
+
)
|
|
232
|
+
let lastNonMergedBlock
|
|
233
|
+
if (blockDelete) {
|
|
234
|
+
// This block has been deleted, so we need to check which text block
|
|
235
|
+
// it is being merged in to. If it has, we need to merge the
|
|
236
|
+
// two blocks.
|
|
237
|
+
if (!previousSibling || !TEXT_TYPES[previousSibling.type]) {
|
|
238
|
+
// We cannot merge into previous block. Therefore, we don't consider
|
|
239
|
+
// this text block as merged.
|
|
240
|
+
blockDelete = false
|
|
241
|
+
} else {
|
|
242
|
+
let searchNode = previousSibling
|
|
243
|
+
while (searchNode && TEXT_TYPES[searchNode.type]) {
|
|
244
|
+
lastNonMergedBlock = searchNode
|
|
245
|
+
if (
|
|
246
|
+
searchNode?.attrs?.track?.find(
|
|
247
|
+
mark => mark.type === "deletion"
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
searchNode =
|
|
251
|
+
siblings[siblings.indexOf(searchNode) - 1]
|
|
252
|
+
} else {
|
|
253
|
+
searchNode = false
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (blockDelete) {
|
|
259
|
+
// This block has been deleted, so instead we just add a text
|
|
260
|
+
// change marker.
|
|
261
|
+
if (previousSibling.type === "paragraph") {
|
|
262
|
+
if (!options.section) {
|
|
263
|
+
options.section = "Text_20_body"
|
|
264
|
+
}
|
|
265
|
+
this.styles.checkParStyle(options.section)
|
|
266
|
+
}
|
|
267
|
+
const trackId = this.tracks.addChange(
|
|
268
|
+
blockDelete,
|
|
269
|
+
`
|
|
270
|
+
<${TEXT_TYPES[previousSibling.type].tag} ${TEXT_TYPES[previousSibling.type].attrs(options)}/>
|
|
271
|
+
<${TEXT_TYPES[node.type].tag} ${TEXT_TYPES[node.type].attrs(options)}/>`
|
|
272
|
+
)
|
|
273
|
+
start += `<text:change text:change-id="${trackId}"/>`
|
|
274
|
+
} else {
|
|
275
|
+
start += `<${TEXT_TYPES[node.type].tag} ${TEXT_TYPES[node.type].attrs(options)}>`
|
|
276
|
+
}
|
|
277
|
+
if (blockInsert && blockInsert.trackId) {
|
|
278
|
+
// The previous block node is a text node , so the insertion is a textblock split.
|
|
279
|
+
// We need to put change track marks in both this and the previous text block.
|
|
280
|
+
start += `<text:change-end text:change-id="${blockInsert.trackId}"/>`
|
|
281
|
+
}
|
|
282
|
+
const nextBlockDeleteTextType =
|
|
283
|
+
nextBlockDelete && TEXT_TYPES[nextSibling.type]
|
|
284
|
+
if (!nextBlockDeleteTextType) {
|
|
285
|
+
const lastNonMergedBlockTextType =
|
|
286
|
+
lastNonMergedBlock &&
|
|
287
|
+
TEXT_TYPES[lastNonMergedBlock.type]
|
|
288
|
+
if (lastNonMergedBlockTextType) {
|
|
289
|
+
// This block has been deleted and the next block is not.
|
|
290
|
+
// So we end it here as the last known non-deleted block type.
|
|
291
|
+
end = `</${lastNonMergedBlockTextType.tag}>` + end
|
|
292
|
+
} else {
|
|
293
|
+
// The next block is not deleted, so we close this block.
|
|
294
|
+
end = `</${TEXT_TYPES[node.type].tag}>` + end
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (nextBlockInsert && TEXT_TYPES[nextSibling.type]) {
|
|
298
|
+
// The following block node is a text node , so the insertion is a textblock split.
|
|
299
|
+
// We need to put change track marks in both this and the next text block.
|
|
300
|
+
const trackId = this.tracks.addChange(nextBlockInsert)
|
|
301
|
+
end =
|
|
302
|
+
`<text:change-start text:change-id="${trackId}"/>` + end
|
|
303
|
+
// Adding the track id here so that we can add find it at the beginning of the next text block.
|
|
304
|
+
nextBlockInsert.trackId = trackId
|
|
305
|
+
}
|
|
306
|
+
if (TEXT_TYPES[node.type].tag === "text:h") {
|
|
307
|
+
start += `<text:bookmark-start text:name="${node.attrs.id}"/>`
|
|
308
|
+
end =
|
|
309
|
+
`<text:bookmark-end text:name="${node.attrs.id}"/>` +
|
|
310
|
+
end
|
|
311
|
+
}
|
|
312
|
+
// Handle code block category labels
|
|
313
|
+
if (node.type === "code_block") {
|
|
314
|
+
const category = node.attrs.category
|
|
315
|
+
if (category && node.attrs.id) {
|
|
316
|
+
const categoryCounter = options.inFootnote
|
|
317
|
+
? this.fnCategoryCounter
|
|
318
|
+
: this.categoryCounter
|
|
319
|
+
if (!categoryCounter[category]) {
|
|
320
|
+
categoryCounter[category] = 1
|
|
321
|
+
}
|
|
322
|
+
const catCount = categoryCounter[category]++
|
|
323
|
+
const catCountXml = `<text:sequence text:ref-name="ref${category}${catCount - 1}${options.inFootnote ? "A" : ""}" text:name="${category}" text:formula="ooow:${category}+1" style:num-format="1">${catCount}${options.inFootnote ? "A" : ""}</text:sequence>`
|
|
324
|
+
const title = node.attrs.title
|
|
325
|
+
? `: ${escapeText(node.attrs.title)}`
|
|
326
|
+
: ""
|
|
327
|
+
const categoryLabel = `<text:bookmark-start text:name="${node.attrs.id}"/>${CATS[category][this.settings.language]} ${catCountXml}${title}<text:bookmark-end text:name="${node.attrs.id}"/><text:line-break/>`
|
|
328
|
+
start += categoryLabel
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
break
|
|
332
|
+
}
|
|
333
|
+
case "blockquote":
|
|
334
|
+
// This is imperfect, but Word doesn't seem to provide section/quotation nesting
|
|
335
|
+
options = Object.assign({}, options)
|
|
336
|
+
options.section = "Quote"
|
|
337
|
+
break
|
|
338
|
+
case "ordered_list": {
|
|
339
|
+
const olId = this.styles.getOrderedListStyleId(node.attrs.order)
|
|
340
|
+
start += `<text:list text:style-name="L${olId[0]}">`
|
|
341
|
+
end = "</text:list>" + end
|
|
342
|
+
options = Object.assign({}, options)
|
|
343
|
+
options.section = `P${olId[1]}`
|
|
344
|
+
options.listStyles = (options.listStyles || []).concat([
|
|
345
|
+
`L${olId[0]}`
|
|
346
|
+
])
|
|
347
|
+
break
|
|
348
|
+
}
|
|
349
|
+
case "bullet_list": {
|
|
350
|
+
const ulId = this.styles.getBulletListStyleId()
|
|
351
|
+
start += `<text:list text:style-name="L${ulId[0]}">`
|
|
352
|
+
end = "</text:list>" + end
|
|
353
|
+
options = Object.assign({}, options)
|
|
354
|
+
options.section = `P${ulId[1]}`
|
|
355
|
+
options.listStyles = (options.listStyles || []).concat([
|
|
356
|
+
`L${ulId[0]}`
|
|
357
|
+
])
|
|
358
|
+
break
|
|
359
|
+
}
|
|
360
|
+
case "list_item":
|
|
361
|
+
start += "<text:list-item>"
|
|
362
|
+
end = "</text:list-item>" + end
|
|
363
|
+
break
|
|
364
|
+
case "footnotecontainer":
|
|
365
|
+
break
|
|
366
|
+
case "footnote": {
|
|
367
|
+
const fnCounter = this.fnAlikeCounter++
|
|
368
|
+
const fnOptions = Object.assign({}, options)
|
|
369
|
+
fnOptions.section = "Footnote"
|
|
370
|
+
fnOptions.tag = `footnote${fnCounter}`
|
|
371
|
+
fnOptions.inFootnote = true
|
|
372
|
+
const fnNode = {
|
|
373
|
+
type: "footnotecontainer",
|
|
374
|
+
content: node.attrs.footnote
|
|
375
|
+
}
|
|
376
|
+
fnOptions.comments = this.findComments(fnNode)
|
|
377
|
+
content += this.transformRichtext(fnNode, fnOptions)
|
|
378
|
+
start += `
|
|
379
|
+
<text:note text:id="ftn${fnCounter}" text:note-class="footnote">
|
|
380
|
+
<text:note-citation>${fnCounter}</text:note-citation>
|
|
381
|
+
<text:note-body>`
|
|
382
|
+
end =
|
|
383
|
+
`
|
|
384
|
+
</text:note-body>
|
|
385
|
+
</text:note>` + end
|
|
386
|
+
break
|
|
387
|
+
}
|
|
388
|
+
case "text": {
|
|
389
|
+
let hyperlink,
|
|
390
|
+
strong,
|
|
391
|
+
em,
|
|
392
|
+
underline,
|
|
393
|
+
sup,
|
|
394
|
+
sub,
|
|
395
|
+
smallcaps,
|
|
396
|
+
code,
|
|
397
|
+
anchor
|
|
398
|
+
// Check for hyperlink, bold/strong and italic/em
|
|
399
|
+
if (node.marks) {
|
|
400
|
+
hyperlink = node.marks.find(mark => mark.type === "link")
|
|
401
|
+
anchor = node.marks.find(mark => mark.type === "anchor")
|
|
402
|
+
strong = node.marks.find(mark => mark.type === "strong")
|
|
403
|
+
em = node.marks.find(mark => mark.type === "em")
|
|
404
|
+
underline = node.marks.find(
|
|
405
|
+
mark => mark.type === "underline"
|
|
406
|
+
)
|
|
407
|
+
smallcaps = node.marks.find(
|
|
408
|
+
mark => mark.type === "smallcaps"
|
|
409
|
+
)
|
|
410
|
+
sup = node.marks.find(mark => mark.type === "sup")
|
|
411
|
+
sub = node.marks.find(mark => mark.type === "sub")
|
|
412
|
+
code = node.marks.find(mark => mark.type === "code")
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (hyperlink) {
|
|
416
|
+
start += `<text:a xlink:type="simple" xlink:href="${escapeText(hyperlink.attrs.href)}">`
|
|
417
|
+
end = "</text:a>" + end
|
|
418
|
+
}
|
|
419
|
+
if (anchor) {
|
|
420
|
+
start += `<text:reference-mark-start text:name="${anchor.attrs.id}"/>`
|
|
421
|
+
end =
|
|
422
|
+
`<text:reference-mark-end text:name="${anchor.attrs.id}"/>` +
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
start += `<text:bookmark-start text:name="${anchor.attrs.id}"/>`
|
|
426
|
+
end =
|
|
427
|
+
`<text:bookmark-end text:name="${anchor.attrs.id}"/>` +
|
|
428
|
+
end
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let attributes = ""
|
|
432
|
+
|
|
433
|
+
if (em) {
|
|
434
|
+
attributes += "e"
|
|
435
|
+
}
|
|
436
|
+
if (strong) {
|
|
437
|
+
attributes += "s"
|
|
438
|
+
}
|
|
439
|
+
if (underline) {
|
|
440
|
+
attributes += "u"
|
|
441
|
+
}
|
|
442
|
+
if (smallcaps) {
|
|
443
|
+
attributes += "c"
|
|
444
|
+
}
|
|
445
|
+
if (sup) {
|
|
446
|
+
attributes += "p"
|
|
447
|
+
} else if (sub) {
|
|
448
|
+
attributes += "b"
|
|
449
|
+
}
|
|
450
|
+
if (code) {
|
|
451
|
+
attributes += "t"
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (attributes.length) {
|
|
455
|
+
const styleId = this.styles.getInlineStyleId(attributes)
|
|
456
|
+
start += `<text:span text:style-name="T${styleId}">`
|
|
457
|
+
end = "</text:span>" + end
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
content += escapeText(node.text).replace(/^\s+|\s+$/g, match =>
|
|
461
|
+
"<text:s/>".repeat(match.length)
|
|
462
|
+
)
|
|
463
|
+
break
|
|
464
|
+
}
|
|
465
|
+
case "citation": {
|
|
466
|
+
let cit
|
|
467
|
+
// We take the first citation from the stack and remove it.
|
|
468
|
+
if (options.inFootnote) {
|
|
469
|
+
cit = this.footnotes.citations.pmCits.shift()
|
|
470
|
+
} else {
|
|
471
|
+
cit = this.citations.pmCits.shift()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Get citation info and formatted text for Zotero export
|
|
475
|
+
const citInfo = this.citations.citInfos[this.citationCounter]
|
|
476
|
+
const formattedText =
|
|
477
|
+
this.citations.citationTexts[this.citationCounter]
|
|
478
|
+
this.citationCounter++
|
|
479
|
+
|
|
480
|
+
// Create Zotero citation data on-the-fly
|
|
481
|
+
const markName =
|
|
482
|
+
citInfo && formattedText
|
|
483
|
+
? createZoteroCitationMark(
|
|
484
|
+
citInfo.references,
|
|
485
|
+
this.citations.bibDB,
|
|
486
|
+
formattedText
|
|
487
|
+
)
|
|
488
|
+
: null
|
|
489
|
+
|
|
490
|
+
if (options.citationType === "note" && !options.inFootnote) {
|
|
491
|
+
// If the citations are in notes (footnotes), we need to
|
|
492
|
+
// put the contents of this citation in a footnote.
|
|
493
|
+
|
|
494
|
+
if (markName && formattedText) {
|
|
495
|
+
// Create Zotero reference mark for footnote citation
|
|
496
|
+
start += `
|
|
497
|
+
<text:note text:id="ftn${this.fnAlikeCounter++}" text:note-class="footnote">
|
|
498
|
+
<text:note-citation>${this.fnAlikeCounter}</text:note-citation>
|
|
499
|
+
<text:note-body>
|
|
500
|
+
<text:p text:style-name="Footnote">
|
|
501
|
+
<text:reference-mark-start text:name="${markName}"/>`
|
|
502
|
+
content = formattedText
|
|
503
|
+
end =
|
|
504
|
+
`<text:reference-mark-end text:name="${markName}"/></text:p>
|
|
505
|
+
</text:note-body>
|
|
506
|
+
</text:note>` + end
|
|
507
|
+
} else {
|
|
508
|
+
// Fallback to non-Zotero format
|
|
509
|
+
start += `
|
|
510
|
+
<text:note text:id="ftn${this.fnAlikeCounter++}" text:note-class="footnote">
|
|
511
|
+
<text:note-citation>${this.fnAlikeCounter}</text:note-citation>
|
|
512
|
+
<text:note-body>`
|
|
513
|
+
end =
|
|
514
|
+
`
|
|
515
|
+
</text:note-body>
|
|
516
|
+
</text:note>` + end
|
|
517
|
+
options = Object.assign({}, options)
|
|
518
|
+
options.section = "Footnote"
|
|
519
|
+
content += this.transformRichtext(
|
|
520
|
+
{type: "paragraph", content: cit.content},
|
|
521
|
+
options
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
// For in-text citations, create Zotero reference mark
|
|
526
|
+
if (markName && formattedText) {
|
|
527
|
+
start += `<text:reference-mark-start text:name="${markName}"/>`
|
|
528
|
+
content = formattedText
|
|
529
|
+
end =
|
|
530
|
+
`<text:reference-mark-end text:name="${markName}"/>` +
|
|
531
|
+
end
|
|
532
|
+
} else {
|
|
533
|
+
// Fallback to formatted text only
|
|
534
|
+
cit.content.forEach(citContent => {
|
|
535
|
+
content += this.transformRichtext(
|
|
536
|
+
citContent,
|
|
537
|
+
options
|
|
538
|
+
)
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
break
|
|
544
|
+
}
|
|
545
|
+
case "figure": {
|
|
546
|
+
// NOTE: The difficulty is to make several images with different
|
|
547
|
+
// alignments/widths not overlap one-another. The below code
|
|
548
|
+
// makes a reasonable attempt at that, but it seems there is no
|
|
549
|
+
// way to guarantee it from happening.
|
|
550
|
+
options = Object.assign({}, options)
|
|
551
|
+
options.section = "Standard"
|
|
552
|
+
this.styles.checkParStyle(options.section)
|
|
553
|
+
start += `<text:p text:style-name="${options.section}">`
|
|
554
|
+
end = "</text:p>" + end
|
|
555
|
+
|
|
556
|
+
if (node.attrs.aligned === "center") {
|
|
557
|
+
// Needed to prevent subsequent image from overlapping
|
|
558
|
+
end = end + '<text:p text:style-name="Standard"></text:p>'
|
|
559
|
+
}
|
|
560
|
+
const figureCaption = node.content.find(
|
|
561
|
+
node => node.type === "figure_caption"
|
|
562
|
+
)
|
|
563
|
+
let caption = node.attrs.caption
|
|
564
|
+
? figureCaption?.content
|
|
565
|
+
?.map((node, index) =>
|
|
566
|
+
this.transformRichtext(
|
|
567
|
+
node,
|
|
568
|
+
options,
|
|
569
|
+
figureCaption,
|
|
570
|
+
index
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
.join("") || ""
|
|
574
|
+
: ""
|
|
575
|
+
// The figure category should not be in the
|
|
576
|
+
// user's language but rather the document language
|
|
577
|
+
const category = node.attrs.category
|
|
578
|
+
if (category !== "none") {
|
|
579
|
+
const categoryCounter = options.inFootnote
|
|
580
|
+
? this.fnCategoryCounter
|
|
581
|
+
: this.categoryCounter
|
|
582
|
+
if (!categoryCounter[category]) {
|
|
583
|
+
categoryCounter[category] = 1
|
|
584
|
+
}
|
|
585
|
+
const catCount = categoryCounter[category]++
|
|
586
|
+
const catCountXml = `<text:sequence text:ref-name="ref${category}${catCount - 1}${options.inFootnote ? "A" : ""}" text:name="${category}" text:formula="ooow:${category}+1" style:num-format="1">${catCount}${options.inFootnote ? "A" : ""}</text:sequence>`
|
|
587
|
+
if (caption.length) {
|
|
588
|
+
caption = `<text:bookmark-start text:name="${node.attrs.id}"/>${CATS[category][this.settings.language]} ${catCountXml}<text:bookmark-end text:name="${node.attrs.id}"/>: ${caption}`
|
|
589
|
+
} else {
|
|
590
|
+
caption = `<text:bookmark-start text:name="${node.attrs.id}"/>${CATS[category][this.settings.language]} ${catCountXml}<text:bookmark-end text:name="${node.attrs.id}"/>`
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
let relWidth = node.attrs.width
|
|
594
|
+
let aligned = node.attrs.aligned
|
|
595
|
+
let frame
|
|
596
|
+
const image =
|
|
597
|
+
node.content.find(node => node.type === "image")?.attrs
|
|
598
|
+
.image || false
|
|
599
|
+
if (caption.length || image === false) {
|
|
600
|
+
frame = true
|
|
601
|
+
this.styles.checkParStyle("Caption")
|
|
602
|
+
this.styles.checkParStyle("Figure")
|
|
603
|
+
const graphicStyleId = this.styles.getGraphicStyleId(
|
|
604
|
+
"Frame",
|
|
605
|
+
aligned
|
|
606
|
+
)
|
|
607
|
+
start += `<draw:frame draw:style-name="fr${graphicStyleId}" draw:name="Frame${graphicStyleId}" text:anchor-type="paragraph" svg:width="0.0161in" style:rel-width="${relWidth}%" draw:z-index="${this.zIndex++}">
|
|
608
|
+
<draw:text-box fo:min-height="0in">
|
|
609
|
+
<text:p text:style-name="Figure">`
|
|
610
|
+
relWidth = "100" // percentage width of image inside of frame is always 100
|
|
611
|
+
aligned = "center" // Aligned inside of frame is always 'center'
|
|
612
|
+
end =
|
|
613
|
+
`
|
|
614
|
+
</text:p>
|
|
615
|
+
</draw:text-box>
|
|
616
|
+
</draw:frame>` + end
|
|
617
|
+
if (caption.length) {
|
|
618
|
+
end = `<text:line-break />${caption}` + end
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (image !== false) {
|
|
622
|
+
const imageEntry = this.images.images[image]
|
|
623
|
+
|
|
624
|
+
const height = (imageEntry.height * 3) / 4 // more or less px to point
|
|
625
|
+
const width = (imageEntry.width * 3) / 4 // more or less px to point
|
|
626
|
+
const graphicStyleId = this.styles.getGraphicStyleId(
|
|
627
|
+
"Graphics",
|
|
628
|
+
aligned
|
|
629
|
+
)
|
|
630
|
+
content += `
|
|
631
|
+
<draw:frame draw:style-name="${graphicStyleId}" draw:name="Image${this.imgCounter++}" text:anchor-type="${frame && !blockInsert ? "char" : "as-char"}" style:rel-width="${relWidth}%" style:rel-height="scale" svg:width="${width}pt" svg:height="${height}pt" draw:z-index="${this.zIndex++}">
|
|
632
|
+
${
|
|
633
|
+
imageEntry.svg
|
|
634
|
+
? `<draw:image xlink:href="Pictures/${imageEntry.svg}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" draw:mime-type="image/svg+xml"/>`
|
|
635
|
+
: ""
|
|
636
|
+
}
|
|
637
|
+
<draw:image xlink:href="Pictures/${imageEntry.id}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad" draw:mime-type="${imageEntry.type}"/>
|
|
638
|
+
</draw:frame>`
|
|
639
|
+
} else {
|
|
640
|
+
const latex = node.content.find(
|
|
641
|
+
node => node.type === "figure_equation"
|
|
642
|
+
)?.attrs.equation
|
|
643
|
+
const objectNumber = this.math.addMath(latex)
|
|
644
|
+
const graphicStyleId =
|
|
645
|
+
this.styles.getGraphicStyleId("Formula")
|
|
646
|
+
content += `
|
|
647
|
+
<draw:frame draw:style-name="${graphicStyleId}" draw:name="Object${objectNumber}" text:anchor-type="as-char" draw:z-index="${this.zIndex++}">
|
|
648
|
+
<draw:object xlink:href="./Object ${objectNumber}.js" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
|
|
649
|
+
<svg:desc>formula</svg:desc>
|
|
650
|
+
</draw:frame>`
|
|
651
|
+
}
|
|
652
|
+
if (category === "none") {
|
|
653
|
+
content = `<text:bookmark-start text:name="${node.attrs.id}"/>${content}<text:bookmark-end text:name="${node.attrs.id}"/>`
|
|
654
|
+
}
|
|
655
|
+
if (blockDelete) {
|
|
656
|
+
const trackId = this.tracks.addChange(
|
|
657
|
+
blockDelete,
|
|
658
|
+
`<text:p text:style-name="Figure">${content}<text:span></text:span></text:p>`
|
|
659
|
+
)
|
|
660
|
+
content = `<text:change text:change-id="${trackId}"/>`
|
|
661
|
+
}
|
|
662
|
+
if (blockInsert) {
|
|
663
|
+
const trackId = this.tracks.addChange(blockInsert)
|
|
664
|
+
start += `<text:change-start text:change-id="${trackId}"/>`
|
|
665
|
+
end = `<text:change-end text:change-id="${trackId}"/>` + end
|
|
666
|
+
}
|
|
667
|
+
break
|
|
668
|
+
}
|
|
669
|
+
case "figure_caption":
|
|
670
|
+
// We are already dealing with this in the figure. Prevent content from being added a second time.
|
|
671
|
+
return ""
|
|
672
|
+
case "figure_equation":
|
|
673
|
+
// We are already dealing with this in the figure.
|
|
674
|
+
break
|
|
675
|
+
case "image":
|
|
676
|
+
// We are already dealing with this in the figure.
|
|
677
|
+
break
|
|
678
|
+
case "table": {
|
|
679
|
+
if (options.listStyles) {
|
|
680
|
+
options.listStyles.forEach(listStyle => {
|
|
681
|
+
end =
|
|
682
|
+
`<text:list text:continue-numbering="true" text:style-name="${listStyle}"><text:list-item>` +
|
|
683
|
+
end
|
|
684
|
+
start += "</text:list-item></text:list>"
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
const tableCaption = node.content[0]
|
|
688
|
+
let caption = node.attrs.caption
|
|
689
|
+
? tableCaption?.content
|
|
690
|
+
?.map((node, index) =>
|
|
691
|
+
this.transformRichtext(
|
|
692
|
+
node,
|
|
693
|
+
options,
|
|
694
|
+
tableCaption,
|
|
695
|
+
index
|
|
696
|
+
)
|
|
697
|
+
)
|
|
698
|
+
.join("") || ""
|
|
699
|
+
: ""
|
|
700
|
+
// The table category should not be in the
|
|
701
|
+
// user's language but rather the document language
|
|
702
|
+
const category = node.attrs.category
|
|
703
|
+
if (category !== "none") {
|
|
704
|
+
const categoryCounter = options.inFootnote
|
|
705
|
+
? this.fnCategoryCounter
|
|
706
|
+
: this.categoryCounter
|
|
707
|
+
if (!categoryCounter[category]) {
|
|
708
|
+
categoryCounter[category] = 1
|
|
709
|
+
}
|
|
710
|
+
const catCount = categoryCounter[category]++
|
|
711
|
+
const catCountXml = `<text:sequence text:ref-name="ref${category}${catCount - 1}${options.inFootnote ? "A" : ""}" text:name="${category}" text:formula="ooow:${category}+1" style:num-format="1">${catCount}${options.inFootnote ? "A" : ""}</text:sequence>`
|
|
712
|
+
if (caption.length) {
|
|
713
|
+
caption = `<text:bookmark-start text:name="${node.attrs.id}"/>${CATS[category][this.settings.language]} ${catCountXml}<text:bookmark-end text:name="${node.attrs.id}"/>: ${caption}`
|
|
714
|
+
} else {
|
|
715
|
+
caption = `<text:bookmark-start text:name="${node.attrs.id}"/>${CATS[category][this.settings.language]} ${catCountXml}<text:bookmark-end text:name="${node.attrs.id}"/>`
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (caption.length) {
|
|
719
|
+
if (!options.section) {
|
|
720
|
+
options.section = "Text_20_body"
|
|
721
|
+
}
|
|
722
|
+
this.styles.checkParStyle(options.section)
|
|
723
|
+
start += `<text:p text:style-name="${options.section}">${caption}</text:p>`
|
|
724
|
+
}
|
|
725
|
+
const columns = node.content[1].content[0].content.length
|
|
726
|
+
const styleId = this.styles.getTableStyleId(
|
|
727
|
+
node.attrs.aligned,
|
|
728
|
+
node.attrs.width
|
|
729
|
+
)
|
|
730
|
+
start += `<table:table table:name="Table${styleId}" table:style-name="Table${styleId}">`
|
|
731
|
+
start += `<table:table-column table:number-columns-repeated="${columns}" />`
|
|
732
|
+
end = "</table:table>" + end
|
|
733
|
+
break
|
|
734
|
+
}
|
|
735
|
+
case "table_body":
|
|
736
|
+
// Pass through to table.
|
|
737
|
+
break
|
|
738
|
+
case "table_caption":
|
|
739
|
+
// We already deal with this in 'table'.
|
|
740
|
+
return ""
|
|
741
|
+
case "table_row":
|
|
742
|
+
start += "<table:table-row>"
|
|
743
|
+
end = "</table:table-row>" + end
|
|
744
|
+
break
|
|
745
|
+
case "table_cell":
|
|
746
|
+
case "table_header":
|
|
747
|
+
if (node.attrs.rowspan && node.attrs.colspan) {
|
|
748
|
+
start += `<table:table-cell${
|
|
749
|
+
node.attrs.rowspan > 1
|
|
750
|
+
? ` table:number-rows-spanned="${node.attrs.rowspan}"`
|
|
751
|
+
: ""
|
|
752
|
+
}${
|
|
753
|
+
node.attrs.colspan > 1
|
|
754
|
+
? ` table:number-columns-spanned="${node.attrs.colspan}"`
|
|
755
|
+
: ""
|
|
756
|
+
} office:value-type="string">`
|
|
757
|
+
end = "</table:table-cell>" + end
|
|
758
|
+
} else {
|
|
759
|
+
start += "<table:covered-table-cell/>"
|
|
760
|
+
}
|
|
761
|
+
break
|
|
762
|
+
case "equation": {
|
|
763
|
+
const latex = node.attrs.equation
|
|
764
|
+
const objectNumber = this.math.addMath(latex)
|
|
765
|
+
const styleId = this.styles.getGraphicStyleId("Formula")
|
|
766
|
+
content += `<draw:frame draw:style-name="${styleId}" draw:name="Object${objectNumber}" text:anchor-type="as-char" draw:z-index="${this.zIndex++}">
|
|
767
|
+
<draw:object xlink:href="./Object ${objectNumber}.js" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>
|
|
768
|
+
<svg:desc>formula</svg:desc>
|
|
769
|
+
</draw:frame>`
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
case "cross_reference": {
|
|
773
|
+
const title = node.attrs.title
|
|
774
|
+
const id = node.attrs.id
|
|
775
|
+
if (title) {
|
|
776
|
+
start += `<text:bookmark-ref text:reference-format="text" text:ref-name="${id}">`
|
|
777
|
+
end = "</text:bookmark-ref>" + end
|
|
778
|
+
}
|
|
779
|
+
content += escapeText(title || "MISSING TARGET")
|
|
780
|
+
break
|
|
781
|
+
}
|
|
782
|
+
case "hard_break":
|
|
783
|
+
content += "<text:line-break/>"
|
|
784
|
+
break
|
|
785
|
+
// CSL bib entries
|
|
786
|
+
case "cslbib": {
|
|
787
|
+
options = Object.assign({}, options)
|
|
788
|
+
options.section = "Bibliography_20_1"
|
|
789
|
+
// Ensure Sect1 section style exists
|
|
790
|
+
this.styles.checkSectionStyle("Sect1")
|
|
791
|
+
// Generate a unique bibliography ID for this document
|
|
792
|
+
const bibId = generateZoteroId()
|
|
793
|
+
start += `<text:section text:style-name="Sect1" text:name="ZOTERO_BIBL CSL_BIBLIOGRAPHY ${bibId}">`
|
|
794
|
+
end = "</text:section>" + end
|
|
795
|
+
break
|
|
796
|
+
}
|
|
797
|
+
case "cslblock":
|
|
798
|
+
end = "<text:line-break/>" + end
|
|
799
|
+
break
|
|
800
|
+
case "cslleftmargin":
|
|
801
|
+
end = "<text:tab/>" + end
|
|
802
|
+
break
|
|
803
|
+
case "cslindent":
|
|
804
|
+
start += "<text:tab/>"
|
|
805
|
+
end = "<text:line-break/>" + end
|
|
806
|
+
break
|
|
807
|
+
case "cslentry":
|
|
808
|
+
this.styles.checkParStyle(options.section)
|
|
809
|
+
start += `<text:p text:style-name="${options.section}">`
|
|
810
|
+
end = "</text:p>" + end
|
|
811
|
+
break
|
|
812
|
+
case "cslinline":
|
|
813
|
+
case "cslrightinline":
|
|
814
|
+
break
|
|
815
|
+
default:
|
|
816
|
+
break
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (node.content) {
|
|
820
|
+
for (let i = 0; i < node.content.length; i++) {
|
|
821
|
+
content += this.transformRichtext(
|
|
822
|
+
node.content[i],
|
|
823
|
+
options,
|
|
824
|
+
node,
|
|
825
|
+
i
|
|
826
|
+
)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (inlineNode) {
|
|
831
|
+
const inlineInsert =
|
|
832
|
+
node.marks?.find(
|
|
833
|
+
mark =>
|
|
834
|
+
mark.type === "insertion" &&
|
|
835
|
+
mark.attrs.approved === false
|
|
836
|
+
)?.attrs || blockInsert
|
|
837
|
+
const inlineDelete =
|
|
838
|
+
node.marks?.find(mark => mark.type === "deletion")?.attrs ||
|
|
839
|
+
options.blockDelete
|
|
840
|
+
if (inlineDelete) {
|
|
841
|
+
if (parent) {
|
|
842
|
+
const trackId = this.tracks.addChange(
|
|
843
|
+
Object.assign({type: "deletion"}, inlineDelete),
|
|
844
|
+
`<${TEXT_TYPES[parent.type]?.tag || "text:p"} ${TEXT_TYPES[parent.type]?.attrs(options) || `text:style-name="${options.section}"`}>${
|
|
845
|
+
start + content + end
|
|
846
|
+
}</${TEXT_TYPES[parent.type]?.tag || "text:p"}>`
|
|
847
|
+
)
|
|
848
|
+
content = `<text:change text:change-id="${trackId}"/>`
|
|
849
|
+
} else {
|
|
850
|
+
content = ""
|
|
851
|
+
}
|
|
852
|
+
start = ""
|
|
853
|
+
end = ""
|
|
854
|
+
}
|
|
855
|
+
if (inlineInsert) {
|
|
856
|
+
const trackId = this.tracks.addChange(
|
|
857
|
+
Object.assign({type: "insertion"}, inlineInsert)
|
|
858
|
+
)
|
|
859
|
+
start += `<text:change-start text:change-id="${trackId}"/>`
|
|
860
|
+
end = `<text:change-end text:change-id="${trackId}"/>` + end
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return start + content + end
|
|
864
|
+
}
|
|
865
|
+
}
|