(source: string)
| 360 | * malformed output. |
| 361 | */ |
| 362 | const parseAttributes = (source: string): readonly (readonly [string, string])[] => { |
| 363 | const attributes: (readonly [string, string])[] = []; |
| 364 | let index = 0; |
| 365 | while (index < source.length) { |
| 366 | while (index < source.length && /\s/.test(source[index] ?? "")) index += 1; |
| 367 | const nameStart = index; |
| 368 | while (index < source.length && !/[\s=/>]/.test(source[index] ?? "")) index += 1; |
| 369 | const name = source.slice(nameStart, index).toLowerCase(); |
| 370 | if (name === "") { |
| 371 | index += 1; |
| 372 | continue; |
| 373 | } |
| 374 | while (index < source.length && /\s/.test(source[index] ?? "")) index += 1; |
| 375 | if (source[index] !== "=") { |
| 376 | // A bare attribute (`hidden`). Valueless attributes carry no payload, so |
| 377 | // the empty string is the faithful reading. |
| 378 | attributes.push([name, ""]); |
| 379 | continue; |
| 380 | } |
| 381 | index += 1; |
| 382 | while (index < source.length && /\s/.test(source[index] ?? "")) index += 1; |
| 383 | const quote = source[index]; |
| 384 | if (quote === '"' || quote === "'") { |
| 385 | index += 1; |
| 386 | const valueStart = index; |
| 387 | while (index < source.length && source[index] !== quote) index += 1; |
| 388 | attributes.push([name, source.slice(valueStart, index)]); |
| 389 | index += 1; |
| 390 | } else { |
| 391 | const valueStart = index; |
| 392 | while (index < source.length && !/[\s>]/.test(source[index] ?? "")) index += 1; |
| 393 | attributes.push([name, source.slice(valueStart, index)]); |
| 394 | } |
| 395 | } |
| 396 | return attributes; |
| 397 | }; |
| 398 | |
| 399 | const sanitizeAttributes = (tag: string, source: string): string => { |
| 400 | const kept: string[] = []; |
no test coverage detected