use quick_xml::events::{BytesStart, Event};
use quick_xml::Reader;
use crate::field::FieldInstruction;
use crate::docx::dimension::Dimension;
use crate::docx::error::Result;
use crate::docx::model::*;
use crate::docx::xml;
use super::{drawing, properties};
pub fn parse_blocks(data: &[u8]) -> Result<Vec<Block>> {
let mut reader = Reader::from_reader(data);
let mut buf = Vec::new();
let root_tag = loop {
match xml::next_event(&mut reader, &mut buf)? {
Event::Start(ref e) => {
break xml::local_name(e.name().as_ref()).to_vec();
}
Event::Eof => return Ok(Vec::new()),
_ => {}
}
};
let (blocks, _) = parse_block_content(&mut reader, &mut buf, &root_tag)?;
Ok(blocks)
}
pub fn parse_body(data: &[u8]) -> Result<(Vec<Block>, SectionProperties)> {
let mut reader = Reader::from_reader(data);
let mut buf = Vec::new();
loop {
match xml::next_event(&mut reader, &mut buf)? {
Event::Start(ref e) if xml::local_name(e.name().as_ref()) == b"body" => break,
Event::Eof => return Ok((Vec::new(), SectionProperties::default())),
_ => {}
}
}
parse_block_content(&mut reader, &mut buf, b"body")
}
fn parse_block_content(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
end_tag: &[u8],
) -> Result<(Vec<Block>, SectionProperties)> {
let mut blocks = Vec::new();
let mut final_section = SectionProperties::default();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"p" => {
let rsids = parse_paragraph_rsids(e)?;
let (para, sect) = parse_paragraph(reader, buf, rsids)?;
blocks.push(Block::Paragraph(Box::new(para)));
if let Some(sp) = sect {
blocks.push(Block::SectionBreak(Box::new(sp)));
}
}
b"tbl" => {
blocks.push(Block::Table(Box::new(parse_table(reader, buf)?)));
}
b"sectPr" => {
let sect_rsids = properties::parse_section_rsids(e)?;
final_section = properties::parse_section_properties(reader, buf)?;
final_section.rsids = sect_rsids;
}
_ => xml::warn_unsupported_element("body", local),
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
if local == b"p" {
let rsids = parse_paragraph_rsids(e)?;
blocks.push(Block::Paragraph(Box::new(Paragraph {
style_id: None,
properties: ParagraphProperties::default(),
mark_run_properties: None,
content: vec![],
rsids,
})));
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == end_tag => break,
Event::Eof => return Err(xml::unexpected_eof(b"container")),
_ => {}
}
}
Ok((blocks, final_section))
}
fn parse_paragraph(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
rsids: ParagraphRevisionIds,
) -> Result<(Paragraph, Option<SectionProperties>)> {
let mut para_props = ParagraphProperties::default();
let mut mark_run_props: Option<RunProperties> = None;
let mut style_id: Option<StyleId> = None;
let mut section_props: Option<SectionProperties> = None;
let mut content = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"pPr" => {
let parsed = properties::parse_paragraph_properties(reader, buf)?;
para_props = parsed.properties;
style_id = parsed.style_id;
mark_run_props = parsed.run_properties;
section_props = parsed.section_properties;
}
b"r" => {
let run_rsids = parse_run_rsids(e)?;
parse_run(reader, buf, &mut content, run_rsids)?;
}
b"hyperlink" => {
let r_id = xml::optional_attr(e, b"id")?;
let anchor = xml::optional_attr(e, b"anchor")?;
let hyperlink = parse_hyperlink_content(r_id, anchor, reader, buf)?;
content.push(Inline::Hyperlink(hyperlink));
}
b"bookmarkStart" => {
if let (Some(id), Some(name)) = (
xml::optional_attr_i64(e, b"id")?,
xml::optional_attr(e, b"name")?,
) {
content.push(Inline::BookmarkStart {
id: BookmarkId::new(id),
name,
});
}
}
b"bookmarkEnd" => {
if let Some(id) = xml::optional_attr_i64(e, b"id")? {
content.push(Inline::BookmarkEnd(BookmarkId::new(id)));
}
}
b"fldSimple" => {
let instr = xml::optional_attr(e, b"instr")?.unwrap_or_default();
let field = parse_simple_field_content(&instr, reader, buf)?;
content.push(Inline::Field(field));
}
_ => xml::warn_unsupported_element("paragraph", local),
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"bookmarkStart" => {
if let (Some(id), Some(name)) = (
xml::optional_attr_i64(e, b"id")?,
xml::optional_attr(e, b"name")?,
) {
content.push(Inline::BookmarkStart {
id: BookmarkId::new(id),
name,
});
}
}
b"bookmarkEnd" => {
if let Some(id) = xml::optional_attr_i64(e, b"id")? {
content.push(Inline::BookmarkEnd(BookmarkId::new(id)));
}
}
_ => xml::warn_unsupported_element("paragraph", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"p" => break,
Event::Eof => return Err(xml::unexpected_eof(b"p")),
_ => {}
}
}
Ok((
Paragraph {
style_id,
properties: para_props,
mark_run_properties: mark_run_props,
content,
rsids,
},
section_props,
))
}
pub fn parse_paragraph_public(
start_event: &BytesStart<'_>,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<(Paragraph, Option<SectionProperties>)> {
let rsids = parse_paragraph_rsids(start_event)?;
parse_paragraph(reader, buf, rsids)
}
pub fn parse_table_public(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Table> {
parse_table(reader, buf)
}
pub fn parse_block_content_public(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
end_tag: &[u8],
) -> Result<(Vec<Block>, SectionProperties)> {
parse_block_content(reader, buf, end_tag)
}
fn parse_run(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
content: &mut Vec<Inline>,
run_rsids: RevisionIds,
) -> Result<()> {
let mut run_props = RunProperties::default();
let mut char_style_id: Option<StyleId> = None;
let mut elements: Vec<RunElement> = Vec::new();
fn flush_run(
elements: &mut Vec<RunElement>,
style_id: &Option<StyleId>,
props: &RunProperties,
rsids: &RevisionIds,
content: &mut Vec<Inline>,
) {
if !elements.is_empty() {
content.push(Inline::TextRun(Box::new(TextRun {
style_id: style_id.clone(),
properties: props.clone(),
content: std::mem::take(elements),
rsids: *rsids,
})));
}
}
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"rPr" => {
let (rp, sid) = properties::parse_run_properties(reader, buf)?;
run_props = rp;
char_style_id = sid;
}
b"t" | b"delText" => {
let text = xml::read_text_content(reader, buf)?;
elements.push(RunElement::Text(text));
}
b"instrText" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
let text = xml::read_text_content(reader, buf)?;
content.push(Inline::InstrText(text));
}
b"drawing" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
if let Some(img) = parse_drawing(reader, buf)? {
content.push(Inline::Image(Box::new(img)));
}
}
b"pict" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::Pict(parse_pict(reader, buf)?));
}
b"AlternateContent" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::AlternateContent(parse_alternate_content(
reader, buf,
)?));
}
_ => xml::warn_unsupported_element("run", local),
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"rPr" => {}
b"tab" => elements.push(RunElement::Tab),
b"br" => {
let br_type = xml::optional_attr(e, b"type")?;
match br_type.as_deref() {
Some("page") => elements.push(RunElement::PageBreak),
Some("column") => elements.push(RunElement::ColumnBreak),
_ => {
let clear = match xml::optional_attr(e, b"clear")?.as_deref() {
Some("left") => BreakClear::Left,
Some("right") => BreakClear::Right,
Some("all") => BreakClear::All,
_ => BreakClear::None,
};
let kind = if clear != BreakClear::None {
BreakKind::Clear(clear)
} else {
BreakKind::TextWrapping
};
elements.push(RunElement::LineBreak(kind));
}
}
}
b"cr" => elements.push(RunElement::LineBreak(BreakKind::TextWrapping)),
b"lastRenderedPageBreak" => elements.push(RunElement::LastRenderedPageBreak),
b"sym" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
let font = xml::optional_attr(e, b"font")?.unwrap_or_default();
let char_str = xml::optional_attr(e, b"char")?.unwrap_or_default();
let char_code = u16::from_str_radix(&char_str, 16).unwrap_or(0);
content.push(Inline::Symbol(Symbol { font, char_code }));
}
b"footnoteReference" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
if let Some(id) = xml::optional_attr_i64(e, b"id")? {
content.push(Inline::FootnoteRef(NoteId::new(id)));
}
}
b"endnoteReference" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
if let Some(id) = xml::optional_attr_i64(e, b"id")? {
content.push(Inline::EndnoteRef(NoteId::new(id)));
}
}
b"separator" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::Separator);
}
b"continuationSeparator" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::ContinuationSeparator);
}
b"footnoteRef" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::FootnoteRefMark);
}
b"endnoteRef" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
content.push(Inline::EndnoteRefMark);
}
b"fldChar" => {
flush_run(
&mut elements,
&char_style_id,
&run_props,
&run_rsids,
content,
);
let field_char_type = match xml::optional_attr(e, b"fldCharType")?
.as_deref()
{
Some("begin") => FieldCharType::Begin,
Some("separate") => FieldCharType::Separate,
Some("end") => FieldCharType::End,
Some(other) => {
return Err(
crate::docx::error::ParseError::InvalidAttributeValue {
attr: "fldChar/fldCharType".into(),
value: other.into(),
reason: "expected begin, separate, or end per §17.18.29"
.into(),
},
);
}
None => {
return Err(crate::docx::error::ParseError::MissingAttribute {
element: "fldChar".into(),
attr: "fldCharType".into(),
});
}
};
content.push(Inline::FieldChar(FieldChar {
field_char_type,
dirty: xml::optional_attr_bool(e, b"dirty")?,
fld_lock: xml::optional_attr_bool(e, b"fldLock")?,
}));
}
_ => xml::warn_unsupported_element("run", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"r" => break,
Event::Eof => return Err(xml::unexpected_eof(b"r")),
_ => {}
}
}
if !elements.is_empty() {
content.push(Inline::TextRun(Box::new(TextRun {
style_id: char_style_id,
properties: run_props,
content: elements,
rsids: run_rsids,
})));
}
Ok(())
}
fn parse_hyperlink_content(
r_id: Option<String>,
anchor: Option<String>,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<Hyperlink> {
let target = if let Some(id) = r_id {
HyperlinkTarget::External(RelId::new(id))
} else if let Some(anchor) = anchor {
HyperlinkTarget::Internal { anchor }
} else {
HyperlinkTarget::Internal {
anchor: String::new(),
}
};
let mut inline_content = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) if xml::local_name(e.name().as_ref()) == b"r" => {
let r_rsids = parse_run_rsids(e)?;
parse_run(reader, buf, &mut inline_content, r_rsids)?;
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"hyperlink" => break,
Event::Eof => return Err(xml::unexpected_eof(b"hyperlink")),
_ => {}
}
}
Ok(Hyperlink {
target,
content: inline_content,
})
}
fn parse_simple_field_content(
instruction: &str,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<Field> {
let mut field_content = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) if xml::local_name(e.name().as_ref()) == b"r" => {
let r_rsids = parse_run_rsids(e)?;
parse_run(reader, buf, &mut field_content, r_rsids)?;
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"fldSimple" => break,
Event::Eof => return Err(xml::unexpected_eof(b"fldSimple")),
_ => {}
}
}
let parsed = parse_field_instruction(instruction);
Ok(Field {
instruction: parsed,
content: field_content,
})
}
fn parse_field_instruction(raw: &str) -> FieldInstruction {
match crate::field::parse(raw) {
Ok(instr) => instr,
Err(e) => {
log::warn!("failed to parse field instruction {:?}: {}", raw, e);
FieldInstruction::Unknown {
field_type: String::new(),
raw: raw.to_owned(),
}
}
}
}
pub fn parse_pict(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Pict> {
let mut shape_type = None;
let mut shapes = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"shapetype" => {
shape_type = Some(parse_vml_shapetype(e, reader, buf)?);
}
b"shape" => {
shapes.push(parse_vml_shape(e, reader, buf)?);
}
_ => {
xml::warn_unsupported_element("pict", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"shapetype" => {
shape_type = Some(parse_vml_shapetype_from_attrs(e)?);
}
b"shape" => {
shapes.push(parse_vml_shape_from_attrs(e)?);
}
_ => xml::warn_unsupported_element("pict", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"pict" => break,
Event::Eof => return Err(xml::unexpected_eof(b"pict")),
_ => {}
}
}
Ok(Pict { shape_type, shapes })
}
fn parse_vml_shapetype_from_attrs(e: &BytesStart<'_>) -> Result<VmlShapeType> {
Ok(VmlShapeType {
id: xml::optional_attr(e, b"id")?.map(VmlShapeId::new),
coord_size: parse_vml_vector2d(xml::optional_attr(e, b"coordsize")?),
spt: xml::optional_attr(e, b"spt")?
.map(|s| s.parse::<f32>())
.transpose()
.ok()
.flatten(),
adj: parse_vml_adj(xml::optional_attr(e, b"adj")?),
path: parse_vml_path_commands(xml::optional_attr(e, b"path")?),
filled: vml_bool_attr(e, b"filled")?,
stroked: vml_bool_attr(e, b"stroked")?,
stroke: None,
vml_path: None,
formulas: Vec::new(),
lock: None,
})
}
fn parse_vml_shapetype(
e: &BytesStart<'_>,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<VmlShapeType> {
let mut st = VmlShapeType {
id: xml::optional_attr(e, b"id")?.map(VmlShapeId::new),
coord_size: parse_vml_vector2d(xml::optional_attr(e, b"coordsize")?),
spt: xml::optional_attr(e, b"spt")?
.map(|s| s.parse::<f32>())
.transpose()
.ok()
.flatten(),
adj: parse_vml_adj(xml::optional_attr(e, b"adj")?),
path: parse_vml_path_commands(xml::optional_attr(e, b"path")?),
filled: vml_bool_attr(e, b"filled")?,
stroked: vml_bool_attr(e, b"stroked")?,
stroke: None,
vml_path: None,
formulas: Vec::new(),
lock: None,
};
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"formulas" => {
st.formulas = parse_vml_formulas(reader, buf)?;
}
_ => {
xml::warn_unsupported_element("shapetype", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"stroke" => st.stroke = Some(parse_vml_stroke_attrs(e)?),
b"path" => st.vml_path = Some(parse_vml_path_attrs(e)?),
b"lock" => st.lock = Some(parse_vml_lock(e)?),
_ => xml::warn_unsupported_element("shapetype", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"shapetype" => break,
Event::Eof => return Err(xml::unexpected_eof(b"shapetype")),
_ => {}
}
}
Ok(st)
}
fn parse_vml_shape_from_attrs(e: &BytesStart<'_>) -> Result<VmlShape> {
Ok(VmlShape {
id: xml::optional_attr(e, b"id")?.map(VmlShapeId::new),
shape_type_ref: xml::optional_attr(e, b"type")?
.map(|s| VmlShapeId::new(s.strip_prefix('#').unwrap_or(&s))),
style: parse_vml_style(xml::optional_attr(e, b"style")?),
fill_color: xml::optional_attr(e, b"fillcolor")?
.map(|s| parse_vml_color(&s))
.transpose()?,
stroked: vml_bool_attr(e, b"stroked")?,
stroke: None,
vml_path: None,
text_box: None,
wrap: None,
image_data: None,
})
}
fn parse_vml_shape(
e: &BytesStart<'_>,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<VmlShape> {
let mut shape = VmlShape {
id: xml::optional_attr(e, b"id")?.map(VmlShapeId::new),
shape_type_ref: xml::optional_attr(e, b"type")?
.map(|s| VmlShapeId::new(s.strip_prefix('#').unwrap_or(&s))),
style: parse_vml_style(xml::optional_attr(e, b"style")?),
fill_color: xml::optional_attr(e, b"fillcolor")?
.map(|s| parse_vml_color(&s))
.transpose()?,
stroked: vml_bool_attr(e, b"stroked")?,
stroke: None,
vml_path: None,
text_box: None,
wrap: None,
image_data: None,
};
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"textbox" => {
shape.text_box = Some(parse_vml_textbox(e, reader, buf)?);
}
b"wrap" => {
shape.wrap = Some(parse_vml_wrap(e)?);
xml::skip_to_end(reader, buf, b"wrap")?;
}
_ => {
xml::warn_unsupported_element("shape", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::Empty(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"stroke" => shape.stroke = Some(parse_vml_stroke_attrs(e)?),
b"path" => shape.vml_path = Some(parse_vml_path_attrs(e)?),
b"imagedata" => shape.image_data = Some(parse_vml_imagedata(e)?),
b"wrap" => shape.wrap = Some(parse_vml_wrap(e)?),
_ => xml::warn_unsupported_element("shape", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"shape" => break,
Event::Eof => return Err(xml::unexpected_eof(b"shape")),
_ => {}
}
}
Ok(shape)
}
fn parse_vml_stroke_attrs(e: &BytesStart<'_>) -> Result<VmlStroke> {
let dash_style = match xml::optional_attr(e, b"dashstyle")?.as_deref() {
Some("solid") => Some(VmlDashStyle::Solid),
Some("shortdash") => Some(VmlDashStyle::ShortDash),
Some("shortdot") => Some(VmlDashStyle::ShortDot),
Some("shortdashdot") => Some(VmlDashStyle::ShortDashDot),
Some("shortdashdotdot") => Some(VmlDashStyle::ShortDashDotDot),
Some("dot") => Some(VmlDashStyle::Dot),
Some("dash") => Some(VmlDashStyle::Dash),
Some("longdash") => Some(VmlDashStyle::LongDash),
Some("dashdot") => Some(VmlDashStyle::DashDot),
Some("longdashdot") => Some(VmlDashStyle::LongDashDot),
Some("longdashdotdot") => Some(VmlDashStyle::LongDashDotDot),
Some(other) => {
log::warn!("vml-stroke: unsupported dashstyle {:?}", other);
None
}
None => None,
};
let join_style = match xml::optional_attr(e, b"joinstyle")?.as_deref() {
Some("round") => Some(VmlJoinStyle::Round),
Some("bevel") => Some(VmlJoinStyle::Bevel),
Some("miter") => Some(VmlJoinStyle::Miter),
Some(other) => {
log::warn!("vml-stroke: unsupported joinstyle {:?}", other);
None
}
None => None,
};
Ok(VmlStroke {
dash_style,
join_style,
})
}
fn parse_vml_path_attrs(e: &BytesStart<'_>) -> Result<VmlPath> {
Ok(VmlPath {
gradient_shape_ok: vml_bool_attr(e, b"gradientshapeok")?,
connect_type: match xml::optional_attr(e, b"connecttype")?.as_deref() {
Some("none") => Some(VmlConnectType::None),
Some("rect") => Some(VmlConnectType::Rect),
Some("segments") => Some(VmlConnectType::Segments),
Some("custom") => Some(VmlConnectType::Custom),
Some(other) => {
log::warn!("vml-path: unsupported connecttype {:?}", other);
None
}
None => None,
},
extrusion_ok: vml_bool_attr(e, b"extrusionok")?,
})
}
fn parse_vml_formulas(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Vec<VmlFormula>> {
let mut formulas = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Empty(ref e) if xml::local_name(e.name().as_ref()) == b"f" => {
if let Some(eqn) = xml::optional_attr(e, b"eqn")? {
if let Some(f) = parse_vml_formula(&eqn) {
formulas.push(f);
} else {
log::warn!("vml-formula: failed to parse {:?}", eqn);
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"formulas" => break,
Event::Eof => return Err(xml::unexpected_eof(b"formulas")),
_ => {}
}
}
Ok(formulas)
}
fn parse_vml_formula(eqn: &str) -> Option<VmlFormula> {
let parts: Vec<&str> = eqn.split_whitespace().collect();
if parts.is_empty() {
return None;
}
let operation = match parts[0] {
"val" => VmlFormulaOp::Val,
"sum" => VmlFormulaOp::Sum,
"prod" => VmlFormulaOp::Product,
"mid" => VmlFormulaOp::Mid,
"abs" => VmlFormulaOp::Abs,
"min" => VmlFormulaOp::Min,
"max" => VmlFormulaOp::Max,
"if" => VmlFormulaOp::If,
"sqrt" => VmlFormulaOp::Sqrt,
"mod" => VmlFormulaOp::Mod,
"sin" => VmlFormulaOp::Sin,
"cos" => VmlFormulaOp::Cos,
"tan" => VmlFormulaOp::Tan,
"atan2" => VmlFormulaOp::Atan2,
"sinatan2" => VmlFormulaOp::SinAtan2,
"cosatan2" => VmlFormulaOp::CosAtan2,
"sumangle" => VmlFormulaOp::SumAngle,
"ellipse" => VmlFormulaOp::Ellipse,
other => {
log::warn!("vml-formula: unsupported operation {:?}", other);
return None;
}
};
let arg = |i: usize| -> VmlFormulaArg {
parts
.get(i)
.and_then(|s| parse_vml_formula_arg(s))
.unwrap_or(VmlFormulaArg::Literal(0))
};
Some(VmlFormula {
operation,
args: [arg(1), arg(2), arg(3)],
})
}
fn parse_vml_formula_arg(s: &str) -> Option<VmlFormulaArg> {
if let Some(rest) = s.strip_prefix('#') {
return rest.parse::<u32>().ok().map(VmlFormulaArg::AdjRef);
}
if let Some(rest) = s.strip_prefix('@') {
return rest.parse::<u32>().ok().map(VmlFormulaArg::FormulaRef);
}
let guide = match s {
"width" => Some(VmlGuide::Width),
"height" => Some(VmlGuide::Height),
"xcenter" => Some(VmlGuide::XCenter),
"ycenter" => Some(VmlGuide::YCenter),
"xrange" => Some(VmlGuide::XRange),
"yrange" => Some(VmlGuide::YRange),
"pixelWidth" => Some(VmlGuide::PixelWidth),
"pixelHeight" => Some(VmlGuide::PixelHeight),
"pixelLineWidth" => Some(VmlGuide::PixelLineWidth),
"emuWidth" => Some(VmlGuide::EmuWidth),
"emuHeight" => Some(VmlGuide::EmuHeight),
"emuWidth2" => Some(VmlGuide::EmuWidth2),
"emuHeight2" => Some(VmlGuide::EmuHeight2),
_ => None,
};
if let Some(g) = guide {
return Some(VmlFormulaArg::Guide(g));
}
s.parse::<i64>().ok().map(VmlFormulaArg::Literal)
}
fn parse_vml_textbox(
e: &BytesStart<'_>,
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<VmlTextBox> {
let style = parse_vml_style(xml::optional_attr(e, b"style")?);
let inset = parse_vml_textbox_inset(xml::optional_attr(e, b"inset")?);
let mut content = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"txbxContent" => {
content = parse_txbx_content(reader, buf)?;
}
_ => {
xml::warn_unsupported_element("textbox", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"textbox" => break,
Event::Eof => return Err(xml::unexpected_eof(b"textbox")),
_ => {}
}
}
Ok(VmlTextBox {
style,
inset,
content,
})
}
fn parse_txbx_content(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Vec<Block>> {
let (blocks, _) = parse_block_content(reader, buf, b"txbxContent")?;
Ok(blocks)
}
fn parse_vml_style(s: Option<String>) -> VmlStyle {
let s = match s {
Some(s) => s,
None => return VmlStyle::default(),
};
let mut style = VmlStyle::default();
for decl in s.split(';') {
let decl = decl.trim();
if decl.is_empty() {
continue;
}
let Some((key, val)) = decl.split_once(':') else {
continue;
};
let key = key.trim();
let val = val.trim();
match key {
"position" => {
style.position = match val {
"static" => Some(CssPosition::Static),
"relative" => Some(CssPosition::Relative),
"absolute" => Some(CssPosition::Absolute),
_ => {
log::warn!("vml-style: unsupported position value {:?}", val);
None
}
};
}
"left" => style.left = parse_vml_length(val),
"top" => style.top = parse_vml_length(val),
"width" => style.width = parse_vml_length(val),
"height" => style.height = parse_vml_length(val),
"margin-left" => style.margin_left = parse_vml_length(val),
"margin-top" => style.margin_top = parse_vml_length(val),
"margin-right" => style.margin_right = parse_vml_length(val),
"margin-bottom" => style.margin_bottom = parse_vml_length(val),
"z-index" => style.z_index = val.parse::<i64>().ok(),
"rotation" => style.rotation = val.parse::<f64>().ok(),
"flip" => {
style.flip = match val {
"x" => Some(VmlFlip::X),
"y" => Some(VmlFlip::Y),
"xy" | "yx" => Some(VmlFlip::XY),
_ => {
log::warn!("vml-style: unsupported flip value {:?}", val);
None
}
};
}
"visibility" => {
style.visibility = match val {
"visible" => Some(CssVisibility::Visible),
"hidden" => Some(CssVisibility::Hidden),
"inherit" => Some(CssVisibility::Inherit),
_ => {
log::warn!("vml-style: unsupported visibility value {:?}", val);
None
}
};
}
"mso-position-horizontal" => {
style.mso_position_horizontal = match val {
"absolute" => Some(MsoPositionH::Absolute),
"left" => Some(MsoPositionH::Left),
"center" => Some(MsoPositionH::Center),
"right" => Some(MsoPositionH::Right),
"inside" => Some(MsoPositionH::Inside),
"outside" => Some(MsoPositionH::Outside),
_ => {
log::warn!(
"vml-style: unsupported mso-position-horizontal value {:?}",
val
);
None
}
};
}
"mso-position-horizontal-relative" => {
style.mso_position_horizontal_relative = match val {
"margin" => Some(MsoPositionHRelative::Margin),
"page" => Some(MsoPositionHRelative::Page),
"text" => Some(MsoPositionHRelative::Text),
"char" => Some(MsoPositionHRelative::Char),
"left-margin-area" => Some(MsoPositionHRelative::LeftMarginArea),
"right-margin-area" => Some(MsoPositionHRelative::RightMarginArea),
"inner-margin-area" => Some(MsoPositionHRelative::InnerMarginArea),
"outer-margin-area" => Some(MsoPositionHRelative::OuterMarginArea),
_ => {
log::warn!(
"vml-style: unsupported mso-position-horizontal-relative value {:?}",
val
);
None
}
};
}
"mso-position-vertical" => {
style.mso_position_vertical = match val {
"absolute" => Some(MsoPositionV::Absolute),
"top" => Some(MsoPositionV::Top),
"center" => Some(MsoPositionV::Center),
"bottom" => Some(MsoPositionV::Bottom),
"inside" => Some(MsoPositionV::Inside),
"outside" => Some(MsoPositionV::Outside),
_ => {
log::warn!(
"vml-style: unsupported mso-position-vertical value {:?}",
val
);
None
}
};
}
"mso-position-vertical-relative" => {
style.mso_position_vertical_relative = match val {
"margin" => Some(MsoPositionVRelative::Margin),
"page" => Some(MsoPositionVRelative::Page),
"text" => Some(MsoPositionVRelative::Text),
"line" => Some(MsoPositionVRelative::Line),
"top-margin-area" => Some(MsoPositionVRelative::TopMarginArea),
"bottom-margin-area" => Some(MsoPositionVRelative::BottomMarginArea),
"inner-margin-area" => Some(MsoPositionVRelative::InnerMarginArea),
"outer-margin-area" => Some(MsoPositionVRelative::OuterMarginArea),
_ => {
log::warn!(
"vml-style: unsupported mso-position-vertical-relative value {:?}",
val
);
None
}
};
}
"mso-wrap-distance-left" => {
style.mso_wrap_distance_left = parse_vml_length(val);
}
"mso-wrap-distance-right" => {
style.mso_wrap_distance_right = parse_vml_length(val);
}
"mso-wrap-distance-top" => {
style.mso_wrap_distance_top = parse_vml_length(val);
}
"mso-wrap-distance-bottom" => {
style.mso_wrap_distance_bottom = parse_vml_length(val);
}
"mso-wrap-style" => {
style.mso_wrap_style = match val {
"square" => Some(MsoWrapStyle::Square),
"none" => Some(MsoWrapStyle::None),
"tight" => Some(MsoWrapStyle::Tight),
"through" => Some(MsoWrapStyle::Through),
_ => {
log::warn!("vml-style: unsupported mso-wrap-style value {:?}", val);
None
}
};
}
_ => log::warn!("vml-style: unsupported property {:?}: {:?}", key, val),
}
}
style
}
fn parse_vml_length(s: &str) -> Option<VmlLength> {
let s = s.trim();
if s.is_empty() {
return None;
}
let (num_str, unit) = if let Some(n) = s.strip_suffix("pt") {
(n, VmlLengthUnit::Pt)
} else if let Some(n) = s.strip_suffix("in") {
(n, VmlLengthUnit::In)
} else if let Some(n) = s.strip_suffix("cm") {
(n, VmlLengthUnit::Cm)
} else if let Some(n) = s.strip_suffix("mm") {
(n, VmlLengthUnit::Mm)
} else if let Some(n) = s.strip_suffix("px") {
(n, VmlLengthUnit::Px)
} else if let Some(n) = s.strip_suffix("em") {
(n, VmlLengthUnit::Em)
} else if let Some(n) = s.strip_suffix('%') {
(n, VmlLengthUnit::Percent)
} else {
let split = s
.find(|c: char| !c.is_ascii_digit() && c != '.' && c != '-' && c != '+')
.unwrap_or(s.len());
if split < s.len() {
log::warn!("vml-length: unsupported unit suffix {:?}", &s[split..]);
return None;
}
(s, VmlLengthUnit::None)
};
let value = num_str.trim().parse::<f64>().ok()?;
Some(VmlLength { value, unit })
}
fn parse_vml_color(s: &str) -> Result<VmlColor> {
let hex = s.strip_prefix('#').unwrap_or(s);
if hex.len() == 6 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
return Ok(VmlColor::Rgb(r, g, b));
}
match parse_vml_named_color(s) {
Some(named) => Ok(VmlColor::Named(named)),
None => Err(crate::docx::error::ParseError::InvalidAttributeValue {
attr: "fillcolor".into(),
value: s.into(),
reason: "unrecognized VML color name per §14.1.2.1".into(),
}),
}
}
fn parse_vml_named_color(s: &str) -> Option<VmlNamedColor> {
Some(match s.to_ascii_lowercase().as_str() {
"black" => VmlNamedColor::Black,
"silver" => VmlNamedColor::Silver,
"gray" | "grey" => VmlNamedColor::Gray,
"white" => VmlNamedColor::White,
"maroon" => VmlNamedColor::Maroon,
"red" => VmlNamedColor::Red,
"purple" => VmlNamedColor::Purple,
"fuchsia" => VmlNamedColor::Fuchsia,
"green" => VmlNamedColor::Green,
"lime" => VmlNamedColor::Lime,
"olive" => VmlNamedColor::Olive,
"yellow" => VmlNamedColor::Yellow,
"navy" => VmlNamedColor::Navy,
"blue" => VmlNamedColor::Blue,
"teal" => VmlNamedColor::Teal,
"aqua" => VmlNamedColor::Aqua,
"orange" => VmlNamedColor::Orange,
"aliceblue" => VmlNamedColor::AliceBlue,
"antiquewhite" => VmlNamedColor::AntiqueWhite,
"beige" => VmlNamedColor::Beige,
"bisque" => VmlNamedColor::Bisque,
"blanchedalmond" => VmlNamedColor::BlanchedAlmond,
"blueviolet" => VmlNamedColor::BlueViolet,
"brown" => VmlNamedColor::Brown,
"burlywood" => VmlNamedColor::BurlyWood,
"cadetblue" => VmlNamedColor::CadetBlue,
"chartreuse" => VmlNamedColor::Chartreuse,
"chocolate" => VmlNamedColor::Chocolate,
"coral" => VmlNamedColor::Coral,
"cornflowerblue" => VmlNamedColor::CornflowerBlue,
"cornsilk" => VmlNamedColor::Cornsilk,
"crimson" => VmlNamedColor::Crimson,
"cyan" => VmlNamedColor::Cyan,
"darkblue" => VmlNamedColor::DarkBlue,
"darkcyan" => VmlNamedColor::DarkCyan,
"darkgoldenrod" => VmlNamedColor::DarkGoldenrod,
"darkgray" | "darkgrey" => VmlNamedColor::DarkGray,
"darkgreen" => VmlNamedColor::DarkGreen,
"darkkhaki" => VmlNamedColor::DarkKhaki,
"darkmagenta" => VmlNamedColor::DarkMagenta,
"darkolivegreen" => VmlNamedColor::DarkOliveGreen,
"darkorange" => VmlNamedColor::DarkOrange,
"darkorchid" => VmlNamedColor::DarkOrchid,
"darkred" => VmlNamedColor::DarkRed,
"darksalmon" => VmlNamedColor::DarkSalmon,
"darkseagreen" => VmlNamedColor::DarkSeaGreen,
"darkslateblue" => VmlNamedColor::DarkSlateBlue,
"darkslategray" | "darkslategrey" => VmlNamedColor::DarkSlateGray,
"darkturquoise" => VmlNamedColor::DarkTurquoise,
"darkviolet" => VmlNamedColor::DarkViolet,
"deeppink" => VmlNamedColor::DeepPink,
"deepskyblue" => VmlNamedColor::DeepSkyBlue,
"dimgray" | "dimgrey" => VmlNamedColor::DimGray,
"dodgerblue" => VmlNamedColor::DodgerBlue,
"firebrick" => VmlNamedColor::Firebrick,
"floralwhite" => VmlNamedColor::FloralWhite,
"forestgreen" => VmlNamedColor::ForestGreen,
"gainsboro" => VmlNamedColor::Gainsboro,
"ghostwhite" => VmlNamedColor::GhostWhite,
"gold" => VmlNamedColor::Gold,
"goldenrod" => VmlNamedColor::Goldenrod,
"greenyellow" => VmlNamedColor::GreenYellow,
"honeydew" => VmlNamedColor::Honeydew,
"hotpink" => VmlNamedColor::HotPink,
"indianred" => VmlNamedColor::IndianRed,
"indigo" => VmlNamedColor::Indigo,
"ivory" => VmlNamedColor::Ivory,
"khaki" => VmlNamedColor::Khaki,
"lavender" => VmlNamedColor::Lavender,
"lavenderblush" => VmlNamedColor::LavenderBlush,
"lawngreen" => VmlNamedColor::LawnGreen,
"lemonchiffon" => VmlNamedColor::LemonChiffon,
"lightblue" => VmlNamedColor::LightBlue,
"lightcoral" => VmlNamedColor::LightCoral,
"lightcyan" => VmlNamedColor::LightCyan,
"lightgoldenrodyellow" => VmlNamedColor::LightGoldenrodYellow,
"lightgray" | "lightgrey" => VmlNamedColor::LightGray,
"lightgreen" => VmlNamedColor::LightGreen,
"lightpink" => VmlNamedColor::LightPink,
"lightsalmon" => VmlNamedColor::LightSalmon,
"lightseagreen" => VmlNamedColor::LightSeaGreen,
"lightskyblue" => VmlNamedColor::LightSkyBlue,
"lightslategray" | "lightslategrey" => VmlNamedColor::LightSlateGray,
"lightsteelblue" => VmlNamedColor::LightSteelBlue,
"lightyellow" => VmlNamedColor::LightYellow,
"limegreen" => VmlNamedColor::LimeGreen,
"linen" => VmlNamedColor::Linen,
"magenta" => VmlNamedColor::Magenta,
"mediumaquamarine" => VmlNamedColor::MediumAquamarine,
"mediumblue" => VmlNamedColor::MediumBlue,
"mediumorchid" => VmlNamedColor::MediumOrchid,
"mediumpurple" => VmlNamedColor::MediumPurple,
"mediumseagreen" => VmlNamedColor::MediumSeaGreen,
"mediumslateblue" => VmlNamedColor::MediumSlateBlue,
"mediumspringgreen" => VmlNamedColor::MediumSpringGreen,
"mediumturquoise" => VmlNamedColor::MediumTurquoise,
"mediumvioletred" => VmlNamedColor::MediumVioletRed,
"midnightblue" => VmlNamedColor::MidnightBlue,
"mintcream" => VmlNamedColor::MintCream,
"mistyrose" => VmlNamedColor::MistyRose,
"moccasin" => VmlNamedColor::Moccasin,
"navajowhite" => VmlNamedColor::NavajoWhite,
"oldlace" => VmlNamedColor::OldLace,
"olivedrab" => VmlNamedColor::OliveDrab,
"orangered" => VmlNamedColor::OrangeRed,
"orchid" => VmlNamedColor::Orchid,
"palegoldenrod" => VmlNamedColor::PaleGoldenrod,
"palegreen" => VmlNamedColor::PaleGreen,
"paleturquoise" => VmlNamedColor::PaleTurquoise,
"palevioletred" => VmlNamedColor::PaleVioletRed,
"papayawhip" => VmlNamedColor::PapayaWhip,
"peachpuff" => VmlNamedColor::PeachPuff,
"peru" => VmlNamedColor::Peru,
"pink" => VmlNamedColor::Pink,
"plum" => VmlNamedColor::Plum,
"powderblue" => VmlNamedColor::PowderBlue,
"rosybrown" => VmlNamedColor::RosyBrown,
"royalblue" => VmlNamedColor::RoyalBlue,
"saddlebrown" => VmlNamedColor::SaddleBrown,
"salmon" => VmlNamedColor::Salmon,
"sandybrown" => VmlNamedColor::SandyBrown,
"seagreen" => VmlNamedColor::SeaGreen,
"seashell" => VmlNamedColor::Seashell,
"sienna" => VmlNamedColor::Sienna,
"skyblue" => VmlNamedColor::SkyBlue,
"slateblue" => VmlNamedColor::SlateBlue,
"slategray" | "slategrey" => VmlNamedColor::SlateGray,
"snow" => VmlNamedColor::Snow,
"springgreen" => VmlNamedColor::SpringGreen,
"steelblue" => VmlNamedColor::SteelBlue,
"tan" => VmlNamedColor::Tan,
"thistle" => VmlNamedColor::Thistle,
"tomato" => VmlNamedColor::Tomato,
"turquoise" => VmlNamedColor::Turquoise,
"violet" => VmlNamedColor::Violet,
"wheat" => VmlNamedColor::Wheat,
"whitesmoke" => VmlNamedColor::WhiteSmoke,
"yellowgreen" => VmlNamedColor::YellowGreen,
"buttonface" => VmlNamedColor::ButtonFace,
"buttonhighlight" => VmlNamedColor::ButtonHighlight,
"buttonshadow" => VmlNamedColor::ButtonShadow,
"buttontext" => VmlNamedColor::ButtonText,
"captiontext" => VmlNamedColor::CaptionText,
"graytext" => VmlNamedColor::GrayText,
"highlight" => VmlNamedColor::Highlight,
"highlighttext" => VmlNamedColor::HighlightText,
"inactiveborder" => VmlNamedColor::InactiveBorder,
"inactivecaption" => VmlNamedColor::InactiveCaption,
"inactivecaptiontext" => VmlNamedColor::InactiveCaptionText,
"infobackground" => VmlNamedColor::InfoBackground,
"infotext" => VmlNamedColor::InfoText,
"menu" => VmlNamedColor::Menu,
"menutext" => VmlNamedColor::MenuText,
"scrollbar" => VmlNamedColor::Scrollbar,
"threeddarkshadow" => VmlNamedColor::ThreeDDarkShadow,
"threedface" => VmlNamedColor::ThreeDFace,
"threedhighlight" => VmlNamedColor::ThreeDHighlight,
"threedlightshadow" => VmlNamedColor::ThreeDLightShadow,
"threedshadow" => VmlNamedColor::ThreeDShadow,
"window" => VmlNamedColor::Window,
"windowframe" => VmlNamedColor::WindowFrame,
"windowtext" => VmlNamedColor::WindowText,
_ => return None,
})
}
fn parse_vml_textbox_inset(s: Option<String>) -> Option<VmlTextBoxInset> {
let s = s?;
let parts: Vec<&str> = s.split(',').collect();
Some(VmlTextBoxInset {
left: parts.first().and_then(|v| parse_vml_length(v)),
top: parts.get(1).and_then(|v| parse_vml_length(v)),
right: parts.get(2).and_then(|v| parse_vml_length(v)),
bottom: parts.get(3).and_then(|v| parse_vml_length(v)),
})
}
fn parse_vml_lock(e: &BytesStart<'_>) -> Result<VmlLock> {
let aspect_ratio = vml_bool_attr(e, b"aspectratio")?;
let ext = match xml::optional_attr(e, b"ext")?.as_deref() {
Some("edit") => Some(VmlExtHandling::Edit),
Some("view") => Some(VmlExtHandling::View),
Some("backwardCompatible") => Some(VmlExtHandling::BackwardCompatible),
Some(other) => {
return Err(crate::docx::error::ParseError::InvalidAttributeValue {
attr: "v:ext".into(),
value: other.into(),
reason: "expected edit, view, or backwardCompatible".into(),
});
}
None => None,
};
Ok(VmlLock { aspect_ratio, ext })
}
fn parse_vml_imagedata(e: &BytesStart<'_>) -> Result<VmlImageData> {
Ok(VmlImageData {
rel_id: xml::optional_attr(e, b"id")?.map(RelId::new),
title: xml::optional_attr(e, b"title")?,
})
}
fn parse_vml_wrap(e: &BytesStart<'_>) -> Result<VmlWrap> {
let wrap_type = match xml::optional_attr(e, b"type")?.as_deref() {
Some("topAndBottom") => Some(VmlWrapType::TopAndBottom),
Some("square") => Some(VmlWrapType::Square),
Some("none") => Some(VmlWrapType::None),
Some("tight") => Some(VmlWrapType::Tight),
Some("through") => Some(VmlWrapType::Through),
Some(other) => {
return Err(crate::docx::error::ParseError::InvalidAttributeValue {
attr: "type".into(),
value: other.into(),
reason: "expected value per VML §14.1.2.23".into(),
});
}
None => None,
};
let side = match xml::optional_attr(e, b"side")?.as_deref() {
Some("both") => Some(VmlWrapSide::Both),
Some("left") => Some(VmlWrapSide::Left),
Some("right") => Some(VmlWrapSide::Right),
Some("largest") => Some(VmlWrapSide::Largest),
Some(other) => {
return Err(crate::docx::error::ParseError::InvalidAttributeValue {
attr: "side".into(),
value: other.into(),
reason: "expected value per VML §14.1.2.23".into(),
});
}
None => None,
};
Ok(VmlWrap { wrap_type, side })
}
fn vml_bool_attr(e: &BytesStart<'_>, name: &[u8]) -> Result<Option<bool>> {
match xml::optional_attr(e, name)?.as_deref() {
Some("t") | Some("true") => Ok(Some(true)),
Some("f") | Some("false") => Ok(Some(false)),
Some(other) => {
xml::warn_unsupported_attr("vml", &String::from_utf8_lossy(name), other);
Ok(None)
}
None => Ok(None),
}
}
fn parse_vml_path_commands(s: Option<String>) -> Vec<VmlPathCommand> {
let s = match s {
Some(s) => s,
None => return Vec::new(),
};
let mut cmds = Vec::new();
let mut tokens: Vec<&str> = Vec::new();
let mut rest = s.as_str();
while !rest.is_empty() {
rest = rest.trim_start_matches(|c: char| c == ',' || c.is_ascii_whitespace());
if rest.is_empty() {
break;
}
let cmd_len = if rest.starts_with("wa")
|| rest.starts_with("wr")
|| rest.starts_with("at")
|| rest.starts_with("ar")
|| rest.starts_with("qx")
|| rest.starts_with("qy")
|| rest.starts_with("nf")
|| rest.starts_with("ns")
|| rest.starts_with("hа")
{
2
} else if rest.starts_with(|c: char| c.is_ascii_alphabetic()) {
1
} else {
0
};
if cmd_len > 0 {
tokens.push(&rest[..cmd_len]);
rest = &rest[cmd_len..];
} else if rest.starts_with('@') {
let end = rest[1..]
.find(|c: char| !c.is_ascii_digit())
.map(|p| p + 1)
.unwrap_or(rest.len());
tokens.push(&rest[..end]);
rest = &rest[end..];
} else {
let end = rest
.find(|c: char| {
c == ',' || c == '@' || c.is_ascii_whitespace() || c.is_ascii_alphabetic()
})
.unwrap_or(rest.len());
if end > 0 {
tokens.push(&rest[..end]);
rest = &rest[end..];
} else {
rest = &rest[1..]; }
}
}
let mut i = 0;
while i < tokens.len() {
let tok = tokens[i];
i += 1;
match tok {
"m" => {
if let Some((x, y)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::MoveTo { x, y });
}
}
"l" => {
if let Some((x, y)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::LineTo { x, y });
}
}
"c" => {
if let Some((x1, y1, x2, y2, x, y)) = take_6_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::CurveTo {
x1,
y1,
x2,
y2,
x,
y,
});
}
}
"r" => {
if let Some((dx, dy)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::RLineTo { dx, dy });
}
}
"v" => {
if let Some((dx1, dy1, dx2, dy2, dx, dy)) = take_6_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::RCurveTo {
dx1,
dy1,
dx2,
dy2,
dx,
dy,
});
}
}
"t" => {
if let Some((dx, dy)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::RMoveTo { dx, dy });
}
}
"x" => cmds.push(VmlPathCommand::Close),
"e" => cmds.push(VmlPathCommand::End),
"qx" => {
if let Some((x, y)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::QuadrantX { x, y });
}
}
"qy" => {
if let Some((x, y)) = take_2_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::QuadrantY { x, y });
}
}
"nf" => cmds.push(VmlPathCommand::NoFill),
"ns" => cmds.push(VmlPathCommand::NoStroke),
"wa" | "wr" | "at" | "ar" => {
let kind = match tok {
"wa" => VmlArcKind::WA,
"wr" => VmlArcKind::WR,
"at" => VmlArcKind::AT,
_ => VmlArcKind::AR,
};
let args = (|| {
Some(VmlPathCommand::Arc {
kind,
bounding_x1: take_coord(&tokens, &mut i)?,
bounding_y1: take_coord(&tokens, &mut i)?,
bounding_x2: take_coord(&tokens, &mut i)?,
bounding_y2: take_coord(&tokens, &mut i)?,
start_x: take_coord(&tokens, &mut i)?,
start_y: take_coord(&tokens, &mut i)?,
end_x: take_coord(&tokens, &mut i)?,
end_y: take_coord(&tokens, &mut i)?,
})
})();
if let Some(cmd) = args {
cmds.push(cmd);
}
}
_ => {
let x = if let Some(rest) = tok.strip_prefix('@') {
rest.parse::<u32>().ok().map(VmlPathCoord::FormulaRef)
} else {
tok.parse::<i64>().ok().map(VmlPathCoord::Literal)
};
if let Some(x) = x {
if let Some(y) = take_coord(&tokens, &mut i) {
cmds.push(VmlPathCommand::LineTo { x, y });
}
} else {
log::warn!("vml-path: unsupported command {:?}", tok);
}
}
}
}
cmds
}
fn take_coord(tokens: &[&str], i: &mut usize) -> Option<VmlPathCoord> {
if *i >= tokens.len() {
return None;
}
let tok = tokens[*i];
let coord = if let Some(rest) = tok.strip_prefix('@') {
VmlPathCoord::FormulaRef(rest.parse::<u32>().ok()?)
} else {
VmlPathCoord::Literal(tok.parse::<i64>().ok()?)
};
*i += 1;
Some(coord)
}
fn take_2_coord(tokens: &[&str], i: &mut usize) -> Option<(VmlPathCoord, VmlPathCoord)> {
let a = take_coord(tokens, i)?;
let b = take_coord(tokens, i)?;
Some((a, b))
}
fn take_6_coord(
tokens: &[&str],
i: &mut usize,
) -> Option<(
VmlPathCoord,
VmlPathCoord,
VmlPathCoord,
VmlPathCoord,
VmlPathCoord,
VmlPathCoord,
)> {
let a = take_coord(tokens, i)?;
let b = take_coord(tokens, i)?;
let c = take_coord(tokens, i)?;
let d = take_coord(tokens, i)?;
let e = take_coord(tokens, i)?;
let f = take_coord(tokens, i)?;
Some((a, b, c, d, e, f))
}
fn parse_vml_adj(s: Option<String>) -> Vec<i64> {
match s {
Some(s) => s
.split(',')
.filter_map(|v| v.trim().parse::<i64>().ok())
.collect(),
None => Vec::new(),
}
}
fn parse_vml_vector2d(s: Option<String>) -> Option<VmlVector2D> {
let s = s?;
let (x_str, y_str) = s.split_once(',')?;
let x = x_str.trim().parse::<i64>().ok()?;
let y = y_str.trim().parse::<i64>().ok()?;
Some(VmlVector2D { x, y })
}
fn parse_alternate_content(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<AlternateContent> {
let mut choices = Vec::new();
let mut fallback = None;
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"Choice" => {
let requires_str = xml::optional_attr(e, b"Requires")?.unwrap_or_default();
let requires = match requires_str.as_str() {
"wps" => McRequires::Wps,
"wpg" => McRequires::Wpg,
"wpc" => McRequires::Wpc,
"wpi" => McRequires::Wpi,
"m" => McRequires::Math,
"a14" => McRequires::A14,
"w14" => McRequires::W14,
"w15" => McRequires::W15,
"w16" => McRequires::W16,
other => {
log::warn!("mc:Choice: unsupported Requires {:?}", other);
xml::skip_to_end(reader, buf, b"Choice")?;
continue;
}
};
let content = parse_run_inline_content(reader, buf, b"Choice")?;
choices.push(McChoice { requires, content });
}
b"Fallback" => {
fallback = Some(parse_run_inline_content(reader, buf, b"Fallback")?);
}
_ => {
xml::warn_unsupported_element("AlternateContent", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"AlternateContent" => break,
Event::Eof => return Err(xml::unexpected_eof(b"AlternateContent")),
_ => {}
}
}
Ok(AlternateContent { choices, fallback })
}
fn parse_run_inline_content(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
end_tag: &[u8],
) -> Result<Vec<Inline>> {
let mut content = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"drawing" => {
if let Some(img) = parse_drawing(reader, buf)? {
content.push(Inline::Image(Box::new(img)));
}
}
b"pict" => {
content.push(Inline::Pict(parse_pict(reader, buf)?));
}
_ => {
xml::warn_unsupported_element("mc-content", local);
xml::skip_to_end(reader, buf, local)?;
}
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == end_tag => break,
Event::Eof => return Err(xml::unexpected_eof(end_tag)),
_ => {}
}
}
Ok(content)
}
fn parse_drawing(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Option<Image>> {
let mut image: Option<Image> = None;
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"inline" => {
image = drawing::parse_inline_image(e, reader, buf)?;
}
b"anchor" => {
image = drawing::parse_anchor_image(e, reader, buf)?;
}
_ => xml::warn_unsupported_element("drawing", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"drawing" => break,
Event::Eof => return Err(xml::unexpected_eof(b"drawing")),
_ => {}
}
}
Ok(image)
}
fn parse_table(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Table> {
let mut tbl_props = TableProperties::default();
let mut grid = Vec::new();
let mut rows = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"tblPr" => {
let (tp, _) = properties::parse_table_properties(reader, buf)?;
tbl_props = tp;
}
b"tblGrid" => {
grid = parse_table_grid(reader, buf)?;
}
b"tr" => {
let tr_rsids = parse_table_row_rsids(e)?;
rows.push(parse_table_row(reader, buf, tr_rsids)?);
}
_ => xml::warn_unsupported_element("table", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"tbl" => break,
Event::Eof => return Err(xml::unexpected_eof(b"tbl")),
_ => {}
}
}
Ok(Table {
properties: tbl_props,
grid,
rows,
})
}
fn parse_table_grid(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<Vec<GridColumn>> {
let mut cols = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Empty(ref e) | Event::Start(ref e)
if xml::local_name(e.name().as_ref()) == b"gridCol" =>
{
let w = xml::optional_attr_i64(e, b"w")?.unwrap_or(0);
cols.push(GridColumn {
width: Dimension::new(w),
});
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"tblGrid" => break,
Event::Eof => return Err(xml::unexpected_eof(b"tblGrid")),
_ => {}
}
}
Ok(cols)
}
fn parse_table_row(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
row_rsids: TableRowRevisionIds,
) -> Result<TableRow> {
let mut row_props = TableRowProperties::default();
let mut cells = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"trPr" => {
row_props = properties::parse_table_row_properties(reader, buf)?;
}
b"tc" => {
cells.push(parse_table_cell(reader, buf)?);
}
_ => xml::warn_unsupported_element("table-row", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"tr" => break,
Event::Eof => return Err(xml::unexpected_eof(b"tr")),
_ => {}
}
}
Ok(TableRow {
properties: row_props,
cells,
rsids: row_rsids,
})
}
fn parse_table_cell(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Result<TableCell> {
let mut cell_props = TableCellProperties::default();
let mut blocks = Vec::new();
loop {
match xml::next_event(reader, buf)? {
Event::Start(ref e) => {
let qn = e.name();
let local = xml::local_name(qn.as_ref());
match local {
b"tcPr" => {
cell_props = properties::parse_table_cell_properties(reader, buf)?;
}
b"p" => {
let p_rsids = parse_paragraph_rsids(e)?;
let (para, sect) = parse_paragraph(reader, buf, p_rsids)?;
blocks.push(Block::Paragraph(Box::new(para)));
if let Some(sp) = sect {
blocks.push(Block::SectionBreak(Box::new(sp)));
}
}
b"tbl" => {
blocks.push(Block::Table(Box::new(parse_table(reader, buf)?)));
}
_ => xml::warn_unsupported_element("table-cell", local),
}
}
Event::End(ref e) if xml::local_name(e.name().as_ref()) == b"tc" => break,
Event::Eof => return Err(xml::unexpected_eof(b"tc")),
_ => {}
}
}
Ok(TableCell {
properties: cell_props,
content: blocks,
})
}
fn parse_paragraph_rsids(e: &BytesStart<'_>) -> Result<ParagraphRevisionIds> {
Ok(ParagraphRevisionIds {
r: xml::optional_rsid(e, b"rsidR")?,
r_default: xml::optional_rsid(e, b"rsidRDefault")?,
p: xml::optional_rsid(e, b"rsidP")?,
r_pr: xml::optional_rsid(e, b"rsidRPr")?,
del: xml::optional_rsid(e, b"rsidDel")?,
})
}
fn parse_run_rsids(e: &BytesStart<'_>) -> Result<RevisionIds> {
Ok(RevisionIds {
r: xml::optional_rsid(e, b"rsidR")?,
r_pr: xml::optional_rsid(e, b"rsidRPr")?,
del: xml::optional_rsid(e, b"rsidDel")?,
})
}
fn parse_table_row_rsids(e: &BytesStart<'_>) -> Result<TableRowRevisionIds> {
Ok(TableRowRevisionIds {
r: xml::optional_rsid(e, b"rsidR")?,
r_pr: xml::optional_rsid(e, b"rsidRPr")?,
del: xml::optional_rsid(e, b"rsidDel")?,
tr: xml::optional_rsid(e, b"rsidTr")?,
})
}