diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml index 79ebebb..69d68df 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/undeepend2.iml b/.idea/undeepend2.iml deleted file mode 100644 index cf84ae4..0000000 --- a/.idea/undeepend2.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ea589e4..9540641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,12 +238,13 @@ dependencies = [ ] [[package]] -name = "undeepend2" +name = "undeepend" version = "0.1.0" dependencies = [ "anyhow", "env_logger", "log", + "regex", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cbfd1e6..f7ae982 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] -name = "undeepend2" +name = "undeepend" version = "0.1.0" edition = "2024" [dependencies] anyhow = "1.0" log = "0.4" -env_logger = "0.11" \ No newline at end of file +env_logger = "0.11" +regex="1.11" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 5fc062e..decf235 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ -mod maven; \ No newline at end of file +pub mod maven; +pub mod xml; diff --git a/src/main.rs b/src/main.rs index e7a11a9..a4a0162 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ fn main() { - println!("Hello, world!"); + let message = &"xmlns:Hello, world!"[6..]; + println!("{}",message); } diff --git a/src/maven/metadata.rs b/src/maven/metadata.rs new file mode 100644 index 0000000..3b5365c --- /dev/null +++ b/src/maven/metadata.rs @@ -0,0 +1,72 @@ +use crate::maven::pom::{ArtifactId, GroupId, Version}; + +/// The Maven variant to parse poms +/// These structs is directly modelled after the XML because that is what strong-xml plugin requires +#[derive(PartialEq, Debug)] +pub struct Metadata { + pub group_id: GroupId, + pub artifact_id: ArtifactId, + pub version: Version, + pub versioning: Versioning, +} + +#[derive(PartialEq, Debug)] +pub struct Versioning { + pub snapshot: Snapshot, + pub last_updated: LastUpdated, + pub snapshot_versions: SnapshotVersions, +} + +#[derive(PartialEq, Debug)] +pub struct Snapshot { + pub timestamp: Timestamp, + pub build_number: BuildNumber, +} + +#[derive(PartialEq, Debug)] +pub struct SnapshotVersions { + pub snapshot_versions: Vec, +} + +#[derive(PartialEq, Debug)] +pub struct SnapshotVersion { + pub classifier: Option, + pub extension: Extension, + pub value: Value, + pub updated: Updated, +} + +#[derive(PartialEq, Debug)] +pub struct Timestamp { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct BuildNumber { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct LastUpdated { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Updated { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Extension { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Classifier { + pub value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Value { + pub value: String, +} diff --git a/src/maven/mod.rs b/src/maven/mod.rs index d1be782..9b5bc2a 100644 --- a/src/maven/mod.rs +++ b/src/maven/mod.rs @@ -1 +1,4 @@ -mod xml; \ No newline at end of file +pub mod metadata; +pub mod pom; +pub mod pom_view; +mod pom_reader; diff --git a/src/maven/pom.rs b/src/maven/pom.rs new file mode 100644 index 0000000..8dac278 --- /dev/null +++ b/src/maven/pom.rs @@ -0,0 +1,132 @@ +/// The Maven variant to parse poms +/// These structs is directly modelled after the XML because that is what strong-xml plugin requires +#[derive(PartialEq, Debug)] +pub struct Pom { + pub(crate) model_version: ModelVersion, + pub(crate) parent: Option, + pub(crate) group_id: Option, + pub(crate) artifact_id: ArtifactId, + pub(crate) version: Option, + pub(crate) name: Name, + pub(crate) packaging: Option, + pub(crate) url: Option, + pub(crate) description: Description, + pub(crate) licences: Option, + pub(crate) scm: Option, + pub(crate) developers: Option, + pub(crate) dependencies: Option, + pub(crate) dependency_management: Option, +} + +#[derive(PartialEq, Debug)] +pub struct ModelVersion { + pub value: String, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct GroupId { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct ArtifactId { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Version { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Name { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Id { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Packaging { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Url { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Description { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct Licenses { + pub(crate) licenses: Vec, +} + +#[derive(PartialEq, Debug)] +pub struct Distribution { + pub(crate) value: String, +} + +#[derive(PartialEq, Debug)] +pub struct License { + pub(crate) name: Name, + pub(crate) url: Url, + pub(crate) distribution: Option, +} + +#[derive(PartialEq, Debug)] +pub struct Parent { + pub(crate) group_id: GroupId, + pub(crate) artifact_id: ArtifactId, + pub(crate) version: Version, +} + +#[derive(PartialEq, Debug)] +pub struct Scm { + pub(crate) url: Url, +} + +#[derive(PartialEq, Debug)] +pub struct Developers { + pub(crate) developers: Vec, +} + +#[derive(PartialEq, Debug)] +struct Developer { + pub(crate) id: Option, + pub(crate) name: Name, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Dependencies { + pub(crate) value: Vec, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct DependencyManagement { + pub(crate) value: Dependencies, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Dependency { + pub(crate) group_id: GroupId, + pub(crate) artifact_id: ArtifactId, + pub(crate) version: Option, +} + +#[cfg(test)] +mod test { + + use crate::maven::pom::Pom; + + #[test] + fn parse_should_not_fail() { + + } +} diff --git a/src/maven/pom_reader.rs b/src/maven/pom_reader.rs new file mode 100644 index 0000000..7e70033 --- /dev/null +++ b/src/maven/pom_reader.rs @@ -0,0 +1,44 @@ +use crate::maven::pom::Pom; +use crate::xml::{Attribute, SaxHandler}; + +fn read(xml: &str){ + +} + +struct PomReader{ + +} + +impl SaxHandler for PomReader{ + fn start_document(&mut self) { + todo!() + } + + fn end_document(&mut self) { + todo!() + } + + fn start_prefix_mapping(&mut self, prefix: &str, uri: &str) { + todo!() + } + + fn end_prefix_mapping(&mut self, prefix: &str, uri: &str) { + todo!() + } + + fn start_element(&mut self, uri: Option, local_name: &str, qualified_name: &str, attributes: Vec) { + todo!() + } + + fn end_element(&mut self, uri: Option, local_name: &str, qualified_name: &str) { + todo!() + } + + fn characters(&mut self, chars: &[char]) { + todo!() + } + + fn error(&mut self, error: &str) { + todo!() + } +} \ No newline at end of file diff --git a/src/maven/pom_view.rs b/src/maven/pom_view.rs new file mode 100644 index 0000000..39b92ef --- /dev/null +++ b/src/maven/pom_view.rs @@ -0,0 +1,30 @@ +use regex::Regex; + +use crate::maven::pom::{Dependency, Parent, Pom}; + +/// offers a (non-mutable) view on the pom-as-xml-representation +/// the main use of this is that it resolves the parent information when needed +/// + +#[derive(Debug)] +pub struct Artifact { + pub group: String, + pub name: String, + pub version: String, + pub path: String, +} + +impl Artifact { + pub fn new(group: &str, name: &str, version: &str) -> Self { + Self { + group: group.into(), + name: name.into(), + version: version.into(), + path: format!("{}/{}/{}", group.replace(".", "/"), name, version), + } + } + + pub fn is_snapshot(&self) -> bool { + self.version.ends_with("-SNAPSHOT") + } +} diff --git a/src/maven/xml/sax_parser_test.rs b/src/maven/xml/sax_parser_test.rs deleted file mode 100644 index 77a2b69..0000000 --- a/src/maven/xml/sax_parser_test.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::maven::xml::{Attribute, SaxHandler, SaxError}; - -#[cfg(test)] -mod tests { - use crate::maven::xml::sax_parser::parse_string; - use crate::maven::xml::sax_parser_test::TestHandler; - use crate::maven::xml::SaxError; - - #[test] - fn test_xml_header() { - let test_xml = include_str!("test/header.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - println!("{:?}", testhandler); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.end_document_called, 1); - } - - #[test] - fn test_single_element_short() { - let test_xml = include_str!("test/header.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.start_element_called, 1); - assert!(!testhandler.elements.is_empty()); - assert_eq!(testhandler.elements[0], ""); - assert_eq!(testhandler.end_document_called, 1); - } - - #[test] - fn test_single_element() { - let test_xml = include_str!("test/element.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.start_element_called, 1); - assert!(!testhandler.elements.is_empty()); - assert_eq!(testhandler.elements[0], ""); - assert_eq!(testhandler.end_document_called, 1); - } - - #[test] - fn test_single_element_single_attribute() { - let test_xml = include_str!("test/element_with_attribute.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.start_element_called, 1); - assert!(!testhandler.elements.is_empty()); - assert_eq!(testhandler.elements[0], r#""#); - assert_eq!(testhandler.end_element_called, 1); - assert_eq!(testhandler.end_document_called, 1); - } - - #[test] - fn test_ignore_comment() { - let test_xml = include_str!("test/comment.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.start_element_called, 1); - assert!(!testhandler.elements.is_empty()); - assert_eq!( - testhandler.elements[0], - r#""# - ); - assert_eq!(testhandler.end_element_called, 1); - assert_eq!(testhandler.end_document_called, 1); - } - - #[test] - fn test_bad_comment() { - let test_xml = include_str!("test/illegal_dashes_comment.xml"); - let mut testhandler = TestHandler::new(); - match parse_string(test_xml.to_string(), Box::new(&mut testhandler)){ - Err(e)=> assert_eq!(e, SaxError::BadCharacter), - Ok(_) => assert!(false), - } - - } - - #[test] - fn test_namespaces() { - let test_xml = include_str!("test/namespaces.xml"); - let mut testhandler = TestHandler::new(); - parse_string(test_xml.to_string(), Box::new(&mut testhandler)) - .expect("Failed to parse test xml"); - assert_eq!(testhandler.start_document_called, 1); - assert_eq!(testhandler.start_element_called, 4); - assert!(!testhandler.elements.is_empty()); - assert_eq!( - testhandler.elements[0], - r#""# - ); - assert_eq!( - testhandler.elements[1], - r#""# - ); - assert_eq!( - testhandler.elements[2], - r#""# - ); - assert_eq!( - testhandler.elements[3], - r#""# - ); - assert_eq!(testhandler.end_element_called, 4); - assert_eq!(testhandler.end_document_called, 1); - } -} - -#[derive(Debug)] -struct TestHandler { - start_document_called: usize, - end_document_called: usize, - start_element_called: usize, - end_element_called: usize, - elements: Vec, -} - -impl TestHandler { - pub fn new() -> Self { - Self { - start_document_called: 0, - end_document_called: 0, - start_element_called: 0, - end_element_called: 0, - elements: vec![], - } - } -} - -impl SaxHandler for TestHandler { - fn start_document(&mut self) { - self.start_document_called += 1; - } - - fn end_document(&mut self) { - self.end_document_called += 1; - } - - fn start_prefix_mapping(&mut self, _prefix: &str, _uri: &str) { - todo!() - } - - fn end_prefix_mapping(&mut self, _prefix: &str, _uri: &str) { - todo!() - } - - fn start_element( - &mut self, - uri: Option, - local_name: &str, - _qualified_name: &str, - attributes: Vec, - ) { - self.start_element_called += 1; - let atts = attributes - .iter() - .map(|att| format!(r#"{}="{}""#, att.name, att.value)) - .collect::>() - .join(" "); - - let uri = if let Some(uri) = uri { - format!("{}:", uri) - } else { - "".to_string() - }; - - let divider = if atts.is_empty() { "" } else { " " }; - self.elements - .push(format!("<{}{}{}{}>", uri, local_name, divider, atts)); - } - - fn end_element(&mut self, _uri: Option, _local_name: &str, _qualified_name: &str) { - self.end_element_called += 1; - } - - fn characters(&mut self, _chars: &[char]) { - todo!() - } - - fn error(&mut self, _error: &str) { - todo!() - } -} diff --git a/src/maven/xml/debug.rs b/src/xml/debug.rs similarity index 91% rename from src/maven/xml/debug.rs rename to src/xml/debug.rs index 67641e1..89030b0 100644 --- a/src/maven/xml/debug.rs +++ b/src/xml/debug.rs @@ -1,5 +1,5 @@ use log::debug; -use crate::maven::xml::SaxHandler; +use crate::xml::SaxHandler; pub struct DebugHandler {} @@ -21,7 +21,7 @@ impl SaxHandler for DebugHandler { _uri: Option, local_name: &str, _qualified_name: &str, - attributes: Vec, + attributes: Vec, ) { debug!("start_element {}, {:?}", local_name, attributes); } diff --git a/src/maven/xml/mod.rs b/src/xml/mod.rs similarity index 82% rename from src/maven/xml/mod.rs rename to src/xml/mod.rs index 221c9d5..8c9af14 100644 --- a/src/maven/xml/mod.rs +++ b/src/xml/mod.rs @@ -1,12 +1,11 @@ -mod sax_parser; -mod sax_parser_test; +pub mod sax_parser; mod debug; #[derive(Debug)] pub struct Attribute { - name: String, - namespace: Option, - value: String, + pub name: String, + pub namespace: Option, + pub value: String, } pub trait SaxHandler { @@ -33,6 +32,7 @@ pub enum SaxError { BadCharacter, UnexpectedEof, UnexpectedCharacter(String), + UndeclaredNamespacePrefix(String), } impl fmt::Display for SaxError { @@ -41,6 +41,7 @@ impl fmt::Display for SaxError { SaxError::BadCharacter => write!(f, "Bad character"), SaxError::UnexpectedEof => write!(f, "Unexpected end of document"), SaxError::UnexpectedCharacter(c) => write!(f, "Unexpected character {}",c), + SaxError::UndeclaredNamespacePrefix(prefix) => write!(f, "Undeclared namespace prefix{}", prefix), } } } diff --git a/src/maven/xml/sax_parser.rs b/src/xml/sax_parser.rs similarity index 79% rename from src/maven/xml/sax_parser.rs rename to src/xml/sax_parser.rs index c52e594..2f0c3ea 100644 --- a/src/maven/xml/sax_parser.rs +++ b/src/xml/sax_parser.rs @@ -1,17 +1,17 @@ -use crate::maven::xml::{Attribute, SaxError, SaxHandler}; -use anyhow::anyhow; +use crate::xml::{Attribute, SaxError, SaxHandler}; +use std::collections::HashMap; pub fn parse_string(xml: String, handler: Box<&mut dyn SaxHandler>) -> Result<(), SaxError> { - let mut parser = SAXParser::new(xml, handler); - parser.parse() + SAXParser::new(xml, handler).parse() } -struct SAXParser<'a> { +pub struct SAXParser<'a> { xml: Vec, handler: Box<&'a mut dyn SaxHandler>, position: usize, current: char, namespace_stack: Vec<(String, isize)>, + prefix_mapping: HashMap, } impl<'a> SAXParser<'a> { @@ -22,6 +22,7 @@ impl<'a> SAXParser<'a> { position: 0, current: '\0', namespace_stack: Vec::new(), + prefix_mapping: HashMap::new(), } } @@ -36,7 +37,7 @@ impl<'a> SAXParser<'a> { self.parse_elements() } - fn parse_elements(&mut self) -> Result<(), SaxError> { + fn parse_elements(&mut self) -> Result<(), SaxError> { while self.position < self.xml.len() { if self.current == '<' { self.advance()?; @@ -80,7 +81,8 @@ impl<'a> SAXParser<'a> { } fn parse_start_element(&mut self) -> Result<(), SaxError> { - let name = self.read_until(" \t\n/>")?; + let qname = self.read_until(" \t\n/>")?; + let mut atts = vec![]; let mut c = self.current; @@ -90,21 +92,38 @@ impl<'a> SAXParser<'a> { c = self.advance()?; } - let namespace = if !self.namespace_stack.is_empty() { + let (namespace, lname) = if qname.contains(":") { + let tokens = qname.splitn(2, ":").collect::>(); + let prefix = tokens[0].to_string(); + let name = tokens[1].to_string(); + let namespace = self.prefix_mapping.get(&prefix); + if let Some(namespace) = namespace { + (Some(namespace.to_string()), name) + } else { + return Err(SaxError::UndeclaredNamespacePrefix(prefix)); + } + } else if !self.namespace_stack.is_empty() { let (name, count) = self.namespace_stack.pop().unwrap(); self.namespace_stack.push((name.clone(), count + 1)); - Some(name.clone()) + (Some(name.clone()), qname) } else { - None + (None, qname) + }; + + let qualified_name = if let Some(namespace) = &namespace{ + &format!("{}:{}", namespace.clone(), &lname) + } else { + &lname }; self.handler - .start_element(namespace.clone(), name.as_str(), "", atts); + .start_element(namespace.clone(), lname.as_str(), qualified_name, atts); self.skip_whitespace()?; + if self.current == '/' { self.advance()?; let namespace = self.pop_namespace(); - self.handler.end_element(namespace, name.as_str(), ""); + self.handler.end_element(namespace, lname.as_str(), qualified_name); } self.expect_char('>')?; self.skip_whitespace()?; @@ -118,6 +137,11 @@ impl<'a> SAXParser<'a> { self.expect("\"", "Expected start of attribute value")?; let att_value = self.read_until("\"")?; + if att_name.starts_with("xmlns:") { + let prefix = att_name[6..].to_string(); + self.prefix_mapping.insert(prefix, att_value.to_string()); + } + let namespace = if att_name == "xmlns" { self.namespace_stack.push((att_value.clone(), -1)); Some(att_value.clone()) @@ -200,7 +224,7 @@ impl<'a> SAXParser<'a> { fn expect(&mut self, expected: &str, message: &str) -> Result<(), SaxError> { for c in expected.chars() { if !self.expect_char(c)? { - return Err(SaxError::UnexpectedCharacter(message.to_string())) + return Err(SaxError::UnexpectedCharacter(message.to_string())); } } Ok(()) @@ -216,4 +240,4 @@ impl<'a> SAXParser<'a> { } Ok(same) } -} \ No newline at end of file +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..d1be782 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1 @@ +mod xml; \ No newline at end of file diff --git a/tests/pom/pom.xml b/tests/pom/pom.xml new file mode 100644 index 0000000..5894332 --- /dev/null +++ b/tests/pom/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + org.mockito + mockito-core + 1.9.5 + Mockito + jar + http://www.mockito.org + Mock objects library for java + + + The MIT License + http://code.google.com/p/mockito/wiki/License + repo + + + + http://code.google.com/p/mockito/source/browse/ + + + + szczepiq + Szczepan Faber + + + + + org.hamcrest + hamcrest-core + 1.1 + + + org.objenesis + objenesis + 1.0 + + + \ No newline at end of file diff --git a/tests/xml/mod.rs b/tests/xml/mod.rs new file mode 100644 index 0000000..c05eada --- /dev/null +++ b/tests/xml/mod.rs @@ -0,0 +1 @@ +mod sax_parser_test; \ No newline at end of file diff --git a/src/maven/xml/test/comment.xml b/tests/xml/resources/comment.xml similarity index 100% rename from src/maven/xml/test/comment.xml rename to tests/xml/resources/comment.xml diff --git a/src/maven/xml/test/element.xml b/tests/xml/resources/element.xml similarity index 100% rename from src/maven/xml/test/element.xml rename to tests/xml/resources/element.xml diff --git a/src/maven/xml/test/element_with_attribute.xml b/tests/xml/resources/element_with_attribute.xml similarity index 100% rename from src/maven/xml/test/element_with_attribute.xml rename to tests/xml/resources/element_with_attribute.xml diff --git a/src/maven/xml/test/full.xml b/tests/xml/resources/full.xml similarity index 100% rename from src/maven/xml/test/full.xml rename to tests/xml/resources/full.xml diff --git a/src/maven/xml/test/header.xml b/tests/xml/resources/header.xml similarity index 100% rename from src/maven/xml/test/header.xml rename to tests/xml/resources/header.xml diff --git a/src/maven/xml/test/illegal_dashes_comment.xml b/tests/xml/resources/illegal_dashes_comment.xml similarity index 100% rename from src/maven/xml/test/illegal_dashes_comment.xml rename to tests/xml/resources/illegal_dashes_comment.xml diff --git a/tests/xml/resources/namespaces-prefix.xml b/tests/xml/resources/namespaces-prefix.xml new file mode 100644 index 0000000..e4e24d1 --- /dev/null +++ b/tests/xml/resources/namespaces-prefix.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/maven/xml/test/namespaces.xml b/tests/xml/resources/namespaces.xml similarity index 100% rename from src/maven/xml/test/namespaces.xml rename to tests/xml/resources/namespaces.xml diff --git a/tests/xml/sax_parser_test.rs b/tests/xml/sax_parser_test.rs new file mode 100644 index 0000000..d77bc63 --- /dev/null +++ b/tests/xml/sax_parser_test.rs @@ -0,0 +1,197 @@ +use undeepend::xml::sax_parser::parse_string; +use undeepend::xml::{Attribute, SaxError, SaxHandler}; + +#[test] +fn test_xml_header() { + let test_xml = include_str!("resources/header.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + println!("{:?}", testhandler); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_single_element_short() { + let test_xml = include_str!("resources/header.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 1); + assert!(!testhandler.elements.is_empty()); + assert_eq!(testhandler.elements[0], ""); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_single_element() { + let test_xml = include_str!("resources/element.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 1); + assert!(!testhandler.elements.is_empty()); + assert_eq!(testhandler.elements[0], ""); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_single_element_single_attribute() { + let test_xml = include_str!("resources/element_with_attribute.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 1); + assert!(!testhandler.elements.is_empty()); + assert_eq!(testhandler.elements[0], r#""#); + assert_eq!(testhandler.end_element_called, 1); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_ignore_comment() { + let test_xml = include_str!("resources/comment.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 1); + assert!(!testhandler.elements.is_empty()); + assert_eq!( + testhandler.elements[0], + r#""# + ); + assert_eq!(testhandler.end_element_called, 1); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_bad_comment() { + let test_xml = include_str!("resources/illegal_dashes_comment.xml"); + let mut testhandler = TestHandler::new(); + match parse_string(test_xml.to_string(), Box::new(&mut testhandler)) { + Err(e) => assert_eq!(e, SaxError::BadCharacter), + Ok(_) => assert!(false), + } +} + +#[test] +fn test_namespaces() { + let test_xml = include_str!("resources/namespaces.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 4); + assert!(!testhandler.elements.is_empty()); + assert_eq!(testhandler.elements[0], r#""#); + assert_eq!( + testhandler.elements[1], + r#""# + ); + assert_eq!( + testhandler.elements[2], + r#""# + ); + assert_eq!(testhandler.elements[3], r#""#); + assert_eq!(testhandler.end_element_called, 4); + assert_eq!(testhandler.end_document_called, 1); +} + +#[test] +fn test_namespace_prefixes() { + let test_xml = include_str!("resources/namespaces-prefix.xml"); + let mut testhandler = TestHandler::new(); + parse_string(test_xml.to_string(), Box::new(&mut testhandler)) + .expect("Failed to parse test xml"); + assert_eq!(testhandler.start_document_called, 1); + assert_eq!(testhandler.start_element_called, 5); + assert!(!testhandler.elements.is_empty()); + assert_eq!(testhandler.elements[0], r#""#); + assert_eq!( + testhandler.elements[1], + r#""# + ); + assert_eq!( + testhandler.elements[2], + r#""# + ); + assert_eq!(testhandler.elements[3], r#""#); + assert_eq!(testhandler.elements[4], r#""#); + assert_eq!(testhandler.end_element_called, 5); + assert_eq!(testhandler.end_document_called, 1); +} + +#[derive(Debug)] +struct TestHandler { + start_document_called: usize, + end_document_called: usize, + start_element_called: usize, + end_element_called: usize, + elements: Vec, +} + +impl TestHandler { + pub fn new() -> Self { + Self { + start_document_called: 0, + end_document_called: 0, + start_element_called: 0, + end_element_called: 0, + elements: vec![], + } + } +} + +impl SaxHandler for TestHandler { + fn start_document(&mut self) { + self.start_document_called += 1; + } + + fn end_document(&mut self) { + self.end_document_called += 1; + } + + fn start_prefix_mapping(&mut self, _prefix: &str, _uri: &str) { + todo!() + } + + fn end_prefix_mapping(&mut self, _prefix: &str, _uri: &str) { + todo!() + } + + fn start_element( + &mut self, + uri: Option, + _local_name: &str, + qualified_name: &str, + attributes: Vec, + ) { + self.start_element_called += 1; + let atts = attributes + .iter() + .map(|att| format!(r#"{}="{}""#, att.name, att.value)) + .collect::>() + .join(" "); + + let divider = if atts.is_empty() { "" } else { " " }; + self.elements + .push(format!("<{}{}{}>", qualified_name, divider, atts)); + } + + fn end_element(&mut self, _uri: Option, _local_name: &str, _qualified_name: &str) { + self.end_element_called += 1; + } + + fn characters(&mut self, _chars: &[char]) { + todo!() + } + + fn error(&mut self, _error: &str) { + todo!() + } +}