diff --git a/scripts/error-codes/generate.mjs b/scripts/error-codes/generate.mjs index 26e3b9d2..3df826fa 100644 --- a/scripts/error-codes/generate.mjs +++ b/scripts/error-codes/generate.mjs @@ -112,13 +112,24 @@ async function fetchPage(url) { // ── HTML helpers ───────────────────────────────────────────────────────────── -const decode = (s) => s - .replace(//gi, " ") - .replace(/<\/(p|li|div)>/gi, " ") - .replace(/<[^>]+>/g, "") - .replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") - .replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'") - .replace(/\s+/g, " ").trim(); +const ENTITIES = { nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', "#39": "'", "#x27": "'" }; + +/** + * Cell markup → plain text. Tags are stripped until none remain (a nested "ipt>" cannot + * survive), and entities are decoded in ONE pass so "&lt;" yields "<", never "<". + */ +const decode = (s) => { + let text = s.replace(//gi, " ").replace(/<\/(p|li|div)>/gi, " "); + let previous; + do { + previous = text; + text = text.replace(/<[^>]*>/g, ""); + } while (text !== previous); + return text + .replace(/&(nbsp|amp|lt|gt|quot|#39|#x27);/g, (_, name) => ENTITIES[name]) + .replace(/\s+/g, " ") + .trim(); +}; /** Every of a page as rows of decoded cell strings, in document order. */ function parseTables(html) {