@bendyline/squisq-formats 0.1.0
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/dist/chunk-532L4D5D.js +21 -0
- package/dist/chunk-532L4D5D.js.map +1 -0
- package/dist/chunk-67KIJHV2.js +21 -0
- package/dist/chunk-67KIJHV2.js.map +1 -0
- package/dist/chunk-KAK4V57E.js +387 -0
- package/dist/chunk-KAK4V57E.js.map +1 -0
- package/dist/chunk-MQHCXI56.js +1035 -0
- package/dist/chunk-MQHCXI56.js.map +1 -0
- package/dist/chunk-TBPD5PCU.js +1136 -0
- package/dist/chunk-TBPD5PCU.js.map +1 -0
- package/dist/chunk-ULLIPBEJ.js +271 -0
- package/dist/chunk-ULLIPBEJ.js.map +1 -0
- package/dist/docx/index.d.ts +108 -0
- package/dist/docx/index.js +14 -0
- package/dist/docx/index.js.map +1 -0
- package/dist/html/index.d.ts +166 -0
- package/dist/html/index.js +17 -0
- package/dist/html/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/ooxml/index.d.ts +351 -0
- package/dist/ooxml/index.js +99 -0
- package/dist/ooxml/index.js.map +1 -0
- package/dist/pdf/index.d.ts +130 -0
- package/dist/pdf/index.js +15 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pptx/index.d.ts +58 -0
- package/dist/pptx/index.js +13 -0
- package/dist/pptx/index.js.map +1 -0
- package/dist/xlsx/index.d.ts +58 -0
- package/dist/xlsx/index.js +13 -0
- package/dist/xlsx/index.js.map +1 -0
- package/package.json +85 -0
- package/src/__tests__/docxExport.test.ts +457 -0
- package/src/__tests__/docxImport.test.ts +410 -0
- package/src/__tests__/html.test.ts +295 -0
- package/src/__tests__/ooxml.test.ts +197 -0
- package/src/__tests__/pdfExport.test.ts +322 -0
- package/src/__tests__/pdfImport.test.ts +290 -0
- package/src/__tests__/roundTrip.test.ts +201 -0
- package/src/docx/export.ts +978 -0
- package/src/docx/import.ts +909 -0
- package/src/docx/index.ts +26 -0
- package/src/docx/styles.ts +145 -0
- package/src/html/htmlTemplate.ts +307 -0
- package/src/html/imageUtils.ts +66 -0
- package/src/html/index.ts +168 -0
- package/src/index.ts +51 -0
- package/src/ooxml/index.ts +87 -0
- package/src/ooxml/namespaces.ts +160 -0
- package/src/ooxml/reader.ts +218 -0
- package/src/ooxml/types.ts +104 -0
- package/src/ooxml/writer.ts +321 -0
- package/src/ooxml/xmlUtils.ts +123 -0
- package/src/pdf/export.ts +1029 -0
- package/src/pdf/import.ts +835 -0
- package/src/pdf/index.ts +29 -0
- package/src/pdf/styles.ts +180 -0
- package/src/pptx/index.ts +78 -0
- package/src/xlsx/index.ts +78 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DOCX import: docxToMarkdownDoc, docxToDoc.
|
|
3
|
+
*
|
|
4
|
+
* Builds minimal .docx test fixtures programmatically using our own
|
|
5
|
+
* OOXML writer (dogfooding the shared layer), then imports them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { createPackage } from '../ooxml/writer';
|
|
10
|
+
import {
|
|
11
|
+
NS_WML,
|
|
12
|
+
NS_R,
|
|
13
|
+
REL_OFFICE_DOCUMENT,
|
|
14
|
+
REL_STYLES,
|
|
15
|
+
REL_NUMBERING,
|
|
16
|
+
REL_HYPERLINK,
|
|
17
|
+
CONTENT_TYPE_DOCX_DOCUMENT,
|
|
18
|
+
CONTENT_TYPE_DOCX_STYLES,
|
|
19
|
+
CONTENT_TYPE_DOCX_NUMBERING,
|
|
20
|
+
} from '../ooxml/namespaces';
|
|
21
|
+
import { xmlDeclaration } from '../ooxml/xmlUtils';
|
|
22
|
+
import { docxToMarkdownDoc, docxToDoc } from '../docx/import';
|
|
23
|
+
import type {
|
|
24
|
+
MarkdownHeading,
|
|
25
|
+
MarkdownParagraph,
|
|
26
|
+
MarkdownList,
|
|
27
|
+
MarkdownTable,
|
|
28
|
+
MarkdownText,
|
|
29
|
+
MarkdownStrong,
|
|
30
|
+
MarkdownEmphasis,
|
|
31
|
+
MarkdownStrikethrough,
|
|
32
|
+
MarkdownLink,
|
|
33
|
+
MarkdownInlineNode,
|
|
34
|
+
} from '@bendyline/squisq/markdown';
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// Test Fixture Builder
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
interface DocxFixtureOptions {
|
|
41
|
+
bodyXml: string;
|
|
42
|
+
stylesXml?: string;
|
|
43
|
+
numberingXml?: string;
|
|
44
|
+
documentRels?: Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
type: string;
|
|
47
|
+
target: string;
|
|
48
|
+
targetMode?: 'External';
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function buildTestDocx(options: DocxFixtureOptions): Promise<ArrayBuffer> {
|
|
53
|
+
const pkg = createPackage();
|
|
54
|
+
|
|
55
|
+
// document.xml
|
|
56
|
+
const documentXml =
|
|
57
|
+
xmlDeclaration() +
|
|
58
|
+
`<w:document xmlns:w="${NS_WML}" xmlns:r="${NS_R}">` +
|
|
59
|
+
`<w:body>${options.bodyXml}</w:body>` +
|
|
60
|
+
`</w:document>`;
|
|
61
|
+
pkg.addPart('word/document.xml', documentXml, CONTENT_TYPE_DOCX_DOCUMENT);
|
|
62
|
+
|
|
63
|
+
// styles.xml
|
|
64
|
+
if (options.stylesXml) {
|
|
65
|
+
pkg.addPart('word/styles.xml', options.stylesXml, CONTENT_TYPE_DOCX_STYLES);
|
|
66
|
+
} else {
|
|
67
|
+
// Minimal default styles
|
|
68
|
+
const defaultStyles =
|
|
69
|
+
xmlDeclaration() +
|
|
70
|
+
`<w:styles xmlns:w="${NS_WML}">` +
|
|
71
|
+
`<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/></w:style>` +
|
|
72
|
+
`<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/></w:style>` +
|
|
73
|
+
`<w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/></w:style>` +
|
|
74
|
+
`<w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/></w:style>` +
|
|
75
|
+
`</w:styles>`;
|
|
76
|
+
pkg.addPart('word/styles.xml', defaultStyles, CONTENT_TYPE_DOCX_STYLES);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// numbering.xml
|
|
80
|
+
if (options.numberingXml) {
|
|
81
|
+
pkg.addPart('word/numbering.xml', options.numberingXml, CONTENT_TYPE_DOCX_NUMBERING);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Root relationship
|
|
85
|
+
pkg.addRelationship('', {
|
|
86
|
+
id: 'rId1',
|
|
87
|
+
type: REL_OFFICE_DOCUMENT,
|
|
88
|
+
target: 'word/document.xml',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Document relationships
|
|
92
|
+
pkg.addRelationship('word/document.xml', {
|
|
93
|
+
id: 'rIdStyles',
|
|
94
|
+
type: REL_STYLES,
|
|
95
|
+
target: 'styles.xml',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (options.numberingXml) {
|
|
99
|
+
pkg.addRelationship('word/document.xml', {
|
|
100
|
+
id: 'rIdNumbering',
|
|
101
|
+
type: REL_NUMBERING,
|
|
102
|
+
target: 'numbering.xml',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (options.documentRels) {
|
|
107
|
+
for (const rel of options.documentRels) {
|
|
108
|
+
pkg.addRelationship('word/document.xml', {
|
|
109
|
+
id: rel.id,
|
|
110
|
+
type: rel.type,
|
|
111
|
+
target: rel.target,
|
|
112
|
+
...(rel.targetMode ? { targetMode: rel.targetMode } : {}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return pkg.toArrayBuffer();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================
|
|
121
|
+
// Headings
|
|
122
|
+
// ============================================
|
|
123
|
+
|
|
124
|
+
describe('docxToMarkdownDoc', () => {
|
|
125
|
+
it('imports headings based on paragraph style', async () => {
|
|
126
|
+
const data = await buildTestDocx({
|
|
127
|
+
bodyXml:
|
|
128
|
+
`<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Title</w:t></w:r></w:p>` +
|
|
129
|
+
`<w:p><w:pPr><w:pStyle w:val="Heading2"/></w:pPr><w:r><w:t>Subtitle</w:t></w:r></w:p>`,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const doc = await docxToMarkdownDoc(data);
|
|
133
|
+
expect(doc.type).toBe('document');
|
|
134
|
+
expect(doc.children.length).toBe(2);
|
|
135
|
+
|
|
136
|
+
expect(doc.children[0].type).toBe('heading');
|
|
137
|
+
const h1 = doc.children[0] as MarkdownHeading;
|
|
138
|
+
expect(h1.depth).toBe(1);
|
|
139
|
+
expect(h1.children[0].type).toBe('text');
|
|
140
|
+
expect((h1.children[0] as MarkdownText).value).toBe('Title');
|
|
141
|
+
|
|
142
|
+
expect(doc.children[1].type).toBe('heading');
|
|
143
|
+
const h2 = doc.children[1] as MarkdownHeading;
|
|
144
|
+
expect(h2.depth).toBe(2);
|
|
145
|
+
expect((h2.children[0] as MarkdownText).value).toBe('Subtitle');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ============================================
|
|
149
|
+
// Paragraphs
|
|
150
|
+
// ============================================
|
|
151
|
+
|
|
152
|
+
it('imports plain paragraphs', async () => {
|
|
153
|
+
const data = await buildTestDocx({
|
|
154
|
+
bodyXml:
|
|
155
|
+
`<w:p><w:r><w:t>Hello world</w:t></w:r></w:p>` +
|
|
156
|
+
`<w:p><w:r><w:t>Second paragraph</w:t></w:r></w:p>`,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const doc = await docxToMarkdownDoc(data);
|
|
160
|
+
expect(doc.children.length).toBe(2);
|
|
161
|
+
expect(doc.children[0].type).toBe('paragraph');
|
|
162
|
+
expect(((doc.children[0] as MarkdownParagraph).children[0] as MarkdownText).value).toBe(
|
|
163
|
+
'Hello world',
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('skips empty paragraphs', async () => {
|
|
168
|
+
const data = await buildTestDocx({
|
|
169
|
+
bodyXml: `<w:p><w:pPr/></w:p><w:p><w:r><w:t>Text</w:t></w:r></w:p>`,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const doc = await docxToMarkdownDoc(data);
|
|
173
|
+
expect(doc.children.length).toBe(1);
|
|
174
|
+
expect(((doc.children[0] as MarkdownParagraph).children[0] as MarkdownText).value).toBe('Text');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// Inline Formatting
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
it('imports bold text', async () => {
|
|
182
|
+
const data = await buildTestDocx({
|
|
183
|
+
bodyXml: `<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>bold</w:t></w:r></w:p>`,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const doc = await docxToMarkdownDoc(data);
|
|
187
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
188
|
+
expect(para.children[0].type).toBe('strong');
|
|
189
|
+
const strong = para.children[0] as MarkdownStrong;
|
|
190
|
+
expect(strong.children[0].type).toBe('text');
|
|
191
|
+
expect((strong.children[0] as MarkdownText).value).toBe('bold');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('imports italic text', async () => {
|
|
195
|
+
const data = await buildTestDocx({
|
|
196
|
+
bodyXml: `<w:p><w:r><w:rPr><w:i/></w:rPr><w:t>italic</w:t></w:r></w:p>`,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const doc = await docxToMarkdownDoc(data);
|
|
200
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
201
|
+
expect(para.children[0].type).toBe('emphasis');
|
|
202
|
+
const em = para.children[0] as MarkdownEmphasis;
|
|
203
|
+
expect((em.children[0] as MarkdownText).value).toBe('italic');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('imports strikethrough text', async () => {
|
|
207
|
+
const data = await buildTestDocx({
|
|
208
|
+
bodyXml: `<w:p><w:r><w:rPr><w:strike/></w:rPr><w:t>struck</w:t></w:r></w:p>`,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const doc = await docxToMarkdownDoc(data);
|
|
212
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
213
|
+
const del = para.children[0] as MarkdownStrikethrough;
|
|
214
|
+
expect(del.type).toBe('delete');
|
|
215
|
+
expect((del.children[0] as MarkdownText).value).toBe('struck');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('imports bold+italic combined formatting', async () => {
|
|
219
|
+
const data = await buildTestDocx({
|
|
220
|
+
bodyXml: `<w:p><w:r><w:rPr><w:b/><w:i/></w:rPr><w:t>bold italic</w:t></w:r></w:p>`,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const doc = await docxToMarkdownDoc(data);
|
|
224
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
225
|
+
// Should be strong > emphasis > text (bold wraps italic wraps text)
|
|
226
|
+
const strong = para.children[0] as MarkdownStrong;
|
|
227
|
+
expect(strong.type).toBe('strong');
|
|
228
|
+
const em = strong.children[0] as MarkdownEmphasis;
|
|
229
|
+
expect(em.type).toBe('emphasis');
|
|
230
|
+
expect((em.children[0] as MarkdownText).value).toBe('bold italic');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('merges adjacent plain text runs', async () => {
|
|
234
|
+
const data = await buildTestDocx({
|
|
235
|
+
bodyXml: `<w:p>` + `<w:r><w:t>Hello </w:t></w:r>` + `<w:r><w:t>world</w:t></w:r>` + `</w:p>`,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const doc = await docxToMarkdownDoc(data);
|
|
239
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
240
|
+
expect(para.children.length).toBe(1);
|
|
241
|
+
expect(para.children[0].type).toBe('text');
|
|
242
|
+
expect((para.children[0] as MarkdownText).value).toBe('Hello world');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// ============================================
|
|
246
|
+
// Hyperlinks
|
|
247
|
+
// ============================================
|
|
248
|
+
|
|
249
|
+
it('imports hyperlinks', async () => {
|
|
250
|
+
const data = await buildTestDocx({
|
|
251
|
+
bodyXml:
|
|
252
|
+
`<w:p>` +
|
|
253
|
+
`<w:hyperlink r:id="rId10">` +
|
|
254
|
+
`<w:r><w:t>click me</w:t></w:r>` +
|
|
255
|
+
`</w:hyperlink>` +
|
|
256
|
+
`</w:p>`,
|
|
257
|
+
documentRels: [
|
|
258
|
+
{
|
|
259
|
+
id: 'rId10',
|
|
260
|
+
type: REL_HYPERLINK,
|
|
261
|
+
target: 'https://example.com',
|
|
262
|
+
targetMode: 'External',
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const doc = await docxToMarkdownDoc(data);
|
|
268
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
269
|
+
const link = para.children[0] as MarkdownLink;
|
|
270
|
+
expect(link.type).toBe('link');
|
|
271
|
+
expect(link.url).toBe('https://example.com');
|
|
272
|
+
expect(link.children[0].type).toBe('text');
|
|
273
|
+
expect((link.children[0] as MarkdownText).value).toBe('click me');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ============================================
|
|
277
|
+
// Lists
|
|
278
|
+
// ============================================
|
|
279
|
+
|
|
280
|
+
it('imports bullet lists', async () => {
|
|
281
|
+
const numberingXml =
|
|
282
|
+
xmlDeclaration() +
|
|
283
|
+
`<w:numbering xmlns:w="${NS_WML}">` +
|
|
284
|
+
`<w:abstractNum w:abstractNumId="1">` +
|
|
285
|
+
`<w:lvl w:ilvl="0"><w:numFmt w:val="bullet"/></w:lvl>` +
|
|
286
|
+
`</w:abstractNum>` +
|
|
287
|
+
`<w:num w:numId="1"><w:abstractNumId w:val="1"/></w:num>` +
|
|
288
|
+
`</w:numbering>`;
|
|
289
|
+
|
|
290
|
+
const data = await buildTestDocx({
|
|
291
|
+
bodyXml:
|
|
292
|
+
`<w:p><w:pPr><w:numPr><w:ilvl w:val="0"/><w:numId w:val="1"/></w:numPr></w:pPr>` +
|
|
293
|
+
`<w:r><w:t>Item A</w:t></w:r></w:p>` +
|
|
294
|
+
`<w:p><w:pPr><w:numPr><w:ilvl w:val="0"/><w:numId w:val="1"/></w:numPr></w:pPr>` +
|
|
295
|
+
`<w:r><w:t>Item B</w:t></w:r></w:p>`,
|
|
296
|
+
numberingXml,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const doc = await docxToMarkdownDoc(data);
|
|
300
|
+
expect(doc.children[0].type).toBe('list');
|
|
301
|
+
const list = doc.children[0] as MarkdownList;
|
|
302
|
+
expect(list.ordered).toBe(false);
|
|
303
|
+
expect(list.children.length).toBe(2);
|
|
304
|
+
expect(list.children[0].type).toBe('listItem');
|
|
305
|
+
const listPara = list.children[0].children[0] as MarkdownParagraph;
|
|
306
|
+
expect((listPara.children[0] as MarkdownText).value).toBe('Item A');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('imports ordered lists', async () => {
|
|
310
|
+
const numberingXml =
|
|
311
|
+
xmlDeclaration() +
|
|
312
|
+
`<w:numbering xmlns:w="${NS_WML}">` +
|
|
313
|
+
`<w:abstractNum w:abstractNumId="2">` +
|
|
314
|
+
`<w:lvl w:ilvl="0"><w:numFmt w:val="decimal"/></w:lvl>` +
|
|
315
|
+
`</w:abstractNum>` +
|
|
316
|
+
`<w:num w:numId="2"><w:abstractNumId w:val="2"/></w:num>` +
|
|
317
|
+
`</w:numbering>`;
|
|
318
|
+
|
|
319
|
+
const data = await buildTestDocx({
|
|
320
|
+
bodyXml:
|
|
321
|
+
`<w:p><w:pPr><w:numPr><w:ilvl w:val="0"/><w:numId w:val="2"/></w:numPr></w:pPr>` +
|
|
322
|
+
`<w:r><w:t>First</w:t></w:r></w:p>` +
|
|
323
|
+
`<w:p><w:pPr><w:numPr><w:ilvl w:val="0"/><w:numId w:val="2"/></w:numPr></w:pPr>` +
|
|
324
|
+
`<w:r><w:t>Second</w:t></w:r></w:p>`,
|
|
325
|
+
numberingXml,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const doc = await docxToMarkdownDoc(data);
|
|
329
|
+
const list = doc.children[0] as MarkdownList;
|
|
330
|
+
expect(list.type).toBe('list');
|
|
331
|
+
expect(list.ordered).toBe(true);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// ============================================
|
|
335
|
+
// Tables
|
|
336
|
+
// ============================================
|
|
337
|
+
|
|
338
|
+
it('imports tables', async () => {
|
|
339
|
+
const data = await buildTestDocx({
|
|
340
|
+
bodyXml:
|
|
341
|
+
`<w:tbl>` +
|
|
342
|
+
`<w:tr>` +
|
|
343
|
+
`<w:tc><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc>` +
|
|
344
|
+
`<w:tc><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc>` +
|
|
345
|
+
`</w:tr>` +
|
|
346
|
+
`<w:tr>` +
|
|
347
|
+
`<w:tc><w:p><w:r><w:t>1</w:t></w:r></w:p></w:tc>` +
|
|
348
|
+
`<w:tc><w:p><w:r><w:t>2</w:t></w:r></w:p></w:tc>` +
|
|
349
|
+
`</w:tr>` +
|
|
350
|
+
`</w:tbl>`,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const doc = await docxToMarkdownDoc(data);
|
|
354
|
+
expect(doc.children[0].type).toBe('table');
|
|
355
|
+
const table = doc.children[0] as MarkdownTable;
|
|
356
|
+
expect(table.children.length).toBe(2);
|
|
357
|
+
expect(table.children[0].children.length).toBe(2);
|
|
358
|
+
const cell00 = table.children[0].children[0];
|
|
359
|
+
const cell01 = table.children[0].children[1];
|
|
360
|
+
const cell10 = table.children[1].children[0];
|
|
361
|
+
expect((cell00.children[0] as MarkdownText).value).toBe('A');
|
|
362
|
+
expect((cell01.children[0] as MarkdownText).value).toBe('B');
|
|
363
|
+
expect((cell10.children[0] as MarkdownText).value).toBe('1');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ============================================
|
|
367
|
+
// Empty Document
|
|
368
|
+
// ============================================
|
|
369
|
+
|
|
370
|
+
it('handles empty document', async () => {
|
|
371
|
+
const data = await buildTestDocx({ bodyXml: '' });
|
|
372
|
+
const doc = await docxToMarkdownDoc(data);
|
|
373
|
+
expect(doc.type).toBe('document');
|
|
374
|
+
expect(doc.children.length).toBe(0);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// ============================================
|
|
378
|
+
// Line Breaks
|
|
379
|
+
// ============================================
|
|
380
|
+
|
|
381
|
+
it('imports line breaks', async () => {
|
|
382
|
+
const data = await buildTestDocx({
|
|
383
|
+
bodyXml: `<w:p><w:r><w:t>Before</w:t></w:r><w:r><w:br/></w:r><w:r><w:t>After</w:t></w:r></w:p>`,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const doc = await docxToMarkdownDoc(data);
|
|
387
|
+
const para = doc.children[0] as MarkdownParagraph;
|
|
388
|
+
expect(para.children.some((c: MarkdownInlineNode) => c.type === 'break')).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// ============================================
|
|
393
|
+
// docxToDoc convenience wrapper
|
|
394
|
+
// ============================================
|
|
395
|
+
|
|
396
|
+
describe('docxToDoc', () => {
|
|
397
|
+
it('converts docx to a squisq Doc', async () => {
|
|
398
|
+
const data = await buildTestDocx({
|
|
399
|
+
bodyXml:
|
|
400
|
+
`<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>Main Title</w:t></w:r></w:p>` +
|
|
401
|
+
`<w:p><w:r><w:t>Some content here.</w:t></w:r></w:p>`,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const doc = await docxToDoc(data);
|
|
405
|
+
expect(doc.articleId).toBeDefined();
|
|
406
|
+
expect(doc.blocks.length).toBeGreaterThan(0);
|
|
407
|
+
// The first block should have the heading
|
|
408
|
+
expect(doc.blocks[0].sourceHeading).toBeDefined();
|
|
409
|
+
});
|
|
410
|
+
});
|