@kerebron/extension-odt 0.4.28 → 0.4.30
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/esm/ExtensionOdt.d.ts.map +1 -1
- package/esm/ExtensionOdt.js +3 -1
- package/esm/ExtensionOdt.js.map +1 -0
- package/esm/OdtParser.js +1 -0
- package/esm/OdtParser.js.map +1 -0
- package/esm/lists.js +1 -0
- package/esm/lists.js.map +1 -0
- package/esm/node_handlers/basic_node_handlers.js +1 -0
- package/esm/node_handlers/basic_node_handlers.js.map +1 -0
- package/esm/node_handlers/list_node_handlers.js +1 -0
- package/esm/node_handlers/list_node_handlers.js.map +1 -0
- package/esm/node_handlers/table_node_handlers.js +1 -0
- package/esm/node_handlers/table_node_handlers.js.map +1 -0
- package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js +1 -0
- package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js.map +1 -0
- package/esm/postprocess/convertMathMl.js +1 -0
- package/esm/postprocess/convertMathMl.js.map +1 -0
- package/esm/postprocess/fixContinuedLists.js +1 -0
- package/esm/postprocess/fixContinuedLists.js.map +1 -0
- package/esm/postprocess/postProcess.js +1 -0
- package/esm/postprocess/postProcess.js.map +1 -0
- package/esm/postprocess/removeUnusedBookmarks.js +1 -0
- package/esm/postprocess/removeUnusedBookmarks.js.map +1 -0
- package/package.json +7 -3
- package/src/ExtensionOdt.ts +203 -0
- package/src/OdtParser.ts +350 -0
- package/src/lists.ts +35 -0
- package/src/node_handlers/basic_node_handlers.ts +155 -0
- package/src/node_handlers/list_node_handlers.ts +131 -0
- package/src/node_handlers/table_node_handlers.ts +28 -0
- package/src/postprocess/convertCodeParagraphsToCodeBlocks.ts +118 -0
- package/src/postprocess/convertMathMl.ts +56 -0
- package/src/postprocess/fixContinuedLists.ts +113 -0
- package/src/postprocess/postProcess.ts +23 -0
- package/src/postprocess/removeUnusedBookmarks.ts +36 -0
package/src/OdtParser.ts
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { Mark, Node, Schema } from 'prosemirror-model';
|
|
2
|
+
import {
|
|
3
|
+
getBasicNodesHandlers,
|
|
4
|
+
getInlineNodesHandlers,
|
|
5
|
+
} from './node_handlers/basic_node_handlers.js';
|
|
6
|
+
import { getListNodesHandlers } from './node_handlers/list_node_handlers.js';
|
|
7
|
+
import { getTableNodesHandlers } from './node_handlers/table_node_handlers.js';
|
|
8
|
+
import { ListTracker } from './lists.js';
|
|
9
|
+
import { type UrlRewriter } from '@kerebron/editor';
|
|
10
|
+
|
|
11
|
+
const COURIER_FONTS = ['Courier New', 'Courier', 'Roboto Mono'];
|
|
12
|
+
|
|
13
|
+
export interface OdtElement {
|
|
14
|
+
$value: 'TODO';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type NodeHandler = (ctx: OdtStashContext, value: any) => void;
|
|
18
|
+
|
|
19
|
+
interface ListStyle {
|
|
20
|
+
'@name': string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Style {
|
|
24
|
+
'@name': string;
|
|
25
|
+
'@parent-style-name'?: string;
|
|
26
|
+
styles: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface StylesTree {
|
|
30
|
+
styles: {
|
|
31
|
+
'list-style': Array<ListStyle>;
|
|
32
|
+
'style': Array<Style>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface AutomaticStyles {
|
|
37
|
+
'style': Array<Style>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveStyle(
|
|
41
|
+
stylesTree: StylesTree,
|
|
42
|
+
automaticStyles: AutomaticStyles,
|
|
43
|
+
name: string,
|
|
44
|
+
): Style {
|
|
45
|
+
let style: Style;
|
|
46
|
+
|
|
47
|
+
if (!style) {
|
|
48
|
+
style = stylesTree.styles['list-style'].find((item) =>
|
|
49
|
+
item['@name'] === name
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!style) {
|
|
53
|
+
style = stylesTree.styles['style'].find((item) => item['@name'] === name);
|
|
54
|
+
}
|
|
55
|
+
if (!style) {
|
|
56
|
+
style = automaticStyles.style.find((item) => item['@name'] === name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!style) {
|
|
60
|
+
style = {
|
|
61
|
+
'@name': name,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
style['styles'] = [name];
|
|
66
|
+
|
|
67
|
+
if (style['@parent-style-name']) {
|
|
68
|
+
const parentStyle = resolveStyle(
|
|
69
|
+
stylesTree,
|
|
70
|
+
automaticStyles,
|
|
71
|
+
style['@parent-style-name'],
|
|
72
|
+
);
|
|
73
|
+
if (parentStyle) {
|
|
74
|
+
const styles = [...style['styles'], ...parentStyle['styles']];
|
|
75
|
+
for (const key in style) {
|
|
76
|
+
if (typeof style[key] === 'undefined') {
|
|
77
|
+
delete style[key];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
style = {
|
|
81
|
+
...parentStyle,
|
|
82
|
+
...style,
|
|
83
|
+
styles,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return style;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TaggedEnum {
|
|
91
|
+
tag: string;
|
|
92
|
+
value: any;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function tagEnum(item: any) {
|
|
96
|
+
if ('string' === typeof item) {
|
|
97
|
+
return {
|
|
98
|
+
tag: item, // '$text',
|
|
99
|
+
value: item,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if ('object' === typeof item) {
|
|
103
|
+
const entries = Object.entries(item);
|
|
104
|
+
if (entries.length === 1) {
|
|
105
|
+
return {
|
|
106
|
+
tag: entries[0][0],
|
|
107
|
+
value: entries[0][1],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw new Error('Incorrect enum: ' + JSON.stringify(item));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function iterateChildren(
|
|
116
|
+
nodes: unknown[],
|
|
117
|
+
callback: (item: TaggedEnum) => void,
|
|
118
|
+
) {
|
|
119
|
+
for (const child of nodes) {
|
|
120
|
+
if ('Unknown' === child) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const item = tagEnum(child);
|
|
125
|
+
|
|
126
|
+
callback(item);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function iterateEnum($value: unknown[]): Array<TaggedEnum> {
|
|
131
|
+
if (!$value) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return $value.map(tagEnum);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface OdtParserConfig {
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface OdtContext {
|
|
141
|
+
handlers: Record<string, NodeHandler>;
|
|
142
|
+
content: Node[];
|
|
143
|
+
marks: Mark[];
|
|
144
|
+
meta: Record<string, any>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class OdtStashContext {
|
|
148
|
+
private ctxStash: Array<OdtContext> = [];
|
|
149
|
+
private currentCtx: OdtContext;
|
|
150
|
+
public listTracker = new ListTracker();
|
|
151
|
+
constructor(
|
|
152
|
+
readonly schema: Schema,
|
|
153
|
+
handlers: Record<string, NodeHandler>,
|
|
154
|
+
readonly stylesTree: StylesTree,
|
|
155
|
+
readonly automaticStyles: AutomaticStyles,
|
|
156
|
+
) {
|
|
157
|
+
this.currentCtx = {
|
|
158
|
+
handlers,
|
|
159
|
+
content: [],
|
|
160
|
+
marks: [],
|
|
161
|
+
meta: {},
|
|
162
|
+
};
|
|
163
|
+
this.stash();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public stash(): number {
|
|
167
|
+
this.ctxStash.push(this.currentCtx);
|
|
168
|
+
const funcs = {};
|
|
169
|
+
const handlers = { ...this.currentCtx.handlers };
|
|
170
|
+
const content = this.currentCtx.content;
|
|
171
|
+
const marks = this.currentCtx.marks;
|
|
172
|
+
this.currentCtx = {
|
|
173
|
+
...structuredClone({
|
|
174
|
+
...this.currentCtx,
|
|
175
|
+
content: undefined,
|
|
176
|
+
marks: undefined,
|
|
177
|
+
handlers: undefined,
|
|
178
|
+
}),
|
|
179
|
+
...funcs,
|
|
180
|
+
content,
|
|
181
|
+
marks,
|
|
182
|
+
handlers,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return this.ctxStash.length - 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public unstash() {
|
|
189
|
+
const ctx = this.ctxStash.pop();
|
|
190
|
+
if (!ctx) {
|
|
191
|
+
throw new Error('Unstash failed');
|
|
192
|
+
}
|
|
193
|
+
this.currentCtx = ctx;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public openNode() {
|
|
197
|
+
this.stash();
|
|
198
|
+
this.current.content = [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public closeNode(type: string, attrs = {}, marks = Mark.none) {
|
|
202
|
+
const node = this.createNode(type, attrs, marks);
|
|
203
|
+
this.unstash();
|
|
204
|
+
if (node) {
|
|
205
|
+
this.current.content.push(node);
|
|
206
|
+
}
|
|
207
|
+
return node;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private createNode(type: string, attrs = {}, marks = Mark.none) {
|
|
211
|
+
const nodeType = this.schema.nodes[type];
|
|
212
|
+
if (!nodeType) {
|
|
213
|
+
throw new Error('Invalid node type: ' + type);
|
|
214
|
+
}
|
|
215
|
+
const node = nodeType.createAndFill(attrs, this.current.content, marks);
|
|
216
|
+
if (!node) {
|
|
217
|
+
throw new Error('Error creating node: ' + type);
|
|
218
|
+
}
|
|
219
|
+
return node;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
createText(text: string) {
|
|
223
|
+
if (!text) return;
|
|
224
|
+
return this.schema.text(text, this.current.marks);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public styleToMarks(style: Style): Mark[] {
|
|
228
|
+
const marks: Mark[] = [];
|
|
229
|
+
const textProperties = style && style['text-properties'] || {};
|
|
230
|
+
|
|
231
|
+
if (COURIER_FONTS.indexOf(textProperties['@font-name'] || '') > -1) {
|
|
232
|
+
const markType = this.schema.mark('code');
|
|
233
|
+
marks.push(markType);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (textProperties['@font-style'] === 'italic') {
|
|
237
|
+
const markType = this.schema.mark('em');
|
|
238
|
+
marks.push(markType);
|
|
239
|
+
}
|
|
240
|
+
if (textProperties['@font-weight'] === 'bold') {
|
|
241
|
+
const markType = this.schema.mark('strong');
|
|
242
|
+
marks.push(markType);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return marks;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public handle(nodeType: string, value: any) {
|
|
249
|
+
if (!this.current.handlers[nodeType]) {
|
|
250
|
+
throw new Error('No handler for node: ' + nodeType);
|
|
251
|
+
}
|
|
252
|
+
if ('function' !== typeof this.current.handlers[nodeType]) {
|
|
253
|
+
throw new Error('Invalid handler for node: ' + nodeType);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.current.handlers[nodeType](this, value);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public getElementStyle(element: any): Style {
|
|
260
|
+
const style = ('object' === typeof element && element['@style-name'])
|
|
261
|
+
? resolveStyle(
|
|
262
|
+
this.stylesTree,
|
|
263
|
+
this.automaticStyles,
|
|
264
|
+
element['@style-name'],
|
|
265
|
+
)
|
|
266
|
+
: {};
|
|
267
|
+
|
|
268
|
+
return style;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
get current() {
|
|
272
|
+
return this.currentCtx;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export class OdtParser {
|
|
277
|
+
constructor(
|
|
278
|
+
private readonly schema: Schema,
|
|
279
|
+
private readonly config: OdtParserConfig = {},
|
|
280
|
+
) {}
|
|
281
|
+
|
|
282
|
+
public filesMap?: Record<string, Uint8Array<ArrayBufferLike>>;
|
|
283
|
+
|
|
284
|
+
parse(files: any) {
|
|
285
|
+
const contentTree = files.contentTree;
|
|
286
|
+
const stylesTree = files.stylesTree;
|
|
287
|
+
|
|
288
|
+
const handlers: Record<string, NodeHandler> = {
|
|
289
|
+
...getInlineNodesHandlers(),
|
|
290
|
+
...getBasicNodesHandlers(),
|
|
291
|
+
...getListNodesHandlers(),
|
|
292
|
+
...getTableNodesHandlers(),
|
|
293
|
+
|
|
294
|
+
'change-start': {
|
|
295
|
+
// custom(state) {
|
|
296
|
+
// state.textMarks.add({
|
|
297
|
+
// markName: 'change',
|
|
298
|
+
// markAttributes: {},
|
|
299
|
+
// });
|
|
300
|
+
// },
|
|
301
|
+
},
|
|
302
|
+
'change-end': {
|
|
303
|
+
// custom(state) {
|
|
304
|
+
// state.textMarks.forEach((x) =>
|
|
305
|
+
// x.markName === 'change' ? state.textMarks.delete(x) : x
|
|
306
|
+
// );
|
|
307
|
+
// },
|
|
308
|
+
},
|
|
309
|
+
'frame': (ctx: OdtStashContext, odtElement: any) => {
|
|
310
|
+
if (odtElement.object && odtElement.object['@href']) {
|
|
311
|
+
const fullPath = odtElement.object['@href'].replace(/^\.\//, '') +
|
|
312
|
+
'/content.xml';
|
|
313
|
+
if (files[fullPath]) {
|
|
314
|
+
const content = new TextDecoder().decode(files[fullPath]);
|
|
315
|
+
ctx.openNode();
|
|
316
|
+
ctx.closeNode('math', {
|
|
317
|
+
lang: 'mathml',
|
|
318
|
+
content,
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (odtElement.image && odtElement.image['@href']) { // TODO links rewrite
|
|
324
|
+
const alt = odtElement.description?.value || '';
|
|
325
|
+
const src = odtElement.image['@href'];
|
|
326
|
+
ctx.openNode();
|
|
327
|
+
ctx.closeNode('image', {
|
|
328
|
+
src,
|
|
329
|
+
alt,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
'annotation': () => {
|
|
335
|
+
// ignore: true,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const ctx = new OdtStashContext(
|
|
340
|
+
this.schema,
|
|
341
|
+
handlers,
|
|
342
|
+
stylesTree,
|
|
343
|
+
contentTree['automatic-styles'],
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
ctx.openNode();
|
|
347
|
+
ctx.handle('body', contentTree.body);
|
|
348
|
+
return ctx.closeNode('doc');
|
|
349
|
+
}
|
|
350
|
+
}
|
package/src/lists.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OdtElement } from './OdtParser.js';
|
|
2
|
+
|
|
3
|
+
export class ListNumbering {
|
|
4
|
+
levels: { [level: number]: number } = {};
|
|
5
|
+
levelNodes: { [level: number]: Node } = {};
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
for (let i = 0; i < 20; i++) {
|
|
9
|
+
this.levels[i] = 1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
clearAbove(level: number) {
|
|
14
|
+
for (let i = level + 1; i < 20; i++) {
|
|
15
|
+
this.levels[i] = 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setLevelNode(level: number, node: Node) {
|
|
20
|
+
this.levelNodes[level] = node;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface List {
|
|
25
|
+
level: number;
|
|
26
|
+
odtElement: OdtElement;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ListTracker {
|
|
30
|
+
listStack: List[] = [];
|
|
31
|
+
|
|
32
|
+
listNumberings: Map<string, ListNumbering> = new Map<string, ListNumbering>();
|
|
33
|
+
lastNumbering?: ListNumbering;
|
|
34
|
+
preserveMinLevel = 999;
|
|
35
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { iterateChildren, NodeHandler, OdtStashContext } from '../OdtParser.js';
|
|
2
|
+
|
|
3
|
+
export function getInlineNodesHandlers(): Record<string, NodeHandler> {
|
|
4
|
+
return {
|
|
5
|
+
'$text': (ctx: OdtStashContext, value: any) => {
|
|
6
|
+
const node = ctx.createText(value);
|
|
7
|
+
if (node) {
|
|
8
|
+
ctx.current.content.push(node);
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
's': (ctx: OdtStashContext, odtElement: any) => {
|
|
12
|
+
const chars = odtElement['@c'] || 1;
|
|
13
|
+
const text = ' '.repeat(chars);
|
|
14
|
+
const node = ctx.createText(text);
|
|
15
|
+
if (node) {
|
|
16
|
+
ctx.current.content.push(node);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
'tab': (ctx: OdtStashContext, odtElement: any) => {
|
|
20
|
+
const node = ctx.createText('\t');
|
|
21
|
+
if (node) {
|
|
22
|
+
ctx.current.content.push(node);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
'rect': (ctx: OdtStashContext, odtElement: any) => {
|
|
26
|
+
// if (odtElement['@rel-width'] === '100%') {
|
|
27
|
+
// ctx.openNode();
|
|
28
|
+
// ctx.closeNode('hr');
|
|
29
|
+
// }
|
|
30
|
+
},
|
|
31
|
+
'line-break': (ctx: OdtStashContext, odtElement: any) => {
|
|
32
|
+
ctx.openNode();
|
|
33
|
+
ctx.closeNode('br');
|
|
34
|
+
},
|
|
35
|
+
'soft-page-break': (ctx: OdtStashContext, odtElement: any) => {
|
|
36
|
+
ctx.openNode();
|
|
37
|
+
ctx.closeNode('br');
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
'bookmark': (ctx: OdtStashContext, element: any) => { // bookmark for parent para
|
|
41
|
+
ctx.openNode();
|
|
42
|
+
ctx.closeNode('bookmark-node', {
|
|
43
|
+
id: element['@name'],
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
'bookmark-start': (ctx: OdtStashContext, element: any) => {
|
|
47
|
+
// state.textMarks.add({
|
|
48
|
+
// markName: 'bookmark',
|
|
49
|
+
// markAttributes: {
|
|
50
|
+
// id: element['@name'],
|
|
51
|
+
// },
|
|
52
|
+
// });
|
|
53
|
+
},
|
|
54
|
+
'bookmark-end': (ctx: OdtStashContext, element: any) => {
|
|
55
|
+
// state.textMarks.forEach((x) =>
|
|
56
|
+
// x.markName === 'bookmark' &&
|
|
57
|
+
// x.markAttributes.id === element['@name']
|
|
58
|
+
// ? state.textMarks.delete(x)
|
|
59
|
+
// : x
|
|
60
|
+
// );
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getBasicNodesHandlers(): Record<string, NodeHandler> {
|
|
66
|
+
return {
|
|
67
|
+
'body': (ctx: OdtStashContext, value: any) => {
|
|
68
|
+
ctx.handle('text', value.text);
|
|
69
|
+
},
|
|
70
|
+
'text': (ctx: OdtStashContext, value: any) => {
|
|
71
|
+
iterateChildren(
|
|
72
|
+
value.$value,
|
|
73
|
+
(child) => ctx.handle(child.tag, child.value),
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
'p': (ctx: OdtStashContext, value: any) => {
|
|
77
|
+
const attrs = {};
|
|
78
|
+
|
|
79
|
+
const style = ctx.getElementStyle(value);
|
|
80
|
+
const heading = style.styles.find((item) =>
|
|
81
|
+
item.startsWith('Heading_20_')
|
|
82
|
+
);
|
|
83
|
+
if (heading) {
|
|
84
|
+
attrs.level = parseInt(heading.substring('Heading_20_'.length));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let nodeType = 'paragraph';
|
|
88
|
+
if (style.styles.find((item) => item.startsWith('Heading_20_'))) {
|
|
89
|
+
nodeType = 'heading';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
ctx.openNode();
|
|
93
|
+
const marks = ctx.styleToMarks(style);
|
|
94
|
+
if (marks.length > 0) {
|
|
95
|
+
ctx.current.marks = [...ctx.current.marks, ...marks];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
iterateChildren(value.$value, (child) => {
|
|
99
|
+
ctx.handle(child.tag, child.value);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
ctx.closeNode(nodeType, attrs);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
'table-of-content': (ctx: OdtStashContext, value: any) => {
|
|
106
|
+
ctx.openNode();
|
|
107
|
+
for (const pElem of value['index-body']['p']) {
|
|
108
|
+
ctx.openNode();
|
|
109
|
+
ctx.handle('p', pElem);
|
|
110
|
+
ctx.closeNode('list_item');
|
|
111
|
+
}
|
|
112
|
+
ctx.closeNode('bullet_list');
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
'span': (ctx: OdtStashContext, value: any) => {
|
|
116
|
+
const style = ctx.getElementStyle(value);
|
|
117
|
+
|
|
118
|
+
const marks = ctx.styleToMarks(style);
|
|
119
|
+
if (marks.length > 0) {
|
|
120
|
+
ctx.stash();
|
|
121
|
+
ctx.current.marks = [...ctx.current.marks, ...marks];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
iterateChildren(value.$value, (child) => {
|
|
125
|
+
ctx.handle(child.tag, child.value);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (marks.length > 0) {
|
|
129
|
+
ctx.unstash();
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
'a': (ctx: OdtStashContext, value: any) => {
|
|
133
|
+
const attrs = {
|
|
134
|
+
href: value['@href'],
|
|
135
|
+
// title: tok.attrGet('title') || null,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const markType = ctx.schema.mark('link', attrs);
|
|
139
|
+
|
|
140
|
+
const marks = [markType];
|
|
141
|
+
if (marks.length > 0) {
|
|
142
|
+
ctx.stash();
|
|
143
|
+
ctx.current.marks = [...ctx.current.marks, ...marks];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
iterateChildren(value.$value, (child) => {
|
|
147
|
+
ctx.handle(child.tag, child.value);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (marks.length > 0) {
|
|
151
|
+
ctx.unstash();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ListNumbering } from '../lists.js';
|
|
2
|
+
import {
|
|
3
|
+
iterateChildren,
|
|
4
|
+
NodeHandler,
|
|
5
|
+
OdtStashContext,
|
|
6
|
+
resolveStyle,
|
|
7
|
+
} from '../OdtParser.js';
|
|
8
|
+
|
|
9
|
+
export function getListNodesHandlers(): Record<string, NodeHandler> {
|
|
10
|
+
return {
|
|
11
|
+
'list': (ctx: OdtStashContext, odtElement: any) => {
|
|
12
|
+
const listTracker = ctx.listTracker;
|
|
13
|
+
const list = {
|
|
14
|
+
level: listTracker.listStack.length + 1,
|
|
15
|
+
odtElement,
|
|
16
|
+
};
|
|
17
|
+
listTracker.listStack.push(list);
|
|
18
|
+
|
|
19
|
+
let style = {};
|
|
20
|
+
let listId = null;
|
|
21
|
+
for (let i = listTracker.listStack.length - 1; i >= 0; i--) {
|
|
22
|
+
const element = listTracker.listStack[i].odtElement;
|
|
23
|
+
if (!listId) {
|
|
24
|
+
if (element['@id']) {
|
|
25
|
+
listId = element['@id'];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!style['@style-name']) {
|
|
29
|
+
style = ('object' === typeof element && element['@style-name'])
|
|
30
|
+
? resolveStyle(
|
|
31
|
+
ctx.stylesTree,
|
|
32
|
+
ctx.automaticStyles,
|
|
33
|
+
element['@style-name'],
|
|
34
|
+
)
|
|
35
|
+
: {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let nodeTypeName = 'bullet_list';
|
|
40
|
+
const attrs = {};
|
|
41
|
+
if (style) {
|
|
42
|
+
const numLevelStyle = style['list-level-style-number'].find(
|
|
43
|
+
(levelStyle) => parseInt(levelStyle['@level']) === list.level,
|
|
44
|
+
);
|
|
45
|
+
if (numLevelStyle) {
|
|
46
|
+
attrs['type'] = numLevelStyle['@num-format'] || '1';
|
|
47
|
+
nodeTypeName = 'ordered_list';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ctx.current.meta['list_type'] = nodeTypeName;
|
|
52
|
+
|
|
53
|
+
let listNumbering = null;
|
|
54
|
+
|
|
55
|
+
if (listId && listTracker.listNumberings.has(listId)) {
|
|
56
|
+
listNumbering = listTracker.listNumberings.get(listId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let isContinue = false;
|
|
60
|
+
if (
|
|
61
|
+
odtElement['@continue-list'] &&
|
|
62
|
+
listTracker.listNumberings.has(odtElement['@continue-list'])
|
|
63
|
+
) {
|
|
64
|
+
listNumbering = listTracker.listNumberings.get(
|
|
65
|
+
odtElement['@continue-list'],
|
|
66
|
+
);
|
|
67
|
+
isContinue = true;
|
|
68
|
+
}
|
|
69
|
+
if (odtElement['@continue-numbering']) {
|
|
70
|
+
listNumbering = listTracker.lastNumbering;
|
|
71
|
+
isContinue = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!listNumbering) {
|
|
75
|
+
listNumbering = new ListNumbering();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isContinue) {
|
|
79
|
+
listTracker.preserveMinLevel = 999;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (listId) {
|
|
83
|
+
listTracker.listNumberings.set(listId, listNumbering);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
listTracker.lastNumbering = listNumbering;
|
|
87
|
+
|
|
88
|
+
if (listTracker.preserveMinLevel <= list.level) {
|
|
89
|
+
listNumbering.clearAbove(list.level - 1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (nodeTypeName === 'ordered_list') {
|
|
93
|
+
attrs['start'] = listNumbering.levels[list.level] || 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ctx.openNode();
|
|
97
|
+
|
|
98
|
+
const children = odtElement['list-item'].map((item) => ({
|
|
99
|
+
'list-item': item,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
if (children) {
|
|
103
|
+
iterateChildren(children, (child) => {
|
|
104
|
+
ctx.handle(child.tag, child.value);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
listNumbering.levels[list.level] += children.length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ctx.closeNode(nodeTypeName, attrs);
|
|
111
|
+
|
|
112
|
+
if (listTracker.preserveMinLevel >= list.level) {
|
|
113
|
+
listTracker.preserveMinLevel = list.level;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
listTracker.listStack.pop();
|
|
117
|
+
},
|
|
118
|
+
'list-item': (ctx: OdtStashContext, odtElement: any) => {
|
|
119
|
+
ctx.openNode();
|
|
120
|
+
iterateChildren(
|
|
121
|
+
odtElement.$value,
|
|
122
|
+
(child) => ctx.handle(child.tag, child.value),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const attrs = {};
|
|
126
|
+
attrs.markup = '* ';
|
|
127
|
+
|
|
128
|
+
ctx.closeNode('list_item', attrs);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { iterateChildren, NodeHandler, OdtStashContext } from '../OdtParser.js';
|
|
2
|
+
|
|
3
|
+
export function getTableNodesHandlers(): Record<string, NodeHandler> {
|
|
4
|
+
return {
|
|
5
|
+
'table': (ctx: OdtStashContext, value: any) => {
|
|
6
|
+
ctx.openNode();
|
|
7
|
+
for (const item of value['table-row']) {
|
|
8
|
+
ctx.handle('table-row', item);
|
|
9
|
+
}
|
|
10
|
+
ctx.closeNode('table');
|
|
11
|
+
},
|
|
12
|
+
'table-row': (ctx: OdtStashContext, value: any) => {
|
|
13
|
+
ctx.openNode();
|
|
14
|
+
for (const item of value['table-cell']) {
|
|
15
|
+
ctx.handle('table-cell', item);
|
|
16
|
+
}
|
|
17
|
+
ctx.closeNode('table_row');
|
|
18
|
+
},
|
|
19
|
+
'table-cell': (ctx: OdtStashContext, value: any) => {
|
|
20
|
+
ctx.openNode();
|
|
21
|
+
iterateChildren(
|
|
22
|
+
value.$value,
|
|
23
|
+
(child) => ctx.handle(child.tag, child.value),
|
|
24
|
+
);
|
|
25
|
+
ctx.closeNode('table_cell');
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|