use std::{
cell::{Cell, RefCell},
fmt::Write as _,
fs::read_dir,
path::Path,
sync::Arc,
};
use anyxml::{
error::{XMLErrorDomain, XMLErrorLevel},
sax::{
Locator,
attributes::Attributes,
handler::{DebugHandler, EntityResolver, ErrorHandler, SAXHandler},
parser::{ParserOption, XMLReaderBuilder},
},
uri::URIString,
};
#[derive(Default)]
struct TestSAXHandler {
warning: Cell<usize>,
error: Cell<usize>,
fatal_error: Cell<usize>,
ns_error: Cell<usize>,
validity_error: Cell<usize>,
buffer: RefCell<String>,
}
impl TestSAXHandler {
fn new() -> Self {
Self {
warning: Cell::new(0),
error: Cell::new(0),
fatal_error: Cell::new(0),
ns_error: Cell::new(0),
validity_error: Cell::new(0),
buffer: RefCell::new(String::new()),
}
}
fn reset(&self) {
self.warning.update(|_| 0);
self.error.update(|_| 0);
self.fatal_error.update(|_| 0);
self.ns_error.update(|_| 0);
self.validity_error.update(|_| 0);
self.buffer.borrow_mut().clear();
}
}
impl EntityResolver for TestSAXHandler {}
impl ErrorHandler for TestSAXHandler {
fn fatal_error(&mut self, error: anyxml::sax::error::SAXParseError) {
assert_eq!(error.level, XMLErrorLevel::FatalError);
self.fatal_error.update(|c| c + 1);
writeln!(self.buffer.borrow_mut(), "{}", error).ok();
}
fn error(&mut self, error: anyxml::sax::error::SAXParseError) {
assert_eq!(error.level, XMLErrorLevel::Error);
match error.domain {
XMLErrorDomain::Parser => self.error.update(|c| c + 1),
XMLErrorDomain::Namespace => self.ns_error.update(|c| c + 1),
XMLErrorDomain::DTDValid => self.validity_error.update(|c| c + 1),
XMLErrorDomain::RngParser | XMLErrorDomain::C14N | XMLErrorDomain::XInclude => {
self.error.update(|c| c + 1)
}
}
writeln!(self.buffer.borrow_mut(), "{}", error).ok();
}
fn warning(&mut self, error: anyxml::sax::error::SAXParseError) {
assert_eq!(error.level, XMLErrorLevel::Warning);
self.warning.update(|c| c + 1);
writeln!(self.buffer.borrow_mut(), "{}", error).ok();
}
}
impl SAXHandler for TestSAXHandler {}
#[test]
fn well_formed_tests() {
let handler = DebugHandler {
child: TestSAXHandler::new(),
buffer: String::new(),
};
let mut reader = XMLReaderBuilder::new().set_handler(handler).build();
for ent in read_dir("resources/well-formed").unwrap() {
if let Ok(ent) = ent
&& ent.metadata().unwrap().is_file()
{
let path = ent.path();
let uri = URIString::parse_file_path(path.canonicalize().unwrap()).unwrap();
reader.parse_uri(&uri, None).ok();
assert_eq!(
reader.handler.child.fatal_error.get(),
0,
"uri: {}\nerrors:\n{}",
uri.as_escaped_str(),
reader.handler.child.buffer.borrow(),
);
let outname = path.file_name().unwrap().to_str().unwrap();
let outname = format!("resources/well-formed/output/{outname}.sax");
let outname = Path::new(outname.as_str());
let output = std::fs::read_to_string(outname).unwrap();
assert_eq!(
output,
reader.handler.buffer,
"uri: {}\n{}",
uri.as_escaped_str(),
reader.handler.buffer,
);
reader.handler.buffer.clear();
reader.handler.child.reset();
}
}
}
#[test]
fn progressive_well_formed_tests() {
let handler = DebugHandler {
child: TestSAXHandler::new(),
buffer: String::new(),
};
let mut reader = XMLReaderBuilder::new()
.set_handler(handler)
.progressive_parser()
.build();
for ent in read_dir("resources/well-formed").unwrap() {
if let Ok(ent) = ent
&& ent.metadata().unwrap().is_file()
{
let path = ent.path();
let buffer = std::fs::read(&path).unwrap();
let uri = URIString::parse_file_path(path.canonicalize().unwrap()).unwrap();
reader.set_default_base_uri(uri.as_ref()).unwrap();
reader.reset().unwrap();
for b in buffer {
reader.parse_chunk([b], false).unwrap();
}
reader.parse_chunk([], true).unwrap();
assert_eq!(
reader.handler.child.fatal_error.get(),
0,
"uri: {}\nerrors:\n{}",
uri.as_escaped_str(),
reader.handler.child.buffer.borrow(),
);
let outname = path.file_name().unwrap().to_str().unwrap();
let outname = format!("resources/well-formed/output/{outname}.sax");
let outname = Path::new(outname.as_str());
let output = std::fs::read_to_string(outname).unwrap();
assert_eq!(
output,
reader.handler.buffer,
"\nuri: {}\n{}",
uri.as_escaped_str(),
reader.handler.buffer,
);
reader.handler.buffer.clear();
reader.handler.child.reset();
}
}
}
const SKIP_TESTS: &[&str] = &[
"pr-xml-euc-jp", "pr-xml-iso-2022-jp", "pr-xml-shift_jis", "weekly-euc-jp", "weekly-iso-2022-jp", "weekly-shift_jis", "ibm-not-wf-P69-ibm69n05.xml", "invalid-bo-7", "invalid-bo-8", "invalid-bo-9", ];
#[derive(Default)]
struct XMLConfWalker {
log: RefCell<String>,
locator: RefCell<Option<Arc<Locator>>>,
unexpected_failure: Cell<usize>,
unexpected_success: Cell<usize>,
progressive: bool,
}
impl XMLConfWalker {}
impl SAXHandler for XMLConfWalker {
fn set_document_locator(&mut self, locator: Arc<Locator>) {
*self.locator.borrow_mut() = Some(locator);
}
fn start_element(
&mut self,
_uri: Option<&str>,
_local_name: Option<&str>,
qname: &str,
atts: &Attributes,
) {
match qname {
"TESTSUITE" => {
for att in atts.iter() {
if att.qname.as_ref() == "PROFILE" {
writeln!(
self.log.borrow_mut(),
"=== Start Test Suite '{}' ===",
att.value
)
.ok();
}
}
}
"TESTCASES" => {
for att in atts.iter() {
if att.qname.as_ref() == "PROFILE" {
writeln!(
self.log.borrow_mut(),
"--- Start Test Case '{}' in '{}' ---",
att.value,
self.locator
.borrow()
.as_ref()
.unwrap()
.system_id()
.as_escaped_str()
)
.ok();
}
}
}
"TEST" => {
let mut id = String::new();
let mut r#type = String::new();
let mut uri = String::new();
let mut recommendation = String::new();
let mut edition = String::new();
let mut entities = String::new();
for att in atts.iter() {
match att.qname.as_ref() {
"TYPE" => r#type = att.value.to_string(),
"ID" => id = att.value.to_string(),
"URI" => uri = att.value.to_string(),
"RECOMMENDATION" => recommendation = att.value.to_string(),
"EDITION" => edition = att.value.to_string(),
"ENTITIES" => entities = att.value.to_string(),
_ => {}
}
}
if SKIP_TESTS.contains(&id.as_str()) {
return;
}
if recommendation == "XML1.1" || recommendation == "NS1.1" {
return;
}
if !edition.is_empty() {
let editions = edition
.split_ascii_whitespace()
.map(|e| e.trim())
.collect::<Vec<_>>();
if !editions.contains(&"5") {
return;
}
}
let uri = self
.locator
.borrow()
.as_ref()
.unwrap()
.system_id()
.resolve(&URIString::parse(uri).unwrap());
let mut reader = XMLReaderBuilder::new().set_handler(DebugHandler {
child: TestSAXHandler::new(),
buffer: String::new(),
});
if entities != "none" || matches!(r#type.as_ref(), "valid" | "invalid") {
reader = reader.enable_option(ParserOption::Validation)
}
let handler = if !self.progressive {
let mut reader = reader.build();
reader.parse_uri(uri, None).ok();
reader.handler.child
} else {
let data = std::fs::read(uri.path()).unwrap();
let mut reader = reader
.set_default_base_uri(uri)
.unwrap()
.progressive_parser()
.build();
for b in data {
reader.parse_chunk([b], false).ok();
}
reader.parse_chunk([], true).ok();
reader.handler.child
};
match r#type.as_str() {
"not-wf" => {
if handler.fatal_error.get() > 0
|| (recommendation.starts_with("NS1.0") && handler.ns_error.get() > 0)
{
} else {
self.unexpected_success.update(|c| c + 1);
writeln!(self.log.borrow_mut(), "{id}: unexpected success").ok();
writeln!(self.log.borrow_mut(), "{}", handler.buffer.borrow()).ok();
}
}
"error" => {
if (handler.error.get() == 0
&& handler.validity_error.get() == 0
&& handler.ns_error.get() == 0)
|| handler.fatal_error.get() > 0
{
self.unexpected_success.update(|c| c + 1);
writeln!(self.log.borrow_mut(), "{id}: unexpected success").ok();
writeln!(self.log.borrow_mut(), "{}", handler.buffer.borrow()).ok();
}
}
"valid" => {
if handler.validity_error.get() > 0 {
self.unexpected_failure.update(|c| c + 1);
writeln!(self.log.borrow_mut(), "{id}: unexpected failure").ok();
writeln!(self.log.borrow_mut(), "{}", handler.buffer.borrow()).ok();
}
}
"invalid" => {
if handler.validity_error.get() == 0 {
self.unexpected_success.update(|c| c + 1);
writeln!(self.log.borrow_mut(), "{id}: unexpected success").ok();
writeln!(self.log.borrow_mut(), "{}", handler.buffer.borrow()).ok();
}
}
r#type => {
writeln!(
self.log.borrow_mut(),
"{id}: test type '{}' is unknown.",
r#type
)
.ok();
}
}
}
_ => {}
}
}
}
impl EntityResolver for XMLConfWalker {}
impl ErrorHandler for XMLConfWalker {}
#[test]
fn xmlconf_tests() {
const XMLCONF_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/resources/xmlconf");
assert!(
std::fs::exists(XMLCONF_DIR).unwrap_or(false),
"Please execute `deno run -A resources/get-xmlconf.ts` on the crate root."
);
let xmlconf = URIString::parse_file_path(format!("{XMLCONF_DIR}/xmlconf.xml")).unwrap();
let mut reader = XMLReaderBuilder::new()
.set_handler(XMLConfWalker::default())
.enable_option(ParserOption::ExternalGeneralEntities)
.build();
reader.parse_uri(xmlconf, None).unwrap();
let handler = reader.take_handler();
assert!(
handler.unexpected_success.get() == 0 && handler.unexpected_failure.get() == 0,
"{}\n=== Unexpected Success: {}, Unexpected Failure: {} ===\n",
handler.log.borrow(),
handler.unexpected_success.get(),
handler.unexpected_failure.get(),
);
}
#[test]
fn progressive_xmlconf_tests() {
const XMLCONF_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/resources/xmlconf");
assert!(
std::fs::exists(XMLCONF_DIR).unwrap_or(false),
"Please execute `deno run -A resources/get-xmlconf.ts` on the crate root."
);
let path = format!("{XMLCONF_DIR}/xmlconf.xml");
let handler = XMLConfWalker {
progressive: true,
..Default::default()
};
let xmlconf = URIString::parse_file_path(path.as_str()).unwrap();
let mut reader = XMLReaderBuilder::new()
.set_handler(handler)
.enable_option(ParserOption::ExternalGeneralEntities)
.set_default_base_uri(xmlconf)
.unwrap()
.progressive_parser()
.build();
for chunk in std::fs::read_to_string(path).unwrap().bytes() {
reader.parse_chunk([chunk], false).unwrap();
}
reader.parse_chunk([], true).unwrap();
let handler = reader.take_handler();
assert!(
handler.unexpected_success.get() == 0 && handler.unexpected_failure.get() == 0,
"{}\n=== Unexpected Success: {}, Unexpected Failure: {} ===\n",
handler.log.borrow(),
handler.unexpected_success.get(),
handler.unexpected_failure.get(),
);
}