@blocknote/xl-docx-exporter 0.48.0 → 0.49.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.
@@ -1,5 +1,180 @@
1
1
  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
2
  <w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16du">
3
+ <w:docDefaults>
4
+ <w:rPrDefault/>
5
+ <w:pPrDefault/>
6
+ </w:docDefaults>
7
+ <w:style w:type="paragraph" w:styleId="Title">
8
+ <w:name w:val="Title"/>
9
+ <w:basedOn w:val="Normal"/>
10
+ <w:next w:val="Normal"/>
11
+ <w:qFormat/>
12
+ <w:rPr>
13
+ <w:sz w:val="56"/>
14
+ <w:szCs w:val="56"/>
15
+ </w:rPr>
16
+ </w:style>
17
+ <w:style w:type="paragraph" w:styleId="Heading1">
18
+ <w:name w:val="Heading 1"/>
19
+ <w:basedOn w:val="Normal"/>
20
+ <w:next w:val="Normal"/>
21
+ <w:qFormat/>
22
+ <w:rPr>
23
+ <w:color w:val="2E74B5"/>
24
+ <w:sz w:val="32"/>
25
+ <w:szCs w:val="32"/>
26
+ </w:rPr>
27
+ </w:style>
28
+ <w:style w:type="paragraph" w:styleId="Heading2">
29
+ <w:name w:val="Heading 2"/>
30
+ <w:basedOn w:val="Normal"/>
31
+ <w:next w:val="Normal"/>
32
+ <w:qFormat/>
33
+ <w:rPr>
34
+ <w:color w:val="2E74B5"/>
35
+ <w:sz w:val="26"/>
36
+ <w:szCs w:val="26"/>
37
+ </w:rPr>
38
+ </w:style>
39
+ <w:style w:type="paragraph" w:styleId="Heading3">
40
+ <w:name w:val="Heading 3"/>
41
+ <w:basedOn w:val="Normal"/>
42
+ <w:next w:val="Normal"/>
43
+ <w:qFormat/>
44
+ <w:rPr>
45
+ <w:color w:val="1F4D78"/>
46
+ <w:sz w:val="24"/>
47
+ <w:szCs w:val="24"/>
48
+ </w:rPr>
49
+ </w:style>
50
+ <w:style w:type="paragraph" w:styleId="Heading4">
51
+ <w:name w:val="Heading 4"/>
52
+ <w:basedOn w:val="Normal"/>
53
+ <w:next w:val="Normal"/>
54
+ <w:qFormat/>
55
+ <w:rPr>
56
+ <w:i/>
57
+ <w:iCs/>
58
+ <w:color w:val="2E74B5"/>
59
+ </w:rPr>
60
+ </w:style>
61
+ <w:style w:type="paragraph" w:styleId="Heading5">
62
+ <w:name w:val="Heading 5"/>
63
+ <w:basedOn w:val="Normal"/>
64
+ <w:next w:val="Normal"/>
65
+ <w:qFormat/>
66
+ <w:rPr>
67
+ <w:color w:val="2E74B5"/>
68
+ </w:rPr>
69
+ </w:style>
70
+ <w:style w:type="paragraph" w:styleId="Heading6">
71
+ <w:name w:val="Heading 6"/>
72
+ <w:basedOn w:val="Normal"/>
73
+ <w:next w:val="Normal"/>
74
+ <w:qFormat/>
75
+ <w:rPr>
76
+ <w:color w:val="1F4D78"/>
77
+ </w:rPr>
78
+ </w:style>
79
+ <w:style w:type="paragraph" w:styleId="Strong">
80
+ <w:name w:val="Strong"/>
81
+ <w:basedOn w:val="Normal"/>
82
+ <w:next w:val="Normal"/>
83
+ <w:qFormat/>
84
+ <w:rPr>
85
+ <w:b/>
86
+ <w:bCs/>
87
+ </w:rPr>
88
+ </w:style>
89
+ <w:style w:type="paragraph" w:styleId="ListParagraph">
90
+ <w:name w:val="List Paragraph"/>
91
+ <w:basedOn w:val="Normal"/>
92
+ <w:qFormat/>
93
+ </w:style>
94
+ <w:style w:type="character" w:styleId="Hyperlink">
95
+ <w:name w:val="Hyperlink"/>
96
+ <w:basedOn w:val="DefaultParagraphFont"/>
97
+ <w:uiPriority w:val="99"/>
98
+ <w:unhideWhenUsed/>
99
+ <w:rPr>
100
+ <w:color w:val="0563C1"/>
101
+ <w:u w:val="single"/>
102
+ </w:rPr>
103
+ </w:style>
104
+ <w:style w:type="character" w:styleId="FootnoteReference">
105
+ <w:name w:val="footnote reference"/>
106
+ <w:basedOn w:val="DefaultParagraphFont"/>
107
+ <w:uiPriority w:val="99"/>
108
+ <w:semiHidden/>
109
+ <w:unhideWhenUsed/>
110
+ <w:rPr>
111
+ <w:vertAlign w:val="superscript"/>
112
+ </w:rPr>
113
+ </w:style>
114
+ <w:style w:type="paragraph" w:styleId="FootnoteText">
115
+ <w:name w:val="footnote text"/>
116
+ <w:basedOn w:val="Normal"/>
117
+ <w:link w:val="FootnoteTextChar"/>
118
+ <w:uiPriority w:val="99"/>
119
+ <w:semiHidden/>
120
+ <w:unhideWhenUsed/>
121
+ <w:pPr>
122
+ <w:spacing w:after="0" w:line="240" w:lineRule="auto"/>
123
+ </w:pPr>
124
+ <w:rPr>
125
+ <w:sz w:val="20"/>
126
+ <w:szCs w:val="20"/>
127
+ </w:rPr>
128
+ </w:style>
129
+ <w:style w:type="character" w:styleId="FootnoteTextChar">
130
+ <w:name w:val="Footnote Text Char"/>
131
+ <w:basedOn w:val="DefaultParagraphFont"/>
132
+ <w:link w:val="FootnoteText"/>
133
+ <w:uiPriority w:val="99"/>
134
+ <w:semiHidden/>
135
+ <w:unhideWhenUsed/>
136
+ <w:rPr>
137
+ <w:sz w:val="20"/>
138
+ <w:szCs w:val="20"/>
139
+ </w:rPr>
140
+ </w:style>
141
+ <w:style w:type="character" w:styleId="EndnoteReference">
142
+ <w:name w:val="endnote reference"/>
143
+ <w:basedOn w:val="DefaultParagraphFont"/>
144
+ <w:uiPriority w:val="99"/>
145
+ <w:semiHidden/>
146
+ <w:unhideWhenUsed/>
147
+ <w:rPr>
148
+ <w:vertAlign w:val="superscript"/>
149
+ </w:rPr>
150
+ </w:style>
151
+ <w:style w:type="paragraph" w:styleId="EndnoteText">
152
+ <w:name w:val="endnote text"/>
153
+ <w:basedOn w:val="Normal"/>
154
+ <w:link w:val="EndnoteTextChar"/>
155
+ <w:uiPriority w:val="99"/>
156
+ <w:semiHidden/>
157
+ <w:unhideWhenUsed/>
158
+ <w:pPr>
159
+ <w:spacing w:after="0" w:line="240" w:lineRule="auto"/>
160
+ </w:pPr>
161
+ <w:rPr>
162
+ <w:sz w:val="20"/>
163
+ <w:szCs w:val="20"/>
164
+ </w:rPr>
165
+ </w:style>
166
+ <w:style w:type="character" w:styleId="EndnoteTextChar">
167
+ <w:name w:val="Endnote Text Char"/>
168
+ <w:basedOn w:val="DefaultParagraphFont"/>
169
+ <w:link w:val="EndnoteText"/>
170
+ <w:uiPriority w:val="99"/>
171
+ <w:semiHidden/>
172
+ <w:unhideWhenUsed/>
173
+ <w:rPr>
174
+ <w:sz w:val="20"/>
175
+ <w:szCs w:val="20"/>
176
+ </w:rPr>
177
+ </w:style>
3
178
  <w:docDefaults>
