From a3ee613b6015c96ad65692a1868d8f1ca78a8b9d Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Sun, 6 Sep 2026 16:33:36 +0200 Subject: [PATCH] scripts/error-codes: decode entities in one pass and strip tags until none remain (CodeQL 89/90) Co-Authored-By: Claude Fable 5.1 --- scripts/error-codes/generate.mjs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) 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) {