use crate::error::{Error, Result};
pub trait XsdChoiceGroup: Sized {
type Variant;
fn choice_element_names() -> &'static [&'static str];
fn parse_choice_element(element_name: &str, xml: &str) -> Result<Self::Variant>;
fn from_choice_variants(variants: Vec<Self::Variant>) -> Result<Self>;
}
pub struct ChoiceGroupParser {
xml: String,
}
impl ChoiceGroupParser {
pub fn from_str(xml: &str) -> Self {
Self {
xml: xml.to_string(),
}
}
pub fn parse_choice_group<T: XsdChoiceGroup>(&self, container_element: &str) -> Result<T> {
let choice_names = T::choice_element_names();
let mut variants: Vec<(usize, T::Variant)> = Vec::new();
let container_start_tag = format!("<{}", container_element);
let container_end_tag = format!("</{}>", container_element);
let container_self_closing = format!("<{}/>", container_element);
if self.xml.contains(&container_self_closing) {
return T::from_choice_variants(Vec::new());
}
let start_pos = if let Some(pos) = self.xml.find(&container_start_tag) {
(self.xml[pos..]
.find('>')
.ok_or_else(|| Error::validation_error("xml", "Malformed container start tag"))?
+ pos
+ 1)
} else {
return Err(Error::validation_error(
"xml",
&format!("Container element '{}' not found", container_element),
));
};
let end_pos = self
.xml
.find(&container_end_tag)
.ok_or_else(|| Error::validation_error("xml", "Container end tag not found"))?;
let content = &self.xml[start_pos..end_pos];
for &element_name in choice_names {
let mut search_pos = 0;
loop {
let element_start_tag = format!("<{}", element_name);
let element_end_tag = format!("</{}>", element_name);
let element_self_closing = format!("<{}/>", element_name);
let element_self_closing_with_space = format!("<{} ", element_name);
let element_pos = if let Some(pos) = content[search_pos..].find(&element_start_tag)
{
search_pos + pos
} else {
break; };
let content_before = &content[search_pos..element_pos];
let mut depth_check = 0;
let mut check_pos = 0;
while check_pos < content_before.len() {
if let Some(open_pos) = content_before[check_pos..].find('<') {
let abs_open_pos = check_pos + open_pos;
if abs_open_pos + 1 < content_before.len() {
let tag_start = abs_open_pos + 1;
if content_before.chars().nth(tag_start).unwrap() == '/' {
depth_check -= 1;
} else {
if let Some(close_pos) = content_before[abs_open_pos..].find('>') {
let tag_content =
&content_before[abs_open_pos..abs_open_pos + close_pos + 1];
if !tag_content.ends_with("/>") {
depth_check += 1;
}
}
}
}
check_pos = abs_open_pos + 1;
} else {
break;
}
}
if depth_check > 0 {
search_pos = element_pos + element_start_tag.len();
continue;
}
let is_self_closing = content[element_pos..].starts_with(&element_self_closing)
|| (content[element_pos..].starts_with(&element_self_closing_with_space)
&& content[element_pos..].find('>').is_some_and(|pos| {
content[element_pos..element_pos + pos + 1].ends_with("/>")
}));
if is_self_closing {
let tag_end = content[element_pos..].find('>').unwrap() + element_pos + 1;
let element_xml = &content[element_pos..tag_end];
let variant = T::parse_choice_element(element_name, element_xml)?;
variants.push((element_pos, variant)); search_pos = tag_end;
} else {
let tag_end_pos = content[element_pos..].find('>').ok_or_else(|| {
Error::validation_error("xml", "Malformed element start tag")
})? + element_pos
+ 1;
let mut depth = 1;
let mut current_pos = tag_end_pos;
let mut element_end_pos = None;
while depth > 0 && current_pos < content.len() {
if let Some(start_pos) = content[current_pos..].find(&element_start_tag) {
let abs_start_pos = current_pos + start_pos;
if let Some(end_pos) = content[current_pos..].find(&element_end_tag) {
let abs_end_pos = current_pos + end_pos;
if abs_start_pos < abs_end_pos {
depth += 1;
current_pos = abs_start_pos + element_start_tag.len();
} else {
depth -= 1;
if depth == 0 {
element_end_pos = Some(abs_end_pos + element_end_tag.len());
}
current_pos = abs_end_pos + element_end_tag.len();
}
} else {
depth -= 1;
if depth == 0 {
if let Some(end_tag_pos) =
content[current_pos..].find(&element_end_tag)
{
element_end_pos =
Some(current_pos + end_tag_pos + element_end_tag.len());
}
}
break;
}
} else if let Some(end_pos) = content[current_pos..].find(&element_end_tag)
{
let abs_end_pos = current_pos + end_pos;
depth -= 1;
if depth == 0 {
element_end_pos = Some(abs_end_pos + element_end_tag.len());
}
current_pos = abs_end_pos + element_end_tag.len();
} else {
break;
}
}
if let Some(end_pos) = element_end_pos {
let element_xml = &content[element_pos..end_pos];
let variant = T::parse_choice_element(element_name, element_xml)?;
variants.push((element_pos, variant)); search_pos = end_pos;
} else {
return Err(Error::validation_error(
"xml",
&format!("Unclosed element '{}'", element_name),
));
}
}
}
}
variants.sort_by_key(|(pos, _)| *pos);
let sorted_variants: Vec<T::Variant> =
variants.into_iter().map(|(_, variant)| variant).collect();
T::from_choice_variants(sorted_variants)
}
}
pub struct ChoiceGroupRegistry {
}
impl ChoiceGroupRegistry {
pub fn new() -> Self {
Self {}
}
pub fn parse<T: XsdChoiceGroup>(&self, container_name: &str, xml: &str) -> Result<T> {
let parser = ChoiceGroupParser::from_str(xml);
parser.parse_choice_group(container_name)
}
}
impl Default for ChoiceGroupRegistry {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_REGISTRY: std::sync::OnceLock<ChoiceGroupRegistry> = std::sync::OnceLock::new();
pub fn parse_choice_group<T: XsdChoiceGroup>(container_name: &str, xml: &str) -> Result<T> {
let registry = GLOBAL_REGISTRY.get_or_init(ChoiceGroupRegistry::new);
registry.parse(container_name, xml)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
enum TestVariant {
ElementA(String),
ElementB(i32),
ElementC(bool),
}
#[derive(Debug, PartialEq)]
struct TestChoiceGroup {
elements: Vec<TestVariant>,
}
impl XsdChoiceGroup for TestChoiceGroup {
type Variant = TestVariant;
fn choice_element_names() -> &'static [&'static str] {
&["ElementA", "ElementB", "ElementC"]
}
fn parse_choice_element(element_name: &str, xml: &str) -> Result<Self::Variant> {
match element_name {
"ElementA" => {
let start = xml
.find('>')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementA"))?;
let end = xml
.rfind('<')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementA"))?;
let content = &xml[start + 1..end];
Ok(TestVariant::ElementA(content.to_string()))
}
"ElementB" => {
let start = xml
.find('>')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementB"))?;
let end = xml
.rfind('<')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementB"))?;
let content = &xml[start + 1..end];
let value = content.parse::<i32>().map_err(|_| {
Error::validation_error("xml", "Invalid integer in ElementB")
})?;
Ok(TestVariant::ElementB(value))
}
"ElementC" => {
let start = xml
.find('>')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementC"))?;
let end = xml
.rfind('<')
.ok_or_else(|| Error::validation_error("xml", "Invalid ElementC"))?;
let content = &xml[start + 1..end];
let value = content.parse::<bool>().map_err(|_| {
Error::validation_error("xml", "Invalid boolean in ElementC")
})?;
Ok(TestVariant::ElementC(value))
}
_ => Err(Error::validation_error("choice_group", "Unknown element")),
}
}
fn from_choice_variants(variants: Vec<Self::Variant>) -> Result<Self> {
Ok(TestChoiceGroup { elements: variants })
}
}
#[test]
fn test_choice_group_trait_system() {
assert_eq!(
TestChoiceGroup::choice_element_names(),
&["ElementA", "ElementB", "ElementC"]
);
let variant_a =
TestChoiceGroup::parse_choice_element("ElementA", "<ElementA>test</ElementA>").unwrap();
assert_eq!(variant_a, TestVariant::ElementA("test".to_string()));
let variant_b =
TestChoiceGroup::parse_choice_element("ElementB", "<ElementB>42</ElementB>").unwrap();
assert_eq!(variant_b, TestVariant::ElementB(42));
let variant_c =
TestChoiceGroup::parse_choice_element("ElementC", "<ElementC>true</ElementC>").unwrap();
assert_eq!(variant_c, TestVariant::ElementC(true));
let variants = vec![variant_a, variant_b, variant_c];
let choice_group = TestChoiceGroup::from_choice_variants(variants).unwrap();
assert_eq!(choice_group.elements.len(), 3);
}
#[test]
fn test_xml_parsing() {
let xml = r#"
<Container>
<ElementA>hello</ElementA>
<ElementB>123</ElementB>
<ElementC>false</ElementC>
</Container>
"#;
let parser = ChoiceGroupParser::from_str(xml);
let result: TestChoiceGroup = parser.parse_choice_group("Container").unwrap();
assert_eq!(result.elements.len(), 3);
assert_eq!(
result.elements[0],
TestVariant::ElementA("hello".to_string())
);
assert_eq!(result.elements[1], TestVariant::ElementB(123));
assert_eq!(result.elements[2], TestVariant::ElementC(false));
}
#[test]
fn test_mixed_order_parsing() {
let xml = r#"
<Container>
<ElementC>true</ElementC>
<ElementA>world</ElementA>
<ElementB>456</ElementB>
<ElementA>again</ElementA>
</Container>
"#;
let parser = ChoiceGroupParser::from_str(xml);
let result: TestChoiceGroup = parser.parse_choice_group("Container").unwrap();
assert_eq!(result.elements.len(), 4);
assert_eq!(result.elements[0], TestVariant::ElementC(true));
assert_eq!(
result.elements[1],
TestVariant::ElementA("world".to_string())
);
assert_eq!(result.elements[2], TestVariant::ElementB(456));
assert_eq!(
result.elements[3],
TestVariant::ElementA("again".to_string())
);
}
#[test]
fn test_empty_container() {
let xml = r#"<Container></Container>"#;
let parser = ChoiceGroupParser::from_str(xml);
let result: TestChoiceGroup = parser.parse_choice_group("Container").unwrap();
assert_eq!(result.elements.len(), 0);
}
#[test]
fn test_self_closing_container() {
let xml = r#"<Container/>"#;
let parser = ChoiceGroupParser::from_str(xml);
let result: TestChoiceGroup = parser.parse_choice_group("Container").unwrap();
assert_eq!(result.elements.len(), 0);
}
#[test]
fn test_container_not_found() {
let xml = r#"<WrongContainer><ElementA>test</ElementA></WrongContainer>"#;
let parser = ChoiceGroupParser::from_str(xml);
let result: Result<TestChoiceGroup> = parser.parse_choice_group("Container");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Container element 'Container' not found"));
}
#[test]
fn test_global_registry() {
let xml = r#"
<Container>
<ElementA>registry_test</ElementA>
<ElementB>999</ElementB>
</Container>
"#;
let result: TestChoiceGroup = parse_choice_group("Container", xml).unwrap();
assert_eq!(result.elements.len(), 2);
assert_eq!(
result.elements[0],
TestVariant::ElementA("registry_test".to_string())
);
assert_eq!(result.elements[1], TestVariant::ElementB(999));
}
#[test]
fn test_registry_instance() {
let registry = ChoiceGroupRegistry::new();
let xml = r#"
<Container>
<ElementC>false</ElementC>
</Container>
"#;
let result: TestChoiceGroup = registry.parse("Container", xml).unwrap();
assert_eq!(result.elements.len(), 1);
assert_eq!(result.elements[0], TestVariant::ElementC(false));
}
}