(html: string, tagName: string)
| 631 | } |
| 632 | |
| 633 | export function extractTag(html: string, tagName: string): string | null { |
| 634 | if (!html.trim() || !tagName.trim()) { |
| 635 | return null |
| 636 | } |
| 637 | |
| 638 | const escapedTag = escapeRegExp(tagName) |
| 639 | |
| 640 | // Create regex pattern that handles: |
| 641 | // 1. Self-closing tags |
| 642 | // 2. Tags with attributes |
| 643 | // 3. Nested tags of the same type |
| 644 | // 4. Multiline content |
| 645 | const pattern = new RegExp( |
| 646 | `<${escapedTag}(?:\\s+[^>]*)?>` + // Opening tag with optional attributes |
| 647 | '([\\s\\S]*?)' + // Content (non-greedy match) |
| 648 | `<\\/${escapedTag}>`, // Closing tag |
| 649 | 'gi', |
| 650 | ) |
| 651 | |
| 652 | let match |
| 653 | let depth = 0 |
| 654 | let lastIndex = 0 |
| 655 | const openingTag = new RegExp(`<${escapedTag}(?:\\s+[^>]*?)?>`, 'gi') |
| 656 | const closingTag = new RegExp(`<\\/${escapedTag}>`, 'gi') |
| 657 | |
| 658 | while ((match = pattern.exec(html)) !== null) { |
| 659 | // Check for nested tags |
| 660 | const content = match[1] |
| 661 | const beforeMatch = html.slice(lastIndex, match.index) |
| 662 | |
| 663 | // Reset depth counter |
| 664 | depth = 0 |
| 665 | |
| 666 | // Count opening tags before this match |
| 667 | openingTag.lastIndex = 0 |
| 668 | while (openingTag.exec(beforeMatch) !== null) { |
| 669 | depth++ |
| 670 | } |
| 671 | |
| 672 | // Count closing tags before this match |
| 673 | closingTag.lastIndex = 0 |
| 674 | while (closingTag.exec(beforeMatch) !== null) { |
| 675 | depth-- |
| 676 | } |
| 677 | |
| 678 | // Only include content if we're at the correct nesting level |
| 679 | if (depth === 0 && content) { |
| 680 | return content |
| 681 | } |
| 682 | |
| 683 | lastIndex = match.index + match[0].length |
| 684 | } |
| 685 | |
| 686 | return null |
| 687 | } |
| 688 | |
| 689 | export function isNotEmptyMessage(message: Message): boolean { |
| 690 | if ( |
no test coverage detected