From 48c373232263de1787bd16a863f94a34c86f2228 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Tue, 8 Sep 2026 00:58:35 +1000 Subject: [PATCH 1/3] test: cover common symbolic named references --- test/html.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/html.test.js b/test/html.test.js index 588f997..27038a1 100644 --- a/test/html.test.js +++ b/test/html.test.js @@ -13,6 +13,24 @@ test('decodeEntities handles common currency and math references', () => { assert.equal(decodeEntities('£ ¢ ¥ ∉ ≤ ≥'), '£ ¢ ¥ ∉ ≤ ≥'); }); +test('decodeEntities handles common Greek, mathematical, and arrow references', () => { + const cases = [ + ['Greek letters', 'Α Ω α β ω', 'Α Ω α β ω'], + ['mathematical operators', '∀ ∂ ∏ ∑ √ ∫', '∀ ∂ ∏ ∑ √ ∫'], + ['mathematical relations', '∈ ∉ ≠ ≡ ⊆ ⊥', '∈ ∉ ≠ ≡ ⊆ ⊥'], + ['directional arrows', '← ↑ → ↓ ↔ ⇒', '← ↑ → ↓ ↔ ⇒'] + ]; + + for (const [name, input, expected] of cases) assert.equal(decodeEntities(input), expected, name); +}); + +test('convertHtmlToText exposes common named-reference symbols', () => { + assert.equal( + convertHtmlToText('

α + β ≠ ∑ → Ω

').text, + 'α + β ≠ ∑ → Ω' + ); +}); + test('decodeEntities preserves undefined reference spellings case-sensitively', () => { assert.equal(decodeEntities('Á á Á £ &NotIn; &madeup;'), 'Á á Á £ &NotIn; &madeup;'); }); From e8d4899c17459e0ff893736bab66588d5a0992c2 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Tue, 8 Sep 2026 00:59:16 +1000 Subject: [PATCH 2/3] feat: decode common symbolic named references --- src/html.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/html.js b/src/html.js index f6758c0..6751694 100644 --- a/src/html.js +++ b/src/html.js @@ -18,7 +18,21 @@ const ENTITIES = new Map([ ['agrave', 'à'], ['aacute', 'á'], ['acirc', 'â'], ['atilde', 'ã'], ['auml', 'ä'], ['aring', 'å'], ['aelig', 'æ'], ['ccedil', 'ç'], ['egrave', 'è'], ['eacute', 'é'], ['ecirc', 'ê'], ['euml', 'ë'], ['igrave', 'ì'], ['iacute', 'í'], ['icirc', 'î'], ['iuml', 'ï'], ['eth', 'ð'], ['ntilde', 'ñ'], ['ograve', 'ò'], ['oacute', 'ó'], ['ocirc', 'ô'], ['otilde', 'õ'], ['ouml', 'ö'], ['oslash', 'ø'], - ['ugrave', 'ù'], ['uacute', 'ú'], ['ucirc', 'û'], ['uuml', 'ü'], ['yacute', 'ý'], ['thorn', 'þ'], ['yuml', 'ÿ'] + ['ugrave', 'ù'], ['uacute', 'ú'], ['ucirc', 'û'], ['uuml', 'ü'], ['yacute', 'ý'], ['thorn', 'þ'], ['yuml', 'ÿ'], + ['Alpha', 'Α'], ['Beta', 'Β'], ['Gamma', 'Γ'], ['Delta', 'Δ'], ['Epsilon', 'Ε'], ['Zeta', 'Ζ'], ['Eta', 'Η'], ['Theta', 'Θ'], + ['Iota', 'Ι'], ['Kappa', 'Κ'], ['Lambda', 'Λ'], ['Mu', 'Μ'], ['Nu', 'Ν'], ['Xi', 'Ξ'], ['Omicron', 'Ο'], ['Pi', 'Π'], + ['Rho', 'Ρ'], ['Sigma', 'Σ'], ['Tau', 'Τ'], ['Upsilon', 'Υ'], ['Phi', 'Φ'], ['Chi', 'Χ'], ['Psi', 'Ψ'], ['Omega', 'Ω'], + ['alpha', 'α'], ['beta', 'β'], ['gamma', 'γ'], ['delta', 'δ'], ['epsilon', 'ε'], ['zeta', 'ζ'], ['eta', 'η'], ['theta', 'θ'], + ['iota', 'ι'], ['kappa', 'κ'], ['lambda', 'λ'], ['mu', 'μ'], ['nu', 'ν'], ['xi', 'ξ'], ['omicron', 'ο'], ['pi', 'π'], + ['rho', 'ρ'], ['sigmaf', 'ς'], ['sigma', 'σ'], ['tau', 'τ'], ['upsilon', 'υ'], ['phi', 'φ'], ['chi', 'χ'], ['psi', 'ψ'], + ['omega', 'ω'], ['thetasym', 'ϑ'], ['upsih', 'ϒ'], ['piv', 'ϖ'], + ['forall', '∀'], ['part', '∂'], ['exist', '∃'], ['empty', '∅'], ['nabla', '∇'], ['isin', '∈'], ['ni', '∋'], ['prod', '∏'], + ['sum', '∑'], ['minus', '−'], ['lowast', '∗'], ['radic', '√'], ['prop', '∝'], ['infin', '∞'], ['ang', '∠'], ['and', '∧'], + ['or', '∨'], ['cap', '∩'], ['cup', '∪'], ['int', '∫'], ['there4', '∴'], ['sim', '∼'], ['cong', '≅'], ['asymp', '≈'], + ['ne', '≠'], ['equiv', '≡'], ['sub', '⊂'], ['sup', '⊃'], ['nsub', '⊄'], ['sube', '⊆'], ['supe', '⊇'], ['oplus', '⊕'], + ['otimes', '⊗'], ['perp', '⊥'], ['sdot', '⋅'], + ['larr', '←'], ['uarr', '↑'], ['rarr', '→'], ['darr', '↓'], ['harr', '↔'], ['crarr', '↵'], + ['lArr', '⇐'], ['uArr', '⇑'], ['rArr', '⇒'], ['dArr', '⇓'], ['hArr', '⇔'] ]); const NUMERIC_REFERENCE_REPLACEMENTS = new Map([ From 7b956a3b0a9827f46c334f8d2e153c7890b1e0e8 Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Tue, 8 Sep 2026 00:59:49 +1000 Subject: [PATCH 3/3] docs: describe symbolic named-reference support --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cf8aa73..6d78d60 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,10 @@ See [`fixtures/sample`](fixtures/sample) for working examples. Conversion decodes semicolon-terminated named references used by the bundled fixtures, including common currency (`£`, `¢`, `¥`) and mathematical -(`∉`, `≤`, `≥`) references. The complete HTML Latin-1 set is -supported, so `½`, `×`, and `÷` become `½`, `×`, and `÷`. +(`∉`, `≤`, `≥`) references. It also supports the HTML 4 Greek-letter +set, common mathematical operators and relations, and directional arrows (for +example, `α`, `∑`, `≠`, and `→`). The complete HTML Latin-1 +set is supported, so `½`, `×`, and `÷` become `½`, `×`, and `÷`. Named references are case-sensitive: defined aliases such as `Á` decode, while undefined spellings such as `Á` remain unchanged. Unknown named references remain unchanged so fixture comparisons do not silently discard source text. @@ -113,9 +115,10 @@ hexadecimal forms may omit the trailing semicolon, null, surrogate, and out-of-range values become `�`, and the HTML C1 replacement table is applied. Common named references, including HTML Latin-1 names such as `ç` and `é`, `½`, `×`, and `÷` and symbols such as `™`, -`€`, and `“`, are decoded only -when the trailing semicolon is present and the name's case exactly matches a -defined reference. Unknown or differently cased named references are preserved. +`€`, and `“`, are supported alongside Greek letters, mathematical +operators and relations, and arrows. Named references are decoded only when +the trailing semicolon is present and the name's case exactly matches a defined +reference. Unknown or differently cased named references are preserved. ## Safety boundaries