} labels - Map of item_id -> label
* @returns {{ html: string, markdown: string }}
*/
export function reconstructMainContent(doc, labels) {
// Clone the document body to prevent altering the original state
const clonedBody = doc.body.cloneNode(true);
// Map each _item_id to its element
const idToElement = new Map();
const allTagged = clonedBody.querySelectorAll(`[${ITEM_ID_ATTR}]`);
for (const el of allTagged) {
const id = el.getAttribute(ITEM_ID_ATTR);
if (id && !idToElement.has(id)) {
idToElement.set(id, el);
}
}
// Set of all elements to preserve (main content + ancestors + descendants)
const elementsToRemain = new Set();
for (const [itemId, label] of Object.entries(labels)) {
if (label !== 'main') continue;
const el = idToElement.get(itemId);
if (!el) continue;
// Add element and all its descendants
elementsToRemain.add(el);
const descendants = el.querySelectorAll('*');
for (const d of descendants) {
elementsToRemain.add(d);
}
// Add ancestors up to body
let ancestor = el.parentElement;
while (ancestor && ancestor !== clonedBody.parentElement) {
elementsToRemain.add(ancestor);
ancestor = ancestor.parentElement;
}
}
// If no elements were kept (e.g. all classified as boilerplate), retain fallback
if (elementsToRemain.size === 0) {
return {
html: 'No main content identified.
',
markdown: '*No main content identified.*',
};
}
// Prune nodes that are not in elementsToRemain
function pruneNode(node) {
const children = Array.from(node.children);
for (const child of children) {
if (!elementsToRemain.has(child)) {
child.remove();
} else {
pruneNode(child);
}
}
}
pruneNode(clonedBody);
// Clean _item_id attributes from final output
const remainingTagged = clonedBody.querySelectorAll(`[${ITEM_ID_ATTR}]`);
for (const el of remainingTagged) {
el.removeAttribute(ITEM_ID_ATTR);
}
const rawExtractedHtml = clonedBody.innerHTML.trim();
const cleanedHtml = stripSpacerImages(rawExtractedHtml);
const markdown = _turndownInstance.turndown(cleanedHtml).trim();
return {
html: cleanedHtml,
markdown,
};
}