use alloc::vec::Vec;
use crate::strings::CompactString;
use crate::{AttrMap, Event, Namespace};
#[derive(Debug)]
pub struct XmlLangStack {
stack: Vec<(Option<CompactString>, usize)>,
}
impl XmlLangStack {
pub fn new() -> Self {
Self { stack: Vec::new() }
}
pub fn push_from_attrs(&mut self, attrs: &AttrMap) {
if let Some(lang) = attrs.get(Namespace::xml(), "lang") {
if let Some((Some(existing), count)) = self.stack.last_mut() {
if existing == lang {
*count += 1;
return;
}
}
self.stack.push((Some(lang.into()), 0));
} else if let Some((_, count)) = self.stack.last_mut() {
*count += 1;
} else {
self.stack.push((None, 0));
}
}
pub fn pop(&mut self) {
let Some((_, count)) = self.stack.last_mut() else {
panic!("pop from empty XmlLangStack");
};
match count.checked_sub(1) {
None => {
self.stack.pop();
}
Some(v) => {
*count = v;
}
}
}
pub fn handle_event(&mut self, ev: &Event) {
match ev {
Event::StartElement(_, _, attrs) => self.push_from_attrs(attrs),
Event::EndElement(..) => self.pop(),
_ => (),
}
}
pub fn current(&self) -> Option<&str> {
self.stack.last().and_then(|x| x.0.as_deref())
}
}
#[cfg_attr(
not(feature = "std"),
doc = "Because the std feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "std", doc = "\n```\n")]
pub struct XmlLangTracker<I> {
stack: XmlLangStack,
inner: I,
}
impl<I: Iterator> XmlLangTracker<I> {
pub fn wrap(inner: I) -> Self {
Self {
inner,
stack: XmlLangStack::new(),
}
}
pub fn inner(&self) -> &I {
&self.inner
}
pub fn language(&self) -> Option<&str> {
self.stack.current()
}
}
impl<E, I: Iterator<Item = Result<Event, E>>> Iterator for XmlLangTracker<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next()?;
if let Ok(event) = event.as_ref() {
self.stack.handle_event(event);
}
Some(event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::borrow::ToOwned;
#[test]
fn xml_lang_stack_empty() {
let stack = XmlLangStack::new();
assert!(stack.current().is_none());
}
#[test]
#[should_panic = "pop from empty XmlLangStack"]
fn xml_lang_stack_empty_pop_panics() {
let mut stack = XmlLangStack::new();
stack.pop();
}
#[test]
fn xml_lang_stack_from_attrs() {
let mut attrs = AttrMap::new();
attrs.insert(Namespace::XML, "lang".try_into().unwrap(), "en".to_owned());
let mut stack = XmlLangStack::new();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert!(stack.current().is_none());
}
#[test]
fn xml_lang_stack_from_attrs_without_xml_lang() {
let attrs = AttrMap::new();
let mut stack = XmlLangStack::new();
stack.push_from_attrs(&attrs);
assert!(stack.current().is_none());
stack.pop();
assert!(stack.current().is_none());
}
#[test]
fn xml_lang_stack_from_attrs_inheritance() {
let mut attrs = AttrMap::new();
attrs.insert(Namespace::XML, "lang".try_into().unwrap(), "en".to_owned());
let mut stack = XmlLangStack::new();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
attrs.clear();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert!(stack.current().is_none());
}
#[test]
fn xml_lang_stack_from_attrs_inheritance_with_override() {
let mut attrs = AttrMap::new();
attrs.insert(Namespace::XML, "lang".try_into().unwrap(), "en".to_owned());
let mut stack = XmlLangStack::new();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
attrs.clear();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
attrs.insert(Namespace::XML, "lang".try_into().unwrap(), "de".to_owned());
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("de"));
attrs.clear();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("de"));
stack.pop();
assert_eq!(stack.current(), Some("de"));
stack.pop();
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert!(stack.current().is_none());
}
#[test]
fn xml_lang_stack_from_attrs_with_nested_same_value() {
let mut attrs = AttrMap::new();
attrs.insert(Namespace::XML, "lang".try_into().unwrap(), "en".to_owned());
let mut stack = XmlLangStack::new();
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
stack.push_from_attrs(&attrs);
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert_eq!(stack.current(), Some("en"));
stack.pop();
assert!(stack.current().is_none());
}
}