use sup_xml_tree::dom::Document;
use super::{AttDefault, Dtd};
pub fn inject_defaults(doc: &Document, dtd: &Dtd) -> usize {
if dtd.is_empty() { return 0; }
inject_defaults_from(doc.root(), dtd, doc)
}
pub fn inject_defaults_from<'a>(
root: &'a sup_xml_tree::dom::Node<'a>,
dtd: &Dtd,
doc: &'a Document,
) -> usize {
if dtd.is_empty() { return 0; }
let mut count = 0usize;
walk(root, dtd, doc, &mut count);
count
}
fn walk<'a>(
node: &'a sup_xml_tree::dom::Node<'a>,
dtd: &Dtd,
doc: &'a Document,
count: &mut usize,
) {
if !node.is_element() { return; }
if let Some(attlist) = dtd.attlists.get(node.name()) {
for decl in attlist {
let default_value: &str = match &decl.default {
AttDefault::Default(v) | AttDefault::Fixed(v) => v.as_str(),
AttDefault::Required | AttDefault::Implied => continue,
};
if node.attributes().any(|a| a.name() == decl.name) {
continue; }
let name = doc.bump().alloc_str(&decl.name);
let value = doc.bump().alloc_str(default_value);
let attr = doc.new_attribute(name, value);
doc.append_attribute(node, attr);
*count += 1;
}
}
for child in node.children() {
walk(child, dtd, doc, count);
}
}
#[cfg(test)]
mod tests {
use crate::options::ParseOptions;
use crate::parser::parse_bytes_with_dtd;
use super::inject_defaults;
fn parse(src: &str) -> (sup_xml_tree::dom::Document, crate::dtd::Dtd) {
let opts = ParseOptions { namespace_aware: false, ..ParseOptions::default() };
parse_bytes_with_dtd(src.as_bytes(), &opts).expect("parse")
}
#[test]
fn injects_literal_default() {
let src = r#"<?xml version="1.0"?>
<!DOCTYPE r [
<!ELEMENT r EMPTY>
<!ATTLIST r kind CDATA "alpha">
]>
<r/>"#;
let (doc, dtd) = parse(src);
let n = inject_defaults(&doc, &dtd);
assert_eq!(n, 1);
let root = doc.root();
let got = root.attributes().find(|a| a.name() == "kind").map(|a| a.value());
assert_eq!(got, Some("alpha"));
}
#[test]
fn injects_fixed_default() {
let src = r#"<?xml version="1.0"?>
<!DOCTYPE r [
<!ELEMENT r EMPTY>
<!ATTLIST r version CDATA #FIXED "1.0">
]>
<r/>"#;
let (doc, dtd) = parse(src);
assert_eq!(inject_defaults(&doc, &dtd), 1);
let root = doc.root();
let got = root.attributes().find(|a| a.name() == "version").map(|a| a.value());
assert_eq!(got, Some("1.0"));
}
#[test]
fn preserves_explicit_value() {
let src = r#"<?xml version="1.0"?>
<!DOCTYPE r [
<!ELEMENT r EMPTY>
<!ATTLIST r kind CDATA "alpha">
]>
<r kind="beta"/>"#;
let (doc, dtd) = parse(src);
assert_eq!(inject_defaults(&doc, &dtd), 0);
let got = doc.root().attributes().find(|a| a.name() == "kind").map(|a| a.value());
assert_eq!(got, Some("beta"));
}
#[test]
fn required_and_implied_skipped() {
let src = r#"<?xml version="1.0"?>
<!DOCTYPE r [
<!ELEMENT r EMPTY>
<!ATTLIST r
must CDATA #REQUIRED
maybe CDATA #IMPLIED>
]>
<r must="x"/>"#;
let (doc, dtd) = parse(src);
assert_eq!(inject_defaults(&doc, &dtd), 0);
}
#[test]
fn injects_on_descendants() {
let src = r#"<?xml version="1.0"?>
<!DOCTYPE r [
<!ELEMENT r (a, a)>
<!ELEMENT a EMPTY>
<!ATTLIST a tag CDATA "default-tag">
]>
<r><a/><a tag="custom"/></r>"#;
let (doc, dtd) = parse(src);
let n = inject_defaults(&doc, &dtd);
assert_eq!(n, 1);
let mut tags: Vec<&str> = Vec::new();
for child in doc.root().children() {
if child.is_element() {
if let Some(a) = child.attributes().find(|a| a.name() == "tag") {
tags.push(a.value());
}
}
}
assert_eq!(tags, vec!["default-tag", "custom"]);
}
}