4
179
  <w:rPrDefault>
5
180
  <w:rPr>
@@ -217,6 +217,53 @@ describe("exporter", () => {
217
217
  ).toMatchFileSnapshot("__snapshots__/withMultiColumn/styles.xml");
218
218
  },
219
219
  );
220
+
221
+ async function exportAndGetStylesEntries(
222
+ locale?: string,
223
+ ) {
224
+ const exporter = new DOCXExporter(
225
+ BlockNoteSchema.create({
226
+ blockSpecs: {
227
+ ...defaultBlockSpecs,
228
+ pageBreak: createPageBreakBlockSpec(),
229
+ },
230
+ }),
231
+ docxDefaultSchemaMappings,
232
+ );
233
+ const doc = await exporter.toDocxJsDocument(testDocument, {
234
+ sectionOptions: {},
235
+ documentOptions: {},
236
+ ...(locale && { locale }),
237
+ });
238
+
239
+ const blob = await Packer.toBlob(doc);
240
+ const zip = new ZipReader(new BlobReader(blob));
241
+ return zip.getEntries();
242
+ }
243
+
244
+ it(
245
+ "should export a document without w:lang when no locale is provided",
246
+ { timeout: 10000 },
247
+ async () => {
248
+ const entries = await exportAndGetStylesEntries();
249
+
250
+ await expect(
251
+ prettify(await getZIPEntryContent(entries, "word/styles.xml")),
252
+ ).toMatchFileSnapshot("__snapshots__/noLocale/styles.xml");
253
+ },
254
+ );
255
+
256
+ it(
257
+ "should export a document with w:lang when locale is provided",
258
+ { timeout: 10000 },
259
+ async () => {
260
+ const entries = await exportAndGetStylesEntries("fr-FR");
261
+
262
+ await expect(
263
+ prettify(await getZIPEntryContent(entries, "word/styles.xml")),
264
+ ).toMatchFileSnapshot("__snapshots__/withLocale/styles.xml");
265
+ },
266
+ );
220
267
  });
