(text: string, xml11: boolean)
| 97 | * @throws {Error} If the text contains invalid or unknown entity references. |
| 98 | */ |
| 99 | export function decodeEntities(text: string, xml11: boolean): string { |
| 100 | // Fast path: no ampersand means no entities to decode |
| 101 | if (!text.includes("&")) return text; |
| 102 | |
| 103 | const isValid = xml11 ? isValidXml11CharRef : isValidXml10CharRef; |
| 104 | |
| 105 | // Single-pass: decode predefined entities and char refs, error on invalid |
| 106 | return text.replace( |
| 107 | ENTITY_OR_AMPERSAND_REGEXP, |
| 108 | ( |
| 109 | match: string, |
| 110 | namedEntity: string | undefined, |
| 111 | decimalRef: string | undefined, |
| 112 | hexRef: string | undefined, |
| 113 | offset: number, |
| 114 | ) => { |
| 115 | // Hex character reference (&#xNN;) |
| 116 | if (hexRef !== undefined) { |
| 117 | const codePoint = parseInt(hexRef.slice(2), 16); |
| 118 | if (!isValid(codePoint)) { |
| 119 | throw new Error( |
| 120 | `Invalid character reference '${match}' at position ${offset}: ` + |
| 121 | `code point ${codePoint} is not a valid XML character`, |
| 122 | ); |
| 123 | } |
| 124 | return String.fromCodePoint(codePoint); |
| 125 | } |
| 126 | |
| 127 | // Decimal character reference (&#NN;) |
| 128 | if (decimalRef !== undefined) { |
| 129 | const codePoint = parseInt(decimalRef.slice(1), 10); |
| 130 | if (!isValid(codePoint)) { |
| 131 | throw new Error( |
| 132 | `Invalid character reference '${match}' at position ${offset}: ` + |
| 133 | `code point ${codePoint} is not a valid XML character`, |
| 134 | ); |
| 135 | } |
| 136 | return String.fromCodePoint(codePoint); |
| 137 | } |
| 138 | |
| 139 | // Named entity (&name;) |
| 140 | if (namedEntity !== undefined) { |
| 141 | const predefined = NAMED_ENTITIES[namedEntity]; |
| 142 | if (predefined !== undefined) { |
| 143 | return predefined; |
| 144 | } |
| 145 | throw new Error( |
| 146 | `Unknown entity '${match}' at position ${offset}: ` + |
| 147 | `only predefined entities (lt, gt, amp, apos, quot) are recognized`, |
| 148 | ); |
| 149 | } |
| 150 | |
| 151 | // Bare ampersand (no valid entity pattern matched) |
| 152 | throw new Error( |
| 153 | `Invalid bare '&' at position ${offset}: ` + |
| 154 | `use & or a valid entity reference (&name;, &#num;, &#xHex;)`, |
| 155 | ); |
| 156 | }, |
no test coverage detected