|
63 | 63 | } |
64 | 64 |
|
65 | 65 | const parser = new DOMParser(); |
66 | | - // TODO: find better solution for <default font> replacement (this was causing xml parse errors) |
67 | | - const xmlDoc = parser.parseFromString( |
68 | | - xmlString.replaceAll("'<default font>',", ''), |
69 | | - 'text/xml' |
70 | | - ); |
| 66 | + // parse as HTML instead of XML. HTML parsing is very forgiving, so we won't really get any parse errors |
| 67 | + const htmlDoc = parser.parseFromString(xmlString, 'text/html'); |
71 | 68 |
|
72 | 69 | // Collect audio elements to add at the end |
73 | 70 | // eslint-disable-next-line svelte/prefer-svelte-reactivity |
74 | 71 | const audioElements = new Map<string, string>(); |
75 | 72 |
|
76 | | - const parseError = xmlDoc.querySelector('parsererror'); |
77 | | - if (parseError) { |
78 | | - console.error('XML parsing error:', parseError.textContent); |
79 | | - return `<span class="text-error">Error parsing XML: Invalid format</span>`; |
80 | | - } |
81 | | -
|
82 | 73 | function processNode(node: Node, parentHasSenseNumber = false): string { |
83 | 74 | if (node.nodeType === Node.TEXT_NODE) { |
84 | 75 | return node.nodeValue?.trim() ?? ''; |
85 | 76 | } else if (node.nodeType === Node.ELEMENT_NODE) { |
86 | 77 | const el = node as HTMLElement; |
| 78 | + const tagName = el.tagName.toLowerCase(); |
87 | 79 | let isSenseNumber = el.classList.contains('sensenumber'); |
88 | 80 |
|
89 | 81 | let parentContainsSenseNumber = |
|
92 | 84 | child.classList.contains('sensenumber') |
93 | 85 | ); |
94 | 86 |
|
95 | | - if (el.tagName === 'a' && el.hasAttribute('href')) { |
| 87 | + if (tagName === 'a' && el.hasAttribute('href')) { |
96 | 88 | const href = el.getAttribute('href'); |
97 | 89 | let dataAttributes = ''; |
98 | 90 | let linkText = el.childNodes |
|
116 | 108 | } else { |
117 | 109 | return createElementString(el, parentContainsSenseNumber || isSenseNumber); |
118 | 110 | } |
119 | | - } else if (el.tagName === 'audio-link' && el.hasAttribute('src')) { |
| 111 | + } else if (tagName === 'audio-link' && el.hasAttribute('src')) { |
120 | 112 | // Handle audio-link tag - create audio element and clickable link |
121 | 113 | const audioFile = el.getAttribute('src'); |
122 | 114 | const src = clips[`./${audioFile}`] ?? 'clips/' + audioFile; |
|
127 | 119 |
|
128 | 120 | // Add just the inline clickable icon - no audio element here |
129 | 121 | return `<button type="button" class="audio-link" data-audio-id="${audioId}" aria-label="Play audio" style="display: inline-block; vertical-align: middle; margin: 0 2px; width: 24px; height: 24px; overflow: visible;"><svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="display: block; overflow: visible;"><path d="M14 20.725v-2.05q2.25-.65 3.625-2.5t1.375-4.2q0-2.35-1.375-4.2T14 5.275v-2.05q3.1.7 5.05 3.137Q21 8.8 21 11.975q0 3.175-1.95 5.612-1.95 2.438-5.05 3.138ZM3 15V9h4l5-5v16l-5-5Zm11 1V7.95q1.175.55 1.838 1.65.662 1.1.662 2.4q0 1.275-.662 2.362Q15.175 15.45 14 16Z"/></svg></button>`; |
130 | | - } else if (el.tagName === 'img' && el.hasAttribute('src')) { |
| 122 | + } else if (tagName === 'img' && el.hasAttribute('src')) { |
131 | 123 | const src = el.getAttribute('src')?.replace(/^illustrations\//, ''); |
132 | 124 | const hashedSrc = src && illustrations[`./${src}`]; |
133 | 125 | if (!hashedSrc) { |
|
168 | 160 | } |
169 | 161 |
|
170 | 162 | return ( |
171 | | - processNode(xmlDoc.documentElement) + |
| 163 | + processNode(htmlDoc.documentElement) + |
172 | 164 | audioElements |
173 | 165 | .entries() |
174 | 166 | .map( |
|
0 commit comments