import * as cheerio from "cheerio"; export function parseNetscapeBookmarkHtmlNode(html) { const $ = cheerio.load(html, { decodeEntities: false }); const rootDl = $("dl").first(); if (!rootDl.length) return { folders: [], bookmarks: [] }; const folders = []; const bookmarks = []; function walkDl($dl, parentTempId) { // Netscape format:

contains repeating

items and nested
const children = $dl.children().toArray(); for (let i = 0; i < children.length; i++) { const node = children[i]; if (!node || node.tagName?.toLowerCase() !== "dt") continue; const $dt = $(node); const $h3 = $dt.children("h3").first(); const $a = $dt.children("a").first(); const $next = $(children[i + 1] || null); const nextIsDl = $next && $next[0]?.tagName?.toLowerCase() === "dl"; if ($h3.length) { const tempId = `${folders.length + 1}`; const name = ($h3.text() || "").trim(); folders.push({ tempId, parentTempId: parentTempId ?? null, name }); if (nextIsDl) walkDl($next, tempId); } else if ($a.length) { const title = ($a.text() || "").trim(); const url = $a.attr("href") || ""; bookmarks.push({ parentTempId: parentTempId ?? null, title, url }); } } } walkDl(rootDl, null); return { folders, bookmarks }; } export function buildNetscapeBookmarkHtml({ folders, bookmarks }) { // folders: [{id, parentId, name}] // bookmarks: [{folderId, title, url}] const folderChildren = new Map(); const bookmarkChildren = new Map(); for (const f of folders) { const key = f.parentId ?? "root"; if (!folderChildren.has(key)) folderChildren.set(key, []); folderChildren.get(key).push(f); } for (const b of bookmarks) { const key = b.folderId ?? "root"; if (!bookmarkChildren.has(key)) bookmarkChildren.set(key, []); bookmarkChildren.get(key).push(b); } function esc(s) { return String(s) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function renderFolder(parentId) { const key = parentId ?? "root"; const subFolders = (folderChildren.get(key) || []).slice().sort((a, b) => a.name.localeCompare(b.name)); const subBookmarks = (bookmarkChildren.get(key) || []).slice().sort((a, b) => a.title.localeCompare(b.title)); let out = "

\n"; for (const f of subFolders) { out += `

${esc(f.name)}

\n`; out += renderFolder(f.id) .split("\n") .map((line) => (line ? ` ${line}` : line)) .join("\n"); out += "\n"; } for (const b of subBookmarks) { out += `
${esc(b.title)}\n`; } out += "

"; return out; } const header = `\n\n\nBookmarks\n

Bookmarks

\n`; const body = renderFolder(null); return header + body + "\n"; }