element with single attribute

This commit is contained in:
Shautvast 2025-07-19 08:42:26 +02:00
commit 8cd82fffbd
13 changed files with 368 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/undeepend2.iml" filepath="$PROJECT_DIR$/.idea/undeepend2.iml" />
</modules>
</component>
</project>

11
.idea/undeepend2.iml generated Normal file
View file

@ -0,0 +1,11 @@
<?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>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
src/lib.rs Normal file
View file

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

1
src/maven/mod.rs Normal file
View file

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

37
src/maven/xml/debug.rs Normal file
View file

@ -0,0 +1,37 @@
use log::debug;
use crate::maven::xml::SaxHandler;
pub struct DebugHandler {}
impl SaxHandler for DebugHandler {
fn start_document(&mut self) {
debug!("start_document");
}
fn end_document(&mut self) {
debug!("end_document");
}
fn start_prefix_mapping(&mut self, _prefix: &str, _uri: &str) {
debug!("start_prefix_mapping");
}
fn end_prefix_mapping(&mut self, _prefix: &str, _uri: &str) {
debug!("end_prefix_mapping");
}
fn start_element(
&mut self,
_uri: &str,
local_name: &str,
_qualified_name: &str,
attributes: Vec<crate::maven::xml::Attribute>,
) {
debug!("start_element {}, {:?}", local_name, attributes);
}
fn end_element(&mut self, _uri: &str, local_name: &str, _qualified_name: &str) {
debug!("end_element {} ", local_name);
}
fn characters(&mut self, chars: &[char]) {
debug!("characters {:?}", chars.iter().collect::<String>());
}
fn error(&mut self, _error: &str) {
debug!("error");
}
}

28
src/maven/xml/mod.rs Normal file
View file

@ -0,0 +1,28 @@
mod sax_parser;
mod sax_parser_test;
mod debug;
#[derive(Debug)]
pub struct Attribute {
name: String,
namespace: Option<String>,
value: String,
}
pub trait SaxHandler {
fn start_document(&mut self);
fn end_document(&mut self);
fn start_prefix_mapping(&mut self, prefix: &str, uri: &str);
fn end_prefix_mapping(&mut self, prefix: &str, uri: &str);
fn start_element(
&mut self,
uri: &str,
local_name: &str,
qualified_name: &str,
attributes: Vec<Attribute>,
);
fn end_element(&mut self, uri: &str, local_name: &str, qualified_name: &str);
fn characters(&mut self, chars: &[char]);
fn error(&mut self, error: &str);
}

130
src/maven/xml/sax_parser.rs Normal file
View file

