From 723da4a5a6d1722cd34f8c2bf7e8c7ba0fc60b8e Mon Sep 17 00:00:00 2001 From: hermes Date: Sun, 18 Oct 2026 13:18:30 +0000 Subject: [PATCH] fix(core): the IPOT table and date inputs must sit in plain divs Inside details, dialog, noscript, object, select and the like they may not show, so the table there rejects the page and such a date input is no echoed date. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh --- crates/idx-core/src/api/ipot/parse.rs | 52 ++++++++++++++++++++------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/crates/idx-core/src/api/ipot/parse.rs b/crates/idx-core/src/api/ipot/parse.rs index 00b5cee..7a6ac58 100644 --- a/crates/idx-core/src/api/ipot/parse.rs +++ b/crates/idx-core/src/api/ipot/parse.rs @@ -163,10 +163,21 @@ fn cell(td: ElementRef) -> Option<(String, Option)> { Some((clean(&text), class)) } -/// The `MM/DD/YYYY` value of the one input with this id. +/// Whether `element` sits in plain divs only, as IPOT's table and date +/// inputs do; inside anything else (details, dialog, noscript, object, +/// select, ...) it may not show. +fn in_divs(element: ElementRef) -> bool { + element + .ancestors() + .filter_map(ElementRef::wrap) + .all(|ancestor| matches!(ancestor.value().name(), "div" | "html")) +} + +/// The `MM/DD/YYYY` value of the one input with this id, in plain divs. fn input_date(page: &Html, id: &str) -> Option { - let value = only(page.root_element(), &format!("input#{id}"))?.attr("value")?; - NaiveDate::parse_from_str(value.trim(), "%m/%d/%Y").ok() + let input = + only(page.root_element(), &format!("input#{id}")).filter(|input| in_divs(*input))?; + NaiveDate::parse_from_str(input.attr("value")?.trim(), "%m/%d/%Y").ok() } /// Whether anything in the page is hidden by markup alone (`hidden`, an @@ -200,6 +211,9 @@ pub fn parse_broker_summary(html: &str) -> Result { } let table = only(page.root_element(), "table") .ok_or_else(|| parse_error("no single table in the page"))?; + if !in_divs(table) { + return Err(parse_error("the table is not in plain page markup")); + } let body = only(table, "tbody").ok_or_else(|| parse_error("no single table body"))?; let mut raw = BrokerSummaryRaw { from: input_date(&page, "startDateBrokerSummary"), @@ -490,6 +504,22 @@ mod tests { 1, ), ), + ( + "the table in a select", + DAY.replacen("", + "", + 1, + ), + ), + ( + "the table in closed details", + DAY.replacen("", + "", + 1, + ), + ), ("no table", "".to_string()), // Text a browser would not show, or not as one figure or code. ( @@ -589,16 +619,6 @@ mod tests { 1, ), ), - // The HTML standard keeps a table inside a select since its 2025 - // select parsing change; html5ever and current browsers agree. - ( - "the table in a select", - DAY.replacen("", - "", - 1, - ), - ), ( "a script after the table", DAY.replacen("", "", 1), @@ -626,5 +646,11 @@ mod tests { None, "an id twice is no echoed date" ); + let closed = DAY.replacen("