* Hybrid question text extraction for radio/checkbox * Step 1: Class-based heuristics (preferred - more precise) * Step 2: DOM traversal fallback (high recall)
(element)
| 215 | * Step 2: DOM traversal fallback (high recall) |
| 216 | */ |
| 217 | function findQuestionText(element) { |
| 218 | // === STEP 1: Class-based heuristics (preferred) === |
| 219 | let ancestor = element.parentElement; |
| 220 | let depth = 0; |
| 221 | |
| 222 | while (ancestor && depth < 6) { |
| 223 | // Check if ancestor has label-like class |
| 224 | const ancestorClasses = (ancestor.className || '').toString().toLowerCase(); |
| 225 | const hasLabelClass = LABEL_CLASS_PATTERNS.some(p => ancestorClasses.includes(p)); |
| 226 | |
| 227 | if (hasLabelClass) { |
| 228 | // Look for children/siblings with question text |
| 229 | const questionEl = findQuestionElementInContainer(ancestor, element); |
| 230 | if (questionEl) { |
| 231 | const text = cleanLabelText(questionEl, element); |
| 232 | if (text && text.length > 10) return text; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // Also check children of ancestor for label-like elements |
| 237 | for (const child of ancestor.children) { |
| 238 | if (child.contains(element)) continue; // Skip branch containing our element |
| 239 | |
| 240 | const childClasses = (child.className || '').toString().toLowerCase(); |
| 241 | if (LABEL_CLASS_PATTERNS.some(p => childClasses.includes(p))) { |
| 242 | const text = child.textContent?.trim(); |
| 243 | if (text && text.length > 10 && text.length < 500) { |
| 244 | return text; |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | ancestor = ancestor.parentElement; |
| 250 | depth++; |
| 251 | } |
| 252 | |
| 253 | // === STEP 2: DOM traversal fallback (high recall) === |
| 254 | return findNearbyLabelText(element); |
| 255 | } |
| 256 | |
| 257 | /** |
| 258 | * Find question element within a container |
no test coverage detected