use xml_core::{BytesStart, Event, Reader};
use crate::error::Result;
use crate::model::{
BlipFill, BlipFillMode, BulletColor, BulletFont, BulletKind, BulletSize, Color, CropRect,
CustomGeometry, EffectList, Fill, Geometry, GeometryAdjustment, Glow, GradientFill,
GradientStop, Hyperlink, Line, LineCap, LineCompound, LineEnd, LineEndSize, LineEndType,
LineJoin, OuterShadow, PathCommand, PatternFill, PresetLineDash, PresetPattern, PresetShape,
Reflection, ShapeProperties, SoftEdge, TabAlignment, TabStop, TextAlign, TextAnchor,
TextAutofit, TextBody, TextBodyProperties, TextCaps, TextParagraph, TextParagraphProperties,
TextRun, TextRunProperties, TextSpacing, TextStrike, TextUnderline, TextVerticalType, TextWrap,
Transform2D,
};
pub fn read_shape_properties(reader: &mut Reader) -> Result<ShapeProperties> {
let mut properties = ShapeProperties::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"xfrm" => {
properties.transform = Some(parse_transform(reader, &start, true)?);
}
Event::Empty(start) if start.local_name().as_ref() == b"xfrm" => {
properties.transform = Some(parse_transform(reader, &start, false)?);
}
Event::Start(start) if start.local_name().as_ref() == b"prstGeom" => {
let (geometry, adjustments) = parse_preset_geometry(reader, &start, true)?;
properties.geometry = Some(geometry);
properties.geometry_adjustments = adjustments;
}
Event::Empty(start) if start.local_name().as_ref() == b"prstGeom" => {
let (geometry, adjustments) = parse_preset_geometry(reader, &start, false)?;
properties.geometry = Some(geometry);
properties.geometry_adjustments = adjustments;
}
Event::Start(start) if start.local_name().as_ref() == b"custGeom" => {
properties.geometry = Some(parse_custom_geometry(reader)?);
}
Event::Empty(ref start) if start.local_name().as_ref() == b"custGeom" => {
properties.geometry = Some(Geometry::Custom(CustomGeometry::default()));
}
Event::Empty(start) if start.local_name().as_ref() == b"noFill" => {
properties.fill = Some(Fill::None);
}
Event::Start(start) if start.local_name().as_ref() == b"noFill" => {
skip_subtree(reader)?;
properties.fill = Some(Fill::None);
}
Event::Start(start) if start.local_name().as_ref() == b"solidFill" => {
properties.fill = Some(Fill::Solid(parse_solid_fill_children(reader)?));
}
Event::Start(start) if start.local_name().as_ref() == b"gradFill" => {
properties.fill = Some(Fill::Gradient(parse_gradient_fill(reader)?));
}
Event::Start(start) if start.local_name().as_ref() == b"pattFill" => {
properties.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &start, true)?));
}
Event::Empty(start) if start.local_name().as_ref() == b"pattFill" => {
properties.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &start, false)?));
}
Event::Start(start) if start.local_name().as_ref() == b"ln" => {
properties.line = Some(parse_line(reader, &start, true)?);
}
Event::Empty(start) if start.local_name().as_ref() == b"ln" => {
properties.line = Some(parse_line(reader, &start, false)?);
}
Event::Start(start) if start.local_name().as_ref() == b"blipFill" => {
properties.fill = Some(Fill::Image(parse_blip_fill(reader, true)?));
}
Event::Empty(start) if start.local_name().as_ref() == b"blipFill" => {
properties.fill = Some(Fill::Image(parse_blip_fill(reader, false)?));
}
Event::Start(start) if start.local_name().as_ref() == b"effectLst" => {
properties.effects = Some(parse_effect_list(reader)?);
}
Event::Empty(start) if start.local_name().as_ref() == b"effectLst" => {
properties.effects = Some(EffectList::default());
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(properties)
}
pub fn read_fill(reader: &mut Reader) -> Result<Option<Fill>> {
loop {
match reader.read_event()? {
Event::Empty(start) if is_fill_choice_element(&start) => {
return Ok(Some(read_fill_choice(reader, &start, false)?));
}
Event::Start(start) if is_fill_choice_element(&start) => {
return Ok(Some(read_fill_choice(reader, &start, true)?));
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => return Ok(None),
_ => {}
}
}
}
pub fn is_fill_choice_element(start: &BytesStart) -> bool {
matches!(
start.local_name().as_ref(),
b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
)
}
pub fn read_fill_choice(reader: &mut Reader, start: &BytesStart, is_start: bool) -> Result<Fill> {
match start.local_name().as_ref() {
b"noFill" => {
if is_start {
skip_subtree(reader)?;
}
Ok(Fill::None)
}
b"solidFill" if is_start => Ok(Fill::Solid(parse_solid_fill_children(reader)?)),
b"solidFill" => Ok(Fill::Solid(Color::Rgb("000000".to_string()))),
b"gradFill" if is_start => Ok(Fill::Gradient(parse_gradient_fill(reader)?)),
b"gradFill" => Ok(Fill::Gradient(GradientFill::default())),
b"pattFill" => Ok(Fill::Pattern(parse_pattern_fill(reader, start, is_start)?)),
b"blipFill" => Ok(Fill::Image(parse_blip_fill(reader, is_start)?)),
_ => {
if is_start {
skip_subtree(reader)?;
}
Ok(Fill::None)
}
}
}
pub fn read_color(reader: &mut Reader) -> Result<Option<Color>> {
loop {
match reader.read_event()? {
Event::Start(start) if is_color_element(&start) => {
let color = parse_color_element(&start);
skip_subtree(reader)?;
return Ok(Some(color));
}
Event::Empty(start) if is_color_element(&start) => {
return Ok(Some(parse_color_element(&start)));
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => return Ok(None),
_ => {}
}
}
}
pub fn read_line(reader: &mut Reader, start: &BytesStart, is_start: bool) -> Result<Line> {
parse_line(reader, start, is_start)
}
fn skip_subtree(reader: &mut Reader) -> Result<()> {
let mut depth: u32 = 0;
loop {
match reader.read_event()? {
Event::Start(_) => depth += 1,
Event::End(_) => {
if depth == 0 {
return Ok(());
}
depth -= 1;
}
Event::Eof => return Ok(()),
_ => {}
}
}
}
fn skip_to_end(reader: &mut Reader, local_name: &[u8]) -> Result<()> {
loop {
match reader.read_event()? {
Event::Start(_) => skip_subtree(reader)?,
Event::End(end) if end.local_name().as_ref() == local_name => return Ok(()),
Event::Eof => return Ok(()),
_ => {}
}
}
}
fn raw_attribute(start: &BytesStart, name: &[u8]) -> Option<String> {
start
.attributes()
.flatten()
.find(|attribute| attribute.key.local_name().as_ref() == name)
.and_then(|attribute| {
xml_core::decode_attribute_value(attribute.value.as_ref())
.ok()
.flatten()
})
}
fn parse_transform(reader: &mut Reader, start: &BytesStart, is_start: bool) -> Result<Transform2D> {
let mut transform = Transform2D {
offset: None,
extent: None,
rotation_60000ths: raw_attribute(start, b"rot")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
flip_horizontal: raw_attribute(start, b"flipH").as_deref() == Some("1"),
flip_vertical: raw_attribute(start, b"flipV").as_deref() == Some("1"),
};
if !is_start {
return Ok(transform);
}
loop {
match reader.read_event()? {
Event::Empty(child) if child.local_name().as_ref() == b"off" => {
let x = raw_attribute(&child, b"x")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let y = raw_attribute(&child, b"y")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
transform.offset = Some((x, y));
}
Event::Empty(child) if child.local_name().as_ref() == b"ext" => {
let cx = raw_attribute(&child, b"cx")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let cy = raw_attribute(&child, b"cy")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
transform.extent = Some((cx, cy));
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(transform)
}
fn parse_preset_geometry(
reader: &mut Reader,
start: &BytesStart,
is_start: bool,
) -> Result<(Geometry, Vec<GeometryAdjustment>)> {
let preset = raw_attribute(start, b"prst")
.map(|token| PresetShape::from_xml_token(&token))
.unwrap_or(PresetShape::Rectangle);
if !is_start {
return Ok((Geometry::Preset(preset), Vec::new()));
}
let mut adjustments = Vec::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"avLst" => {
adjustments = parse_geometry_adjustment_list(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"avLst" => {}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(end) if end.local_name().as_ref() == b"prstGeom" => break,
Event::Eof => break,
_ => {}
}
}
Ok((Geometry::Preset(preset), adjustments))
}
fn parse_geometry_adjustment_list(reader: &mut Reader) -> Result<Vec<GeometryAdjustment>> {
let mut adjustments = Vec::new();
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"gd" => {
let name = raw_attribute(&start, b"name").unwrap_or_default();
let formula = raw_attribute(&start, b"fmla").unwrap_or_default();
adjustments.push(GeometryAdjustment::new(name, formula));
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(end) if end.local_name().as_ref() == b"avLst" => break,
Event::Eof => break,
_ => {}
}
}
Ok(adjustments)
}
fn parse_custom_geometry(reader: &mut Reader) -> Result<Geometry> {
let mut custom = CustomGeometry::default();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"pathLst" => {
custom = parse_path_list(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(Geometry::Custom(custom))
}
fn parse_path_list(reader: &mut Reader) -> Result<CustomGeometry> {
let mut custom = CustomGeometry::default();
let mut found_path = false;
loop {
match reader.read_event()? {
Event::Start(start) if !found_path && start.local_name().as_ref() == b"path" => {
custom.width_emu = raw_attribute(&start, b"w")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
custom.height_emu = raw_attribute(&start, b"h")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
custom.commands = parse_path_commands(reader)?;
found_path = true;
}
Event::Empty(start) if !found_path && start.local_name().as_ref() == b"path" => {
custom.width_emu = raw_attribute(&start, b"w")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
custom.height_emu = raw_attribute(&start, b"h")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
found_path = true;
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(end) if end.local_name().as_ref() == b"pathLst" => break,
Event::Eof => break,
_ => {}
}
}
Ok(custom)
}
fn parse_path_commands(reader: &mut Reader) -> Result<Vec<PathCommand>> {
let mut commands = Vec::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"moveTo" => {
if let Some(&(x, y)) = parse_path_points(reader, b"moveTo")?.first() {
commands.push(PathCommand::MoveTo { x, y });
}
}
Event::Start(start) if start.local_name().as_ref() == b"lnTo" => {
if let Some(&(x, y)) = parse_path_points(reader, b"lnTo")?.first() {
commands.push(PathCommand::LineTo { x, y });
}
}
Event::Start(start) if start.local_name().as_ref() == b"cubicBezTo" => {
let points = parse_path_points(reader, b"cubicBezTo")?;
if let [(x1, y1), (x2, y2), (x, y)] = points[..] {
commands.push(PathCommand::CubicBezierTo {
x1,
y1,
x2,
y2,
x,
y,
});
}
}
Event::Empty(start) if start.local_name().as_ref() == b"close" => {
commands.push(PathCommand::Close);
}
Event::Start(start) if start.local_name().as_ref() == b"close" => {
commands.push(PathCommand::Close);
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(end) if end.local_name().as_ref() == b"path" => break,
Event::Eof => break,
_ => {}
}
}
Ok(commands)
}
fn parse_path_points(reader: &mut Reader, wrapper_local_name: &[u8]) -> Result<Vec<(i64, i64)>> {
let mut points = Vec::new();
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"pt" => {
let x = raw_attribute(&start, b"x")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let y = raw_attribute(&start, b"y")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
points.push((x, y));
}
Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
Event::Eof => break,
_ => {}
}
}
Ok(points)
}
fn parse_solid_fill_children(reader: &mut Reader) -> Result<Color> {
let mut color = None;
loop {
match reader.read_event()? {
Event::Start(start) if is_color_element(&start) => {
color = Some(parse_color_element(&start));
skip_subtree(reader)?;
}
Event::Empty(start) if is_color_element(&start) => {
color = Some(parse_color_element(&start));
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(color.unwrap_or(Color::Rgb("000000".to_string())))
}
fn is_color_element(start: &BytesStart) -> bool {
matches!(
start.local_name().as_ref(),
b"srgbClr" | b"scrgbClr" | b"hslClr" | b"sysClr" | b"prstClr"
)
}
fn parse_color_element(start: &BytesStart) -> Color {
match start.local_name().as_ref() {
b"srgbClr" => Color::Rgb(raw_attribute(start, b"val").unwrap_or_default()),
b"scrgbClr" => Color::RgbPercent {
red: raw_attribute(start, b"r")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
green: raw_attribute(start, b"g")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
blue: raw_attribute(start, b"b")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
},
b"hslClr" => Color::Hsl {
hue_60000ths: raw_attribute(start, b"hue")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
saturation_1000ths_percent: raw_attribute(start, b"sat")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
luminance_1000ths_percent: raw_attribute(start, b"lum")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
},
b"sysClr" => Color::System {
value: raw_attribute(start, b"val").unwrap_or_default(),
last_color: raw_attribute(start, b"lastClr"),
},
b"prstClr" => Color::Preset(raw_attribute(start, b"val").unwrap_or_default()),
_ => Color::Rgb("000000".to_string()),
}
}
fn parse_gradient_fill(reader: &mut Reader) -> Result<GradientFill> {
let mut gradient = GradientFill::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"gsLst" => {
gradient.stops = parse_gradient_stops(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"lin" => {
gradient.angle_60000ths =
raw_attribute(&start, b"ang").and_then(|v| v.parse().ok());
}
Event::Start(start) if start.local_name().as_ref() == b"lin" => {
gradient.angle_60000ths =
raw_attribute(&start, b"ang").and_then(|v| v.parse().ok());
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(gradient)
}
fn parse_gradient_stops(reader: &mut Reader) -> Result<Vec<GradientStop>> {
let mut stops = Vec::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"gs" => {
let position = raw_attribute(&start, b"pos")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let color = parse_solid_fill_children(reader)?;
stops.push(GradientStop {
position_1000ths_percent: position,
color,
});
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(stops)
}
fn parse_pattern_fill(
reader: &mut Reader,
start: &BytesStart,
is_start: bool,
) -> Result<PatternFill> {
let preset = raw_attribute(start, b"prst")
.and_then(|token| PresetPattern::from_xml_token(&token))
.unwrap_or(PresetPattern::Percent5);
let mut pattern = PatternFill::new(preset);
if !is_start {
return Ok(pattern);
}
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"fgClr" => {
pattern.foreground = Some(parse_solid_fill_children(reader)?);
}
Event::Start(start) if start.local_name().as_ref() == b"bgClr" => {
pattern.background = Some(parse_solid_fill_children(reader)?);
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(pattern)
}
fn parse_line(reader: &mut Reader, start: &BytesStart, is_start: bool) -> Result<Line> {
let mut line = Line::new();
line.width_emu = raw_attribute(start, b"w").and_then(|v| v.parse().ok());
line.cap = raw_attribute(start, b"cap").and_then(|token| LineCap::from_xml_token(&token));
line.compound =
raw_attribute(start, b"cmpd").and_then(|token| LineCompound::from_xml_token(&token));
if !is_start {
return Ok(line);
}
loop {
match reader.read_event()? {
Event::Empty(child) if child.local_name().as_ref() == b"noFill" => {
line.fill = Some(Fill::None)
}
Event::Start(child) if child.local_name().as_ref() == b"solidFill" => {
line.fill = Some(Fill::Solid(parse_solid_fill_children(reader)?));
}
Event::Start(child) if child.local_name().as_ref() == b"gradFill" => {
line.fill = Some(Fill::Gradient(parse_gradient_fill(reader)?));
}
Event::Start(child) if child.local_name().as_ref() == b"pattFill" => {
line.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &child, true)?));
}
Event::Empty(child) if child.local_name().as_ref() == b"pattFill" => {
line.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &child, false)?));
}
Event::Empty(child) if child.local_name().as_ref() == b"prstDash" => {
line.dash = raw_attribute(&child, b"val")
.and_then(|token| PresetLineDash::from_xml_token(&token));
}
Event::Empty(child) if child.local_name().as_ref() == b"round" => {
line.join = Some(LineJoin::Round)
}
Event::Empty(child) if child.local_name().as_ref() == b"bevel" => {
line.join = Some(LineJoin::Bevel)
}
Event::Empty(child) if child.local_name().as_ref() == b"miter" => {
line.join = Some(LineJoin::Miter {
limit_1000ths_percent: raw_attribute(&child, b"lim")
.and_then(|v| v.parse().ok()),
});
}
Event::Empty(child) if child.local_name().as_ref() == b"headEnd" => {
line.head_end = Some(parse_line_end(&child));
}
Event::Empty(child) if child.local_name().as_ref() == b"tailEnd" => {
line.tail_end = Some(parse_line_end(&child));
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(line)
}
fn parse_line_end(start: &BytesStart) -> LineEnd {
LineEnd {
kind: raw_attribute(start, b"type").and_then(|token| LineEndType::from_xml_token(&token)),
width: raw_attribute(start, b"w").and_then(|token| LineEndSize::from_xml_token(&token)),
length: raw_attribute(start, b"len").and_then(|token| LineEndSize::from_xml_token(&token)),
}
}
fn parse_effect_list(reader: &mut Reader) -> Result<EffectList> {
let mut effects = EffectList::new();
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"outerShdw" => {
let mut shadow = OuterShadow::new(Color::Rgb("000000".to_string()));
shadow.blur_radius_emu =
raw_attribute(&start, b"blurRad").and_then(|v| v.parse().ok());
shadow.distance_emu = raw_attribute(&start, b"dist").and_then(|v| v.parse().ok());
shadow.direction_60000ths =
raw_attribute(&start, b"dir").and_then(|v| v.parse().ok());
if let Some(color) = read_color(reader)? {
shadow.color = color;
}
skip_to_end(reader, b"outerShdw")?;
effects.outer_shadow = Some(shadow);
}
Event::Start(start) if start.local_name().as_ref() == b"glow" => {
let radius = raw_attribute(&start, b"rad")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let color = read_color(reader)?.unwrap_or(Color::Rgb("000000".to_string()));
skip_to_end(reader, b"glow")?;
effects.glow = Some(Glow::new(radius, color));
}
Event::Empty(start) if start.local_name().as_ref() == b"reflection" => {
effects.reflection = Some(Reflection {
blur_radius_emu: raw_attribute(&start, b"blurRad").and_then(|v| v.parse().ok()),
distance_emu: raw_attribute(&start, b"dist").and_then(|v| v.parse().ok()),
direction_60000ths: raw_attribute(&start, b"dir").and_then(|v| v.parse().ok()),
start_alpha_1000ths_percent: raw_attribute(&start, b"stA")
.and_then(|v| v.parse().ok()),
end_alpha_1000ths_percent: raw_attribute(&start, b"endA")
.and_then(|v| v.parse().ok()),
});
}
Event::Start(start) if start.local_name().as_ref() == b"reflection" => {
effects.reflection = Some(Reflection {
blur_radius_emu: raw_attribute(&start, b"blurRad").and_then(|v| v.parse().ok()),
distance_emu: raw_attribute(&start, b"dist").and_then(|v| v.parse().ok()),
direction_60000ths: raw_attribute(&start, b"dir").and_then(|v| v.parse().ok()),
start_alpha_1000ths_percent: raw_attribute(&start, b"stA")
.and_then(|v| v.parse().ok()),
end_alpha_1000ths_percent: raw_attribute(&start, b"endA")
.and_then(|v| v.parse().ok()),
});
skip_subtree(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"softEdge" => {
let radius = raw_attribute(&start, b"rad")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
effects.soft_edge = Some(SoftEdge::new(radius));
}
Event::Start(start) if start.local_name().as_ref() == b"softEdge" => {
let radius = raw_attribute(&start, b"rad")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
effects.soft_edge = Some(SoftEdge::new(radius));
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(end) if end.local_name().as_ref() == b"effectLst" => break,
Event::Eof => break,
_ => {}
}
}
Ok(effects)
}
fn parse_blip_fill(reader: &mut Reader, is_start: bool) -> Result<BlipFill> {
let mut blip_fill = BlipFill::default();
if !is_start {
return Ok(blip_fill);
}
loop {
match reader.read_event()? {
Event::Empty(child) if child.local_name().as_ref() == b"blip" => {
blip_fill.relationship_id = raw_attribute(&child, b"embed");
}
Event::Start(child) if child.local_name().as_ref() == b"blip" => {
blip_fill.relationship_id = raw_attribute(&child, b"embed");
skip_subtree(reader)?;
}
Event::Empty(child) if child.local_name().as_ref() == b"stretch" => {
blip_fill.mode = Some(BlipFillMode::Stretch);
}
Event::Start(child) if child.local_name().as_ref() == b"stretch" => {
blip_fill.mode = Some(BlipFillMode::Stretch);
skip_subtree(reader)?;
}
Event::Empty(child) if child.local_name().as_ref() == b"tile" => {
blip_fill.mode = Some(BlipFillMode::Tile);
}
Event::Empty(child) if child.local_name().as_ref() == b"srcRect" => {
blip_fill.crop = Some(CropRect {
left_1000ths_percent: raw_attribute(&child, b"l").and_then(|v| v.parse().ok()),
top_1000ths_percent: raw_attribute(&child, b"t").and_then(|v| v.parse().ok()),
right_1000ths_percent: raw_attribute(&child, b"r").and_then(|v| v.parse().ok()),
bottom_1000ths_percent: raw_attribute(&child, b"b")
.and_then(|v| v.parse().ok()),
});
}
Event::Start(child) if child.local_name().as_ref() == b"srcRect" => {
blip_fill.crop = Some(CropRect {
left_1000ths_percent: raw_attribute(&child, b"l").and_then(|v| v.parse().ok()),
top_1000ths_percent: raw_attribute(&child, b"t").and_then(|v| v.parse().ok()),
right_1000ths_percent: raw_attribute(&child, b"r").and_then(|v| v.parse().ok()),
bottom_1000ths_percent: raw_attribute(&child, b"b")
.and_then(|v| v.parse().ok()),
});
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(blip_fill)
}
pub fn read_text_body(reader: &mut Reader) -> Result<TextBody> {
let mut body = TextBody::new();
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"bodyPr" => {
body.properties = parse_text_body_properties(&start);
}
Event::Start(start) if start.local_name().as_ref() == b"bodyPr" => {
let properties = parse_text_body_properties(&start);
body.properties = parse_text_body_properties_children(reader, properties)?;
}
Event::Start(start) if start.local_name().as_ref() == b"p" => {
body.paragraphs.push(parse_text_paragraph(reader)?);
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(body)
}
fn parse_text_body_properties(start: &BytesStart) -> TextBodyProperties {
TextBodyProperties {
wrap: raw_attribute(start, b"wrap").and_then(|token| TextWrap::from_xml_token(&token)),
anchor: raw_attribute(start, b"anchor")
.and_then(|token| TextAnchor::from_xml_token(&token)),
anchor_center: raw_attribute(start, b"anchorCtr").as_deref() == Some("1"),
inset_left_emu: raw_attribute(start, b"lIns").and_then(|v| v.parse().ok()),
inset_top_emu: raw_attribute(start, b"tIns").and_then(|v| v.parse().ok()),
inset_right_emu: raw_attribute(start, b"rIns").and_then(|v| v.parse().ok()),
inset_bottom_emu: raw_attribute(start, b"bIns").and_then(|v| v.parse().ok()),
autofit: None,
vertical_direction: raw_attribute(start, b"vert")
.and_then(|token| TextVerticalType::from_xml_token(&token)),
rotation_60000ths: raw_attribute(start, b"rot")
.and_then(|v| v.parse().ok())
.unwrap_or(0),
}
}
fn parse_text_body_properties_children(
reader: &mut Reader,
mut properties: TextBodyProperties,
) -> Result<TextBodyProperties> {
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"noAutofit" => {
properties.autofit = Some(TextAutofit::None);
}
Event::Start(start) if start.local_name().as_ref() == b"noAutofit" => {
properties.autofit = Some(TextAutofit::None);
skip_subtree(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"spAutoFit" => {
properties.autofit = Some(TextAutofit::Shape);
}
Event::Start(start) if start.local_name().as_ref() == b"spAutoFit" => {
properties.autofit = Some(TextAutofit::Shape);
skip_subtree(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"normAutofit" => {
properties.autofit = Some(TextAutofit::Normal {
font_scale_1000ths_percent: raw_attribute(&start, b"fontScale")
.and_then(|v| v.parse().ok()),
line_spacing_reduction_1000ths_percent: raw_attribute(
&start,
b"lnSpcReduction",
)
.and_then(|v| v.parse().ok()),
});
}
Event::Start(start) if start.local_name().as_ref() == b"normAutofit" => {
properties.autofit = Some(TextAutofit::Normal {
font_scale_1000ths_percent: raw_attribute(&start, b"fontScale")
.and_then(|v| v.parse().ok()),
line_spacing_reduction_1000ths_percent: raw_attribute(
&start,
b"lnSpcReduction",
)
.and_then(|v| v.parse().ok()),
});
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(properties)
}
fn parse_text_paragraph(reader: &mut Reader) -> Result<TextParagraph> {
let mut paragraph = TextParagraph::new();
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"pPr" => {
paragraph.properties = Some(parse_text_paragraph_properties(&start));
}
Event::Start(start) if start.local_name().as_ref() == b"pPr" => {
let properties = parse_text_paragraph_properties(&start);
paragraph.properties = Some(parse_text_paragraph_properties_children(
reader, properties,
)?);
}
Event::Start(start) if start.local_name().as_ref() == b"r" => {
paragraph.runs.push(parse_text_run_regular(reader)?);
}
Event::Empty(start) if start.local_name().as_ref() == b"br" => {
paragraph.runs.push(TextRun::LineBreak { properties: None });
}
Event::Start(start) if start.local_name().as_ref() == b"br" => {
paragraph.runs.push(parse_text_run_line_break(reader)?);
}
Event::Start(start) if start.local_name().as_ref() == b"fld" => {
let id = raw_attribute(&start, b"id").unwrap_or_default();
let field_type = raw_attribute(&start, b"type");
paragraph
.runs
.push(parse_text_run_field(reader, id, field_type)?);
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(paragraph)
}
fn parse_text_paragraph_properties(start: &BytesStart) -> TextParagraphProperties {
TextParagraphProperties {
alignment: raw_attribute(start, b"algn")
.and_then(|token| TextAlign::from_xml_token(&token)),
margin_left_emu: raw_attribute(start, b"marL").and_then(|v| v.parse().ok()),
margin_right_emu: raw_attribute(start, b"marR").and_then(|v| v.parse().ok()),
indent_emu: raw_attribute(start, b"indent").and_then(|v| v.parse().ok()),
level: raw_attribute(start, b"lvl").and_then(|v| v.parse().ok()),
tab_stops: Vec::new(),
line_spacing: None,
space_before: None,
space_after: None,
bullet_color: None,
bullet_size: None,
bullet_font: None,
bullet: None,
default_run_properties: None,
}
}
fn parse_text_paragraph_properties_children(
reader: &mut Reader,
mut properties: TextParagraphProperties,
) -> Result<TextParagraphProperties> {
loop {
match reader.read_event()? {
Event::Start(start) if start.local_name().as_ref() == b"lnSpc" => {
properties.line_spacing = parse_text_spacing(reader, b"lnSpc")?;
}
Event::Start(start) if start.local_name().as_ref() == b"spcBef" => {
properties.space_before = parse_text_spacing(reader, b"spcBef")?;
}
Event::Start(start) if start.local_name().as_ref() == b"spcAft" => {
properties.space_after = parse_text_spacing(reader, b"spcAft")?;
}
Event::Empty(start) if start.local_name().as_ref() == b"buClrTx" => {
properties.bullet_color = Some(BulletColor::FollowText);
}
Event::Start(start) if start.local_name().as_ref() == b"buClr" => {
properties.bullet_color =
Some(BulletColor::Explicit(parse_solid_fill_children(reader)?));
}
Event::Empty(start) if start.local_name().as_ref() == b"buSzTx" => {
properties.bullet_size = Some(BulletSize::FollowText);
}
Event::Empty(start) if start.local_name().as_ref() == b"buSzPct" => {
let value = raw_attribute(&start, b"val")
.and_then(|v| v.parse().ok())
.unwrap_or(100_000);
properties.bullet_size = Some(BulletSize::Percent(value));
}
Event::Empty(start) if start.local_name().as_ref() == b"buSzPts" => {
let value = raw_attribute(&start, b"val")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
properties.bullet_size = Some(BulletSize::Points(value));
}
Event::Empty(start) if start.local_name().as_ref() == b"buFontTx" => {
properties.bullet_font = Some(BulletFont::FollowText);
}
Event::Empty(start) if start.local_name().as_ref() == b"buFont" => {
properties.bullet_font = Some(BulletFont::Typeface(
raw_attribute(&start, b"typeface").unwrap_or_default(),
));
}
Event::Empty(start) if start.local_name().as_ref() == b"buNone" => {
properties.bullet = Some(BulletKind::None);
}
Event::Empty(start) if start.local_name().as_ref() == b"buChar" => {
properties.bullet = Some(BulletKind::Character(
raw_attribute(&start, b"char").unwrap_or_default(),
));
}
Event::Empty(start) if start.local_name().as_ref() == b"buAutoNum" => {
let scheme = raw_attribute(&start, b"type").unwrap_or_default();
let start_at = raw_attribute(&start, b"startAt").and_then(|v| v.parse().ok());
properties.bullet = Some(BulletKind::AutoNumber { scheme, start_at });
}
Event::Start(start) if start.local_name().as_ref() == b"tabLst" => {
properties.tab_stops = parse_tab_stops(reader)?;
}
Event::Empty(start) if start.local_name().as_ref() == b"tabLst" => {}
Event::Empty(start) if start.local_name().as_ref() == b"defRPr" => {
properties.default_run_properties =
Some(Box::new(parse_text_run_properties(&start)));
}
Event::Start(start) if start.local_name().as_ref() == b"defRPr" => {
let parsed = parse_text_run_properties(&start);
properties.default_run_properties = Some(Box::new(
parse_text_run_properties_children(reader, parsed)?,
));
}
Event::Start(_) => skip_subtree(reader)?,
Event::Empty(_) => {}
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(properties)
}
fn parse_text_spacing(
reader: &mut Reader,
wrapper_local_name: &[u8],
) -> Result<Option<TextSpacing>> {
let mut spacing = None;
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"spcPct" => {
let value = raw_attribute(&start, b"val")
.and_then(|v| v.parse().ok())
.unwrap_or(100_000);
spacing = Some(TextSpacing::Percent(value));
}
Event::Empty(start) if start.local_name().as_ref() == b"spcPts" => {
let value = raw_attribute(&start, b"val")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
spacing = Some(TextSpacing::Points(value));
}
Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
Event::Eof => break,
_ => {}
}
}
Ok(spacing)
}
fn parse_tab_stops(reader: &mut Reader) -> Result<Vec<TabStop>> {
let mut tab_stops = Vec::new();
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"tab" => {
let position_emu = raw_attribute(&start, b"pos")
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let alignment = raw_attribute(&start, b"algn")
.and_then(|token| TabAlignment::from_xml_token(&token));
tab_stops.push(TabStop {
position_emu,
alignment,
});
}
Event::End(end) if end.local_name().as_ref() == b"tabLst" => break,
Event::Eof => break,
_ => {}
}
}
Ok(tab_stops)
}
fn parse_text_run_regular(reader: &mut Reader) -> Result<TextRun> {
let mut properties = TextRunProperties::new();
let mut text = String::new();
let mut in_text = false;
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"rPr" => {
properties = parse_text_run_properties(&start);
}
Event::Start(start) if start.local_name().as_ref() == b"rPr" => {
properties = parse_text_run_properties(&start);
properties = parse_text_run_properties_children(reader, properties)?;
}
Event::Start(start) if start.local_name().as_ref() == b"t" => in_text = true,
Event::End(end) if end.local_name().as_ref() == b"t" => in_text = false,
Event::Text(content) if in_text => text.push_str(&xml_core::decode_text(&content)?),
Event::GeneralRef(reference) if in_text => {
if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
text.push_str(&resolved);
}
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(end) if end.local_name().as_ref() == b"r" => break,
Event::Eof => break,
_ => {}
}
}
Ok(TextRun::Regular { text, properties })
}
fn parse_text_run_line_break(reader: &mut Reader) -> Result<TextRun> {
let mut properties = None;
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"rPr" => {
properties = Some(parse_text_run_properties(&start));
}
Event::Start(start) if start.local_name().as_ref() == b"rPr" => {
let parsed = parse_text_run_properties(&start);
properties = Some(parse_text_run_properties_children(reader, parsed)?);
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(TextRun::LineBreak { properties })
}
fn parse_text_run_field(
reader: &mut Reader,
id: String,
field_type: Option<String>,
) -> Result<TextRun> {
let mut properties = TextRunProperties::new();
let mut cached_text = String::new();
let mut in_text = false;
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"rPr" => {
properties = parse_text_run_properties(&start);
}
Event::Start(start) if start.local_name().as_ref() == b"rPr" => {
properties = parse_text_run_properties(&start);
properties = parse_text_run_properties_children(reader, properties)?;
}
Event::Start(start) if start.local_name().as_ref() == b"t" => in_text = true,
Event::End(end) if end.local_name().as_ref() == b"t" => in_text = false,
Event::Text(content) if in_text => {
cached_text.push_str(&xml_core::decode_text(&content)?)
}
Event::GeneralRef(reference) if in_text => {
if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
cached_text.push_str(&resolved);
}
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(end) if end.local_name().as_ref() == b"fld" => break,
Event::Eof => break,
_ => {}
}
}
Ok(TextRun::Field {
id,
field_type,
cached_text,
properties,
})
}
fn parse_text_run_properties(start: &BytesStart) -> TextRunProperties {
TextRunProperties {
bold: raw_attribute(start, b"b").as_deref() == Some("1"),
italic: raw_attribute(start, b"i").as_deref() == Some("1"),
underline: raw_attribute(start, b"u")
.and_then(|token| TextUnderline::from_xml_token(&token)),
strike: raw_attribute(start, b"strike")
.and_then(|token| TextStrike::from_xml_token(&token)),
fill: None,
font_size_100ths_point: raw_attribute(start, b"sz").and_then(|v| v.parse().ok()),
font_family: None,
hyperlink: None,
baseline_1000ths_percent: raw_attribute(start, b"baseline").and_then(|v| v.parse().ok()),
highlight: None,
text_caps: raw_attribute(start, b"cap").and_then(|token| TextCaps::from_xml_token(&token)),
character_spacing_100ths_point: raw_attribute(start, b"spc").and_then(|v| v.parse().ok()),
}
}
fn parse_text_run_properties_children(
reader: &mut Reader,
mut properties: TextRunProperties,
) -> Result<TextRunProperties> {
loop {
match reader.read_event()? {
Event::Empty(start) if start.local_name().as_ref() == b"noFill" => {
properties.fill = Some(Fill::None)
}
Event::Start(start) if start.local_name().as_ref() == b"solidFill" => {
properties.fill = Some(Fill::Solid(parse_solid_fill_children(reader)?));
}
Event::Start(start) if start.local_name().as_ref() == b"gradFill" => {
properties.fill = Some(Fill::Gradient(parse_gradient_fill(reader)?));
}
Event::Start(start) if start.local_name().as_ref() == b"pattFill" => {
properties.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &start, true)?));
}
Event::Empty(start) if start.local_name().as_ref() == b"pattFill" => {
properties.fill = Some(Fill::Pattern(parse_pattern_fill(reader, &start, false)?));
}
Event::Start(start) if start.local_name().as_ref() == b"blipFill" => {
properties.fill = Some(Fill::Image(parse_blip_fill(reader, true)?));
}
Event::Empty(start) if start.local_name().as_ref() == b"blipFill" => {
properties.fill = Some(Fill::Image(parse_blip_fill(reader, false)?));
}
Event::Start(start) if start.local_name().as_ref() == b"highlight" => {
properties.highlight = read_color(reader)?;
skip_to_end(reader, b"highlight")?;
}
Event::Empty(start) if start.local_name().as_ref() == b"latin" => {
properties.font_family = raw_attribute(&start, b"typeface");
}
Event::Empty(start) if start.local_name().as_ref() == b"hlinkClick" => {
properties.hyperlink = Some(Hyperlink {
relationship_id: raw_attribute(&start, b"id"),
tooltip: raw_attribute(&start, b"tooltip"),
});
}
Event::Start(start) if start.local_name().as_ref() == b"hlinkClick" => {
properties.hyperlink = Some(Hyperlink {
relationship_id: raw_attribute(&start, b"id"),
tooltip: raw_attribute(&start, b"tooltip"),
});
skip_subtree(reader)?;
}
Event::Start(_) => skip_subtree(reader)?,
Event::End(_) | Event::Eof => break,
_ => {}
}
}
Ok(properties)
}