labels/bdmt: drop description fields that are just XML child elements
Disc-04 (Top Gun: Maverick) re-test 2026-05-11 surfaced a real-world
bdmt_eng.xml where <di:description> contained no prose, only nested
<di:thumbnail href="…"/> elements. The previous parser surfaced
the raw XML fragment as the description string ("<di:thumbnail
href=\"tgm_meta_sm.jpg\" />\\r\\n <di:thumbnail
href=\"tgm_meta_lg.jpg\" />"). Worse than no description.
Fix: filter description candidates that begin with `<` after
trimming. Real prose never starts with an angle bracket; XML-only
content always does. Net: title extraction unaffected (it uses its
own element-priority path); description field drops when it would
otherwise carry XML noise.
Two new bdmt tests, 12 of 12 passing.
This commit is contained in:
+53
-1
@@ -136,11 +136,25 @@ pub(crate) type BdmtFields = (String, Option<String>, Option<(u32, u32)>);
|
|||||||
/// authoring-tool conventions documented at the module level).
|
/// authoring-tool conventions documented at the module level).
|
||||||
pub(crate) fn parse_bdmt_xml(_lang_code: &str, xml_text: &str) -> Option<BdmtFields> {
|
pub(crate) fn parse_bdmt_xml(_lang_code: &str, xml_text: &str) -> Option<BdmtFields> {
|
||||||
let title = extract_title(xml_text)?;
|
let title = extract_title(xml_text)?;
|
||||||
let description = xml::text(xml_text, "description").filter(|s| !s.is_empty());
|
let description = xml::text(xml_text, "description")
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.filter(|s| !looks_like_xml(s))
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
let disc_set = extract_disc_set(xml_text);
|
let disc_set = extract_disc_set(xml_text);
|
||||||
Some((title, description, disc_set))
|
Some((title, description, disc_set))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reject candidate description strings that are themselves XML
|
||||||
|
/// fragments — observed on disc-04 (Top Gun: Maverick), where
|
||||||
|
/// `<di:description>` contained `<di:thumbnail href="…"/>` child
|
||||||
|
/// elements and no actual prose. Surfacing that raw to the JSON
|
||||||
|
/// output is worse than dropping the field entirely.
|
||||||
|
fn looks_like_xml(s: &str) -> bool {
|
||||||
|
let t = s.trim_start();
|
||||||
|
t.starts_with('<')
|
||||||
|
}
|
||||||
|
|
||||||
/// Try title-bearing element variants in priority order. The `xml`
|
/// Try title-bearing element variants in priority order. The `xml`
|
||||||
/// helpers are case- and namespace-insensitive, so callers pass the
|
/// helpers are case- and namespace-insensitive, so callers pass the
|
||||||
/// bare local name (no `di:` prefix).
|
/// bare local name (no `di:` prefix).
|
||||||
@@ -326,6 +340,44 @@ mod tests {
|
|||||||
assert!(parse_bdmt_xml("eng", truncated).is_none());
|
assert!(parse_bdmt_xml("eng", truncated).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn description_with_only_child_xml_is_dropped() {
|
||||||
|
// Real-world bug from disc-04 (Top Gun: Maverick, 2026-05-11
|
||||||
|
// capture): <di:description> contained only <di:thumbnail/>
|
||||||
|
// child elements with no actual prose. The previous parser
|
||||||
|
// surfaced the raw XML fragment as the description string.
|
||||||
|
// Now we reject candidates that begin with `<`.
|
||||||
|
let xml = r#"<discInfo>
|
||||||
|
<di:name>Top Gun: Maverick</di:name>
|
||||||
|
<di:description>
|
||||||
|
<di:thumbnail href="tgm_meta_sm.jpg" />
|
||||||
|
<di:thumbnail href="tgm_meta_lg.jpg" />
|
||||||
|
</di:description>
|
||||||
|
</discInfo>"#;
|
||||||
|
let (title, description, _) =
|
||||||
|
parse_bdmt_xml("eng", xml).expect("title is present so parse must succeed");
|
||||||
|
assert_eq!(title, "Top Gun: Maverick");
|
||||||
|
assert!(
|
||||||
|
description.is_none(),
|
||||||
|
"description containing only XML children must be dropped, got {description:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn description_with_plain_text_passes_through() {
|
||||||
|
// The legitimate case still works: a description with actual
|
||||||
|
// prose survives the looks_like_xml filter.
|
||||||
|
let xml = r#"<discInfo>
|
||||||
|
<di:name>Some Movie</di:name>
|
||||||
|
<di:description>An epic tale of one man's quest for tea.</di:description>
|
||||||
|
</discInfo>"#;
|
||||||
|
let (_, description, _) = parse_bdmt_xml("eng", xml).expect("must parse");
|
||||||
|
assert_eq!(
|
||||||
|
description.as_deref(),
|
||||||
|
Some("An epic tale of one man's quest for tea.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn whitespace_in_title_is_trimmed() {
|
fn whitespace_in_title_is_trimmed() {
|
||||||
let xml = r#"<discInfo><di:name>
|
let xml = r#"<discInfo><di:name>
|
||||||
|
|||||||
Reference in New Issue
Block a user