// @ts-nocheck
/**
* @module research/extractor/html-to-content/html-to-basic-html
* @description Research library module.
*/
import {
convertURLSafeHTMLToHTML,
convertURLToAbsoluteURL,
convertMarkdownToHTML,
} from "./html-utils";
/**
* Strip HTML to ~30 basic markup HTML tags, lists, tables, images.
* Convert anchors and relative urls to absolute urls. Basic HTML supports the same
* elements as Markdown, which is used in writing plain text. Markdown is converted
* to HTML anyways to display it, and it is better to edit basic HTML in a rich text editor.
*
* [Mozilla DOM Reference](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
* [Source Code of Browser HTML DOM](https://chromium.googlesource.com/chromium/src/+/HEAD/third_party/blink/renderer/core/dom/)
* [RegExp JS V8 Code](https://github.com/v8/v8/blob/94cde7c7f3fffc62f621e43f65be3d517b8a9f3d/src/regexp/regexp-compiler.cc#L3827)
* @param {string} html Any page's HTML to process
* @param {Object} [options]
* @param {boolean} options.images default=true - Whether to include images
* @param {boolean} options.links default=true - Whether to include links
* @param {boolean} options.videos default=true - Whether to include videos or not
* @param {boolean} options.formatting default=true - Whether to include formatting
* @param {string} options.url base URL for converting relative URLs to absolute
* @param {string} options.allowTags default="br,p,u,b,i ,em,strong,h1,h2,h3,h4, h5,h6,blockquote,
* code,ul,ol,li,dd,dl, table,th,tr,td,sub,sup" - Comma-separated list of allowed HTML tags.
* @param {string} options.allowedAttributes default="text,tag,href, src,type,width, height,id,data"
* List of allowed HTML attributes
* @returns {string} basic text formatting html
* @author [vtempest (2025)](https://github.com/vtempest)
* @category HTML Utilities
*/
export function convertHTMLToBasicHTML(html, options = {}) {
var {
images = true,
links = true,
videos = true,
formatting = true,
url = "",
openLinksNewWindow = false,
allowTags = "br,p,u,b,i,em,strong,h1,h2,h3,h4,h5,h6,blockquote,code,\
ul,ol,li,dd,dl,table,th,tr,td,thead,tbody,sub,sup,math,iframe",
allowedAttributes = "href,src,type,width,height,id,data,target",
} = options;
// return convertMarkdownToHTML(convertMarkdownToHTML(html, false), true)
allowTags = allowTags.split(",");
if (links) allowTags.push("a");
if (images) allowTags.push("img");
if (videos)
allowTags = allowTags.concat("video,source,embed,object".split(","));
if (!formatting) allowTags = ["text"];
allowTags.push("text");
allowedAttributes = allowedAttributes
.split(",")
.concat("text,tagName".split(","));
// Convert html string to array like [{tag:"p",attr:""},{text:""}]
var basicHtml = convertHTMLToTokens(html);
if (!basicHtml) return;
basicHtml = basicHtml
.filter(
(token) =>
token.text ||
(token.tagName[0] == "/"
? allowTags.includes(token.tagName?.substring(1)?.toLowerCase())
: allowTags.includes(token.tagName?.toLowerCase()))
)
.map((el) => {
for (var key of Object.keys(el))
if (!allowedAttributes.includes(key)) delete el[key];
var urlValue = el.href || el.src;
//non-anchor links should be opened in new window
if (urlValue && openLinksNewWindow)
if (!urlValue.startsWith("#")) el.target = "_blank";
// remove broken images
if (el.tagName?.toLowerCase() == "img") {
if (!el.src || el.src.startsWith("data:")) return false;
}
//convert relative urls to absolute urls
if (el.src) {
el.src = new URL(urlValue, url).href;
}
if (el.href) el.href = new URL(urlValue, url).href;
// convertURLToAbsoluteURL(url, urlValue);
return el;
})
.filter(Boolean)
.reduce((acc, el) => {
acc += el.text
? `${el.text}`
: `<${el.tagName}${Object.keys(el).length > 1 ? " " : ""}${Object.keys(
el
)
.filter((key) => key != "tagName" && key != "text")
.map((key) => `${key}="${el[key]}"`)
.join(" ")}>`;
return acc;
}, "")
.replace(/
<\/p>/g, " ") .replace(/[\r\n\t]+/g, " ") //remove linebreaks .replace(/ \s+/g, " "); basicHtml = convertURLSafeHTMLToHTML(basicHtml).replace(/ /g, " "); // // CNN news edge case of data=attr <> inside of attr // const reHTMLInsideDataAttr = // /(["'])(?:(?!(?:\1|<)).)*?(?:<(?:(?!["'<>]).)*?>)?(?:(?!(?:\1|<)).)*?\1/gis; // if (reHTMLInsideDataAttr.test(html)) // html = html.replaceAll(reHTMLInsideDataAttr, ""); return basicHtml; } /** * Convert html string to array of JSON Objects tokens to translate, * convert, or filter all elements. * Flat array is faster than DOMParser which uses nested trees. * @param {string} html * @returns {array} Example [{"tag": "img","src": ""}, ...] * @private */ export function convertHTMLToTokens(html) { if (!html) return; var dom = []; //remove script style to prevent it from counting as text html = html .replace(/(<(noscript|script|style)\b[^>]*>).*?(<\/\2>)/gis, "$1$3") .replace(/