use std::collections::hash_map::HashMap;
#[derive(Debug,PartialEq)]
pub enum XmlSchemaError {
InvalidTag(String),
SchemaTxtInvalid(String),
TagMismatch(String),
DuplicateElementName(String),
FirstTagIsClosing(String)
}
pub(crate) struct XmlSchema
{
raw_string: String,
pub element_names: Vec<String>,
pub element_position: Vec<Vec<usize>>,
buffer: String ,
last_tag: Vec<String> ,
ind_open: bool ,
current_level: usize ,
current_position: Vec<usize>,
pub(crate) element_no_lookup: HashMap<String,Vec<usize>>,
}
fn validate_tag_check_if_opening(txt_tag: &str) -> Result<bool,XmlSchemaError>
{
let mut ind_opening: bool = true;
if txt_tag.is_empty() {
return Err(XmlSchemaError::SchemaTxtInvalid("Schema Txt passed has length of zero".to_string()));
}
let mut no_skip: usize = 0 ;
if txt_tag.starts_with('/') { ind_opening = false; no_skip = 1;}
for (i, c) in txt_tag.char_indices().skip(no_skip) {
if !c.is_alphanumeric() && !(c == '!') && !(c == '-' && i > 0) && !(c == '_' && i > 0)&& !(c == '.' && i > 0) {
let msg = "Character: {} found in tag name: {} at position {}: not a valid tag".to_string();
println!("{}",msg);
return Err(XmlSchemaError::InvalidTag(msg));
}
}
return Ok(ind_opening);
}
impl XmlSchema
{
#[allow(dead_code)]
pub(crate) fn new() -> Self {
Self {
raw_string: String::new(),
element_names: Vec::new(),
element_position: Vec::new(),
buffer: String::new(),
last_tag: Vec::new(),
ind_open: false,
current_level: 0,
current_position: Vec::new(),
element_no_lookup: HashMap::default(),
}
}
#[allow(dead_code)]
pub(crate) fn set_schema(&mut self, txt_schema: &str)
{
self.raw_string = txt_schema.to_string();
}
fn handle_close_tag(&mut self) -> Result<(), XmlSchemaError>
{
let ind_opening = validate_tag_check_if_opening(&self.buffer)?;
if ind_opening
{
match &mut self.element_names.contains(&self.buffer)
{
false => { self.element_names.push(self.buffer.clone());},
true => { return Err(XmlSchemaError::DuplicateElementName((format!("Duplicate element: {} - to use duplicate element names\n apply !uniquesuffix to element in schemea eg <element></element><element!2></element!2>\n the suffix will not appear in final output\n but suffix should be used in all method calls eg add_element(\"element!2\",\"value\"))", self.buffer)))) }
}
let new_no_element = self.element_names.len() -1 ;
if self.current_level >= self.current_position.len() { self.current_position.push(new_no_element);}
else {
self.current_position[self.current_level] = new_no_element;
self.current_position.truncate(self.current_level + 1); }
self.element_position.push(self.current_position.clone());
self.element_no_lookup.insert(self.buffer.clone(), self.current_position.clone() );
self.last_tag.push(self.buffer.clone());
self.current_level += 1;
}
else {
self.current_level -= 1;
if let Some(last) = self.last_tag.pop() {
if last == self.buffer[1..] {
} else {
return Err(XmlSchemaError::TagMismatch(format!(
"Closing tag {} doesn't match last opening tag {}",
&self.buffer, last
)));
}
} else {
return Err(XmlSchemaError::FirstTagIsClosing(
format!("Closing Tag {} found when no Tags open", &self.buffer),
));
}
}
return Ok(());
}
#[allow(dead_code)]
pub(crate)fn parse_schema(&mut self)
{
let chars: Vec<char> = self.raw_string.chars().collect();
for s in chars
{
if self.ind_open
{
match s {
'<' => panic!("< symbol in open tag"),
'>' => {
self.ind_open = false;
self.handle_close_tag().unwrap();
self.buffer.clear();
}
_ => { self.buffer.push(s); }
}
}
else
{
match s {
c if c.is_whitespace() => continue,
'<' => {self.ind_open = true},
_ => panic!("Outside of tags, chracter: {} found",s)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_schema() {
let s = "<root> </root>";
let mut schema = XmlSchema::new();
schema.set_schema(s);
assert_eq!(schema.raw_string, s);
schema.parse_schema();
}
#[test]
fn check_tag_validation() {
assert_eq!(validate_tag_check_if_opening("validtag"), Ok(true));
assert_eq!(validate_tag_check_if_opening("valid-tag"), Ok(true));
assert_eq!(validate_tag_check_if_opening("/validclosing"), Ok(false));
assert_eq!(validate_tag_check_if_opening("/valid-closing"), Ok(false));
assert_eq!(validate_tag_check_if_opening("/valid-clo123sing"), Ok(false));
assert!(matches!(validate_tag_check_if_opening(""), Err(XmlSchemaError::SchemaTxtInvalid(_))));
assert!(matches!(validate_tag_check_if_opening("//invaliddoubleclose"), Err(XmlSchemaError::InvalidTag(_))));
let l = "".to_string().len();
println!("{}",l);
}
#[test]
fn check_duplicate_detected()
{
let s = "<root> <field1> </field1> <1> <2> <3> <7> </7> <4> <5> </5> </4> </3> </2> </1> </root>";
let mut schema = XmlSchema::new();
schema.set_schema(s);
schema.parse_schema();
for r in &schema.element_names
{println!("{}",r.to_string()) };
for r in &schema.element_position
{
print!("{:?}",r)
}
}
}