added maven pom model, reorganized tests, added namespace prefix support (wip)

This commit is contained in:
Shautvast 2025-07-23 12:42:31 +02:00
parent df4bd8dc34
commit 4cd9ecceb4
28 changed files with 588 additions and 239 deletions

8
.idea/.gitignore generated vendored
View file

@ -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

2
.idea/modules.xml generated
View file

@ -2,7 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/undeepend2.iml" filepath="$PROJECT_DIR$/.idea/undeepend2.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/undeepend.iml" filepath="$PROJECT_DIR$/.idea/undeepend.iml" />
</modules> </modules>
</component> </component>
</project> </project>

11
.idea/undeepend2.iml generated
View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

3
Cargo.lock generated
View file

@ -238,12 +238,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "undeepend2" name = "undeepend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"env_logger", "env_logger",
"log", "log",
"regex",
] ]
[[package]] [[package]]

View file

@ -1,5 +1,5 @@
[package] [package]
name = "undeepend2" name = "undeepend"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
@ -7,3 +7,4 @@ edition = "2024"
anyhow = "1.0" anyhow = "1.0"
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
regex="1.11"

View file

@ -1 +1,2 @@
mod maven; pub mod maven;
pub mod xml;

View file

@ -1,3 +1,4 @@
fn main() { fn main() {
println!("Hello, world!"); let message = &"xmlns:Hello, world!"[6..];
println!("{}",message);
} }

72
src/maven/metadata.rs Normal file
View file

@ -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<SnapshotVersion>,
}
#[derive(PartialEq, Debug)]
pub struct SnapshotVersion {
pub classifier: Option<Classifier>,
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,
}

View file

@ -1 +1,4 @@
mod xml; pub mod metadata;
pub mod pom;
pub mod pom_view;
mod pom_reader;

132
src/maven/pom.rs Normal file
View file

@ -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<Parent>,
pub(crate) group_id: Option<GroupId>,
pub(crate) artifact_id: ArtifactId,
pub(crate) version: Option<Version>,
pub(crate) name: Name,
pub(crate) packaging: Option<Packaging>,
pub(crate) url: Option<Url>,
pub(crate) description: Description,
pub(crate) licences: Option<Licenses>,
pub(crate) scm: Option<Scm>,
pub(crate) developers: Option<Developers>,
pub(crate) dependencies: Option<Dependencies>,
pub(crate) dependency_management: Option<DependencyManagement>,
}
#[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<License>,
}
#[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<Distribution>,
}
#[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<Developer>,
}
#[derive(PartialEq, Debug)]
struct Developer {
pub(crate) id: Option<Id>,
pub(crate) name: Name,
}
#[derive(PartialEq, Debug, Clone)]
pub struct Dependencies {
pub(crate) value: Vec<Dependency>,
}
#[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<Version>,
}
#[cfg(test)]
mod test {
use crate::maven::pom::Pom;
#[test]
fn parse_should_not_fail() {
}
}

44
src/maven/pom_reader.rs Normal file
View file

@ -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<String>, local_name: &str, qualified_name: &str, attributes: Vec<Attribute>) {
todo!()
}
fn end_element(&mut self, uri: Option<String>, local_name: &str, qualified_name: &str) {
todo!()
}
fn characters(&mut self, chars: &[char]) {
todo!()
}
fn error(&mut self, error: &str) {
todo!()
}
}

30
src/maven/pom_view.rs Normal file
View file

@ -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")
}
}

View file

@ -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], "<xml>");
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], "<element>");
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#"<element a="1">"#);
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#"<http://example.com/books:bookstore xmlns="http://example.com/books">"#
);
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#"<bookstore>"#
);
assert_eq!(
testhandler.elements[1],
r#"<http://example.com/books:book xmlns="http://example.com/books" id="1" category="fiction">"#
);
assert_eq!(
testhandler.elements[2],
r#"<http://example.com/books:page>"#
);
assert_eq!(
testhandler.elements[3],
r#"<publisher>"#
);
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<String>,
}
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<String>,
local_name: &str,
_qualified_name: &str,
attributes: Vec<Attribute>,
) {
self.start_element_called += 1;
let atts = attributes
.iter()
.map(|att| format!(r#"{}="{}""#, att.name, att.value))
.collect::<Vec<String>>()
.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<String>, _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!()
}
}

View file

@ -1,5 +1,5 @@
use log::debug; use log::debug;
use crate::maven::xml::SaxHandler; use crate::xml::SaxHandler;
pub struct DebugHandler {} pub struct DebugHandler {}
@ -21,7 +21,7 @@ impl SaxHandler for DebugHandler {
_uri: Option<String>, _uri: Option<String>,
local_name: &str, local_name: &str,
_qualified_name: &str, _qualified_name: &str,
attributes: Vec<crate::maven::xml::Attribute>, attributes: Vec<crate::xml::Attribute>,
) { ) {
debug!("start_element {}, {:?}", local_name, attributes); debug!("start_element {}, {:?}", local_name, attributes);
} }

