excel-csv-handler 1.0.6 → 1.0.10

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/debug.js ADDED
@@ -0,0 +1,121 @@
1
+ // debug.js - 测试 ExcelCsvHandler
2
+ import ExcelCsvHandler from './src/index.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ const handler = new ExcelCsvHandler();
7
+
8
+ // 创建测试目录
9
+ const testDir = './test-output';
10
+ if (!fs.existsSync(testDir)) {
11
+ fs.mkdirSync(testDir, { recursive: true });
12
+ }
13
+
14
+ console.log('🧪 开始测试 ExcelCsvHandler...\n');
15
+
16
+ // 测试数据
17
+ const testData = [
18
+ { 姓名: '张三', 年龄: '25', 城市: '北京' },
19
+ { 姓名: '李四', 年龄: '30', 城市: '上海' },
20
+ { 姓名: '王五', 年龄: '28', 城市: '广州' }
21
+ ];
22
+
23
+ const appendData = [
24
+ { 姓名: '赵六', 年龄: '32', 城市: '深圳' },
25
+ { 姓名: '孙七', 年龄: '27', 城市: '杭州' }
26
+ ];
27
+
28
+ (async () => {
29
+ try {
30
+ // ==================== 测试 1: CSV 写入 ====================
31
+ console.log('📝 测试 1: CSV 写入');
32
+ const csvPath = path.join(testDir, 'test.csv');
33
+ await handler.write(csvPath, testData, ['姓名', '年龄', '城市']);
34
+ console.log('✅ CSV 写入成功:', csvPath);
35
+
36
+ // ==================== 测试 2: CSV 读取 ====================
37
+ console.log('\n📖 测试 2: CSV 读取');
38
+ const csvData = await handler.read(csvPath, 0);
39
+ console.log('✅ CSV 读取成功,数据:');
40
+ console.table(csvData);
41
+
42
+ // ==================== 测试 3: appendCsv - 追加到已存在的文件 ====================
43
+ console.log('\n➕ 测试 3: appendCsv - 追加数据到已存在文件');
44
+ await handler.appendCsv(csvPath, appendData, ['姓名', '年龄', '城市']);
45
+ console.log('✅ 追加成功');
46
+
47
+ // 读取追加后的数据
48
+ const appendedData = await handler.read(csvPath, 0);
49
+ console.log('📊 追加后的完整数据:');
50
+ console.table(appendedData);
51
+
52
+ // ==================== 测试 4: appendCsv - 自动创建新文件 ====================
53
+ console.log('\n🆕 测试 4: appendCsv - 自动创建新文件');
54
+ const newCsvPath = path.join(testDir, 'new-file.csv');
55
+ // 确保文件不存在
56
+ if (fs.existsSync(newCsvPath)) {
57
+ fs.unlinkSync(newCsvPath);
58
+ }
59
+ await handler.appendCsv(newCsvPath, testData, ['姓名', '年龄', '城市']);
60
+ console.log('✅ 新文件创建成功:', newCsvPath);
61
+
62
+ const newFileData = await handler.read(newCsvPath, 0);
63
+ console.log('📊 新文件数据:');
64
+ console.table(newFileData);
65
+
66
+ // ==================== 测试 5: Excel 写入和读取 ====================
67
+ console.log('\n📊 测试 5: Excel 写入和读取');
68
+ const xlsxPath = path.join(testDir, 'test.xlsx');
69
+ await handler.write(xlsxPath, testData, ['姓名', '年龄', '城市'], 'Sheet1');
70
+ console.log('✅ Excel 写入成功:', xlsxPath);
71
+
72
+ const xlsxData = await handler.read(xlsxPath, 0);
73
+ console.log('✅ Excel 读取成功,数据:');
74
+ console.table(xlsxData);
75
+
76
+ // ==================== 测试 6: 包含特殊字符的数据 ====================
77
+ console.log('\n🔤 测试 6: 包含特殊字符的数据');
78
+ const specialData = [
79
+ { 姓名: '测试,逗号', 年龄: '25', 备注: '包含"引号"的内容' },
80
+ { 姓名: '测试换行', 年龄: '30', 备注: '第一行\n第二行' }
81
+ ];
82
+ const specialPath = path.join(testDir, 'special.csv');
83
+ await handler.write(specialPath, specialData, ['姓名', '年龄', '备注']);
84
+ console.log('✅ 特殊字符数据写入成功');
85
+
86
+ const specialReadData = await handler.read(specialPath, 0);
87
+ console.log('✅ 特殊字符数据读取成功:');
88
+ console.table(specialReadData);
89
+
90
+ // ==================== 测试 7: 连续追加多次 ====================
91
+ console.log('\n🔄 测试 7: 连续追加多次');
92
+ const multiAppendPath = path.join(testDir, 'multi-append.csv');
93
+ if (fs.existsSync(multiAppendPath)) {
94
+ fs.unlinkSync(multiAppendPath);
95
+ }
96
+
97
+ // 第一次追加(创建文件)
98
+ await handler.appendCsv(multiAppendPath, [testData[0]], ['姓名', '年龄', '城市']);
99
+ console.log(' ✓ 第 1 次追加完成');
100
+
101
+ // 第二次追加
102
+ await handler.appendCsv(multiAppendPath, [testData[1]], ['姓名', '年龄', '城市']);
103
+ console.log(' ✓ 第 2 次追加完成');
104
+
105
+ // 第三次追加
106
+ await handler.appendCsv(multiAppendPath, [testData[2]], ['姓名', '年龄', '城市']);
107
+ console.log(' ✓ 第 3 次追加完成');
108
+
109
+ const multiAppendData = await handler.read(multiAppendPath, 0);
110
+ console.log('✅ 连续追加测试成功,最终数据:');
111
+ console.table(multiAppendData);
112
+
113
+ console.log('\n✨ 所有测试通过!');
114
+ console.log(`\n📁 测试文件已保存到: ${path.resolve(testDir)}`);
115
+
116
+ } catch (error) {
117
+ console.error('\n❌ 测试失败:', error.message);
118
+ console.error(error.stack);
119
+ process.exit(1);
120
+ }
121
+ })();
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
1
  {
2
- "name": "excel-csv-handler",
3
- "version": "1.0.6",
4
- "description": "A Node.js utility to read/write Excel and CSV files with GBK encoding support",
5
- "main": "src/index.js",
6
- "types": "src/excel-csv-handler.d.ts",
7
- "type": "module",
8
- "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1"
10
- },
11
- "keywords": [
12
- "excel",
13
- "csv",
14
- "xlsx",
15
- "gbk",
16
- "node",
17
- "file"
18
- ],
19
- "author": "Chao_bei",
20
- "license": "MIT",
21
- "dependencies": {
22
- "fast-csv": "^5.0.5",
23
- "iconv-lite": "^0.7.0",
24
- "xlsx": "^0.18.5"
25
- }
26
- }
2
+ "name": "excel-csv-handler",
3
+ "version": "1.0.10",
4
+ "description": "A Node.js utility to read/write Excel and CSV files with GBK encoding support",
5
+ "main": "src/index.js",
6
+ "types": "src/excel-csv-handler.d.ts",
7
+ "type": "module",
8
+ "keywords": [
9
+ "excel",
10
+ "csv",
11
+ "xlsx",
12
+ "gbk",
13
+ "node",
14
+ "file"
15
+ ],
16
+ "author": "Chao_bei",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "fast-csv": "^5.0.5",
20
+ "iconv-lite": "^0.7.0",
21
+ "xlsx": "^0.18.5"
22
+ },
23
+ "scripts": {
24
+ "test": "echo \"Error: no test specified\" && exit 1"
25
+ }
26
+ }
@@ -1,10 +1,37 @@
1
1
  // src/excel-csv-handler.d.ts
