use quick_xml::Decoder;
use quick_xml::events::attributes::Attribute;
mod error;
mod xml;
pub use error::{
SdkError, invalid_enum_value, invalid_field_value, missing_field, unexpected_eof, unexpected_tag,
validation_error,
};
pub use xml::resolve_relationship_target_path;
pub use xml::resolve_zip_file_path;
pub(crate) use xml::{
IoReader, IoTagEvent, SliceReader, SliceTagEvent, decode_attr_value, from_bytes_inner,
from_reader_inner, from_str_inner, read_outer_xml_borrowed, read_outer_xml_io, write_attr_value,
write_attr_value_str, write_end_tag, write_escaped_str, write_escaped_text, write_start_tag_open,
write_xmlns_attr,
};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum XmlHeaderType {
#[default]
None,
Plain,
Standalone,
}
#[inline(always)]
pub fn parse_bool_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_bool_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_attr_value<T>(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<T, SdkError>
where
T: std::str::FromStr,
{
xml::parse_attr_value(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_u8_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<u8, SdkError> {
xml::parse_u8_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_i8_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<i8, SdkError> {
xml::parse_i8_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_u16_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<u16, SdkError> {
xml::parse_u16_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_i16_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<i16, SdkError> {
xml::parse_i16_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_u32_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<u32, SdkError> {
xml::parse_u32_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_i32_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<i32, SdkError> {
xml::parse_i32_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_u64_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<u64, SdkError> {
xml::parse_u64_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_i64_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<i64, SdkError> {
xml::parse_i64_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_enum_attr<T>(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
) -> Result<T, SdkError>
where
T: crate::sdk::SdkEnum,
{
xml::parse_enum_attr(attr, decoder, ty)
}
#[inline(always)]
pub fn parse_value<T>(value: &str, ty: &'static str, field: &'static str) -> Result<T, SdkError>
where
T: std::str::FromStr,
{
xml::parse_value(value, ty, field)
}
#[inline(always)]
pub fn parse_bool_str(
value: &str,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_bool_str(value, ty, field)
}
#[inline(always)]
pub fn parse_bool_bytes(b: &[u8]) -> Result<bool, SdkError> {
xml::parse_bool_bytes(b)
}
#[inline(always)]
pub fn parse_boolean_value_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_boolean_value_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_on_off_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_on_off_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_true_false_blank_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_true_false_blank_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_true_false_attr(
attr: &Attribute<'_>,
decoder: Decoder,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_true_false_attr(attr, decoder, ty, field)
}
#[inline(always)]
pub fn parse_boolean_value_str(
value: &str,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_boolean_value_str(value, ty, field)
}
#[inline(always)]
pub fn parse_on_off_str(
value: &str,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_on_off_str(value, ty, field)
}
#[inline(always)]
pub fn parse_true_false_blank_str(
value: &str,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_true_false_blank_str(value, ty, field)
}
#[inline(always)]
pub fn parse_true_false_str(
value: &str,
ty: &'static str,
field: &'static str,
) -> Result<bool, SdkError> {
xml::parse_true_false_str(value, ty, field)
}
#[inline(always)]
pub(crate) fn push_xml_text(
value: &mut Option<String>,
text: quick_xml::events::BytesText<'_>,
) -> Result<(), SdkError> {
xml::push_xml_text(value, text)
}
#[inline(always)]
pub(crate) fn push_xml_general_ref(
value: &mut Option<String>,
text: quick_xml::events::BytesRef<'_>,
ty: &'static str,
field: &'static str,
) -> Result<(), SdkError> {
xml::push_xml_general_ref(value, text, ty, field)
}
pub(crate) fn is_foreign_prefixed_child(name: &[u8], expected_prefix: &str) -> bool {
let Some(separator_index) = name.iter().position(|b| *b == b':') else {
return false;
};
let prefix = &name[..separator_index];
prefix != b"mc" && prefix != expected_prefix.as_bytes()
}
#[inline]
#[cfg(feature = "parts")]
pub(crate) fn parent_zip_path(path: &str) -> String {
path
.rsplit_once('/')
.map(|(dir_path, _)| {
let resolved = resolve_zip_file_path(&format!("{dir_path}/"));
if resolved.is_empty() {
resolved
} else {
format!("{resolved}/")
}
})
.unwrap_or_default()
}
pub(crate) fn process_foreign_element_children_borrowed<'de, F>(
xml_reader: &mut SliceReader<'de>,
empty_tag: bool,
visitor: &mut F,
) -> Result<(), SdkError>
where
F: FnMut(
&mut SliceReader<'de>,
quick_xml::events::BytesStart<'de>,
bool,
) -> Result<bool, SdkError>,
{
if empty_tag {
return Ok(());
}
loop {
match xml_reader.next_tag_event()? {
crate::common::SliceTagEvent::Start(e, false) => match visitor(xml_reader, e, false)? {
true => {}
false => {
process_foreign_element_children_borrowed(xml_reader, false, visitor)?;
}
},
crate::common::SliceTagEvent::Start(e, true) => {
visitor(xml_reader, e, true)?;
}
crate::common::SliceTagEvent::End(_) => break,
crate::common::SliceTagEvent::Eof => Err(unexpected_eof("process_foreign_element_children"))?,
crate::common::SliceTagEvent::Decl(_) | crate::common::SliceTagEvent::Other => {}
}
}
Ok(())
}
pub(crate) fn process_foreign_element_children_io<R, F>(
xml_reader: &mut IoReader<R>,
empty_tag: bool,
visitor: &mut F,
) -> Result<(), SdkError>
where
R: std::io::BufRead,
F:
FnMut(&mut IoReader<R>, quick_xml::events::BytesStart<'static>, bool) -> Result<bool, SdkError>,
{
if empty_tag {
return Ok(());
}
loop {
let next_event = match xml_reader.next_borrowed()? {
quick_xml::events::Event::Start(e) => Some((e.into_owned(), false)),
quick_xml::events::Event::Empty(e) => Some((e.into_owned(), true)),
quick_xml::events::Event::End(_) => break,
quick_xml::events::Event::Eof => Err(unexpected_eof("process_foreign_element_children_io"))?,
_ => None,
};
match next_event {
Some((e, false)) => match visitor(xml_reader, e, false)? {
true => {}
false => {
process_foreign_element_children_io(xml_reader, false, visitor)?;
}
},
Some((e, true)) => {
visitor(xml_reader, e, true)?;
}
None => {}
}
}
Ok(())
}
pub(crate) fn skip_foreign_element_children_borrowed<'de>(
xml_reader: &mut SliceReader<'de>,
empty_tag: bool,
) -> Result<(), SdkError> {
process_foreign_element_children_borrowed(
xml_reader,
empty_tag,
&mut |_xml_reader, _e, _e_empty| Ok(false),
)
}
pub(crate) fn skip_foreign_element_children_io<R: std::io::BufRead>(
xml_reader: &mut IoReader<R>,
empty_tag: bool,
) -> Result<(), SdkError> {
process_foreign_element_children_io(xml_reader, empty_tag, &mut |_xml_reader, _e, _e_empty| {
Ok(false)
})
}
#[cfg(test)]
mod tests {
use super::*;
use quick_xml::events::Event;
fn with_first_attr<T>(
xml: &str,
f: impl FnOnce(Attribute<'_>, Decoder) -> Result<T, SdkError>,
) -> Result<T, SdkError> {
let mut reader = from_str_inner(xml)?;
let event = reader.next()?;
let e = match event {
Event::Start(e) | Event::Empty(e) => e,
other => panic!("expected start or empty tag, got {other:?}"),
};
let decoder = reader.decoder();
let attr = e
.attributes()
.with_checks(false)
.next()
.expect("attribute")
.unwrap();
f(attr, decoder)
}
#[test]
fn bool_like_attr_parsers_accept_raw_bytes_forms() {
let on_off = with_first_attr(r#"<x val="off"/>"#, |attr, decoder| {
parse_on_off_attr(&attr, decoder, "X", "val")
})
.expect("parse on_off");
assert!(!on_off);
let true_false_blank = with_first_attr(r#"<x val=""/>"#, |attr, decoder| {
parse_true_false_blank_attr(&attr, decoder, "X", "val")
})
.expect("parse true_false_blank");
assert!(!true_false_blank);
let true_false = with_first_attr(r#"<x val="t"/>"#, |attr, decoder| {
parse_true_false_attr(&attr, decoder, "X", "val")
})
.expect("parse true_false");
assert!(true_false);
}
#[test]
fn integer_attr_parsers_accept_bytes_fast_paths() {
let unsigned = with_first_attr(r#"<x val="+42"/>"#, |attr, decoder| {
parse_u32_attr(&attr, decoder, "X", "val")
})
.expect("parse u32");
assert_eq!(unsigned, 42);
let signed = with_first_attr(r#"<x val="-2147483648"/>"#, |attr, decoder| {
parse_i32_attr(&attr, decoder, "X", "val")
})
.expect("parse i32");
assert_eq!(signed, i32::MIN);
let byte = with_first_attr(r#"<x val="255"/>"#, |attr, decoder| {
parse_u8_attr(&attr, decoder, "X", "val")
})
.expect("parse u8");
assert_eq!(byte, u8::MAX);
}
}