@niicojs/excel 0.3.0 → 0.3.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/src/range.ts CHANGED
@@ -1,154 +1,154 @@
1
- import type { CellValue, CellStyle, RangeAddress } from './types';
2
- import type { Worksheet } from './worksheet';
3
- import { toAddress, normalizeRange } from './utils/address';
4
-
5
- /**
6
- * Represents a range of cells in a worksheet
7
- */
8
- export class Range {
9
- private _worksheet: Worksheet;
10
- private _range: RangeAddress;
11
-
12
- constructor(worksheet: Worksheet, range: RangeAddress) {
13
- this._worksheet = worksheet;
14
- this._range = normalizeRange(range);
15
- }
16
-
17
- /**
18
- * Get the range address as a string
19
- */
20
- get address(): string {
21
- const start = toAddress(this._range.start.row, this._range.start.col);
22
- const end = toAddress(this._range.end.row, this._range.end.col);
23
- if (start === end) return start;
24
- return `${start}:${end}`;
25
- }
26
-
27
- /**
28
- * Get the number of rows in the range
29
- */
30
- get rowCount(): number {
31
- return this._range.end.row - this._range.start.row + 1;
32
- }
33
-
34
- /**
35
- * Get the number of columns in the range
36
- */
37
- get colCount(): number {
38
- return this._range.end.col - this._range.start.col + 1;
39
- }
40
-
41
- /**
42
- * Get all values in the range as a 2D array
43
- */
44
- get values(): CellValue[][] {
45
- return this.getValues();
46
- }
47
-
48
- /**
49
- * Get all values in the range as a 2D array with options
50
- */
51
- getValues(options: { createMissing?: boolean } = {}): CellValue[][] {
52
- const { createMissing = true } = options;
53
- const result: CellValue[][] = [];
54
- for (let r = this._range.start.row; r <= this._range.end.row; r++) {
55
- const row: CellValue[] = [];
56
- for (let c = this._range.start.col; c <= this._range.end.col; c++) {
57
- if (createMissing) {
58
- const cell = this._worksheet.cell(r, c);
59
- row.push(cell.value);
60
- } else {
61
- const cell = this._worksheet.getCellIfExists(r, c);
62
- row.push(cell?.value ?? null);
63
- }
64
- }
65
- result.push(row);
66
- }
67
- return result;
68
- }
69
-
70
- /**
71
- * Set values in the range from a 2D array
72
- */
73
- set values(data: CellValue[][]) {
74
- for (let r = 0; r < data.length && r < this.rowCount; r++) {
75
- const row = data[r];
76
- for (let c = 0; c < row.length && c < this.colCount; c++) {
77
- const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
78
- cell.value = row[c];
79
- }
80
- }
81
- }
82
-
83
- /**
84
- * Get all formulas in the range as a 2D array
85
- */
86
- get formulas(): (string | undefined)[][] {
87
- const result: (string | undefined)[][] = [];
88
- for (let r = this._range.start.row; r <= this._range.end.row; r++) {
89
- const row: (string | undefined)[] = [];
90
- for (let c = this._range.start.col; c <= this._range.end.col; c++) {
91
- const cell = this._worksheet.cell(r, c);
92
- row.push(cell.formula);
93
- }
94
- result.push(row);
95
- }
96
- return result;
97
- }
98
-
99
- /**
100
- * Set formulas in the range from a 2D array
101
- */
102
- set formulas(data: (string | undefined)[][]) {
103
- for (let r = 0; r < data.length && r < this.rowCount; r++) {
104
- const row = data[r];
105
- for (let c = 0; c < row.length && c < this.colCount; c++) {
106
- const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
107
- cell.formula = row[c];
108
- }
109
- }
110
- }
111
-
112
- /**
113
- * Get the style of the top-left cell
114
- */
115
- get style(): CellStyle {
116
- return this._worksheet.cell(this._range.start.row, this._range.start.col).style;
117
- }
118
-
119
- /**
120
- * Set style for all cells in the range
121
- */
122
- set style(style: CellStyle) {
123
- for (let r = this._range.start.row; r <= this._range.end.row; r++) {
124
- for (let c = this._range.start.col; c <= this._range.end.col; c++) {
125
- const cell = this._worksheet.cell(r, c);
126
- cell.style = style;
127
- }
128
- }
129
- }
130
-
131
- /**
132
- * Iterate over all cells in the range
133
- */
134
- *[Symbol.iterator]() {
135
- for (let r = this._range.start.row; r <= this._range.end.row; r++) {
136
- for (let c = this._range.start.col; c <= this._range.end.col; c++) {
137
- yield this._worksheet.cell(r, c);
138
- }
139
- }
140
- }
141
-
142
- /**
143
- * Iterate over cells row by row
144
- */
145
- *rows() {
146
- for (let r = this._range.start.row; r <= this._range.end.row; r++) {
147
- const row = [];
148
- for (let c = this._range.start.col; c <= this._range.end.col; c++) {
149
- row.push(this._worksheet.cell(r, c));
150
- }
151
- yield row;
152
- }
153
- }
154
- }
1
+ import type { CellValue, CellStyle, RangeAddress } from './types';
2
+ import type { Worksheet } from './worksheet';
3
+ import { toAddress, normalizeRange } from './utils/address';
4
+
5
+ /**
6
+ * Represents a range of cells in a worksheet
7
+ */
8
+ export class Range {
9
+ private _worksheet: Worksheet;
10
+ private _range: RangeAddress;
11
+
12
+ constructor(worksheet: Worksheet, range: RangeAddress) {
13
+ this._worksheet = worksheet;
14
+ this._range = normalizeRange(range);
15
+ }
16
+
17
+ /**
18
+ * Get the range address as a string
19
+ */
20
+ get address(): string {
21
+ const start = toAddress(this._range.start.row, this._range.start.col);
22
+ const end = toAddress(this._range.end.row, this._range.end.col);
23
+ if (start === end) return start;
24
+ return `${start}:${end}`;
25
+ }
26
+
27
+ /**
28
+ * Get the number of rows in the range
29
+ */
30
+ get rowCount(): number {
31
+ return this._range.end.row - this._range.start.row + 1;
32
+ }
33
+
34
+ /**
35
+ * Get the number of columns in the range
36
+ */
37
+ get colCount(): number {
38
+ return this._range.end.col - this._range.start.col + 1;
39
+ }
40
+
41
+ /**
42
+ * Get all values in the range as a 2D array
43
+ */
44
+ get values(): CellValue[][] {
45
+ return this.getValues();
46
+ }
47
+
48
+ /**
49
+ * Get all values in the range as a 2D array with options
50
+ */
51
+ getValues(options: { createMissing?: boolean } = {}): CellValue[][] {
52
+ const { createMissing = true } = options;
53
+ const result: CellValue[][] = [];
54
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
55
+ const row: CellValue[] = [];
56
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
57
+ if (createMissing) {
58
+ const cell = this._worksheet.cell(r, c);
59
+ row.push(cell.value);
60
+ } else {
61
+ const cell = this._worksheet.getCellIfExists(r, c);
62
+ row.push(cell?.value ?? null);
63
+ }
64
+ }
65
+ result.push(row);
66
+ }
67
+ return result;
68
+ }
69
+
70
+ /**
71
+ * Set values in the range from a 2D array
72
+ */
73
+ set values(data: CellValue[][]) {
74
+ for (let r = 0; r < data.length && r < this.rowCount; r++) {
75
+ const row = data[r];
76
+ for (let c = 0; c < row.length && c < this.colCount; c++) {
77
+ const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
78
+ cell.value = row[c];
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get all formulas in the range as a 2D array
85
+ */
86
+ get formulas(): (string | undefined)[][] {
87
+ const result: (string | undefined)[][] = [];
88
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
89
+ const row: (string | undefined)[] = [];
90
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
91
+ const cell = this._worksheet.cell(r, c);
92
+ row.push(cell.formula);
93
+ }
94
+ result.push(row);
95
+ }
96
+ return result;
97
+ }
98
+
99
+ /**
100
+ * Set formulas in the range from a 2D array
101
+ */
102
+ set formulas(data: (string | undefined)[][]) {
103
+ for (let r = 0; r < data.length && r < this.rowCount; r++) {
104
+ const row = data[r];
105
+ for (let c = 0; c < row.length && c < this.colCount; c++) {
106
+ const cell = this._worksheet.cell(this._range.start.row + r, this._range.start.col + c);
107
+ cell.formula = row[c];
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get the style of the top-left cell
114
+ */
115
+ get style(): CellStyle {
116
+ return this._worksheet.cell(this._range.start.row, this._range.start.col).style;
117
+ }
118
+
119
+ /**
120
+ * Set style for all cells in the range
121
+ */
122
+ set style(style: CellStyle) {
123
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
124
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
125
+ const cell = this._worksheet.cell(r, c);
126
+ cell.style = style;
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Iterate over all cells in the range
133
+ */
134
+ *[Symbol.iterator]() {
135
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
136
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
137
+ yield this._worksheet.cell(r, c);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Iterate over cells row by row
144
+ */
145
+ *rows() {
146
+ for (let r = this._range.start.row; r <= this._range.end.row; r++) {
147
+ const row = [];
148
+ for (let c = this._range.start.col; c <= this._range.end.col; c++) {
149
+ row.push(this._worksheet.cell(r, c));
150
+ }
151
+ yield row;
152
+ }
153
+ }
154
+ }
@@ -1,178 +1,178 @@
1
- import {
2
- parseXml,
3
- findElement,
4
- getChildren,
5
- getAttr,
6
- XmlNode,
7
- stringifyXml,
8
- createElement,
9
- createText,
10
- } from './utils/xml';
11
-
12
- /**
13
- * Manages the shared strings table from xl/sharedStrings.xml
14
- * Excel stores strings in a shared table to reduce file size
15
- */
16
- export class SharedStrings {
17
- private entries: SharedStringEntry[] = [];
18
- private stringToIndex: Map<string, number> = new Map();
19
- private _dirty = false;
20
- private _totalCount = 0;
21
-
22
- /**
23
- * Parse shared strings from XML content
24
- */
25
- static parse(xml: string): SharedStrings {
26
- const ss = new SharedStrings();
27
- const parsed = parseXml(xml);
28
- const sst = findElement(parsed, 'sst');
29
- if (!sst) return ss;
30
-
31
- const countAttr = getAttr(sst, 'count');
32
- if (countAttr) {
33
- const total = parseInt(countAttr, 10);
34
- if (Number.isFinite(total) && total >= 0) {
35
- ss._totalCount = total;
36
- }
37
- }
38
-
39
- const children = getChildren(sst, 'sst');
40
- for (const child of children) {
41
- if ('si' in child) {
42
- const siChildren = getChildren(child, 'si');
43
- const text = ss.extractText(siChildren);
44
- ss.entries.push({ text, node: child });
45
- ss.stringToIndex.set(text, ss.entries.length - 1);
46
- }
47
- }
48
-
49
- if (ss._totalCount === 0 && ss.entries.length > 0) {
50
- ss._totalCount = ss.entries.length;
51
- }
52
-
53
- return ss;
54
- }
55
-
56
- /**
57
- * Extract text from a string item (si element)
58
- * Handles both simple <t> elements and rich text <r> elements
59
- */
60
- private extractText(nodes: XmlNode[]): string {
61
- let text = '';
62
- for (const node of nodes) {
63
- if ('t' in node) {
64
- // Simple text: <t>value</t>
65
- const tChildren = getChildren(node, 't');
66
- for (const child of tChildren) {
67
- if ('#text' in child) {
68
- text += child['#text'] as string;
69
- }
70
- }
71
- } else if ('r' in node) {
72
- // Rich text: <r><t>value</t></r>
73
- const rChildren = getChildren(node, 'r');
74
- for (const rChild of rChildren) {
75
- if ('t' in rChild) {
76
- const tChildren = getChildren(rChild, 't');
77
- for (const child of tChildren) {
78
- if ('#text' in child) {
79
- text += child['#text'] as string;
80
- }
81
- }
82
- }
83
- }
84
- }
85
- }
86
- return text;
87
- }
88
-
89
- /**
90
- * Get a string by index
91
- */
92
- getString(index: number): string | undefined {
93
- return this.entries[index]?.text;
94
- }
95
-
96
- /**
97
- * Add a string and return its index
98
- * If the string already exists, returns the existing index
99
- */
100
- addString(str: string): number {
101
- const existing = this.stringToIndex.get(str);
102
- if (existing !== undefined) {
103
- this._totalCount++;
104
- this._dirty = true;
105
- return existing;
106
- }
107
- const index = this.entries.length;
108
- const tElement = createElement('t', str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {}, [
109
- createText(str),
110
- ]);
111
- const siElement = createElement('si', {}, [tElement]);
112
- this.entries.push({ text: str, node: siElement });
113
- this.stringToIndex.set(str, index);
114
- this._totalCount++;
115
- this._dirty = true;
116
- return index;
117
- }
118
-
119
- /**
120
- * Check if the shared strings table has been modified
121
- */
122
- get dirty(): boolean {
123
- return this._dirty;
124
- }
125
-
126
- /**
127
- * Get the count of strings
128
- */
129
- get count(): number {
130
- return this.entries.length;
131
- }
132
-
133
- /**
134
- * Get total usage count of shared strings
135
- */
136
- get totalCount(): number {
137
- return Math.max(this._totalCount, this.entries.length);
138
- }
139
-
140
- /**
141
- * Generate XML for the shared strings table
142
- */
143
- toXml(): string {
144
- const siElements: XmlNode[] = [];
145
- for (const entry of this.entries) {
146
- if (entry.node) {
147
- siElements.push(entry.node);
148
- } else {
149
- const str = entry.text;
150
- const tElement = createElement(
151
- 't',
152
- str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {},
153
- [createText(str)],
154
- );
155
- const siElement = createElement('si', {}, [tElement]);
156
- siElements.push(siElement);
157
- }
158
- }
159
-
160
- const totalCount = Math.max(this._totalCount, this.entries.length);
161
- const sst = createElement(
162
- 'sst',
163
- {
164
- xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
165
- count: String(totalCount),
166
- uniqueCount: String(this.entries.length),
167
- },
168
- siElements,
169
- );
170
-
171
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n${stringifyXml([sst])}`;
172
- }
173
- }
174
-
175
- interface SharedStringEntry {
176
- text: string;
177
- node?: XmlNode;
178
- }
1
+ import {
2
+ parseXml,
3
+ findElement,
4
+ getChildren,
5
+ getAttr,
6
+ XmlNode,
7
+ stringifyXml,
8
+ createElement,
9
+ createText,
10
+ } from './utils/xml';
11
+
12
+ /**
13
+ * Manages the shared strings table from xl/sharedStrings.xml
14
+ * Excel stores strings in a shared table to reduce file size
15
+ */
16
+ export class SharedStrings {
17
+ private entries: SharedStringEntry[] = [];
18
+ private stringToIndex: Map<string, number> = new Map();
19
+ private _dirty = false;
20
+ private _totalCount = 0;
21
+
22
+ /**
23
+ * Parse shared strings from XML content
24
+ */
25
+ static parse(xml: string): SharedStrings {
26
+ const ss = new SharedStrings();
27
+ const parsed = parseXml(xml);
28
+ const sst = findElement(parsed, 'sst');
29
+ if (!sst) return ss;
30
+
31
+ const countAttr = getAttr(sst, 'count');
32
+ if (countAttr) {
33
+ const total = parseInt(countAttr, 10);
34
+ if (Number.isFinite(total) && total >= 0) {
35
+ ss._totalCount = total;
36
+ }
37
+ }
38
+
39
+ const children = getChildren(sst, 'sst');
40
+ for (const child of children) {
41
+ if ('si' in child) {
42
+ const siChildren = getChildren(child, 'si');
43
+ const text = ss.extractText(siChildren);
44
+ ss.entries.push({ text, node: child });
45
+ ss.stringToIndex.set(text, ss.entries.length - 1);
46
+ }
47
+ }
48
+
49
+ if (ss._totalCount === 0 && ss.entries.length > 0) {
50
+ ss._totalCount = ss.entries.length;
51
+ }
52
+
53
+ return ss;
54
+ }
55
+
56
+ /**
57
+ * Extract text from a string item (si element)
58
+ * Handles both simple <t> elements and rich text <r> elements
59
+ */
60
+ private extractText(nodes: XmlNode[]): string {
61
+ let text = '';
62
+ for (const node of nodes) {
63
+ if ('t' in node) {
64
+ // Simple text: <t>value</t>
65
+ const tChildren = getChildren(node, 't');
66
+ for (const child of tChildren) {
67
+ if ('#text' in child) {
68
+ text += child['#text'] as string;
69
+ }
70
+ }
71
+ } else if ('r' in node) {
72
+ // Rich text: <r><t>value</t></r>
73
+ const rChildren = getChildren(node, 'r');
74
+ for (const rChild of rChildren) {
75
+ if ('t' in rChild) {
76
+ const tChildren = getChildren(rChild, 't');
77
+ for (const child of tChildren) {
78
+ if ('#text' in child) {
79
+ text += child['#text'] as string;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return text;
87
+ }
88
+
89
+ /**
90
+ * Get a string by index
91
+ */
92
+ getString(index: number): string | undefined {
93
+ return this.entries[index]?.text;
94
+ }
95
+
96
+ /**
97
+ * Add a string and return its index
98
+ * If the string already exists, returns the existing index
99
+ */
100
+ addString(str: string): number {
101
+ const existing = this.stringToIndex.get(str);
102
+ if (existing !== undefined) {
103
+ this._totalCount++;
104
+ this._dirty = true;
105
+ return existing;
106
+ }
107
+ const index = this.entries.length;
108
+ const tElement = createElement('t', str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {}, [
109
+ createText(str),
110
+ ]);
111
+ const siElement = createElement('si', {}, [tElement]);
112
+ this.entries.push({ text: str, node: siElement });
113
+ this.stringToIndex.set(str, index);
114
+ this._totalCount++;
115
+ this._dirty = true;
116
+ return index;
117
+ }
118
+
119
+ /**
120
+ * Check if the shared strings table has been modified
121
+ */
122
+ get dirty(): boolean {
123
+ return this._dirty;
124
+ }
125
+
126
+ /**
127
+ * Get the count of strings
128
+ */
129
+ get count(): number {
130
+ return this.entries.length;
131
+ }
132
+
133
+ /**
134
+ * Get total usage count of shared strings
135
+ */
136
+ get totalCount(): number {
137
+ return Math.max(this._totalCount, this.entries.length);
138
+ }
139
+
140
+ /**
141
+ * Generate XML for the shared strings table
142
+ */
143
+ toXml(): string {
144
+ const siElements: XmlNode[] = [];
145
+ for (const entry of this.entries) {
146
+ if (entry.node) {
147
+ siElements.push(entry.node);
148
+ } else {
149
+ const str = entry.text;
150
+ const tElement = createElement(
151
+ 't',
152
+ str.startsWith(' ') || str.endsWith(' ') ? { 'xml:space': 'preserve' } : {},
153
+ [createText(str)],
154
+ );
155
+ const siElement = createElement('si', {}, [tElement]);
156
+ siElements.push(siElement);
157
+ }
158
+ }
159
+
160
+ const totalCount = Math.max(this._totalCount, this.entries.length);
161
+ const sst = createElement(
162
+ 'sst',
163
+ {
164
+ xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
165
+ count: String(totalCount),
166
+ uniqueCount: String(this.entries.length),
167
+ },
168
+ siElements,
169
+ );
170
+
171
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n${stringifyXml([sst])}`;
172
+ }
173
+ }
174
+
175
+ interface SharedStringEntry {
176
+ text: string;
177
+ node?: XmlNode;
178
+ }