mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +08:00
611a36fb98
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
221 lines
5.4 KiB
TypeScript
221 lines
5.4 KiB
TypeScript
/**
|
||
* 导入导出模块 — 对应 C 版 import_export.c
|
||
* 支持 JSON、CSV、TXT 三种格式
|
||
*/
|
||
import type { PathEntry } from './path-entry';
|
||
|
||
export type ExportFormat = 'json' | 'csv' | 'txt';
|
||
|
||
export interface ExportData {
|
||
system: PathEntry[];
|
||
user: PathEntry[];
|
||
}
|
||
|
||
/** 根据文件扩展名检测格式 */
|
||
export function detectExportFormat(filepath: string): ExportFormat {
|
||
const lower = filepath.toLowerCase();
|
||
if (lower.endsWith('.csv')) return 'csv';
|
||
if (lower.endsWith('.txt')) return 'txt';
|
||
return 'json';
|
||
}
|
||
|
||
// ── JSON 导出 ──
|
||
|
||
export function exportToJson(data: ExportData): string {
|
||
const obj = {
|
||
version: '1.0',
|
||
type: 'PathEditor',
|
||
exported: new Date().toISOString(),
|
||
system: data.system.map(e => e.path),
|
||
user: data.user.map(e => e.path),
|
||
};
|
||
return JSON.stringify(obj, null, 2);
|
||
}
|
||
|
||
// ── CSV 导出 ──
|
||
|
||
export function exportToCsv(data: ExportData): string {
|
||
const lines: string[] = [];
|
||
// UTF-8 BOM
|
||
lines.push('type,path');
|
||
|
||
for (const entry of data.system) {
|
||
lines.push(`system,${escapeCsvField(entry.path)}`);
|
||
}
|
||
for (const entry of data.user) {
|
||
lines.push(`user,${escapeCsvField(entry.path)}`);
|
||
}
|
||
|
||
return lines.join('\n') + '\n';
|
||
}
|
||
|
||
function escapeCsvField(field: string): string {
|
||
if (field.includes(',') || field.includes('"') || field.includes('\n')) {
|
||
return `"${field.replace(/"/g, '""')}"`;
|
||
}
|
||
return field;
|
||
}
|
||
|
||
// ── CSV 导入 ──
|
||
|
||
export interface ImportResult {
|
||
system: PathEntry[];
|
||
user: PathEntry[];
|
||
}
|
||
|
||
export function importFromCsv(content: string): ImportResult {
|
||
const result: ImportResult = { system: [], user: [] };
|
||
const lines = content.split(/\r?\n/);
|
||
|
||
let hasHeader = false;
|
||
|
||
for (let i = 0; i < lines.length; i++) {
|
||
// 跳过 BOM(仅首行)
|
||
let line = lines[i];
|
||
if (i === 0 && line.startsWith('')) {
|
||
line = line.slice(1);
|
||
}
|
||
|
||
if (line.trim() === '') continue;
|
||
|
||
const fields = parseCsvLine(line);
|
||
if (fields.length < 2) continue;
|
||
|
||
// 检测头部行
|
||
if (!hasHeader && isHeaderRow(fields[0], fields[1])) {
|
||
hasHeader = true;
|
||
continue;
|
||
}
|
||
|
||
const type = fields[0].trim().toLowerCase();
|
||
const path = fields[1].trim();
|
||
|
||
if (path.length === 0) continue;
|
||
|
||
if (type === 'system') {
|
||
result.system.push({ path, enabled: true });
|
||
} else if (type === 'user') {
|
||
result.user.push({ path, enabled: true });
|
||
}
|
||
// 未知类型忽略
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function isHeaderRow(col0: string, col1: string): boolean {
|
||
const c0 = col0.trim().toLowerCase();
|
||
const c1 = col1.trim().toLowerCase();
|
||
return c0 === 'type' && c1 === 'path';
|
||
}
|
||
|
||
function parseCsvLine(line: string): string[] {
|
||
const fields: string[] = [];
|
||
let current = '';
|
||
let inQuotes = false;
|
||
|
||
for (let i = 0; i < line.length; i++) {
|
||
const ch = line[i];
|
||
|
||
if (inQuotes) {
|
||
if (ch === '"') {
|
||
if (i + 1 < line.length && line[i + 1] === '"') {
|
||
current += '"';
|
||
i++; // 跳过转义引号
|
||
} else {
|
||
inQuotes = false;
|
||
}
|
||
} else {
|
||
current += ch;
|
||
}
|
||
} else {
|
||
if (ch === '"') {
|
||
inQuotes = true;
|
||
} else if (ch === ',') {
|
||
fields.push(current);
|
||
current = '';
|
||
} else {
|
||
current += ch;
|
||
}
|
||
}
|
||
}
|
||
|
||
fields.push(current);
|
||
return fields;
|
||
}
|
||
|
||
// ── JSON 导入 ──
|
||
|
||
export function importFromJson(content: string): ImportResult {
|
||
const result: ImportResult = { system: [], user: [] };
|
||
|
||
let obj: Record<string, unknown>;
|
||
try {
|
||
obj = JSON.parse(content);
|
||
} catch {
|
||
return result; // 无效 JSON 返回空结果,由调用方显示错误
|
||
}
|
||
|
||
if (typeof obj !== 'object' || obj === null) return result;
|
||
|
||
if (Array.isArray(obj.system)) {
|
||
result.system = obj.system
|
||
.filter((p: unknown) => typeof p === 'string' && p.trim().length > 0)
|
||
.map((p: string) => ({ path: p.trim(), enabled: true }));
|
||
}
|
||
if (Array.isArray(obj.user)) {
|
||
result.user = obj.user
|
||
.filter((p: unknown) => typeof p === 'string' && p.trim().length > 0)
|
||
.map((p: string) => ({ path: p.trim(), enabled: true }));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// ── TXT 导入 ──
|
||
|
||
export function importFromTxt(content: string): PathEntry[] {
|
||
const paths: PathEntry[] = [];
|
||
const lines = content.split(/\r?\n/);
|
||
|
||
for (let i = 0; i < lines.length; i++) {
|
||
// 跳过 BOM(仅首行)
|
||
let line = lines[i];
|
||
if (i === 0 && line.startsWith('')) line = line.slice(1);
|
||
|
||
const trimmed = line.trim();
|
||
if (trimmed.length === 0 || trimmed.startsWith('#')) continue;
|
||
|
||
paths.push({ path: trimmed, enabled: true });
|
||
}
|
||
|
||
return paths;
|
||
}
|
||
|
||
// ── 自动检测导入 ──
|
||
|
||
export function importFromContent(
|
||
content: string,
|
||
filepath: string,
|
||
): ImportResult {
|
||
const lower = filepath.toLowerCase();
|
||
if (lower.endsWith('.csv')) {
|
||
return importFromCsv(content);
|
||
} else if (lower.endsWith('.json')) {
|
||
return importFromJson(content);
|
||
} else {
|
||
// TXT 文件:所有路径放入 system(用户后续可选择目标)
|
||
return { system: importFromTxt(content), user: [] };
|
||
}
|
||
}
|
||
|
||
/** 将 ImportResult 合并为单个路径数组 */
|
||
export function flattenImportResult(
|
||
result: ImportResult,
|
||
target: 'system' | 'user' | 'both',
|
||
): ExportData {
|
||
if (target === 'system') return { system: result.system, user: [] };
|
||
if (target === 'user') return { system: [], user: result.user };
|
||
return result; // both
|
||
}
|