fix: 修复书签转换脚本
This commit is contained in:
@@ -72,9 +72,14 @@ const ICON_MAPPING = {
|
|||||||
// 获取最新的书签文件
|
// 获取最新的书签文件
|
||||||
function getLatestBookmarkFile() {
|
function getLatestBookmarkFile() {
|
||||||
try {
|
try {
|
||||||
|
console.log('[DEBUG] 开始查找书签文件...');
|
||||||
|
console.log('[DEBUG] 书签目录:', BOOKMARKS_DIR);
|
||||||
|
|
||||||
// 确保书签目录存在
|
// 确保书签目录存在
|
||||||
if (!fs.existsSync(BOOKMARKS_DIR)) {
|
if (!fs.existsSync(BOOKMARKS_DIR)) {
|
||||||
|
console.log('[DEBUG] 书签目录不存在,创建目录...');
|
||||||
fs.mkdirSync(BOOKMARKS_DIR, { recursive: true });
|
fs.mkdirSync(BOOKMARKS_DIR, { recursive: true });
|
||||||
|
console.log('[WARN] 书签目录为空,未找到HTML文件');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +87,13 @@ function getLatestBookmarkFile() {
|
|||||||
const files = fs.readdirSync(BOOKMARKS_DIR)
|
const files = fs.readdirSync(BOOKMARKS_DIR)
|
||||||
.filter(file => file.toLowerCase().endsWith('.html'));
|
.filter(file => file.toLowerCase().endsWith('.html'));
|
||||||
|
|
||||||
|
console.log('[DEBUG] 找到的HTML文件数量:', files.length);
|
||||||
|
if (files.length > 0) {
|
||||||
|
console.log('[DEBUG] HTML文件列表:', files);
|
||||||
|
}
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
|
console.log('[WARN] 未找到任何HTML书签文件');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,16 +106,23 @@ function getLatestBookmarkFile() {
|
|||||||
// 找出最新的文件
|
// 找出最新的文件
|
||||||
fileStats.sort((a, b) => b.mtime - a.mtime);
|
fileStats.sort((a, b) => b.mtime - a.mtime);
|
||||||
const latestFile = fileStats[0].file;
|
const latestFile = fileStats[0].file;
|
||||||
|
const latestFilePath = path.join(BOOKMARKS_DIR, latestFile);
|
||||||
|
|
||||||
return path.join(BOOKMARKS_DIR, latestFile);
|
console.log('[INFO] 选择最新的书签文件:', latestFile);
|
||||||
|
console.log('[DEBUG] 完整路径:', latestFilePath);
|
||||||
|
|
||||||
|
return latestFilePath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error finding bookmark file:', error);
|
console.error('[ERROR] 查找书签文件时出错:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析书签HTML内容,支持2-4层级嵌套结构
|
// 解析书签HTML内容,支持2-4层级嵌套结构
|
||||||
function parseBookmarks(htmlContent) {
|
function parseBookmarks(htmlContent) {
|
||||||
|
console.log('[DEBUG] 开始解析书签HTML内容...');
|
||||||
|
console.log('[DEBUG] HTML内容长度:', htmlContent.length, '字符');
|
||||||
|
|
||||||
// 正则表达式匹配文件夹和书签
|
// 正则表达式匹配文件夹和书签
|
||||||
const folderRegex = /<DT><H3([^>]*)>(.*?)<\/H3>/g;
|
const folderRegex = /<DT><H3([^>]*)>(.*?)<\/H3>/g;
|
||||||
const bookmarkRegex = /<DT><A HREF="([^"]+)"[^>]*>(.*?)<\/A>/g;
|
const bookmarkRegex = /<DT><A HREF="([^"]+)"[^>]*>(.*?)<\/A>/g;
|
||||||
@@ -116,40 +134,78 @@ function parseBookmarks(htmlContent) {
|
|||||||
|
|
||||||
// 递归解析嵌套文件夹
|
// 递归解析嵌套文件夹
|
||||||
function parseNestedFolder(htmlContent, parentPath = [], level = 1) {
|
function parseNestedFolder(htmlContent, parentPath = [], level = 1) {
|
||||||
|
console.log(`[DEBUG] parseNestedFolder 被调用 - 层级:${level}, 路径:${parentPath.join('/')}, 内容长度:${htmlContent.length}`);
|
||||||
|
|
||||||
const folders = [];
|
const folders = [];
|
||||||
let match;
|
let match;
|
||||||
|
let matchCount = 0;
|
||||||
|
|
||||||
// 使用正则表达式匹配文件夹
|
// 创建新的正则表达式实例,避免全局正则的 lastIndex 问题
|
||||||
folderRegex.lastIndex = 0;
|
const localFolderRegex = /<DT><H3([^>]*)>(.*?)<\/H3>/g;
|
||||||
|
|
||||||
while ((match = folderRegex.exec(htmlContent)) !== null) {
|
while ((match = localFolderRegex.exec(htmlContent)) !== null) {
|
||||||
|
matchCount++;
|
||||||
const folderName = match[2].trim();
|
const folderName = match[2].trim();
|
||||||
const folderStart = match.index;
|
const folderStart = match.index;
|
||||||
const folderEnd = match.index + match[0].length;
|
const folderEnd = match.index + match[0].length;
|
||||||
|
|
||||||
|
console.log(`[DEBUG] 找到文件夹 #${matchCount}: "${folderName}" (层级${level}) 在位置 ${folderStart}`);
|
||||||
|
|
||||||
// 查找文件夹的结束位置
|
// 查找文件夹的结束位置
|
||||||
let folderContentEnd = htmlContent.length;
|
let folderContentEnd = htmlContent.length;
|
||||||
let depth = 1;
|
let depth = 1;
|
||||||
let pos = folderEnd;
|
let pos = folderEnd;
|
||||||
|
let loopCount = 0;
|
||||||
|
const maxLoops = 10000; // 防止无限循环
|
||||||
|
|
||||||
|
console.log(`[DEBUG] 开始查找文件夹"${folderName}"的边界,起始位置:${pos}`);
|
||||||
|
|
||||||
while (pos < htmlContent.length && depth > 0) {
|
while (pos < htmlContent.length && depth > 0) {
|
||||||
const dlStart = htmlContent.substring(pos).match(/<DL><p>/gi);
|
loopCount++;
|
||||||
const dlEnd = htmlContent.substring(pos).match(/<\/DL><p>/gi);
|
if (loopCount > maxLoops) {
|
||||||
|
console.error(`[ERROR] 检测到可能的无限循环! 文件夹:"${folderName}", 层级:${level}, 循环次数:${loopCount}`);
|
||||||
|
console.error(`[ERROR] 当前位置:${pos}, 深度:${depth}`);
|
||||||
|
console.error(`[ERROR] 周围内容:`, htmlContent.substring(pos, pos + 100));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (dlStart && dlStart.index < (dlEnd ? dlEnd.index : htmlContent.length)) {
|
// 修复:使用 search() 而不是 match(),因为 match() 返回数组没有 index 属性
|
||||||
|
const remainingContent = htmlContent.substring(pos);
|
||||||
|
const dlStartIndex = remainingContent.search(/<DL><p>/i);
|
||||||
|
const dlEndIndex = remainingContent.search(/<\/DL><p>/i);
|
||||||
|
|
||||||
|
if (loopCount % 100 === 0) {
|
||||||
|
console.log(`[DEBUG] 循环 ${loopCount}: pos=${pos}, depth=${depth}, dlStart=${dlStartIndex}, dlEnd=${dlEndIndex}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到开始标签且在结束标签之前(或没有结束标签)
|
||||||
|
if (dlStartIndex !== -1 && (dlEndIndex === -1 || dlStartIndex < dlEndIndex)) {
|
||||||
depth++;
|
depth++;
|
||||||
pos += dlStart.index + dlStart[0].length;
|
pos += dlStartIndex + '<DL><p>'.length;
|
||||||
} else if (dlEnd) {
|
console.log(`[DEBUG] 找到 <DL><p> 在位置 ${pos}, depth增加到 ${depth}`);
|
||||||
|
}
|
||||||
|
// 找到结束标签
|
||||||
|
else if (dlEndIndex !== -1) {
|
||||||
depth--;
|
depth--;
|
||||||
pos += dlEnd.index + dlEnd[0].length;
|
pos += dlEndIndex + '</DL><p>'.length;
|
||||||
} else {
|
console.log(`[DEBUG] 找到 </DL><p> 在位置 ${pos}, depth减少到 ${depth}`);
|
||||||
|
}
|
||||||
|
// 都没找到,退出循环
|
||||||
|
else {
|
||||||
|
console.log(`[DEBUG] 未找到更多标签,退出循环`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loopCount > 100) {
|
||||||
|
console.log(`[DEBUG] 文件夹"${folderName}"边界查找循环${loopCount}次`);
|
||||||
|
}
|
||||||
|
|
||||||
folderContentEnd = pos;
|
folderContentEnd = pos;
|
||||||
const folderContent = htmlContent.substring(folderEnd, folderContentEnd);
|
const folderContent = htmlContent.substring(folderEnd, folderContentEnd);
|
||||||
|
|
||||||
|
console.log(`[DEBUG] 文件夹"${folderName}"内容长度: ${folderContent.length}`);
|
||||||
|
|
||||||
// 解析文件夹内容
|
// 解析文件夹内容
|
||||||
const folder = {
|
const folder = {
|
||||||
name: folderName,
|
name: folderName,
|
||||||
@@ -157,14 +213,19 @@ function parseBookmarks(htmlContent) {
|
|||||||
path: [...parentPath, folderName]
|
path: [...parentPath, folderName]
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否包含子文件夹
|
// 检查是否包含子文件夹 - 创建新的正则实例避免干扰主循环
|
||||||
const hasSubfolders = folderRegex.test(folderContent);
|
const testFolderRegex = /<DT><H3([^>]*)>(.*?)<\/H3>/;
|
||||||
folderRegex.lastIndex = 0;
|
const hasSubfolders = testFolderRegex.test(folderContent);
|
||||||
|
|
||||||
|
console.log(`[DEBUG] 文件夹"${folderName}"包含子文件夹: ${hasSubfolders}`);
|
||||||
|
|
||||||
if (hasSubfolders && level < 4) {
|
if (hasSubfolders && level < 4) {
|
||||||
|
console.log(`[DEBUG] 递归解析文件夹"${folderName}"的子文件夹...`);
|
||||||
// 递归解析子文件夹
|
// 递归解析子文件夹
|
||||||
const subfolders = parseNestedFolder(folderContent, folder.path, level + 1);
|
const subfolders = parseNestedFolder(folderContent, folder.path, level + 1);
|
||||||
|
|
||||||
|
console.log(`[DEBUG] 文件夹"${folderName}"解析到 ${subfolders.length} 个子项`);
|
||||||
|
|
||||||
// 根据层级深度决定数据结构
|
// 根据层级深度决定数据结构
|
||||||
if (level === 1) {
|
if (level === 1) {
|
||||||
folder.subcategories = subfolders;
|
folder.subcategories = subfolders;
|
||||||
@@ -175,6 +236,7 @@ function parseBookmarks(htmlContent) {
|
|||||||
folder.sites = parseSitesInFolder(folderContent);
|
folder.sites = parseSitesInFolder(folderContent);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log(`[DEBUG] 解析文件夹"${folderName}"中的书签...`);
|
||||||
// 解析书签
|
// 解析书签
|
||||||
folder.sites = parseSitesInFolder(folderContent);
|
folder.sites = parseSitesInFolder(folderContent);
|
||||||
}
|
}
|
||||||
@@ -185,10 +247,14 @@ function parseBookmarks(htmlContent) {
|
|||||||
folder.groups && folder.groups.length > 0;
|
folder.groups && folder.groups.length > 0;
|
||||||
|
|
||||||
if (hasContent) {
|
if (hasContent) {
|
||||||
|
console.log(`[DEBUG] 添加文件夹"${folderName}" (包含内容)`);
|
||||||
folders.push(folder);
|
folders.push(folder);
|
||||||
|
} else {
|
||||||
|
console.log(`[DEBUG] 跳过空文件夹"${folderName}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[DEBUG] parseNestedFolder 完成 - 层级:${level}, 返回 ${folders.length} 个文件夹`);
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,9 +262,11 @@ function parseBookmarks(htmlContent) {
|
|||||||
function parseSitesInFolder(folderContent) {
|
function parseSitesInFolder(folderContent) {
|
||||||
const sites = [];
|
const sites = [];
|
||||||
let bookmarkMatch;
|
let bookmarkMatch;
|
||||||
|
let siteCount = 0;
|
||||||
bookmarkRegex.lastIndex = 0;
|
bookmarkRegex.lastIndex = 0;
|
||||||
|
|
||||||
while ((bookmarkMatch = bookmarkRegex.exec(folderContent)) !== null) {
|
while ((bookmarkMatch = bookmarkRegex.exec(folderContent)) !== null) {
|
||||||
|
siteCount++;
|
||||||
const url = bookmarkMatch[1];
|
const url = bookmarkMatch[1];
|
||||||
const name = bookmarkMatch[2].trim();
|
const name = bookmarkMatch[2].trim();
|
||||||
|
|
||||||
@@ -219,11 +287,14 @@ function parseBookmarks(htmlContent) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[DEBUG] parseSitesInFolder 完成 - 找到 ${siteCount} 个书签`);
|
||||||
return sites;
|
return sites;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始解析
|
// 开始解析
|
||||||
|
console.log('[DEBUG] 开始递归解析顶层分类...');
|
||||||
bookmarks.categories = parseNestedFolder(htmlContent);
|
bookmarks.categories = parseNestedFolder(htmlContent);
|
||||||
|
console.log(`[INFO] 解析完成 - 共找到 ${bookmarks.categories.length} 个顶层分类`);
|
||||||
|
|
||||||
return bookmarks;
|
return bookmarks;
|
||||||
}
|
}
|
||||||
@@ -343,53 +414,85 @@ function updateNavigationFile(filePath) {
|
|||||||
|
|
||||||
// 主函数
|
// 主函数
|
||||||
async function main() {
|
async function main() {
|
||||||
|
console.log('========================================');
|
||||||
|
console.log('[INFO] 书签处理脚本启动');
|
||||||
|
console.log('[INFO] 时间:', new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }));
|
||||||
|
console.log('========================================\n');
|
||||||
|
|
||||||
// 获取最新的书签文件
|
// 获取最新的书签文件
|
||||||
|
console.log('[步骤 1/5] 查找书签文件...');
|
||||||
const bookmarkFile = getLatestBookmarkFile();
|
const bookmarkFile = getLatestBookmarkFile();
|
||||||
if (!bookmarkFile) {
|
if (!bookmarkFile) {
|
||||||
|
console.log('[ERROR] 未找到书签文件,处理终止');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('[SUCCESS] ✓ 找到书签文件\n');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
|
console.log('[步骤 2/5] 读取书签文件...');
|
||||||
const htmlContent = fs.readFileSync(bookmarkFile, 'utf8');
|
const htmlContent = fs.readFileSync(bookmarkFile, 'utf8');
|
||||||
|
console.log('[SUCCESS] ✓ 文件读取成功,大小:', htmlContent.length, '字符\n');
|
||||||
|
|
||||||
// 解析书签
|
// 解析书签
|
||||||
|
console.log('[步骤 3/5] 解析书签结构...');
|
||||||
const bookmarks = parseBookmarks(htmlContent);
|
const bookmarks = parseBookmarks(htmlContent);
|
||||||
if (bookmarks.categories.length === 0) {
|
if (bookmarks.categories.length === 0) {
|
||||||
console.error('ERROR: No bookmark categories found in the HTML file. Processing aborted.');
|
console.error('[ERROR] HTML文件中未找到书签分类,处理终止');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('[SUCCESS] ✓ 解析完成\n');
|
||||||
|
|
||||||
// 生成YAML
|
// 生成YAML
|
||||||
const yaml = generateBookmarksYaml(bookmarks);
|
console.log('[步骤 4/5] 生成YAML配置...');
|
||||||
if (!yaml) {
|
const yamlContent = generateBookmarksYaml(bookmarks);
|
||||||
console.error('ERROR: Failed to generate YAML from bookmarks. Processing aborted.');
|
if (!yamlContent) {
|
||||||
|
console.error('[ERROR] YAML生成失败,处理终止');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('[DEBUG] YAML内容长度:', yamlContent.length, '字符');
|
||||||
|
console.log('[SUCCESS] ✓ YAML生成成功\n');
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
console.log('[步骤 5/5] 保存配置文件...');
|
||||||
try {
|
try {
|
||||||
// 确保目标目录存在
|
// 确保目标目录存在
|
||||||
if (!fs.existsSync(CONFIG_USER_PAGES_DIR)) {
|
if (!fs.existsSync(CONFIG_USER_PAGES_DIR)) {
|
||||||
|
console.log('[DEBUG] 创建目录:', CONFIG_USER_PAGES_DIR);
|
||||||
fs.mkdirSync(CONFIG_USER_PAGES_DIR, { recursive: true });
|
fs.mkdirSync(CONFIG_USER_PAGES_DIR, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存YAML到模块化位置
|
// 保存YAML到模块化位置
|
||||||
fs.writeFileSync(MODULAR_OUTPUT_FILE, yaml, 'utf8');
|
console.log('[DEBUG] 写入文件:', MODULAR_OUTPUT_FILE);
|
||||||
|
fs.writeFileSync(MODULAR_OUTPUT_FILE, yamlContent, 'utf8');
|
||||||
|
|
||||||
// 验证文件是否确实被创建
|
// 验证文件是否确实被创建
|
||||||
if (!fs.existsSync(MODULAR_OUTPUT_FILE)) {
|
if (!fs.existsSync(MODULAR_OUTPUT_FILE)) {
|
||||||
console.error(`ERROR: File was not created: ${MODULAR_OUTPUT_FILE}`);
|
console.error(`[ERROR] 文件未能创建: ${MODULAR_OUTPUT_FILE}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[SUCCESS] ✓ 文件保存成功');
|
||||||
|
console.log('[INFO] 输出文件:', MODULAR_OUTPUT_FILE, '\n');
|
||||||
|
|
||||||
// 更新导航
|
// 更新导航
|
||||||
|
console.log('[附加步骤] 更新导航配置...');
|
||||||
updateNavigationWithBookmarks();
|
updateNavigationWithBookmarks();
|
||||||
|
console.log('[SUCCESS] ✓ 导航配置已更新\n');
|
||||||
|
|
||||||
} catch (writeError) {
|
} catch (writeError) {
|
||||||
console.error(`ERROR writing file:`, writeError);
|
console.error(`[ERROR] 写入文件时出错:`, writeError);
|
||||||
|
console.error('[ERROR] 错误堆栈:', writeError.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('========================================');
|
||||||
|
console.log('[SUCCESS] ✓✓✓ 书签处理完成!✓✓✓');
|
||||||
|
console.log('========================================');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing bookmark file:', error);
|
console.error('[FATAL] 处理书签文件时发生错误:', error);
|
||||||
|
console.error('[ERROR] 错误堆栈:', error.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user