added maven pom model, reorganized tests, added namespace prefix support (wip)
This commit is contained in:
parent
df4bd8dc34
commit
4cd9ecceb4
28 changed files with 588 additions and 239 deletions
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
|
|
@ -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
2
.idea/modules.xml
generated
|
|
@ -2,7 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<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>
|
||||
</component>
|
||||
</project>
|
||||
11
.idea/undeepend2.iml
generated
11
.idea/undeepend2.iml
generated
|
|
@ -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
3
Cargo.lock
generated
|
|
@ -238,12 +238,13 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "undeepend2"
|
||||
name = "undeepend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"env_logger",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "undeepend2"
|
||||
name = "undeepend"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
|
|
@ -7,3 +7,4 @@ edition = "2024"
|
|||
anyhow = "1.0"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
regex="1.11"
|
||||
|
|
@ -1 +1,2 @@
|
|||
mod maven;
|
||||
pub mod maven;
|
||||
pub mod xml;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let message = &"xmlns:Hello, world!"[6..];
|
||||
println!("{}",message);
|
||||
}
|
||||
|
|
|
|||
72
src/maven/metadata.rs
Normal file
72
src/maven/metadata.rs
Normal 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,
|
||||
}
|
||||
|
|
@ -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
132
src/maven/pom.rs
Normal 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
44
src/maven/pom_reader.rs
Normal 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
30
src/maven/pom_view.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
local_name: &str,
|
||||
_qualified_name: &str,
|
||||
attributes: Vec<crate::maven::xml::Attribute>,
|
||||
attributes: Vec<crate::xml::Attribute>,
|
||||
) {
|
||||
debug!("start_element {}, {:?}", local_name, attributes);
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
value: String,
|
||||
pub name: String,
|
||||
pub namespace: Option<String>,
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<char>,
|
||||
handler: Box<&'a mut dyn SaxHandler>,
|
||||
position: usize,
|
||||
current: char,
|
||||
namespace_stack: Vec<(String, isize)>,
|
||||
prefix_mapping: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl<'a> SAXParser<'a> {
|
||||
|
|
@ -22,6 +22,7 @@ impl<'a> SAXParser<'a> {
|
|||
position: 0,
|
||||
current: '\0',
|
||||
namespace_stack: Vec::new(),
|
||||
prefix_mapping: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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::<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();
|
||||
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(())
|
||||
1
tests/mod.rs
Normal file
1
tests/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
mod xml;
|
||||
40
tests/pom/pom.xml
Normal file
40
tests/pom/pom.xml
Normal 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
1
tests/xml/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
mod sax_parser_test;
|
||||
11
tests/xml/resources/namespaces-prefix.xml
Normal file
11
tests/xml/resources/namespaces-prefix.xml
Normal 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>
|
||||
197
tests/xml/sax_parser_test.rs
Normal file
197
tests/xml/sax_parser_test.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue