ofdsdk 0.1.1

Strongly typed OFD SDK for Rust
Documentation
use quick_xml::events::attributes::Attribute;
use quick_xml::{Decoder, events::Event};
use std::io::BufRead;

mod error;
mod xml;
mod zip;

pub use error::{
  SdkError, invalid_enum_value, invalid_field_value, missing_field, unexpected_eof, unexpected_tag,
  xml_entity_error,
};
pub(crate) use xml::{attr_raw_value, decode_attr_value, from_reader_inner, from_str_inner};
#[cfg(feature = "parts")]
pub use zip::{
  load_optional_zip_part, load_optional_zip_part_with_context, load_required_zip_part,
  load_required_zip_part_with_context, load_zip_parts, load_zip_parts_with_context, read_zip_data,
  save_optional_zip_part, save_required_zip_part, save_zip_data, save_zip_parts, zip_parent_dirs,
};
pub use zip::{resolve_zip_child_path, resolve_zip_file_path, zip_parent_dir};

pub(crate) struct TextReadSpec<'a> {
  pub ty: &'static str,
  pub field: &'static str,
  pub tag_name_prefix: &'a [u8],
  pub tag_name: &'a [u8],
}

#[inline(always)]
pub(crate) fn parse_bool_attr(
  attr: &Attribute<'_>,
  decoder: Decoder,
  ty: &'static str,
  field: &'static str,
) -> Result<bool, SdkError> {
  if let Some(value) = attr_raw_value(attr)
    && let Ok(value) = parse_bool_bytes(value)
  {
    return Ok(value);
  }

  let value = decode_attr_value(attr, decoder)?;
  parse_bool_bytes(value.as_bytes()).map_err(|_| invalid_field_value(ty, field, value.to_string()))
}

#[inline(always)]
pub(crate) fn parse_f64_attr(
  attr: &Attribute<'_>,
  decoder: Decoder,
  ty: &'static str,
  field: &'static str,
) -> Result<f64, SdkError> {
  if let Some(value) = attr_raw_value(attr)
    && let Ok(value) = std::str::from_utf8(value)
    && let Ok(value) = value.parse::<f64>()
  {
    return Ok(value);
  }

  let value = decode_attr_value(attr, decoder)?;
  value
    .parse::<f64>()
    .map_err(|_| invalid_field_value(ty, field, value.to_string()))
}

#[inline(always)]
pub(crate) fn parse_i32_attr(
  attr: &Attribute<'_>,
  decoder: Decoder,
  ty: &'static str,
  field: &'static str,
) -> Result<i32, SdkError> {
  if let Some(value) = attr_raw_value(attr)
    && let Ok(value) = std::str::from_utf8(value)
    && let Ok(value) = value.parse::<i32>()
  {
    return Ok(value);
  }

  let value = decode_attr_value(attr, decoder)?;
  value
    .parse::<i32>()
    .map_err(|_| invalid_field_value(ty, field, value.to_string()))
}

#[inline(always)]
pub(crate) fn parse_u32_attr(
  attr: &Attribute<'_>,
  decoder: Decoder,
  ty: &'static str,
  field: &'static str,
) -> Result<u32, SdkError> {
  if let Some(value) = attr_raw_value(attr)
    && let Ok(value) = std::str::from_utf8(value)
    && let Ok(value) = value.parse::<u32>()
  {
    return Ok(value);
  }

  let value = decode_attr_value(attr, decoder)?;
  value
    .parse::<u32>()
    .map_err(|_| invalid_field_value(ty, field, value.to_string()))
}

#[inline(always)]
pub(crate) fn push_xml_text(
  value: &mut Option<String>,
  text: quick_xml::events::BytesText<'_>,
) -> Result<(), SdkError> {
  let text = text.xml10_content()?;
  if let Some(value) = value {
    value.push_str(text.as_ref());
  } else {
    *value = Some(text.into_owned());
  }

  Ok(())
}

#[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> {
  let entity = text.xml10_content()?;
  let entity = quick_xml::escape::resolve_predefined_entity(entity.as_ref())
    .ok_or_else(|| invalid_field_value(ty, field, entity.to_string()))?;

  if let Some(value) = value {
    value.push_str(entity);
  } else {
    *value = Some(entity.to_owned());
  }

  Ok(())
}

#[inline(always)]
pub(crate) fn read_text_content_io<R: BufRead>(
  xml_reader: &mut quick_xml::Reader<R>,
  buf: &mut Vec<u8>,
  spec: TextReadSpec<'_>,
) -> Result<String, SdkError> {
  let mut value = None;

  loop {
    buf.clear();
    match xml_reader.read_event_into(buf)? {
      Event::Text(text) => push_xml_text(&mut value, text)?,
      Event::GeneralRef(text) => push_xml_general_ref(&mut value, text, spec.ty, spec.field)?,
      Event::End(end) => match end.name().as_ref() {
        name if name == spec.tag_name_prefix || name == spec.tag_name => break,
        _ => {}
      },
      Event::Eof => Err(unexpected_eof(spec.ty))?,
      _ => {}
    }
  }

  Ok(value.unwrap_or_default())
}