View file

@ -1,12 +1,11 @@
mod sax_parser; pub mod sax_parser;
mod sax_parser_test;
mod debug; mod debug;
#[derive(Debug)] #[derive(Debug)]
pub struct Attribute { pub struct Attribute {
name: String, pub name: String,
namespace: Option<String>, pub namespace: Option<String>,
value: String, pub value: String,
} }
pub trait SaxHandler { pub trait SaxHandler {
@ -33,6 +32,7 @@ pub enum SaxError {
BadCharacter, BadCharacter,
UnexpectedEof, UnexpectedEof,
UnexpectedCharacter(String), UnexpectedCharacter(String),
UndeclaredNamespacePrefix(String),
} }
impl fmt::Display for SaxError { impl fmt::Display for SaxError {
@ -41,6 +41,7 @@ impl fmt::Display for SaxError {
SaxError::BadCharacter => write!(f, "Bad character"), SaxError::BadCharacter => write!(f, "Bad character"),
SaxError::UnexpectedEof => write!(f, "Unexpected end of document"), SaxError::UnexpectedEof => write!(f, "Unexpected end of document"),
SaxError::UnexpectedCharacter(c) => write!(f, "Unexpected character {}",c), SaxError::UnexpectedCharacter(c) => write!(f, "Unexpected character {}",c),
SaxError::UndeclaredNamespacePrefix(prefix) => write!(f, "Undeclared namespace prefix{}", prefix),
} }
} }
} }

View file

