(selfClosing: boolean)
| 302 | } |
| 303 | |
| 304 | onStartTagClose(selfClosing: boolean): void { |
| 305 | if (this.#hasPendingElement) { |
| 306 | // XML 1.0 §2.8: Only one root element is allowed |
| 307 | if (this.#rootClosed) { |
| 308 | throw new XmlSyntaxError( |
| 309 | "Only one root element is allowed (XML 1.0 §2.8)", |
| 310 | { |
| 311 | line: this.#pendingLine, |
| 312 | column: this.#pendingColumn, |
| 313 | offset: this.#pendingOffset, |
| 314 | }, |
| 315 | ); |
| 316 | } |
| 317 | |
| 318 | // Validate element prefix is bound (if it has one) and resolve URI |
| 319 | let elementUri: string | undefined; |
| 320 | if (this.#pendingColonIndex !== -1) { |
| 321 | const prefix = this.#pendingName.slice(0, this.#pendingColonIndex); |
| 322 | // xml prefix is always implicitly bound, xmlns prefix is handled separately |
| 323 | if (prefix !== "xml" && prefix !== "xmlns") { |
| 324 | elementUri = this.#nsBindings?.get(prefix); |
| 325 | if (elementUri === undefined) { |
| 326 | throw new XmlSyntaxError( |
| 327 | `Unbound namespace prefix '${prefix}' in element <${this.#pendingName}>`, |
| 328 | { |
| 329 | line: this.#pendingLine, |
| 330 | column: this.#pendingColumn, |
| 331 | offset: this.#pendingOffset, |
| 332 | }, |
| 333 | ); |
| 334 | } |
| 335 | } else if (prefix === "xml") { |
| 336 | elementUri = XML_NAMESPACE; |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | // Validate attribute prefixes are bound, resolve URIs, and check for duplicate expanded names |
| 341 | // Optimized: only do expensive expansion check when same local name appears with different prefixes |
| 342 | const attrCount = this.#attrIterator.count; |
| 343 | let localNameToPrefixes: Map<string, string[]> | null = null; |
| 344 | |
| 345 | for (let i = 0; i < attrCount; i++) { |
| 346 | const colonIdx = this.#attrIterator.getColonIndex(i); |
| 347 | if (colonIdx !== -1) { |
| 348 | const attrName = this.#attrIterator.getName(i); |
| 349 | const prefix = attrName.slice(0, colonIdx); |
| 350 | // Skip xmlns: attributes (they declare, not use, prefixes) |
| 351 | if (prefix === "xmlns") { |
| 352 | // xmlns: attributes don't have namespace URIs (they ARE the declarations) |
| 353 | continue; |
| 354 | } |
| 355 | // xml prefix is always implicitly bound |
| 356 | if (prefix === "xml") { |
| 357 | this.#attrIterator._setUri(i, XML_NAMESPACE); |
| 358 | continue; |
| 359 | } |
| 360 | const attrUri = this.#nsBindings?.get(prefix); |
| 361 | if (attrUri === undefined) { |
nothing calls this directly
no test coverage detected