#[inline(always)]
pub(crate) fn read_text_parsed_content_io<R: BufRead, T, FB, FS>(
  xml_reader: &mut quick_xml::Reader<R>,
  buf: &mut Vec<u8>,
  spec: TextReadSpec<'_>,
  parse_bytes: FB,
  parse_str: FS,
) -> Result<T, SdkError>
where
  FB: Fn(&[u8]) -> Result<T, SdkError>,
  FS: Fn(&str) -> Result<T, SdkError>,
{
  let mut first_text = None;
  let mut value = None;

  loop {
    buf.clear();
    match xml_reader.read_event_into(buf)? {
      Event::Text(text) => {
        if let Some(first) = first_text.take() {
          push_xml_text(&mut value, first)?;
          push_xml_text(&mut value, text)?;
        } else if value.is_some() {
          push_xml_text(&mut value, text)?;
        } else {
          first_text = Some(text.into_owned());
        }
      }
      Event::GeneralRef(text) => {
        if let Some(first) = first_text.take() {
          push_xml_text(&mut value, first)?;
        }
        push_xml_general_ref(&mut value, text, spec.ty, spec.field)?;
      }
      Event::End(end) => match end.name().as_ref() {
        name if name == spec.tag_name_prefix || name == spec.tag_name => {
          if let Some(first) = first_text {
            return match parse_bytes(first.as_ref()) {
              Ok(value) => Ok(value),
              Err(_) => {
                let content = first.xml10_content()?;
                parse_str(content.as_ref())
              }
            };
          }

          let value = value.unwrap_or_default();
          return match parse_bytes(value.as_bytes()) {
            Ok(value) => Ok(value),
            Err(_) => parse_str(value.as_str()),
          };
        }
        _ => {}
      },
      Event::Eof => Err(unexpected_eof(spec.ty))?,
      _ => {}
    }
  }
}

#[inline(always)]
pub(crate) fn read_text_enum_content_io<R: BufRead, T, F>(
  xml_reader: &mut quick_xml::Reader<R>,
  buf: &mut Vec<u8>,
  spec: TextReadSpec<'_>,
  parse_bytes: F,
) -> Result<T, SdkError>
where
  T: std::str::FromStr<Err = SdkError>,
  F: Fn(&[u8]) -> Result<T, SdkError>,
{
  read_text_parsed_content_io(xml_reader, buf, spec, parse_bytes, |value| {
    value.parse::<T>()
  })
}

#[inline(always)]
pub fn parse_bool_bytes(b: &[u8]) -> Result<bool, SdkError> {
  match b {
    b"true" | b"1" | b"True" | b"TRUE" | b"t" | b"Yes" | b"YES" | b"yes" | b"y" => Ok(true),
    b"false" | b"0" | b"False" | b"FALSE" | b"f" | b"No" | b"NO" | b"no" | b"n" | b"" => Ok(false),
    other => Err(invalid_field_value(
      "bool",
      "value",
      String::from_utf8_lossy(other).into_owned(),
    )),
  }
}

macro_rules! expect_event_start_slice {
  ($xml_reader:expr, $xml_event:expr, $context:expr, $expected:expr, $tag_prefix:expr, $tag:expr) => {{
    if let Some((e, empty_tag)) = $xml_event {
      (e, empty_tag)
    } else {
      let (e, empty_tag) = loop {
        match $xml_reader.read_event()? {
          quick_xml::events::Event::Start(b) => break (b, false),
          quick_xml::events::Event::Empty(b) => break (b, true),
          quick_xml::events::Event::Eof => {
            return Err(super::super::common::unexpected_eof($context));
          }
          _ => continue,
        }
      };

      match e.name().as_ref() {
        name if name == $tag_prefix || name == $tag => (),
        _ => {
          Err(super::super::common::unexpected_tag(
            $context,
            $expected,
            e.name().as_ref(),
          ))?;
        }
      }

      (e, empty_tag)
    }
  }};
}

macro_rules! expect_event_start_io {
  ($xml_reader:expr, $buf:expr, $xml_event:expr, $context:expr, $expected:expr, $tag_prefix:expr, $tag:expr) => {{
    if let Some((e, empty_tag)) = $xml_event {
      (e, empty_tag)
    } else {
      let (e, empty_tag) = loop {
        $buf.clear();
        match $xml_reader.read_event_into($buf)? {
          quick_xml::events::Event::Start(b) => break (b.into_owned(), false),
          quick_xml::events::Event::Empty(b) => break (b.into_owned(), true),
          quick_xml::events::Event::Eof => {
            return Err(super::super::common::unexpected_eof($context));
          }
          _ => continue,
        }
      };

      match e.name().as_ref() {
        name if name == $tag_prefix || name == $tag => (),
        _ => {
          Err(super::super::common::unexpected_tag(
            $context,
            $expected,
            e.name().as_ref(),
          ))?;
        }
      }

      (e, empty_tag)
    }
  }};
}

pub(crate) use expect_event_start_io;
pub(crate) use expect_event_start_slice;