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">
|
<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
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]]
|
[[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]]
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "undeepend2"
|
name = "undeepend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
regex="1.11"
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
mod maven;
|
pub mod maven;
|
||||||
|
pub mod xml;
|
||||||
|
|
|
||||||
|
|
@ -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
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 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);
|
||||||
}
|
}
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ impl<'a> SAXParser<'a> {
|
||||||
self.parse_elements()
|
self.parse_elements()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_elements(&mut self) -> Result<(), SaxError> {
|
fn parse_elements(&mut self) -> Result<(), SaxError> {
|
||||||
while self.position < self.xml.len() {
|
while self.position < self.xml.len() {
|
||||||
if self.current == '<' {
|
if self.current == '<' {
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
|
|
@ -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(())
|
||||||
|
|
@ -216,4 +240,4 @@ impl<'a> SAXParser<'a> {
|
||||||
}
|
}
|
||||||
Ok(same)
|
Ok(same)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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