221
268
 
222
269
  function prettify(sourceXml: string) {
@@ -193,14 +193,19 @@ export class DOCXExporter<
193
193
  let externalStyles = (await import("./template/word/styles.xml?raw"))
194
194
  .default;
195
195
 
196
- // Replace the default language in styles.xml with the provided locale.
197
- // If not provided, default to en-US.
198
- const resolvedLocale = (locale && locale.trim()) || "en-US";
199
-
200
- externalStyles = externalStyles.replace(
201
- /(<w:lang\b[^>]*\bw:val=")([^"]+)("[^>]*\/>)/g,
202
- `$1${resolvedLocale}$3`,
203
- );
196
+ // Replace the language in styles.xml with the provided locale, or remove
197
+ // the w:lang element entirely if no locale is provided (per ECMA-376
198
+ // §17.3.2.20: omitting w:lang lets the application auto-detect language).
199
+ const trimmedLocale = locale?.trim();
200
+ if (trimmedLocale) {
201
+ externalStyles = externalStyles.replace(
202
+ /(<w:lang\b[^>]*\bw:val=")([^"]+)("[^>]*\/>)/g,
203
+ (_match, prefix, _oldVal, suffix) =>
204
+ `${prefix}${trimmedLocale}${suffix}`,
205
+ );
206
+ } else {
207
+ externalStyles = externalStyles.replace(/\s*<w:lang\b[^>]*\/>/g, "");
208
+ }
204
209
 
205
210
  const bullets = ["•"]; //, "◦", "▪"]; (these don't look great, just use solid bullet for now)
206
211
  return {
@@ -251,7 +256,7 @@ export class DOCXExporter<
251
256
  }
252
257
 
253
258
  /**
254
- * Convert a document (array of Blocks to a Blob representing a .docx file)
259
+ * Converts blocks to a .docx Blob with optional locale support.
255
260
  */
256
261
  public async toBlob(
257
262
  blocks: Block<B, I, S>[],
@@ -260,7 +265,7 @@ export class DOCXExporter<
260
265
  documentOptions: DocumentOptions;
261
266
  /**
262
267
  * The document locale in OOXML format (e.g. en-US, fr-FR, de-DE).
263
- * If omitted, defaults to en-US.
268
+ * If omitted, no language is set and the consuming application will use its own default.
264
269
  */
265
270
  locale?: string;
266
271
  } = {
@@ -285,7 +290,7 @@ export class DOCXExporter<
285
290
  }
286
291
 
287
292
  /**
288
- * Convert a document (array of Blocks to a docxjs Document)
293
+ * Converts blocks to a docxjs Document with optional locale support.
289
294
  */
290
295
  public async toDocxJsDocument(
291
296
  blocks: Block<B, I, S>[],
@@ -294,7 +299,7 @@ export class DOCXExporter<
294
299
  documentOptions: DocumentOptions;
295
300
  /**
296
301
  * The document locale in OOXML format (e.g. en-US, fr-FR, de-DE).
297
- * If omitted, defaults to en-US.
302
+ * If omitted, no language is set and the consuming application will use its own default.
298
303
  */
299
304
  locale?: string;
300
305
  } = {
@@ -36,26 +36,26 @@ export declare class DOCXExporter<B extends BlockSchema, S extends StyleSchema,
36
36
  protected getFonts(): Promise<DocumentOptions["fonts"]>;
37
37
  protected createDefaultDocumentOptions(locale?: string): Promise<DocumentOptions>;
38
38
  /**
39
- * Convert a document (array of Blocks to a Blob representing a .docx file)
39
+ * Converts blocks to a .docx Blob with optional locale support.
40
40
  */
41
41
  toBlob(blocks: Block<B, I, S>[], options?: {
42
42
  sectionOptions: Omit<ISectionOptions, "children">;
43
43
  documentOptions: DocumentOptions;
44
44
  /**
45
45
  * The document locale in OOXML format (e.g. en-US, fr-FR, de-DE).
46
- * If omitted, defaults to en-US.
46
+ * If omitted, no language is set and the consuming application will use its own default.
47
47
  */
48
48
  locale?: string;
49
49
  }): Promise<Blob>;
50
50
  /**
51
- * Convert a document (array of Blocks to a docxjs Document)
51
+ * Converts blocks to a docxjs Document with optional locale support.
52
52
  */
53
53
  toDocxJsDocument(blocks: Block<B, I, S>[], options?: {
54
54
  sectionOptions: Omit<ISectionOptions, "children">;
55
55
  documentOptions: DocumentOptions;
56
56
  /**
57
57
  * The document locale in OOXML format (e.g. en-US, fr-FR, de-DE).
58
- * If omitted, defaults to en-US.
58
+ * If omitted, no language is set and the consuming application will use its own default.
59
59
  */
60
60
  locale?: string;
61
61
  }): Promise<Document>;