namespace handling
This commit is contained in:
parent
e6f1eb0f08
commit
b301901d0a
6 changed files with 154 additions and 49 deletions
|
|
@ -18,14 +18,14 @@ impl SaxHandler for DebugHandler {
|
|||
}
|
||||
fn start_element(
|
||||
&mut self,
|
||||
_uri: &str,
|
||||
_uri: Option<String>,
|
||||
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) {
|
||||
fn end_element(&mut self, _uri: Option<String>, local_name: &str, _qualified_name: &str) {
|
||||
debug!("end_element {} ", local_name);
|
||||
}
|
||||
fn characters(&mut self, chars: &[char]) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ mod debug;
|
|||
#[derive(Debug)]
|
||||
pub struct Attribute {
|
||||
name: String,
|
||||
_namespace: Option<String>,
|
||||
namespace: Option<String>,
|
||||
value: String,
|
||||
}
|
||||
|
||||
|
|
@ -16,12 +16,12 @@ pub trait SaxHandler {
|
|||
fn end_prefix_mapping(&mut self, prefix: &str, uri: &str);
|
||||
fn start_element(
|
||||
&mut self,
|
||||
uri: &str,
|
||||
uri: Option<String>,
|
||||
local_name: &str,
|
||||
qualified_name: &str,
|
||||
attributes: Vec<Attribute>,
|
||||
);
|
||||
fn end_element(&mut self, uri: &str, local_name: &str, qualified_name: &str);
|
||||
fn end_element(&mut self, uri: Option<String>, local_name: &str, qualified_name: &str);
|
||||
fn characters(&mut self, chars: &[char]);
|
||||
fn error(&mut self, error: &str);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ struct SAXParser<'a> {
|
|||
handler: Box<&'a mut dyn SaxHandler>,
|
||||
position: usize,
|
||||
current: char,
|
||||
namespace_stack: Vec<(String, isize)>,
|
||||
}
|
||||
|
||||
impl<'a> SAXParser<'a> {
|
||||
|
|
@ -19,6 +20,7 @@ impl<'a> SAXParser<'a> {
|
|||
handler,
|
||||
position: 0,
|
||||
current: '\0',
|
||||
namespace_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,19 +76,31 @@ impl<'a> SAXParser<'a> {
|
|||
}
|
||||
|
||||
fn parse_start_element(&mut self) -> anyhow::Result<()> {
|
||||
let name = self.read_until(" />")?;
|
||||
let name = self.read_until(" \t\n/>")?;
|
||||
let mut atts = vec![];
|
||||
let mut c = self.current;
|
||||
while c == ' ' {
|
||||
|
||||
while c.is_whitespace() {
|
||||
self.skip_whitespace()?;
|
||||
atts.push(self.parse_attribute()?);
|
||||
c = self.advance()?;
|
||||
}
|
||||
|
||||
self.handler.start_element("", name.as_str(), "", atts);
|
||||
let namespace = 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())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.handler
|
||||
.start_element(namespace.clone(), name.as_str(), "", atts);
|
||||
self.skip_whitespace()?;
|
||||
if self.current == '/' {
|
||||
self.advance()?;
|
||||
let namespace = self.pop_namespace();
|
||||
self.handler.end_element(namespace, name.as_str(), "");
|
||||
}
|
||||
self.expect_char('>')?;
|
||||
self.skip_whitespace()?;
|
||||
|
|
@ -100,9 +114,16 @@ impl<'a> SAXParser<'a> {
|
|||
self.expect("\"", "Expected start of attribute value")?;
|
||||
let att_value = self.read_until("\"")?;
|
||||
|
||||
let namespace = if att_name == "xmlns" {
|
||||
self.namespace_stack.push((att_value.clone(), -1));
|
||||
Some(att_value.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Attribute {
|
||||
name: att_name.trim().to_string(),
|
||||
_namespace: Some("".to_string()),
|
||||
namespace,
|
||||
value: att_value,
|
||||
})
|
||||
}
|
||||
|
|
@ -110,11 +131,32 @@ impl<'a> SAXParser<'a> {
|
|||
fn parse_end_element(&mut self) -> anyhow::Result<()> {
|
||||
self.advance()?;
|
||||
let name = self.read_until(">")?;
|
||||
self.handler.end_element("", name.as_str(), "");
|
||||
|
||||
let namespace = self.pop_namespace();
|
||||
|
||||
self.handler.end_element(namespace, name.as_str(), "");
|
||||
|
||||
self.expect(">", "Expect end of element")?;
|
||||
self.skip_whitespace()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop_namespace(&mut self) -> Option<String> {
|
||||
let namespace = if !self.namespace_stack.is_empty() {
|
||||
let (name, count) = self.namespace_stack.pop().unwrap();
|
||||
|
||||
if count > 0 {
|
||||
self.namespace_stack.push((name.to_string(), count - 1));
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
namespace
|
||||
}
|
||||
|
||||
fn read_until(&mut self, until: &str) -> anyhow::Result<String> {
|
||||
let start = self.position;
|
||||
let mut c = self.current;
|
||||
|
|
|
|||
|
|
@ -4,25 +4,16 @@ use crate::maven::xml::{Attribute, SaxHandler};
|
|||
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() {
|
||||
initialize();
|
||||
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);
|
||||
assert_eq!(testhandler.start_document_called, 1);
|
||||
assert_eq!(testhandler.end_document_called, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -31,10 +22,11 @@ mod tests {
|
|||
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_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]
|
||||
|
|
@ -43,10 +35,11 @@ mod tests {
|
|||
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_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]
|
||||
|
|
@ -55,12 +48,12 @@ mod tests {
|
|||
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_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!(testhandler.end_element_called);
|
||||
assert!(testhandler.end_document_called);
|
||||
assert_eq!(testhandler.end_element_called, 1);
|
||||
assert_eq!(testhandler.end_document_called, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -69,31 +62,63 @@ mod tests {
|
|||
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_eq!(testhandler.start_document_called, 1);
|
||||
assert_eq!(testhandler.start_element_called, 1);
|
||||
assert!(!testhandler.elements.is_empty());
|
||||
assert_eq!(testhandler.elements[0], r#"<bookstore xmlns="http://example.com/books">"#);
|
||||
assert!(testhandler.end_element_called);
|
||||
assert!(testhandler.end_document_called);
|
||||
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_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: bool,
|
||||
end_document_called: bool,
|
||||
start_element_called: bool,
|
||||
end_element_called: bool,
|
||||
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: false,
|
||||
end_document_called: false,
|
||||
start_element_called: false,
|
||||
end_element_called: false,
|
||||
start_document_called: 0,
|
||||
end_document_called: 0,
|
||||
start_element_called: 0,
|
||||
end_element_called: 0,
|
||||
elements: vec![],
|
||||
}
|
||||
}
|
||||
|
|
@ -101,11 +126,11 @@ impl TestHandler {
|
|||
|
||||
impl SaxHandler for TestHandler {
|
||||
fn start_document(&mut self) {
|
||||
self.start_document_called = true;
|
||||
self.start_document_called += 1;
|
||||
}
|
||||
|
||||
fn end_document(&mut self) {
|
||||
self.end_document_called = true;
|
||||
self.end_document_called += 1;
|
||||
}
|
||||
|
||||
fn start_prefix_mapping(&mut self, _prefix: &str, _uri: &str) {
|
||||
|
|
@ -118,25 +143,31 @@ impl SaxHandler for TestHandler {
|
|||
|
||||
fn start_element(
|
||||
&mut self,
|
||||
_uri: &str,
|
||||
uri: Option<String>,
|
||||
local_name: &str,
|
||||
_qualified_name: &str,
|
||||
attributes: Vec<Attribute>,
|
||||
) {
|
||||
self.start_element_called = true;
|
||||
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!("<{}{}{}>", local_name, divider, atts));
|
||||
.push(format!("<{}{}{}{}>", uri, local_name, divider, atts));
|
||||
}
|
||||
|
||||
fn end_element(&mut self, _uri: &str, _local_name: &str, _qualified_name: &str) {
|
||||
self.end_element_called = true;
|
||||
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]) {
|
||||
|
|
|
|||
24
src/maven/xml/test/full.xml
Normal file
24
src/maven/xml/test/full.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Test XML file for SAX parser -->
|
||||
<bookstore xmlns="http://example.com/books">
|
||||
<book id="1" category="fiction">
|
||||
<title lang="en">The Great Gatsby</title>
|
||||
<author>F. Scott Fitzgerald</author>
|
||||
<price currency="USD">12.99</price>
|
||||
<description>A classic American novel about the Jazz Age</description>
|
||||
</book>
|
||||
<book id="2" category="science">
|
||||
<title lang="en">A Brief History of Time</title>
|
||||
<author>Stephen Hawking</author>
|
||||
<price currency="USD">15.50</price>
|
||||
<description>An exploration of space & time</description>
|
||||
<chapters>
|
||||
<chapter number="1">Our Picture of the Universe</chapter>
|
||||
<chapter number="2">Space and Time</chapter>
|
||||
</chapters>
|
||||
</book>
|
||||
<metadata>
|
||||
<created>2024-01-15</created>
|
||||
<lastModified>2024-01-20</lastModified>
|
||||
</metadata>
|
||||
</bookstore>
|
||||
8
src/maven/xml/test/namespaces.xml
Normal file
8
src/maven/xml/test/namespaces.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Test XML file for SAX parser -->
|
||||
<bookstore>
|
||||
<book xmlns="http://example.com/books" id="1" category="fiction">
|
||||
<page/>
|
||||
</book>
|
||||
<publisher/>
|
||||
</bookstore>
|
||||
Loading…
Add table
Reference in a new issue