@ -0,0 +1,130 @@
use crate::maven::xml::{Attribute, SaxHandler};
pub fn parse_string(xml: String, handler: Box<&mut dyn SaxHandler>) -> anyhow::Result<()> {
let mut parser = SAXParser::new(xml, handler);
parser.parse()
}
struct SAXParser<'a> {
xml: Vec<char>,
handler: Box<&'a mut dyn SaxHandler>,
position: usize,
current: char,
}
impl<'a> SAXParser<'a> {
pub fn new(xml: String, handler: Box<&'a mut dyn SaxHandler>) -> Self {
Self {
xml: xml.chars().collect(),
handler,
position: 0,
current: '\0',
}
}
fn parse(&mut self) -> anyhow::Result<()> {
self.expect(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"Content is not allowed in prolog.",
)?;
self.skip_whitespace()?;
self.handler.start_document();
self.parse_elements()
}
fn parse_elements(&mut self) -> anyhow::Result<()> {
if self.current == '<' {
self.advance()?;
if self.next_char()? != '/' {
self.parse_start_element()?;
} else {
self.parse_end_element()?;
}
}
Ok(())
}
fn parse_start_element(&mut self) -> anyhow::Result<()> {
let name = self.read_until(" />")?;
let mut atts = vec![];
let mut c = self.current;
while c == ' ' {
self.skip_whitespace()?;
atts.push(self.parse_attribute()?);
c = self.advance()?;
}
self.handler.start_element("", name.as_str(), "", atts);
Ok(())
}
fn parse_attribute(&mut self) -> anyhow::Result<Attribute> {
let att_name = self.read_until("=")?;
self.skip_whitespace()?;
self.expect("\"", "Expected start of attribute value")?;
let att_value = self.read_until("\"")?;
Ok(Attribute {
name: att_name.trim().to_string(),
namespace: Some("".to_string()),
value: att_value,
})
}
fn parse_end_element(&mut self) -> anyhow::Result<()> {
let name = self.read_until(">")?;
self.handler.end_element("", name.as_str(), "");
Ok(())
}
fn read_until(&mut self, until: &str) -> anyhow::Result<String> {
let start = self.position;
let mut c = self.current;
let until = until.chars().collect::<Vec<char>>();
while !until.contains(&c) {
c = self.advance()?;
}
Ok(self.xml[start - 1..self.position - 1]
.iter()
.collect::<String>())
}
fn skip_whitespace(&mut self) -> anyhow::Result<()> {
let mut c = self.current;
while (c.is_whitespace()) && self.position < self.xml.len() {
c = self.advance()?;
}
Ok(())
}
fn advance(&mut self) -> anyhow::Result<char> {
self.position += 1;
self.current = self.xml[self.position - 1];
Ok(self.current)
}
fn next_char(&mut self) -> anyhow::Result<char> {
if self.position >= self.xml.len() {
Err(anyhow::anyhow!("End reached"))
} else {
Ok(self.xml[self.position + 1])
}
}
fn expect(&mut self, header_line: &str, message: &str) -> anyhow::Result<()> {
for c in header_line.chars() {
if !self.expect_char(c)? {
return Err(anyhow::anyhow!(message.to_string()));
}
}
self.advance()?;
Ok(())
}
fn expect_char(&mut self, expected: char) -> anyhow::Result<bool> {
if self.position >= self.xml.len() {
return Ok(false);
}
Ok(self.advance()? == expected)
}
}

View file

@ -0,0 +1,132 @@
use crate::maven::xml::{Attribute, SaxHandler};
#[cfg(test)]
mod tests {
use crate::maven::xml::sax_parser::parse_string;
use crate::maven::xml::sax_parser_test::TestHandler;
use std::sync::Once;
static INIT: Once = Once::new();
pub fn initialize() {
INIT.call_once(|| {
env_logger::init();
});
}
#[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!(testhandler.start_document_called);
}
#[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!(testhandler.start_document_called);
assert!(testhandler.start_element_called);
assert!(!testhandler.elements.is_empty());
assert_eq!(testhandler.elements[0], "<xml>");
}
#[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!(testhandler.start_document_called);
assert!(testhandler.start_element_called);
assert!(!testhandler.elements.is_empty());
assert_eq!(testhandler.elements[0], "<element>");
}
#[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!(testhandler.start_document_called);
assert!(testhandler.start_element_called);
assert!(!testhandler.elements.is_empty());
assert_eq!(testhandler.elements[0], r#"<element a="1">"#);
}
}
#[derive(Debug)]
struct TestHandler {
start_document_called: bool,
end_document_called: bool,
start_element_called: bool,
end_element_called: bool,
elements: Vec<String>,
}
impl TestHandler {
pub fn new() -> Self {
Self {
start_document_called: false,
end_document_called: false,
start_element_called: false,
end_element_called: false,
elements: vec![],
}
}
}
impl SaxHandler for TestHandler {
fn start_document(&mut self) {
self.start_document_called = true;
}
fn end_document(&mut self) {
self.end_document_called = true;
}
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: &str,
local_name: &str,
_qualified_name: &str,
attributes: Vec<Attribute>,
) {
self.start_element_called = true;
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!("<{}{}{}>", local_name, divider, atts));
}
fn end_element(&mut self, _uri: &str, _local_name: &str, _qualified_name: &str) {
self.end_element_called = true;
}
fn characters(&mut self, _chars: &[char]) {
todo!()
}
fn error(&mut self, _error: &str) {
todo!()
}
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<element></element>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<element a="1"></element>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<xml/>