use quick_xml::events::{BytesStart, Event};
use quick_xml::Reader;
use crate::error::{PptxError, PptxResult};
use crate::units::{RelationshipId, SlideId};
use crate::xml_util::local_name;
pub fn parse_slide_ids(presentation_xml: &[u8]) -> PptxResult<Vec<(String, SlideId)>> {
parse_id_list(presentation_xml, b"sldIdLst", b"sldId")
}
pub fn parse_slide_master_ids(presentation_xml: &[u8]) -> PptxResult<Vec<(String, SlideId)>> {
parse_id_list(presentation_xml, b"sldMasterIdLst", b"sldMasterId")
}
fn parse_id_list(
xml: &[u8],
list_tag: &[u8],
item_tag: &[u8],
) -> PptxResult<Vec<(String, SlideId)>> {
let mut reader = Reader::from_reader(xml);
reader.config_mut().trim_text(true);
let mut ids = Vec::new();
let mut in_list = false;
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e) | Event::Empty(ref e)) => {
let qn = e.name();
let ln = local_name(qn.as_ref());
if ln == list_tag {
in_list = true;
} else if ln == item_tag && in_list {
let (r_id, id) = parse_sld_id_attrs(e)?;
ids.push((r_id.to_string(), id));
}
}
Ok(Event::End(ref e)) => {
let qn = e.name();
if local_name(qn.as_ref()) == list_tag {
in_list = false;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(PptxError::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(ids)
}
pub fn parse_layout_name(layout_xml: &[u8]) -> PptxResult<String> {
let mut reader = Reader::from_reader(layout_xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e) | Event::Empty(ref e)) => {
let qn = e.name();
let ln = local_name(qn.as_ref());
if ln == b"cSld" {
for attr in e.attributes() {
let attr = attr.map_err(PptxError::XmlAttr)?;
if attr.key.as_ref() == b"name" {
let name = std::str::from_utf8(&attr.value)
.map_err(|e| {
PptxError::InvalidXml(format!(
"cSld name attribute invalid UTF-8: {e}"
))
})?
.to_string();
return Ok(name);
}
}
return Ok(String::new());
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(PptxError::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(String::new())
}
pub fn parse_slide_size(presentation_xml: &[u8]) -> PptxResult<Option<(i64, i64)>> {
let mut reader = Reader::from_reader(presentation_xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e) | Event::Empty(ref e)) => {
let qn = e.name();
if local_name(qn.as_ref()) == b"sldSz" {
return parse_sld_sz_attrs(e);
}
}
Ok(Event::Eof) => break,
_ => {}
}
buf.clear();
}
Ok(None)
}
#[allow(clippy::similar_names)]
fn parse_sld_sz_attrs(e: &BytesStart<'_>) -> PptxResult<Option<(i64, i64)>> {
let mut cx_val: Option<i64> = None;
let mut cy_val: Option<i64> = None;
for attr in e.attributes() {
let attr = attr.map_err(PptxError::XmlAttr)?;
match attr.key.as_ref() {
b"cx" => {
let s = std::str::from_utf8(&attr.value)
.map_err(|e| PptxError::InvalidXml(format!("sldSz cx: {e}")))?;
cx_val = Some(
s.parse()
.map_err(|e| PptxError::InvalidXml(format!("sldSz cx parse: {e}")))?,
);
}
b"cy" => {
let s = std::str::from_utf8(&attr.value)
.map_err(|e| PptxError::InvalidXml(format!("sldSz cy: {e}")))?;
cy_val = Some(
s.parse()
.map_err(|e| PptxError::InvalidXml(format!("sldSz cy parse: {e}")))?,
);
}
_ => {}
}
}
let cx = cx_val
.ok_or_else(|| PptxError::InvalidXml("sldSz missing required cx attribute".into()))?;
let cy = cy_val
.ok_or_else(|| PptxError::InvalidXml("sldSz missing required cy attribute".into()))?;
Ok(Some((cx, cy)))
}
fn parse_sld_id_attrs(elem: &BytesStart<'_>) -> PptxResult<(RelationshipId, SlideId)> {
let mut r_id: Option<RelationshipId> = None;
let mut id: Option<u32> = None;
for attr in elem.attributes() {
let attr = attr.map_err(PptxError::XmlAttr)?;
let key = attr.key.as_ref();
let local = local_name(key);
match local {
b"id" if key != local => {
let value = std::str::from_utf8(&attr.value)
.map_err(|e| PptxError::InvalidXml(format!("invalid UTF-8: {e}")))?;
r_id =
Some(RelationshipId::try_from(value).map_err(|_| {
PptxError::InvalidXml(format!("invalid r:id value: {value}"))
})?);
}
b"id" => {
let s = std::str::from_utf8(&attr.value)
.map_err(|e| PptxError::InvalidXml(format!("sldId id: {e}")))?;
id = Some(
s.parse::<u32>()
.map_err(|e| PptxError::InvalidXml(format!("sldId id parse: {e}")))?,
);
}
_ => {}
}
}
let r_id = r_id.ok_or_else(|| PptxError::InvalidXml("sldId missing r:id".to_string()))?;
let id = id.ok_or_else(|| PptxError::InvalidXml("sldId missing id".to_string()))?;
Ok((r_id, SlideId(id)))
}