@niicojs/excel 0.3.3 → 0.3.5

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.
@@ -232,6 +232,8 @@ const formatDatePart = (value: Date, token: string, locale: string): string => {
232
232
  return padNumber(value.getMonth() + 1, 2);
233
233
  case 'm':
234
234
  return String(value.getMonth() + 1);
235
+ case 'dddd':
236
+ return value.toLocaleString(locale, { weekday: 'long' });
235
237
  case 'dd':
236
238
  return padNumber(value.getDate(), 2);
237
239
  case 'd':
@@ -285,7 +287,7 @@ const tokenizeDateFormat = (format: string): string[] => {
285
287
  }
286
288
 
287
289
  const lower = format.slice(i).toLowerCase();
288
- const match = ['yyyy', 'yy', 'mmmm', 'mmm', 'mm', 'm', 'dd', 'd', 'hh', 'h', 'ss', 's'].find((t) =>
290
+ const match = ['yyyy', 'yy', 'mmmm', 'mmm', 'mm', 'm', 'dddd', 'dd', 'd', 'hh', 'h', 'ss', 's'].find((t) =>
289
291
  lower.startsWith(t),
290
292
  );
291
293
  if (match) {
@@ -324,22 +326,21 @@ const isDateFormat = (format: string): boolean => {
324
326
  };
325
327
 
326
328
  const formatDate = (value: Date, format: string, locale: string): string => {
329
+ if (locale === 'fr-FR' && format === '[$-F800]dddd\\,\\ mmmm\\ dd\\,\\ yyyy') {
330
+ format = 'dddd, dd mmmm yyyy';
331
+ }
327
332
  const tokens = tokenizeDateFormat(format);
328
333
  return tokens.map((token) => formatDatePart(value, token, locale)).join('');
329
334
  };
330
335
 
331
- export const formatCellValue = (
332
- value: number | Date,
333
- style: CellStyle | undefined,
334
- locale?: string,
335
- ): string | null => {
336
+ export const formatCellValue = (value: number | Date, style: CellStyle | undefined, locale?: string): string | null => {
336
337
  const numberFormat = style?.numberFormat;
337
338
  if (!numberFormat) return null;
338
339
 
339
340
  const normalizedLocale = locale || DEFAULT_LOCALE;
340
341
  const sections = splitFormatSections(numberFormat);
341
342
  const hasNegativeSection = sections.length > 1;
342
- const section = value instanceof Date ? sections[0] : value < 0 ? sections[1] ?? sections[0] : sections[0];
343
+ const section = value instanceof Date ? sections[0] : value < 0 ? (sections[1] ?? sections[0]) : sections[0];
343
344
 
344
345
  if (value instanceof Date && isDateFormat(section)) {
345
346
  return formatDate(value, section, normalizedLocale);
package/src/utils/xml.ts CHANGED
@@ -22,7 +22,7 @@ const builderOptions = {
22
22
  commentPropName: '#comment',
23
23
  cdataPropName: '#cdata',
24
24
  format: false,
25
- suppressEmptyNode: false,
25
+ suppressEmptyNode: true,
26
26
  suppressBooleanAttributes: false,
27
27
  };
28
28
 
package/src/utils/zip.ts CHANGED
@@ -2,12 +2,155 @@ import { unzip, unzipSync, zip, zipSync, strFromU8, strToU8 } from 'fflate';
2
2
 
3
3
  export type ZipFiles = Map<string, Uint8Array>;
4
4
 
5
+ export interface ZipStore {
6
+ get(path: string): Uint8Array | undefined;
7
+ set(path: string, content: Uint8Array): void;
8
+ has(path: string): boolean;
9
+ delete(path: string): void;
10
+ getText(path: string): string | undefined;
11
+ setText(path: string, content: string): void;
12
+ toFiles(): Promise<ZipFiles>;
13
+ }
14
+
15
+ class EagerZipStore implements ZipStore {
16
+ private _files: ZipFiles;
17
+
18
+ constructor(files: ZipFiles) {
19
+ this._files = files;
20
+ }
21
+
22
+ get(path: string): Uint8Array | undefined {
23
+ return this._files.get(path);
24
+ }
25
+
26
+ set(path: string, content: Uint8Array): void {
27
+ this._files.set(path, content);
28
+ }
29
+
30
+ has(path: string): boolean {
31
+ return this._files.has(path);
32
+ }
33
+
34
+ delete(path: string): void {
35
+ this._files.delete(path);
36
+ }
37
+
38
+ getText(path: string): string | undefined {
39
+ const data = this._files.get(path);
40
+ if (!data) return undefined;
41
+ return strFromU8(data);
42
+ }
43
+
44
+ setText(path: string, content: string): void {
45
+ this._files.set(path, strToU8(content));
46
+ }
47
+
48
+ toFiles(): Promise<ZipFiles> {
49
+ return Promise.resolve(this._files);
50
+ }
51
+ }
52
+
53
+ class LazyZipStore implements ZipStore {
54
+ private _data: Uint8Array;
55
+ private _files: ZipFiles = new Map();
56
+ private _deleted: Set<string> = new Set();
57
+ private _entryNames: Set<string> | null = null;
58
+
59
+ constructor(data: Uint8Array) {
60
+ this._data = data;
61
+ }
62
+
63
+ get(path: string): Uint8Array | undefined {
64
+ if (this._deleted.has(path)) return undefined;
65
+ const cached = this._files.get(path);
66
+ if (cached) return cached;
67
+
68
+ this._ensureIndex();
69
+ if (this._entryNames && !this._entryNames.has(path)) return undefined;
70
+
71
+ const result = unzipSync(this._data, {
72
+ filter: (file) => file.name === path,
73
+ });
74
+ const data = result[path];
75
+ if (data) {
76
+ this._files.set(path, data);
77
+ }
78
+ return data;
79
+ }
80
+
81
+ set(path: string, content: Uint8Array): void {
82
+ this._files.set(path, content);
83
+ this._deleted.delete(path);
84
+ if (this._entryNames) {
85
+ this._entryNames.add(path);
86
+ }
87
+ }
88
+
89
+ has(path: string): boolean {
90
+ if (this._deleted.has(path)) return false;
91
+ if (this._files.has(path)) return true;
92
+ this._ensureIndex();
93
+ return this._entryNames?.has(path) ?? false;
94
+ }
95
+
96
+ delete(path: string): void {
97
+ this._files.delete(path);
98
+ this._deleted.add(path);
99
+ if (this._entryNames) {
100
+ this._entryNames.delete(path);
101
+ }
102
+ }
103
+
104
+ getText(path: string): string | undefined {
105
+ const data = this.get(path);
106
+ if (!data) return undefined;
107
+ return strFromU8(data);
108
+ }
109
+
110
+ setText(path: string, content: string): void {
111
+ this.set(path, strToU8(content));
112
+ }
113
+
114
+ async toFiles(): Promise<ZipFiles> {
115
+ const unzipped = unzipSync(this._data);
116
+ const files = new Map<string, Uint8Array>(Object.entries(unzipped));
117
+ for (const path of this._deleted) {
118
+ files.delete(path);
119
+ }
120
+ for (const [path, content] of this._files) {
121
+ files.set(path, content);
122
+ }
123
+ return files;
124
+ }
125
+
126
+ private _ensureIndex(): void {
127
+ if (this._entryNames) return;
128
+ const names = new Set<string>();
129
+ unzipSync(this._data, {
130
+ filter: (file) => {
131
+ names.add(file.name);
132
+ return false;
133
+ },
134
+ });
135
+ this._entryNames = names;
136
+ }
137
+ }
138
+
139
+ export const createZipStore = (): ZipStore => {
140
+ return new EagerZipStore(new Map());
141
+ };
142
+
5
143
  /**
6
144
  * Reads a ZIP file and returns a map of path -> content
7
145
  * @param data - ZIP file as Uint8Array
8
146
  * @returns Promise resolving to a map of file paths to contents
9
147
  */
10
- export const readZip = (data: Uint8Array): Promise<ZipFiles> => {
148
+ export const readZip = (data: Uint8Array, options?: { lazy?: boolean }): Promise<ZipStore> => {
149
+ const lazy = options?.lazy ?? false;
150
+ if (lazy) {
151
+ return Promise.resolve(new LazyZipStore(data));
152
+ }
153
+
11
154
  const isBun = typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined';
12
155
  if (isBun) {
13
156
  try {
@@ -16,7 +159,7 @@ export const readZip = (data: Uint8Array): Promise<ZipFiles> => {
16
159
  for (const [path, content] of Object.entries(result)) {
17
160
  files.set(path, content);
18
161
  }
19
- return Promise.resolve(files);
162
+ return Promise.resolve(new EagerZipStore(files));
20
163
  } catch (error) {
21
164
  return Promise.reject(error);
22
165
  }
@@ -32,7 +175,7 @@ export const readZip = (data: Uint8Array): Promise<ZipFiles> => {
32
175
  for (const [path, content] of Object.entries(result)) {
33
176
  files.set(path, content);
34
177
  }
35
- resolve(files);
178
+ resolve(new EagerZipStore(files));
36
179
  });
37
180
  });
38
181
  };
@@ -42,9 +185,10 @@ export const readZip = (data: Uint8Array): Promise<ZipFiles> => {
42
185
  * @param files - Map of file paths to contents
43
186
  * @returns Promise resolving to ZIP file as Uint8Array
44
187
  */
45
- export const writeZip = (files: ZipFiles): Promise<Uint8Array> => {
188
+ export const writeZip = async (files: ZipStore): Promise<Uint8Array> => {
189
+ const resolved = await files.toFiles();
46
190
  const zipData: Record<string, Uint8Array> = {};
47
- for (const [path, content] of files) {
191
+ for (const [path, content] of resolved) {
48
192
  zipData[path] = content;
49
193
  }
50
194
 
@@ -71,15 +215,13 @@ export const writeZip = (files: ZipFiles): Promise<Uint8Array> => {
71
215
  /**
72
216
  * Reads a file from the ZIP as a UTF-8 string
73
217
  */
74
- export const readZipText = (files: ZipFiles, path: string): string | undefined => {
75
- const data = files.get(path);
76
- if (!data) return undefined;
77
- return strFromU8(data);
218
+ export const readZipText = (files: ZipStore, path: string): string | undefined => {
219
+ return files.getText(path);
78
220
  };
79
221
 
80
222
  /**
81
223
  * Writes a UTF-8 string to the ZIP files map
82
224
  */
83
- export const writeZipText = (files: ZipFiles, path: string, content: string): void => {
84
- files.set(path, strToU8(content));
225
+ export const writeZipText = (files: ZipStore, path: string, content: string): void => {
226
+ files.setText(path, content);
85
227
  };