@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/LICENSE +20 -20
- package/README.md +585 -585
- package/dist/index.cjs +498 -489
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +498 -489
- package/package.json +1 -1
- package/src/index.ts +45 -45
- package/src/pivot-cache.ts +300 -300
- package/src/pivot-table.ts +684 -684
- package/src/range.ts +154 -154
- package/src/shared-strings.ts +178 -178
- package/src/styles.ts +819 -819
- package/src/table.ts +386 -386
- package/src/types.ts +313 -307
- package/src/utils/address.ts +121 -121
- package/src/utils/xml.ts +140 -140
- package/src/workbook.ts +1390 -1390
- package/src/worksheet.ts +879 -869
package/src/utils/address.ts
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import type { CellAddress, RangeAddress } from '../types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Converts a column index (0-based) to Excel column letters (A, B, ..., Z, AA, AB, ...)
|
|
5
|
-
* @param col - 0-based column index
|
|
6
|
-
* @returns Column letter(s)
|
|
7
|
-
*/
|
|
8
|
-
export const colToLetter = (col: number): string => {
|
|
9
|
-
let result = '';
|
|
10
|
-
let n = col;
|
|
11
|
-
while (n >= 0) {
|
|
12
|
-
result = String.fromCharCode((n % 26) + 65) + result;
|
|
13
|
-
n = Math.floor(n / 26) - 1;
|
|
14
|
-
}
|
|
15
|
-
return result;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Converts Excel column letters to a 0-based column index
|
|
20
|
-
* @param letters - Column letter(s) like 'A', 'B', 'AA'
|
|
21
|
-
* @returns 0-based column index
|
|
22
|
-
*/
|
|
23
|
-
export const letterToCol = (letters: string): number => {
|
|
24
|
-
const upper = letters.toUpperCase();
|
|
25
|
-
let col = 0;
|
|
26
|
-
for (let i = 0; i < upper.length; i++) {
|
|
27
|
-
col = col * 26 + (upper.charCodeAt(i) - 64);
|
|
28
|
-
}
|
|
29
|
-
return col - 1;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parses an Excel cell address (e.g., 'A1', '$B$2') to row/col indices
|
|
34
|
-
* @param address - Cell address string
|
|
35
|
-
* @returns CellAddress with 0-based row and col
|
|
36
|
-
*/
|
|
37
|
-
export const parseAddress = (address: string): CellAddress => {
|
|
38
|
-
// Remove $ signs for absolute references
|
|
39
|
-
const clean = address.replace(/\$/g, '');
|
|
40
|
-
const match = clean.match(/^([A-Z]+)(\d+)$/i);
|
|
41
|
-
if (!match) {
|
|
42
|
-
throw new Error(`Invalid cell address: ${address}`);
|
|
43
|
-
}
|
|
44
|
-
const rowNumber = +match[2];
|
|
45
|
-
if (rowNumber <= 0) throw new Error(`Invalid cell address: ${address}`);
|
|
46
|
-
|
|
47
|
-
const col = letterToCol(match[1].toUpperCase());
|
|
48
|
-
const row = rowNumber - 1; // Convert to 0-based
|
|
49
|
-
return { row, col };
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Converts row/col indices to an Excel cell address
|
|
54
|
-
* @param row - 0-based row index
|
|
55
|
-
* @param col - 0-based column index
|
|
56
|
-
* @returns Cell address string like 'A1'
|
|
57
|
-
*/
|
|
58
|
-
export const toAddress = (row: number, col: number): string => {
|
|
59
|
-
return `${colToLetter(col)}${row + 1}`;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parses an Excel range (e.g., 'A1:B10') to start/end addresses
|
|
64
|
-
* @param range - Range string
|
|
65
|
-
* @returns RangeAddress with start and end
|
|
66
|
-
*/
|
|
67
|
-
export const parseRange = (range: string): RangeAddress => {
|
|
68
|
-
const parts = range.split(':');
|
|
69
|
-
if (parts.length === 1) {
|
|
70
|
-
// Single cell range
|
|
71
|
-
const addr = parseAddress(parts[0]);
|
|
72
|
-
return { start: addr, end: addr };
|
|
73
|
-
}
|
|
74
|
-
if (parts.length !== 2) {
|
|
75
|
-
throw new Error(`Invalid range: ${range}`);
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
start: parseAddress(parts[0]),
|
|
79
|
-
end: parseAddress(parts[1]),
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Converts a RangeAddress to a range string
|
|
85
|
-
* @param range - RangeAddress object
|
|
86
|
-
* @returns Range string like 'A1:B10'
|
|
87
|
-
*/
|
|
88
|
-
export const toRange = (range: RangeAddress): string => {
|
|
89
|
-
const start = toAddress(range.start.row, range.start.col);
|
|
90
|
-
const end = toAddress(range.end.row, range.end.col);
|
|
91
|
-
if (start === end) {
|
|
92
|
-
return start;
|
|
93
|
-
}
|
|
94
|
-
return `${start}:${end}`;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Normalizes a range so start is always top-left and end is bottom-right
|
|
99
|
-
*/
|
|
100
|
-
export const normalizeRange = (range: RangeAddress): RangeAddress => {
|
|
101
|
-
return {
|
|
102
|
-
start: {
|
|
103
|
-
row: Math.min(range.start.row, range.end.row),
|
|
104
|
-
col: Math.min(range.start.col, range.end.col),
|
|
105
|
-
},
|
|
106
|
-
end: {
|
|
107
|
-
row: Math.max(range.start.row, range.end.row),
|
|
108
|
-
col: Math.max(range.start.col, range.end.col),
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Checks if an address is within a range
|
|
115
|
-
*/
|
|
116
|
-
export const isInRange = (addr: CellAddress, range: RangeAddress): boolean => {
|
|
117
|
-
const norm = normalizeRange(range);
|
|
118
|
-
return (
|
|
119
|
-
addr.row >= norm.start.row && addr.row <= norm.end.row && addr.col >= norm.start.col && addr.col <= norm.end.col
|
|
120
|
-
);
|
|
121
|
-
};
|
|
1
|
+
import type { CellAddress, RangeAddress } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts a column index (0-based) to Excel column letters (A, B, ..., Z, AA, AB, ...)
|
|
5
|
+
* @param col - 0-based column index
|
|
6
|
+
* @returns Column letter(s)
|
|
7
|
+
*/
|
|
8
|
+
export const colToLetter = (col: number): string => {
|
|
9
|
+
let result = '';
|
|
10
|
+
let n = col;
|
|
11
|
+
while (n >= 0) {
|
|
12
|
+
result = String.fromCharCode((n % 26) + 65) + result;
|
|
13
|
+
n = Math.floor(n / 26) - 1;
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Converts Excel column letters to a 0-based column index
|
|
20
|
+
* @param letters - Column letter(s) like 'A', 'B', 'AA'
|
|
21
|
+
* @returns 0-based column index
|
|
22
|
+
*/
|
|
23
|
+
export const letterToCol = (letters: string): number => {
|
|
24
|
+
const upper = letters.toUpperCase();
|
|
25
|
+
let col = 0;
|
|
26
|
+
for (let i = 0; i < upper.length; i++) {
|
|
27
|
+
col = col * 26 + (upper.charCodeAt(i) - 64);
|
|
28
|
+
}
|
|
29
|
+
return col - 1;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parses an Excel cell address (e.g., 'A1', '$B$2') to row/col indices
|
|
34
|
+
* @param address - Cell address string
|
|
35
|
+
* @returns CellAddress with 0-based row and col
|
|
36
|
+
*/
|
|
37
|
+
export const parseAddress = (address: string): CellAddress => {
|
|
38
|
+
// Remove $ signs for absolute references
|
|
39
|
+
const clean = address.replace(/\$/g, '');
|
|
40
|
+
const match = clean.match(/^([A-Z]+)(\d+)$/i);
|
|
41
|
+
if (!match) {
|
|
42
|
+
throw new Error(`Invalid cell address: ${address}`);
|
|
43
|
+
}
|
|
44
|
+
const rowNumber = +match[2];
|
|
45
|
+
if (rowNumber <= 0) throw new Error(`Invalid cell address: ${address}`);
|
|
46
|
+
|
|
47
|
+
const col = letterToCol(match[1].toUpperCase());
|
|
48
|
+
const row = rowNumber - 1; // Convert to 0-based
|
|
49
|
+
return { row, col };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Converts row/col indices to an Excel cell address
|
|
54
|
+
* @param row - 0-based row index
|
|
55
|
+
* @param col - 0-based column index
|
|
56
|
+
* @returns Cell address string like 'A1'
|
|
57
|
+
*/
|
|
58
|
+
export const toAddress = (row: number, col: number): string => {
|
|
59
|
+
return `${colToLetter(col)}${row + 1}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses an Excel range (e.g., 'A1:B10') to start/end addresses
|
|
64
|
+
* @param range - Range string
|
|
65
|
+
* @returns RangeAddress with start and end
|
|
66
|
+
*/
|
|
67
|
+
export const parseRange = (range: string): RangeAddress => {
|
|
68
|
+
const parts = range.split(':');
|
|
69
|
+
if (parts.length === 1) {
|
|
70
|
+
// Single cell range
|
|
71
|
+
const addr = parseAddress(parts[0]);
|
|
72
|
+
return { start: addr, end: addr };
|
|
73
|
+
}
|
|
74
|
+
if (parts.length !== 2) {
|
|
75
|
+
throw new Error(`Invalid range: ${range}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
start: parseAddress(parts[0]),
|
|
79
|
+
end: parseAddress(parts[1]),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Converts a RangeAddress to a range string
|
|
85
|
+
* @param range - RangeAddress object
|
|
86
|
+
* @returns Range string like 'A1:B10'
|
|
87
|
+
*/
|
|
88
|
+
export const toRange = (range: RangeAddress): string => {
|
|
89
|
+
const start = toAddress(range.start.row, range.start.col);
|
|
90
|
+
const end = toAddress(range.end.row, range.end.col);
|
|
91
|
+
if (start === end) {
|
|
92
|
+
return start;
|
|
93
|
+
}
|
|
94
|
+
return `${start}:${end}`;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Normalizes a range so start is always top-left and end is bottom-right
|
|
99
|
+
*/
|
|
100
|
+
export const normalizeRange = (range: RangeAddress): RangeAddress => {
|
|
101
|
+
return {
|
|
102
|
+
start: {
|
|
103
|
+
row: Math.min(range.start.row, range.end.row),
|
|
104
|
+
col: Math.min(range.start.col, range.end.col),
|
|
105
|
+
},
|
|
106
|
+
end: {
|
|
107
|
+
row: Math.max(range.start.row, range.end.row),
|
|
108
|
+
col: Math.max(range.start.col, range.end.col),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Checks if an address is within a range
|
|
115
|
+
*/
|
|
116
|
+
export const isInRange = (addr: CellAddress, range: RangeAddress): boolean => {
|
|
117
|
+
const norm = normalizeRange(range);
|
|
118
|
+
return (
|
|
119
|
+
addr.row >= norm.start.row && addr.row <= norm.end.row && addr.col >= norm.start.col && addr.col <= norm.end.col
|
|
120
|
+
);
|
|
121
|
+
};
|
package/src/utils/xml.ts
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
|
2
|
-
|
|
3
|
-
// Parser options that preserve structure and attributes
|
|
4
|
-
const parserOptions = {
|
|
5
|
-
ignoreAttributes: false,
|
|
6
|
-
attributeNamePrefix: '@_',
|
|
7
|
-
textNodeName: '#text',
|
|
8
|
-
preserveOrder: true,
|
|
9
|
-
commentPropName: '#comment',
|
|
10
|
-
cdataPropName: '#cdata',
|
|
11
|
-
trimValues: false,
|
|
12
|
-
parseTagValue: false,
|
|
13
|
-
parseAttributeValue: false,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// Builder options matching parser for round-trip compatibility
|
|
17
|
-
const builderOptions = {
|
|
18
|
-
ignoreAttributes: false,
|
|
19
|
-
attributeNamePrefix: '@_',
|
|
20
|
-
textNodeName: '#text',
|
|
21
|
-
preserveOrder: true,
|
|
22
|
-
commentPropName: '#comment',
|
|
23
|
-
cdataPropName: '#cdata',
|
|
24
|
-
format: false,
|
|
25
|
-
suppressEmptyNode: false,
|
|
26
|
-
suppressBooleanAttributes: false,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const parser = new XMLParser(parserOptions);
|
|
30
|
-
const builder = new XMLBuilder(builderOptions);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parses an XML string into a JavaScript object
|
|
34
|
-
* Preserves element order and attributes for round-trip compatibility
|
|
35
|
-
*/
|
|
36
|
-
export const parseXml = (xml: string): XmlNode[] => {
|
|
37
|
-
return parser.parse(xml);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Converts a JavaScript object back to an XML string
|
|
42
|
-
*/
|
|
43
|
-
export const stringifyXml = (obj: XmlNode[]): string => {
|
|
44
|
-
return builder.build(obj);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* XML node type from fast-xml-parser with preserveOrder
|
|
49
|
-
* Each node is an object with a single key (the tag name)
|
|
50
|
-
* containing an array of child nodes, plus optional :@
|
|
51
|
-
* for attributes
|
|
52
|
-
*/
|
|
53
|
-
export interface XmlNode {
|
|
54
|
-
[tagName: string]: XmlNode[] | string | Record<string, string> | undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Finds the first element with the given tag name in the XML tree
|
|
59
|
-
*/
|
|
60
|
-
export const findElement = (nodes: XmlNode[], tagName: string): XmlNode | undefined => {
|
|
61
|
-
for (const node of nodes) {
|
|
62
|
-
if (tagName in node) {
|
|
63
|
-
return node;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Finds all elements with the given tag name (immediate children only)
|
|
71
|
-
*/
|
|
72
|
-
export const findElements = (nodes: XmlNode[], tagName: string): XmlNode[] => {
|
|
73
|
-
return nodes.filter((node) => tagName in node);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Gets the children of an element
|
|
78
|
-
*/
|
|
79
|
-
export const getChildren = (node: XmlNode, tagName: string): XmlNode[] => {
|
|
80
|
-
const children = node[tagName];
|
|
81
|
-
if (Array.isArray(children)) {
|
|
82
|
-
return children;
|
|
83
|
-
}
|
|
84
|
-
return [];
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Gets an attribute value from a node
|
|
89
|
-
*/
|
|
90
|
-
export const getAttr = (node: XmlNode, name: string): string | undefined => {
|
|
91
|
-
const attrs = node[':@'] as Record<string, string> | undefined;
|
|
92
|
-
return attrs?.[`@_${name}`];
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Sets an attribute value on a node
|
|
97
|
-
*/
|
|
98
|
-
export const setAttr = (node: XmlNode, name: string, value: string): void => {
|
|
99
|
-
if (!node[':@']) {
|
|
100
|
-
node[':@'] = {};
|
|
101
|
-
}
|
|
102
|
-
(node[':@'] as Record<string, string>)[`@_${name}`] = value;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Gets the text content of a node
|
|
107
|
-
*/
|
|
108
|
-
export const getText = (node: XmlNode, tagName: string): string | undefined => {
|
|
109
|
-
const children = getChildren(node, tagName);
|
|
110
|
-
for (const child of children) {
|
|
111
|
-
if ('#text' in child) {
|
|
112
|
-
return child['#text'] as string;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Creates a new XML element
|
|
120
|
-
*/
|
|
121
|
-
export const createElement = (tagName: string, attrs?: Record<string, string>, children?: XmlNode[]): XmlNode => {
|
|
122
|
-
const node: XmlNode = {
|
|
123
|
-
[tagName]: children || [],
|
|
124
|
-
};
|
|
125
|
-
if (attrs && Object.keys(attrs).length > 0) {
|
|
126
|
-
const attrObj: Record<string, string> = {};
|
|
127
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
128
|
-
attrObj[`@_${key}`] = value;
|
|
129
|
-
}
|
|
130
|
-
node[':@'] = attrObj;
|
|
131
|
-
}
|
|
132
|
-
return node;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Creates a text node
|
|
137
|
-
*/
|
|
138
|
-
export const createText = (text: string): XmlNode => {
|
|
139
|
-
return { '#text': text } as unknown as XmlNode;
|
|
140
|
-
};
|
|
1
|
+
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
|
2
|
+
|
|
3
|
+
// Parser options that preserve structure and attributes
|
|
4
|
+
const parserOptions = {
|
|
5
|
+
ignoreAttributes: false,
|
|
6
|
+
attributeNamePrefix: '@_',
|
|
7
|
+
textNodeName: '#text',
|
|
8
|
+
preserveOrder: true,
|
|
9
|
+
commentPropName: '#comment',
|
|
10
|
+
cdataPropName: '#cdata',
|
|
11
|
+
trimValues: false,
|
|
12
|
+
parseTagValue: false,
|
|
13
|
+
parseAttributeValue: false,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Builder options matching parser for round-trip compatibility
|
|
17
|
+
const builderOptions = {
|
|
18
|
+
ignoreAttributes: false,
|
|
19
|
+
attributeNamePrefix: '@_',
|
|
20
|
+
textNodeName: '#text',
|
|
21
|
+
preserveOrder: true,
|
|
22
|
+
commentPropName: '#comment',
|
|
23
|
+
cdataPropName: '#cdata',
|
|
24
|
+
format: false,
|
|
25
|
+
suppressEmptyNode: false,
|
|
26
|
+
suppressBooleanAttributes: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const parser = new XMLParser(parserOptions);
|
|
30
|
+
const builder = new XMLBuilder(builderOptions);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parses an XML string into a JavaScript object
|
|
34
|
+
* Preserves element order and attributes for round-trip compatibility
|
|
35
|
+
*/
|
|
36
|
+
export const parseXml = (xml: string): XmlNode[] => {
|
|
37
|
+
return parser.parse(xml);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Converts a JavaScript object back to an XML string
|
|
42
|
+
*/
|
|
43
|
+
export const stringifyXml = (obj: XmlNode[]): string => {
|
|
44
|
+
return builder.build(obj);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* XML node type from fast-xml-parser with preserveOrder
|
|
49
|
+
* Each node is an object with a single key (the tag name)
|
|
50
|
+
* containing an array of child nodes, plus optional :@
|
|
51
|
+
* for attributes
|
|
52
|
+
*/
|
|
53
|
+
export interface XmlNode {
|
|
54
|
+
[tagName: string]: XmlNode[] | string | Record<string, string> | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Finds the first element with the given tag name in the XML tree
|
|
59
|
+
*/
|
|
60
|
+
export const findElement = (nodes: XmlNode[], tagName: string): XmlNode | undefined => {
|
|
61
|
+
for (const node of nodes) {
|
|
62
|
+
if (tagName in node) {
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Finds all elements with the given tag name (immediate children only)
|
|
71
|
+
*/
|
|
72
|
+
export const findElements = (nodes: XmlNode[], tagName: string): XmlNode[] => {
|
|
73
|
+
return nodes.filter((node) => tagName in node);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets the children of an element
|
|
78
|
+
*/
|
|
79
|
+
export const getChildren = (node: XmlNode, tagName: string): XmlNode[] => {
|
|
80
|
+
const children = node[tagName];
|
|
81
|
+
if (Array.isArray(children)) {
|
|
82
|
+
return children;
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Gets an attribute value from a node
|
|
89
|
+
*/
|
|
90
|
+
export const getAttr = (node: XmlNode, name: string): string | undefined => {
|
|
91
|
+
const attrs = node[':@'] as Record<string, string> | undefined;
|
|
92
|
+
return attrs?.[`@_${name}`];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sets an attribute value on a node
|
|
97
|
+
*/
|
|
98
|
+
export const setAttr = (node: XmlNode, name: string, value: string): void => {
|
|
99
|
+
if (!node[':@']) {
|
|
100
|
+
node[':@'] = {};
|
|
101
|
+
}
|
|
102
|
+
(node[':@'] as Record<string, string>)[`@_${name}`] = value;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Gets the text content of a node
|
|
107
|
+
*/
|
|
108
|
+
export const getText = (node: XmlNode, tagName: string): string | undefined => {
|
|
109
|
+
const children = getChildren(node, tagName);
|
|
110
|
+
for (const child of children) {
|
|
111
|
+
if ('#text' in child) {
|
|
112
|
+
return child['#text'] as string;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a new XML element
|
|
120
|
+
*/
|
|
121
|
+
export const createElement = (tagName: string, attrs?: Record<string, string>, children?: XmlNode[]): XmlNode => {
|
|
122
|
+
const node: XmlNode = {
|
|
123
|
+
[tagName]: children || [],
|
|
124
|
+
};
|
|
125
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
126
|
+
const attrObj: Record<string, string> = {};
|
|
127
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
128
|
+
attrObj[`@_${key}`] = value;
|
|
129
|
+
}
|
|
130
|
+
node[':@'] = attrObj;
|
|
131
|
+
}
|
|
132
|
+
return node;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates a text node
|
|
137
|
+
*/
|
|
138
|
+
export const createText = (text: string): XmlNode => {
|
|
139
|
+
return { '#text': text } as unknown as XmlNode;
|
|
140
|
+
};
|