2
2
  export default class ExcelCsvHandler {
3
+ /**
4
+ * 读取 Excel 或 CSV 文件(支持 GBK 编码)
5
+ * @param filePath - 文件路径
6
+ * @param headerRow - 标题行索引(从 0 开始)
7
+ * @returns Promise<Array<Object>>
8
+ */
3
9
  read(
4
10
  filePath: string,
5
11
  headerRow?: number
6
12
  ): Promise<Array<Record<string, string>>>;
13
+
14
+ /**
15
+ * 写入 Excel 或 CSV 文件(以 GBK 编码保存 CSV,Excel 保持默认)
16
+ * @param filePath - 文件路径
17
+ * @param data - 要写入的数据
18
+ * @param headers - 可选列顺序
19
+ * @param sheetName - Excel sheet 名称(默认为 'Sheet1')
20
+ */
7
21
  write(
22
+ filePath: string,
23
+ data: Array<Record<string, any>>,
24
+ headers?: string[] | null,
25
+ sheetName?: string
26
+ ): Promise<void>;
27
+
28
+ /**
29
+ * 追加数据到 CSV 文件末尾(以 GBK 编码)
30
+ * @param filePath - 文件路径
31
+ * @param data - 要追加的数据
32
+ * @param headers - 可选列顺序(如果文件不存在会自动创建并写入标题行)
33
+ */
34
+ appendCsv(
8
35
  filePath: string,
9
36
  data: Array<Record<string, any>>,
10
37
  headers?: string[] | null
package/src/index.js CHANGED
@@ -19,27 +19,28 @@ class ExcelCsvHandler {
19
19
  const ext = path.extname(filePath).toLowerCase();
20
20
 
21
21
  if (ext === '.xlsx' || ext === '.xls') {
22
- return this.readExcelFile(filePath, headerRow);
22
+ return this.#readExcelFile(filePath, headerRow);
23
23
  } else if (ext === '.csv') {
24
- return this.readCsvFile(filePath, headerRow);
24
+ return this.#readCsvFile(filePath, headerRow);
25
25
  } else {
26
26
  throw new Error(`不支持的文件格式: ${ext}`);
27
27
  }
28
28
  }
29
29
 
30
30
  /**
31
- * 写入 Excel 或 CSV 文件(以 GBK 编码保存 CSV,Excel 保持默认)
32
- * @param {string} filePath - 文件路径
33
- * @param {Array<Object>} data - 要写入的数据
34
- * @param {Array<string>} headers - 可选列顺序
35
- */
36
- async write(filePath, data, headers = null) {
31
+ * 写入 Excel 或 CSV 文件(以 GBK 编码保存 CSV,Excel 保持默认)
32
+ * @param {string} filePath - 文件路径
33
+ * @param {Array<Object>} data - 要写入的数据
34
+ * @param {Array<string>} headers - 可选列顺序
35
+ * @param {string} sheetName - Excel sheet 名称(默认为 'Sheet1')
36
+ */
37
+ async write(filePath, data, headers = null, sheetName = 'Sheet1') {
37
38
  const ext = path.extname(filePath).toLowerCase();
38
39
 
39
40
  if (ext === '.xlsx' || ext === '.xls') {
40
- this.writeExcelFile(filePath, data, headers);
41
+ this.#writeExcelFile(filePath, data, headers, sheetName);
41
42
  } else if (ext === '.csv') {
42
- await this.writeCsvFile(filePath, data, headers);
43
+ await this.#writeCsvFile(filePath, data, headers);
43
44
  } else {
44
45
  throw new Error(`不支持的文件格式: ${ext}`);
45
46
  }
@@ -47,7 +48,7 @@ class ExcelCsvHandler {
47
48
 
48
49
  // ========= Excel 读写 =========
49
50
 
50
- readExcelFile(filePath, headerRow) {
51
+ #readExcelFile(filePath, headerRow) {
51
52
  const workbook = XLSX.readFile(filePath);
52
53
  const sheetName = workbook.SheetNames[0];
53
54
  const worksheet = workbook.Sheets[sheetName];
@@ -79,7 +80,7 @@ class ExcelCsvHandler {
79
80
  });
80
81
  }
81
82
 
82
- writeExcelFile(filePath, data, headers = null) {
83
+ #writeExcelFile(filePath, data, headers = null, sheetName = 'Sheet1') {
83
84
  if (!headers && data.length > 0) {
84
85
  headers = Object.keys(data[0]);
85
86
  } else if (!headers) {
@@ -89,13 +90,13 @@ class ExcelCsvHandler {
89
90
  const wsData = [headers, ...data.map(row => headers.map(h => row[h] ?? ''))];
90
91
  const worksheet = XLSX.utils.aoa_to_sheet(wsData);
91
92
  const workbook = XLSX.utils.book_new();
92
- XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
93
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
93
94
  XLSX.writeFile(workbook, filePath);
94
95
  }
95
96
 
96
97
  // ========= CSV 读写 =========
97
98
 
98
- async readCsvFile(filePath, headerRow) {
99
+ async #readCsvFile(filePath, headerRow) {
99
100
  return new Promise((resolve, reject) => {
100
101
  const allRows = [];
101
102
  const stream = fs.createReadStream(filePath)
@@ -131,7 +132,7 @@ class ExcelCsvHandler {
131
132
  });
132
133
  }
133
134
 
134
- async writeCsvFile(filePath, data, headers = null) {
135
+ async #writeCsvFile(filePath, data, headers = null) {
135
136
  if (!headers && data.length > 0) {
136
137
  headers = Object.keys(data[0]);
137
138
  } else if (!headers) {
@@ -147,6 +148,52 @@ class ExcelCsvHandler {
147
148
  const gbkBuffer = iconv.encode(csvString, 'gbk');
148
149
  fs.writeFileSync(filePath, gbkBuffer);
149
150
  }
151
+
152
+ /**
153
+ * 追加数据到 CSV 文件末尾(以 GBK 编码)
154
+ * @param {string} filePath - 文件路径
155
+ * @param {Array<Object>} data - 要追加的数据
156
+ * @param {Array<string>} headers - 可选列顺序(如果文件不存在会自动创建并写入标题行)
157
+ */
158
+ async appendCsv(filePath, data, headers = null) {
159
+ if (!headers && data.length > 0) {
160
+ headers = Object.keys(data[0]);
161
+ } else if (!headers) {
162
+ headers = [];
163
+ }
164
+
165
+ // 检查文件是否存在
166
+ const fileExists = fs.existsSync(filePath);
167
+
168
+ if (!fileExists) {
169
+ // 文件不存在,自动创建并写入标题行和数据
170
+ await this.#writeCsvFile(filePath, data, headers);
171
+ return;
172
+ }
173
+
174
+ // 文件存在,追加数据(不包含标题行)
175
+ const csvString = await writeToString(data, {
176
+ headers: false,
177
+ includeEndRowDelimiter: true,
178
+ rowDelimiter: '\r\n',
179
+ writeHeaders: false,
180
+ });
181
+
182
+ // 将数据按照指定的列顺序格式化
183
+ const rows = data.map(row =>
184
+ headers.map(h => {
185
+ const value = row[h] ?? '';
186
+ // 如果值包含逗号、引号或换行符,需要用引号包裹并转义
187
+ if (value.toString().includes(',') || value.toString().includes('"') || value.toString().includes('\n')) {
188
+ return '"' + value.toString().replace(/"/g, '""') + '"';
189
+ }
190
+ return value;
191
+ }).join(',')
192
+ ).join('\r\n') + '\r\n';
193
+
194
+ const gbkBuffer = iconv.encode(rows, 'gbk');
195
+ fs.appendFileSync(filePath, gbkBuffer);
196
+ }
150
197
  }
151
198
 
152
199
  export default ExcelCsvHandler;
@@ -0,0 +1,4 @@
1
+ ����,����,����
2
+ ����,25,����
3
+ ����,30,�Ϻ�
4
+ ����,28,����
@@ -0,0 +1,4 @@
1
+ ����,����,����
2
+ ����,25,����
3
+ ����,30,�Ϻ�
4
+ ����,28,����
@@ -0,0 +1,4 @@
1
+ ����,����,��ע
2
+ "����,����",25,"����""����""������"
3
+ ���Ի���,30,"��һ��
4
+ �ڶ���"
@@ -0,0 +1,6 @@
1
+ ����,����,����
2
+ ����,25,����
3
+ ����,30,�Ϻ�
4
+ ����,28,����
5
+ ����,32,����
6
+ ����,27,����
Binary file
Binary file