use oxml::{Limits, parse, parse_with_external};
fn with_dtd(doc: &str, dtd: &str) -> Result<(), oxml::ErrorKind> {
let source: &[(&str, &str)] = &[("d.dtd", dtd)];
parse_with_external(doc, Limits::default(), &source)
.map(|_| ())
.map_err(|e| e.kind)
}
const DOC: &str = r#"<!DOCTYPE root SYSTEM "d.dtd">
<root/>"#;
#[test]
fn include_and_ignore_are_the_only_keywords() {
for keyword in ["INCLUDE", "IGNORE"] {
let dtd = format!(
"<!ELEMENT root EMPTY>\n<![{keyword}[ <!ELEMENT a EMPTY> ]]>"
);
assert!(with_dtd(DOC, &dtd).is_ok(), "{keyword} is a keyword");
}
for keyword in ["CDATA", "INCLUDED", "", "include"] {
let dtd = format!(
"<!ELEMENT root EMPTY>\n<![{keyword}[ <!ELEMENT a EMPTY> ]]>"
);
assert!(
with_dtd(DOC, &dtd).is_err(),
"{keyword:?} is not a conditional-section keyword"
);
}
}
#[test]
fn a_keyword_from_a_parameter_entity_is_still_checked() {
for (value, ok) in [
("INCLUDE", true),
("IGNORE", true),
("CDATA", false),
("", false),
] {
let dtd = format!(
"<!ENTITY % MAYBE \"{value}\">\n<![%MAYBE;[ <!ELEMENT a EMPTY> ]]>"
);
assert_eq!(
with_dtd(DOC, &dtd).is_ok(),
ok,
"keyword {value:?} from a parameter entity"
);
}
}
#[test]
fn the_internal_subset_overrides_the_keyword() {
let dtd =
"<!ENTITY % MAYBE \"IGNORE\">\n<![%MAYBE;[ <!ENTITY root EMTPY> ]]>";
for bad in ["CDATA", ""] {
let doc = format!(
"<!DOCTYPE root SYSTEM \"d.dtd\" [\n<!ENTITY % MAYBE \"{bad}\">\n]>\n<root/>"
);
assert!(
with_dtd(&doc, dtd).is_err(),
"{bad:?} from the internal subset must be refused"
);
}
let doc = "<!DOCTYPE root SYSTEM \"d.dtd\" [\n<!ENTITY % MAYBE \"IGNORE\">\n]>\n<root/>";
assert!(with_dtd(doc, dtd).is_ok());
}
#[test]
fn a_parameter_entity_may_supply_the_bracket() {
let dtd = "<!ENTITY % e \"INCLUDE[\">\n<!ELEMENT doc (#PCDATA)>\n<![ %e; <!ATTLIST doc a1 CDATA \"v1\"> ]]>";
let doc = "<!DOCTYPE doc SYSTEM \"d.dtd\">\n<doc></doc>";
assert!(
with_dtd(doc, dtd).is_ok(),
"an entity may carry the bracket with the keyword"
);
let bad = "<!ENTITY % e \"CDATA[\">\n<!ELEMENT doc (#PCDATA)>\n<![ %e; <!ELEMENT a EMPTY> ]]>";
assert!(with_dtd(doc, bad).is_err(), "{bad:?} is not a keyword");
}
#[test]
fn an_unavailable_subset_is_not_an_error() {
let doc = "<!DOCTYPE root SYSTEM \"d.dtd\" [\n<!ENTITY % MAYBE \"CDATA\">\n]>\n<root/>";
assert!(parse(doc).is_ok(), "nothing was read, so nothing is wrong");
}
#[test]
fn conditional_sections_stay_out_of_the_internal_subset() {
let doc = "<!DOCTYPE root [\n<!ELEMENT root EMPTY>\n<![INCLUDE[ <!ELEMENT a EMPTY> ]]>\n]>\n<root/>";
assert!(parse(doc).is_err(), "only the external subset may have one");
}