@ -1,17 +1,17 @@
use crate::maven::xml::{Attribute, SaxError, SaxHandler}; use crate::xml::{Attribute, SaxError, SaxHandler};
use anyhow::anyhow; use std::collections::HashMap;
pub fn parse_string(xml: String, handler: Box<&mut dyn SaxHandler>) -> Result<(), SaxError> { pub fn parse_string(xml: String, handler: Box<&mut dyn SaxHandler>) -> Result<(), SaxError> {
let mut parser = SAXParser::new(xml, handler); SAXParser::new(xml, handler).parse()
parser.parse()
} }
struct SAXParser<'a> { pub struct SAXParser<'a> {
xml: Vec<char>, xml: Vec<char>,
handler: Box<&'a mut dyn SaxHandler>, handler: Box<&'a mut dyn SaxHandler>,
position: usize, position: usize,
current: char, current: char,
namespace_stack: Vec<(String, isize)>, namespace_stack: Vec<(String, isize)>,
prefix_mapping: HashMap<String, String>,
} }
impl<'a> SAXParser<'a> { impl<'a> SAXParser<'a> {
@ -22,6 +22,7 @@ impl<'a> SAXParser<'a> {
position: 0, position: 0,
current: '\0', current: '\0',
namespace_stack: Vec::new(), namespace_stack: Vec::new(),
prefix_mapping: HashMap::new(),
} }
} }
@ -80,7 +81,8 @@ impl<'a> SAXParser<'a> {
} }
fn parse_start_element(&mut self) -> Result<(), SaxError> { 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 atts = vec![];
let mut c = self.current; let mut c = self.current;
@ -90,21 +92,38 @@ impl<'a> SAXParser<'a> {
c = self.advance()?; c = self.advance()?;
} }
let namespace = if !self.namespace_stack.is_empty() { let (namespace, lname) = if qname.contains(":") {
let tokens = qname.splitn(2, ":").collect::<Vec<&str>>();
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(); let (name, count) = self.namespace_stack.pop().unwrap();
self.namespace_stack.push((name.clone(), count + 1)); self.namespace_stack.push((name.clone(), count + 1));
Some(name.clone()) (Some(name.clone()), qname)
} else { } else {
None (None, qname)
};
let qualified_name = if let Some(namespace) = &namespace{
&format!("{}:{}", namespace.clone(), &lname)
} else {
&lname
}; };
self.handler self.handler
.start_element(namespace.clone(), name.as_str(), "", atts); .start_element(namespace.clone(), lname.as_str(), qualified_name, atts);
self.skip_whitespace()?; self.skip_whitespace()?;
if self.current == '/' { if self.current == '/' {
self.advance()?; self.advance()?;
let namespace = self.pop_namespace(); 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.expect_char('>')?;
self.skip_whitespace()?; self.skip_whitespace()?;
@ -118,6 +137,11 @@ impl<'a> SAXParser<'a> {
self.expect("\"", "Expected start of attribute value")?; self.expect("\"", "Expected start of attribute value")?;
let att_value = self.read_until("\"")?; 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" { let namespace = if att_name == "xmlns" {
self.namespace_stack.push((att_value.clone(), -1)); self.namespace_stack.push((att_value.clone(), -1));
Some(att_value.clone()) Some(att_value.clone())
@ -200,7 +224,7 @@ impl<'a> SAXParser<'a> {
fn expect(&mut self, expected: &str, message: &str) -> Result<(), SaxError> { fn expect(&mut self, expected: &str, message: &str) -> Result<(), SaxError> {
for c in expected.chars() { for c in expected.chars() {
if !self.expect_char(c)? { if !self.expect_char(c)? {
return Err(SaxError::UnexpectedCharacter(message.to_string())) return Err(SaxError::UnexpectedCharacter(message.to_string()));
} }
} }
Ok(()) Ok(())

1
tests/mod.rs Normal file
View file

@ -0,0 +1 @@
mod xml;

40
tests/pom/pom.xml Normal file
View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<name>Mockito</name>
<packaging>jar</packaging>
<url>http://www.mockito.org</url>
<description>Mock objects library for java</description>
<licenses>
<license>
<name>The MIT License</name>
<url>http://code.google.com/p/mockito/wiki/License</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>http://code.google.com/p/mockito/source/browse/</url>
</scm>
<developers>
<developer>
<id>szczepiq</id>
<name>Szczepan Faber</name>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>

1
tests/xml/mod.rs Normal file
View file

@ -0,0 +1 @@
mod sax_parser_test;

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Test XML file for SAX parser -->
<bookstore>
<book xmlns:books="http://example.com/books"
xmlns:covers="http://example.com/covers"
id="1" category="fiction">
<books:page/>
<covers:cover/>
</book>
<publisher/>
</bookstore>

View file

@ -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], "<xml>");
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], "<element>");
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#"<element a="1">"#);
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#"<http://example.com/books:bookstore xmlns="http://example.com/books">"#
);
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#"<bookstore>"#);
assert_eq!(
testhandler.elements[1],
r#"<http://example.com/books:book xmlns="http://example.com/books" id="1" category="fiction">"#
);
assert_eq!(
testhandler.elements[2],
r#"<http://example.com/books:page>"#
);
assert_eq!(testhandler.elements[3], r#"<publisher>"#);
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#"<bookstore>"#);
assert_eq!(
testhandler.elements[1],
r#"<book xmlns:books="http://example.com/books" xmlns:covers="http://example.com/covers" id="1" category="fiction">"#
);
assert_eq!(
testhandler.elements[2],
r#"<http://example.com/books:page>"#
);
assert_eq!(testhandler.elements[3], r#"<http://example.com/covers:cover>"#);
assert_eq!(testhandler.elements[4], r#"<publisher>"#);
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<String>,
}
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<String>,
_local_name: &str,
qualified_name: &str,
attributes: Vec<Attribute>,
) {
self.start_element_called += 1;
let atts = attributes
.iter()
.map(|att| format!(r#"{}="{}""#, att.name, att.value))
.collect::<Vec<String>>()
.join(" ");
let divider = if atts.is_empty() { "" } else { " " };
self.elements
.push(format!("<{}{}{}>", qualified_name, divider, atts));
}
fn end_element(&mut self, _uri: Option<String>, _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!()
}
}