use quick_xml::events::{BytesEnd, BytesStart, Event};
use quick_xml::{Reader, Writer, XmlVersion};
use crate::borders::{CT_PBdr, CT_Tabs};
use crate::document::CT_SectPr;
use crate::error::Result;
use crate::namespace::{W_NS, matches_local_name};
use crate::shared::{ST_HighlightColor, ST_Jc, ST_OnOff, ST_Underline};
use crate::units::{HalfPoint, Twips};
#[derive(Debug, Clone, PartialEq)]
pub struct CT_Shd {
pub val: String,
pub color: Option<String>,
pub fill: Option<String>,
}
impl CT_Shd {
pub fn from_xml_attrs(e: &BytesStart) -> Result<Self> {
let mut val = "clear".to_string();
let mut color = None;
let mut fill = None;
for attr in e.attributes() {
let attr = attr?;
let key = attr.key.as_ref();
let v = std::str::from_utf8(&attr.value)?;
if matches_local_name(key, b"val") {
val = v.to_string();
} else if matches_local_name(key, b"color") {
color = Some(v.to_string());
} else if matches_local_name(key, b"fill") {
fill = Some(v.to_string());
}
}
Ok(CT_Shd { val, color, fill })
}
pub fn write_xml<W: std::io::Write>(&self, writer: &mut Writer<W>, tag: &str) -> Result<()> {
let mut e = BytesStart::new(tag);
e.push_attribute(("w:val", self.val.as_str()));
if let Some(ref c) = self.color {
e.push_attribute(("w:color", c.as_str()));
}
if let Some(ref f) = self.fill {
e.push_attribute(("w:fill", f.as_str()));
}
writer.write_event(Event::Empty(e))?;
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CT_PPr {
pub style_id: Option<String>,
pub jc: Option<ST_Jc>,
pub space_before: Option<Twips>,
pub space_after: Option<Twips>,
pub line_spacing: Option<Twips>,
pub line_rule: Option<String>,
pub before_autospacing: Option<bool>,
pub after_autospacing: Option<bool>,
pub ind_left: Option<Twips>,
pub ind_right: Option<Twips>,
pub ind_first_line: Option<Twips>,
pub ind_hanging: Option<Twips>,
pub keep_next: Option<bool>,
pub keep_lines: Option<bool>,
pub page_break_before: Option<bool>,
pub widow_control: Option<bool>,
pub suppress_auto_hyphens: Option<bool>,
pub outline_lvl: Option<u32>,
pub borders: Option<CT_PBdr>,
pub tabs: Option<CT_Tabs>,
pub shading: Option<CT_Shd>,
pub rpr: Option<CT_RPr>,
pub num_ilvl: Option<u32>,
pub num_id: Option<u32>,
pub sect_pr: Option<CT_SectPr>,
}
#[allow(non_snake_case)]
impl CT_PPr {
pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
Self::from_xml_with_prefixes(reader, &["w".to_string()])
}
fn from_xml_with_prefixes(
reader: &mut Reader<&[u8]>,
word_prefixes: &[String],
) -> Result<Self> {
let mut ppr = CT_PPr::default();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let name = e.name();
let prefixes = word_prefixes_at(e, word_prefixes)?;
if is_word_element(name.as_ref(), b"rPr", &prefixes) {
ppr.rpr = Some(CT_RPr::from_xml(reader)?);
} else if is_word_element(name.as_ref(), b"numPr", &prefixes) {
Self::parse_num_pr(reader, &mut ppr, &prefixes)?;
} else if is_word_element(name.as_ref(), b"pBdr", &prefixes) {
ppr.borders = Some(CT_PBdr::from_xml(reader)?);
} else if is_word_element(name.as_ref(), b"tabs", &prefixes) {
ppr.tabs = Some(CT_Tabs::from_xml_with_prefixes(reader, &prefixes)?);
} else if is_word_element(name.as_ref(), b"sectPr", &prefixes) {
ppr.sect_pr = Some(CT_SectPr::from_xml(reader)?);
} else {
reader.read_to_end_into(name, &mut Vec::new())?;
}
}
Ok(Event::Empty(ref e)) => {
let name = e.name();
let prefixes = word_prefixes_at(e, word_prefixes)?;
if is_word_element(name.as_ref(), b"pStyle", &prefixes) {
ppr.style_id = get_word_val_attr(e, &prefixes)?;
} else if is_word_element(name.as_ref(), b"jc", &prefixes) {
if let Some(val) = get_word_val_attr(e, &prefixes)? {
ppr.jc = Some(ST_Jc::from_str(&val)?);
}
} else if is_word_element(name.as_ref(), b"spacing", &prefixes) {
for attr in e.attributes() {
let attr = attr?;
let key = attr.key.as_ref();
let val_str = std::str::from_utf8(&attr.value)?;
if is_word_attribute(key, b"before", &prefixes) {
ppr.space_before = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"after", &prefixes) {
ppr.space_after = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"line", &prefixes) {
ppr.line_spacing = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"lineRule", &prefixes) {
ppr.line_rule = Some(val_str.to_string());
} else if is_word_attribute(key, b"beforeAutospacing", &prefixes) {
ppr.before_autospacing = Some(val_str == "1" || val_str == "true");
} else if is_word_attribute(key, b"afterAutospacing", &prefixes) {
ppr.after_autospacing = Some(val_str == "1" || val_str == "true");
}
}
} else if is_word_element(name.as_ref(), b"ind", &prefixes) {
for attr in e.attributes() {
let attr = attr?;
let key = attr.key.as_ref();
let val_str = std::str::from_utf8(&attr.value)?;
if is_word_attribute(key, b"left", &prefixes)
|| is_word_attribute(key, b"start", &prefixes)
{
ppr.ind_left = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"right", &prefixes)
|| is_word_attribute(key, b"end", &prefixes)
{
ppr.ind_right = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"firstLine", &prefixes) {
ppr.ind_first_line = Some(Twips(val_str.parse()?));
} else if is_word_attribute(key, b"hanging", &prefixes) {
ppr.ind_hanging = Some(Twips(val_str.parse()?));
}
}
} else if is_word_element(name.as_ref(), b"keepNext", &prefixes) {
ppr.keep_next = Some(parse_word_toggle(e, &prefixes)?);
} else if is_word_element(name.as_ref(), b"keepLines", &prefixes) {
ppr.keep_lines = Some(parse_word_toggle(e, &prefixes)?);
} else if is_word_element(name.as_ref(), b"pageBreakBefore", &prefixes) {
ppr.page_break_before = Some(parse_word_toggle(e, &prefixes)?);
} else if is_word_element(name.as_ref(), b"widowControl", &prefixes) {
ppr.widow_control = Some(parse_word_toggle(e, &prefixes)?);
} else if is_word_element(name.as_ref(), b"suppressAutoHyphens", &prefixes) {
ppr.suppress_auto_hyphens = Some(parse_word_toggle(e, &prefixes)?);
} else if is_word_element(name.as_ref(), b"outlineLvl", &prefixes) {
if let Some(val) = get_word_val_attr(e, &prefixes)? {
ppr.outline_lvl = Some(val.parse()?);
}
} else if is_word_element(name.as_ref(), b"shd", &prefixes) {
ppr.shading = Some(CT_Shd::from_xml_attrs(e)?);
}
}
Ok(Event::End(ref e))
if is_word_element(e.name().as_ref(), b"pPr", word_prefixes) =>
{
break;
}
Ok(Event::Eof) => break,
Err(e) => return Err(e.into()),
_ => {}
}
buf.clear();
}
Ok(ppr)
}
fn parse_num_pr(
reader: &mut Reader<&[u8]>,
ppr: &mut CT_PPr,
word_prefixes: &[String],
) -> Result<()> {
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(ref e)) => {
let name = e.name();
let prefixes = word_prefixes_at(e, word_prefixes)?;
if is_word_element(name.as_ref(), b"ilvl", &prefixes) {
if let Some(val) = get_word_val_attr(e, &prefixes)? {
ppr.num_ilvl = Some(val.parse()?);
}
} else if is_word_element(name.as_ref(), b"numId", &prefixes)
&& let Some(val) = get_word_val_attr(e, &prefixes)?
{
ppr.num_id = Some(val.parse()?);
}
}
Ok(Event::End(ref e))
if is_word_element(e.name().as_ref(), b"numPr", word_prefixes) =>
{
break;
}
Ok(Event::Eof) => break,
Err(e) => return Err(e.into()),
_ => {}
}
buf.clear();
}
Ok(())
}
pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
if self.is_empty() {
return Ok(());
}
let mut buf = itoa::Buffer::new();
writer.write_event(Event::Start(BytesStart::new("w:pPr")))?;
if let Some(ref style_id) = self.style_id {
let mut e = BytesStart::new("w:pStyle");
e.push_attribute(("w:val", style_id.as_str()));
writer.write_event(Event::Empty(e))?;
}
if let Some(keep_next) = self.keep_next {
write_toggle(writer, "w:keepNext", keep_next)?;
}
if let Some(keep_lines) = self.keep_lines {
write_toggle(writer, "w:keepLines", keep_lines)?;
}
if let Some(page_break) = self.page_break_before {
write_toggle(writer, "w:pageBreakBefore", page_break)?;
}
if let Some(widow) = self.widow_control {
write_toggle(writer, "w:widowControl", widow)?;
}
if self.num_id.is_some() || self.num_ilvl.is_some() {
writer.write_event(Event::Start(BytesStart::new("w:numPr")))?;
if let Some(ilvl) = self.num_ilvl {
let mut e = BytesStart::new("w:ilvl");
e.push_attribute(("w:val", buf.format(ilvl)));
writer.write_event(Event::Empty(e))?;
}
if let Some(num_id) = self.num_id {
let mut e = BytesStart::new("w:numId");
e.push_attribute(("w:val", buf.format(num_id)));
writer.write_event(Event::Empty(e))?;
}
writer.write_event(Event::End(BytesEnd::new("w:numPr")))?;
}
if let Some(ref borders) = self.borders
&& !borders.is_empty()
{
borders.to_xml(writer)?;
}
if let Some(ref shd) = self.shading {
shd.write_xml(writer, "w:shd")?;
}
if let Some(ref tabs) = self.tabs {
tabs.to_xml(writer)?;
}
if let Some(suppress) = self.suppress_auto_hyphens {
write_toggle(writer, "w:suppressAutoHyphens", suppress)?;
}
if self.space_before.is_some()
|| self.space_after.is_some()
|| self.line_spacing.is_some()
|| self.before_autospacing.is_some()
|| self.after_autospacing.is_some()
{
let mut e = BytesStart::new("w:spacing");
if let Some(before) = self.space_before {
e.push_attribute(("w:before", buf.format(before.0)));
}
if let Some(after) = self.space_after {
e.push_attribute(("w:after", buf.format(after.0)));
}
if let Some(line) = self.line_spacing {
e.push_attribute(("w:line", buf.format(line.0)));
}
if let Some(ref rule) = self.line_rule {
e.push_attribute(("w:lineRule", rule.as_str()));
}
if let Some(ba) = self.before_autospacing {
e.push_attribute(("w:beforeAutospacing", if ba { "1" } else { "0" }));
}
if let Some(aa) = self.after_autospacing {
e.push_attribute(("w:afterAutospacing", if aa { "1" } else { "0" }));
}
writer.write_event(Event::Empty(e))?;
}
if self.ind_left.is_some()
|| self.ind_right.is_some()
|| self.ind_first_line.is_some()
|| self.ind_hanging.is_some()
{
let mut e = BytesStart::new("w:ind");
if let Some(left) = self.ind_left {
e.push_attribute(("w:left", buf.format(left.0)));
}
if let Some(right) = self.ind_right {
e.push_attribute(("w:right", buf.format(right.0)));
}
if let Some(fl) = self.ind_first_line {
e.push_attribute(("w:firstLine", buf.format(fl.0)));
}
if let Some(hang) = self.ind_hanging {
e.push_attribute(("w:hanging", buf.format(hang.0)));
}
writer.write_event(Event::Empty(e))?;
}
if let Some(jc) = self.jc {
let mut e = BytesStart::new("w:jc");
e.push_attribute(("w:val", jc.to_str()));
writer.write_event(Event::Empty(e))?;
}
if let Some(lvl) = self.outline_lvl {
let mut e = BytesStart::new("w:outlineLvl");
e.push_attribute(("w:val", buf.format(lvl)));
writer.write_event(Event::Empty(e))?;
}
if let Some(ref rpr) = self.rpr {
rpr.to_xml(writer)?;
}
if let Some(ref sect) = self.sect_pr {
sect.to_xml(writer)?;
}
writer.write_event(Event::End(BytesEnd::new("w:pPr")))?;
Ok(())
}
fn is_empty(&self) -> bool {
self.style_id.is_none()
&& self.jc.is_none()
&& self.space_before.is_none()
&& self.space_after.is_none()
&& self.line_spacing.is_none()
&& self.before_autospacing.is_none()
&& self.after_autospacing.is_none()
&& self.ind_left.is_none()
&& self.ind_right.is_none()
&& self.ind_first_line.is_none()
&& self.ind_hanging.is_none()
&& self.keep_next.is_none()
&& self.keep_lines.is_none()
&& self.page_break_before.is_none()
&& self.widow_control.is_none()
&& self.suppress_auto_hyphens.is_none()
&& self.outline_lvl.is_none()
&& self.borders.is_none()
&& self.tabs.is_none()
&& self.shading.is_none()
&& self.rpr.is_none()
&& self.num_id.is_none()
&& self.num_ilvl.is_none()
&& self.sect_pr.is_none()
}
pub fn merge_from(&mut self, other: &CT_PPr) {
if other.style_id.is_some() {
self.style_id = other.style_id.clone();
}
if other.jc.is_some() {
self.jc = other.jc;
}
if other.space_before.is_some() {
self.space_before = other.space_before;
}
if other.space_after.is_some() {
self.space_after = other.space_after;
}
if other.line_spacing.is_some() {
self.line_spacing = other.line_spacing;
}
if other.line_rule.is_some() {
self.line_rule = other.line_rule.clone();
}
if other.before_autospacing.is_some() {
self.before_autospacing = other.before_autospacing;
}
if other.after_autospacing.is_some() {
self.after_autospacing = other.after_autospacing;
}
if other.ind_left.is_some() {
self.ind_left = other.ind_left;
}
if other.ind_right.is_some() {
self.ind_right = other.ind_right;
}
if other.ind_first_line.is_some() {
self.ind_first_line = other.ind_first_line;
}
if other.ind_hanging.is_some() {
self.ind_hanging = other.ind_hanging;
}
if other.keep_next.is_some() {
self.keep_next = other.keep_next;
}
if other.keep_lines.is_some() {
self.keep_lines = other.keep_lines;
}
if other.page_break_before.is_some() {
self.page_break_before = other.page_break_before;
}
if other.widow_control.is_some() {
self.widow_control = other.widow_control;
}
if other.suppress_auto_hyphens.is_some() {
self.suppress_auto_hyphens = other.suppress_auto_hyphens;
}
if other.outline_lvl.is_some() {
self.outline_lvl = other.outline_lvl;
}
if other.borders.is_some() {
self.borders = other.borders.clone();
}
if other.tabs.is_some() {
self.tabs = other.tabs.clone();
}
if other.shading.is_some() {
self.shading = other.shading.clone();
}
if other.num_ilvl.is_some() {
self.num_ilvl = other.num_ilvl;
}
if other.num_id.is_some() {
self.num_id = other.num_id;
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[allow(non_snake_case)]
pub struct CT_RPr {
pub style_id: Option<String>,
pub font_ascii: Option<String>,
pub font_hansi: Option<String>,
pub font_east_asia: Option<String>,
pub font_cs: Option<String>,
pub font_ascii_theme: Option<String>,
pub font_hansi_theme: Option<String>,
pub bold: Option<bool>,
pub bold_cs: Option<bool>,
pub italic: Option<bool>,
pub italic_cs: Option<bool>,
pub underline: Option<ST_Underline>,
pub strike: Option<bool>,
pub dstrike: Option<bool>,
pub sz: Option<HalfPoint>,
pub sz_cs: Option<HalfPoint>,
pub color: Option<String>,
pub color_theme: Option<String>,
pub highlight: Option<ST_HighlightColor>,
pub caps: Option<bool>,
pub small_caps: Option<bool>,
pub vert_align: Option<String>,
pub spacing: Option<Twips>,
pub width_scale: Option<u32>,
pub position: Option<i32>,
pub shading: Option<CT_Shd>,
pub vanish: Option<bool>,
}
#[allow(non_snake_case)]
impl CT_RPr {
pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
let mut rpr = CT_RPr::default();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Empty(ref e)) => {
let name = e.name();
if matches_local_name(name.as_ref(), b"rStyle") {
rpr.style_id = get_val_attr(e)?;
} else if matches_local_name(name.as_ref(), b"rFonts") {
for attr in e.attributes() {
let attr = attr?;
let key = attr.key.as_ref();
let val = std::str::from_utf8(&attr.value)?.to_string();
if matches_local_name(key, b"ascii") {
rpr.font_ascii = Some(val);
} else if matches_local_name(key, b"hAnsi") {
rpr.font_hansi = Some(val);
} else if matches_local_name(key, b"eastAsia") {
rpr.font_east_asia = Some(val);
} else if matches_local_name(key, b"cs") {
rpr.font_cs = Some(val);
} else if matches_local_name(key, b"asciiTheme") {
rpr.font_ascii_theme = Some(val);
} else if matches_local_name(key, b"hAnsiTheme") {
rpr.font_hansi_theme = Some(val);
}
}
} else if matches_local_name(name.as_ref(), b"b") {
rpr.bold = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"bCs") {
rpr.bold_cs = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"i") {
rpr.italic = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"iCs") {
rpr.italic_cs = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"u") {
if let Some(val) = get_val_attr(e)? {
rpr.underline = Some(ST_Underline::from_str(&val)?);
} else {
rpr.underline = Some(ST_Underline::Single);
}
} else if matches_local_name(name.as_ref(), b"strike") {
rpr.strike = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"dstrike") {
rpr.dstrike = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"sz") {
if let Some(val) = get_val_attr(e)? {
rpr.sz = Some(HalfPoint(val.parse()?));
}
} else if matches_local_name(name.as_ref(), b"szCs") {
if let Some(val) = get_val_attr(e)? {
rpr.sz_cs = Some(HalfPoint(val.parse()?));
}
} else if matches_local_name(name.as_ref(), b"color") {
for attr in e.attributes() {
let attr = attr?;
let key = attr.key.as_ref();
let v = std::str::from_utf8(&attr.value)?.to_string();
if matches_local_name(key, b"val") {
rpr.color = Some(v);
} else if matches_local_name(key, b"themeColor") {
rpr.color_theme = Some(v);
}
}
} else if matches_local_name(name.as_ref(), b"highlight") {
if let Some(val) = get_val_attr(e)? {
rpr.highlight = Some(ST_HighlightColor::from_str(&val)?);
}
} else if matches_local_name(name.as_ref(), b"caps") {
rpr.caps = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"smallCaps") {
rpr.small_caps = Some(parse_toggle(e)?);
} else if matches_local_name(name.as_ref(), b"vertAlign") {
rpr.vert_align = get_val_attr(e)?;
} else if matches_local_name(name.as_ref(), b"spacing") {
if let Some(val) = get_val_attr(e)? {
rpr.spacing = Some(Twips(val.parse()?));
}
} else if matches_local_name(name.as_ref(), b"w") {
if let Some(val) = get_val_attr(e)? {
rpr.width_scale = Some(val.parse()?);
}
} else if matches_local_name(name.as_ref(), b"position") {
if let Some(val) = get_val_attr(e)? {
rpr.position = Some(val.parse()?);
}
} else if matches_local_name(name.as_ref(), b"shd") {
rpr.shading = Some(CT_Shd::from_xml_attrs(e)?);
} else if matches_local_name(name.as_ref(), b"vanish") {
rpr.vanish = Some(parse_toggle(e)?);
}
}
Ok(Event::Start(ref e)) => {
reader.read_to_end_into(e.name(), &mut Vec::new())?;
}
Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"rPr") => {
break;
}
Ok(Event::Eof) => break,
Err(e) => return Err(e.into()),
_ => {}
}
buf.clear();
}
Ok(rpr)
}
pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
if self.is_empty() {
return Ok(());
}
let mut buf = itoa::Buffer::new();
writer.write_event(Event::Start(BytesStart::new("w:rPr")))?;
if let Some(ref style_id) = self.style_id {
let mut e = BytesStart::new("w:rStyle");
e.push_attribute(("w:val", style_id.as_str()));
writer.write_event(Event::Empty(e))?;
}
if self.font_ascii.is_some()
|| self.font_hansi.is_some()
|| self.font_east_asia.is_some()
|| self.font_cs.is_some()
|| self.font_ascii_theme.is_some()
|| self.font_hansi_theme.is_some()
{
let mut e = BytesStart::new("w:rFonts");
if let Some(ref f) = self.font_ascii {
e.push_attribute(("w:ascii", f.as_str()));
}
if let Some(ref f) = self.font_hansi {
e.push_attribute(("w:hAnsi", f.as_str()));
}
if let Some(ref f) = self.font_east_asia {
e.push_attribute(("w:eastAsia", f.as_str()));
}
if let Some(ref f) = self.font_cs {
e.push_attribute(("w:cs", f.as_str()));
}
if let Some(ref f) = self.font_ascii_theme {
e.push_attribute(("w:asciiTheme", f.as_str()));
}
if let Some(ref f) = self.font_hansi_theme {
e.push_attribute(("w:hAnsiTheme", f.as_str()));
}
writer.write_event(Event::Empty(e))?;
}
if let Some(bold) = self.bold {
write_toggle(writer, "w:b", bold)?;
}
if let Some(bold_cs) = self.bold_cs {
write_toggle(writer, "w:bCs", bold_cs)?;
}
if let Some(italic) = self.italic {
write_toggle(writer, "w:i", italic)?;
}
if let Some(italic_cs) = self.italic_cs {
write_toggle(writer, "w:iCs", italic_cs)?;
}
if let Some(caps) = self.caps {
write_toggle(writer, "w:caps", caps)?;
}
if let Some(small_caps) = self.small_caps {
write_toggle(writer, "w:smallCaps", small_caps)?;
}
if let Some(strike) = self.strike {
write_toggle(writer, "w:strike", strike)?;
}
if let Some(dstrike) = self.dstrike {
write_toggle(writer, "w:dstrike", dstrike)?;
}
if let Some(vanish) = self.vanish {
write_toggle(writer, "w:vanish", vanish)?;
}
if let Some(ref color) = self.color {
let mut e = BytesStart::new("w:color");
e.push_attribute(("w:val", color.as_str()));
if let Some(ref tc) = self.color_theme {
e.push_attribute(("w:themeColor", tc.as_str()));
}
writer.write_event(Event::Empty(e))?;
}
if let Some(ref spacing) = self.spacing {
let mut e = BytesStart::new("w:spacing");
e.push_attribute(("w:val", buf.format(spacing.0)));
writer.write_event(Event::Empty(e))?;
}
if let Some(ws) = self.width_scale {
let mut e = BytesStart::new("w:w");
e.push_attribute(("w:val", buf.format(ws)));
writer.write_event(Event::Empty(e))?;
}
if let Some(pos) = self.position {
let mut e = BytesStart::new("w:position");
e.push_attribute(("w:val", buf.format(pos)));
writer.write_event(Event::Empty(e))?;
}
if let Some(ref sz) = self.sz {
let mut e = BytesStart::new("w:sz");
e.push_attribute(("w:val", buf.format(sz.0)));
writer.write_event(Event::Empty(e))?;
}
if let Some(ref sz_cs) = self.sz_cs {
let mut e = BytesStart::new("w:szCs");
e.push_attribute(("w:val", buf.format(sz_cs.0)));
writer.write_event(Event::Empty(e))?;
}
if let Some(ref highlight) = self.highlight {
let mut e = BytesStart::new("w:highlight");
e.push_attribute(("w:val", highlight.to_str()));
writer.write_event(Event::Empty(e))?;
}
if let Some(underline) = self.underline {
let mut e = BytesStart::new("w:u");
e.push_attribute(("w:val", underline.to_str()));
writer.write_event(Event::Empty(e))?;
}
if let Some(ref shd) = self.shading {
shd.write_xml(writer, "w:shd")?;
}
if let Some(ref vert_align) = self.vert_align {
let mut e = BytesStart::new("w:vertAlign");
e.push_attribute(("w:val", vert_align.as_str()));
writer.write_event(Event::Empty(e))?;
}
writer.write_event(Event::End(BytesEnd::new("w:rPr")))?;
Ok(())
}
fn is_empty(&self) -> bool {
self.style_id.is_none()
&& self.font_ascii.is_none()
&& self.font_hansi.is_none()
&& self.font_east_asia.is_none()
&& self.font_cs.is_none()
&& self.font_ascii_theme.is_none()
&& self.font_hansi_theme.is_none()
&& self.bold.is_none()
&& self.bold_cs.is_none()
&& self.italic.is_none()
&& self.italic_cs.is_none()
&& self.underline.is_none()
&& self.strike.is_none()
&& self.dstrike.is_none()
&& self.sz.is_none()
&& self.sz_cs.is_none()
&& self.color.is_none()
&& self.color_theme.is_none()
&& self.highlight.is_none()
&& self.caps.is_none()
&& self.small_caps.is_none()
&& self.vert_align.is_none()
&& self.spacing.is_none()
&& self.width_scale.is_none()
&& self.position.is_none()
&& self.shading.is_none()
&& self.vanish.is_none()
}
pub fn merge_from(&mut self, other: &CT_RPr) {
if other.style_id.is_some() {
self.style_id = other.style_id.clone();
}
if other.font_ascii.is_some() {
self.font_ascii = other.font_ascii.clone();
}
if other.font_hansi.is_some() {
self.font_hansi = other.font_hansi.clone();
}
if other.font_east_asia.is_some() {
self.font_east_asia = other.font_east_asia.clone();
}
if other.font_cs.is_some() {
self.font_cs = other.font_cs.clone();
}
if other.font_ascii_theme.is_some() {
self.font_ascii_theme = other.font_ascii_theme.clone();
}
if other.font_hansi_theme.is_some() {
self.font_hansi_theme = other.font_hansi_theme.clone();
}
if other.bold.is_some() {
self.bold = other.bold;
}
if other.bold_cs.is_some() {
self.bold_cs = other.bold_cs;
}
if other.italic.is_some() {
self.italic = other.italic;
}
if other.italic_cs.is_some() {
self.italic_cs = other.italic_cs;
}
if other.underline.is_some() {
self.underline = other.underline;
}
if other.strike.is_some() {
self.strike = other.strike;
}
if other.dstrike.is_some() {
self.dstrike = other.dstrike;
}
if other.sz.is_some() {
self.sz = other.sz;
}
if other.sz_cs.is_some() {
self.sz_cs = other.sz_cs;
}
if other.color.is_some() {
self.color = other.color.clone();
}
if other.color_theme.is_some() {
self.color_theme = other.color_theme.clone();
}
if other.highlight.is_some() {
self.highlight = other.highlight;
}
if other.caps.is_some() {
self.caps = other.caps;
}
if other.small_caps.is_some() {
self.small_caps = other.small_caps;
}
if other.vert_align.is_some() {
self.vert_align = other.vert_align.clone();
}
if other.spacing.is_some() {
self.spacing = other.spacing;
}
if other.width_scale.is_some() {
self.width_scale = other.width_scale;
}
if other.position.is_some() {
self.position = other.position;
}
if other.shading.is_some() {
self.shading = other.shading.clone();
}
if other.vanish.is_some() {
self.vanish = other.vanish;
}
}
}
pub(crate) fn word_prefixes_at(
start: &BytesStart<'_>,
inherited: &[String],
) -> Result<Vec<String>> {
let mut prefixes = inherited.to_vec();
for attribute in start.attributes() {
let attribute = attribute?;
let name = attribute.key.as_ref();
let prefix = if name == b"xmlns" {
b"".as_slice()
} else if let Some(prefix) = name.strip_prefix(b"xmlns:") {
prefix
} else {
continue;
};
let prefix = std::str::from_utf8(prefix)?.to_string();
prefixes.retain(|candidate| candidate != &prefix);
let value =
attribute.decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?;
if value.as_bytes() == W_NS.as_bytes() {
prefixes.push(prefix);
}
}
Ok(prefixes)
}
fn is_word_name(name: &[u8], word_prefixes: &[String]) -> bool {
let Some(separator) = name.iter().position(|byte| *byte == b':') else {
return word_prefixes.iter().any(String::is_empty);
};
word_prefixes
.iter()
.any(|prefix| prefix.as_bytes() == &name[..separator])
}
pub(crate) fn is_word_element(name: &[u8], local: &[u8], word_prefixes: &[String]) -> bool {
matches_local_name(name, local) && is_word_name(name, word_prefixes)
}
fn is_word_attribute(key: &[u8], local: &[u8], word_prefixes: &[String]) -> bool {
let Some(separator) = key.iter().position(|byte| *byte == b':') else {
return false;
};
key.get(separator + 1..) == Some(local)
&& word_prefixes
.iter()
.any(|prefix| prefix.as_bytes() == &key[..separator])
}
fn get_word_val_attr(e: &BytesStart, word_prefixes: &[String]) -> Result<Option<String>> {
for attr in e.attributes() {
let attr = attr?;
if is_word_attribute(attr.key.as_ref(), b"val", word_prefixes) {
return Ok(Some(std::str::from_utf8(&attr.value)?.to_string()));
}
}
Ok(None)
}
fn parse_word_toggle(e: &BytesStart, word_prefixes: &[String]) -> Result<bool> {
let val = get_word_val_attr(e, word_prefixes)?;
Ok(ST_OnOff::from_str_or_default(val.as_deref()).is_on())
}
pub(crate) fn get_val_attr(e: &BytesStart) -> Result<Option<String>> {
for attr in e.attributes() {
let attr = attr?;
if matches_local_name(attr.key.as_ref(), b"val") {
return Ok(Some(std::str::from_utf8(&attr.value)?.to_string()));
}
}
Ok(None)
}
fn parse_toggle(e: &BytesStart) -> Result<bool> {
let val = get_val_attr(e)?;
Ok(ST_OnOff::from_str_or_default(val.as_deref()).is_on())
}
pub(crate) fn write_toggle<W: std::io::Write>(
writer: &mut Writer<W>,
tag: &str,
value: bool,
) -> Result<()> {
if value {
writer.write_event(Event::Empty(BytesStart::new(tag)))?;
} else {
let mut e = BytesStart::new(tag);
e.push_attribute(("w:val", "false"));
writer.write_event(Event::Empty(e))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::ST_Border;
fn parse_ppr(xml: &str) -> CT_PPr {
let full = format!("<w:pPr>{xml}</w:pPr>");
let mut reader = Reader::from_str(&full);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) if matches_local_name(e.name().as_ref(), b"pPr") => break,
_ => {}
}
buf.clear();
}
CT_PPr::from_xml(&mut reader).unwrap()
}
fn parse_rpr(xml: &str) -> CT_RPr {
let full = format!("<w:rPr>{xml}</w:rPr>");
let mut reader = Reader::from_str(&full);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) if matches_local_name(e.name().as_ref(), b"rPr") => break,
_ => {}
}
buf.clear();
}
CT_RPr::from_xml(&mut reader).unwrap()
}
#[test]
fn parse_basic_ppr() {
let ppr = parse_ppr(r#"<w:pStyle w:val="Heading1"/><w:jc w:val="center"/>"#);
assert_eq!(ppr.style_id, Some("Heading1".to_string()));
assert_eq!(ppr.jc, Some(ST_Jc::Center));
}
#[test]
fn parse_spacing() {
let ppr = parse_ppr(r#"<w:spacing w:before="240" w:after="120" w:line="360"/>"#);
assert_eq!(ppr.space_before, Some(Twips(240)));
assert_eq!(ppr.space_after, Some(Twips(120)));
assert_eq!(ppr.line_spacing, Some(Twips(360)));
}
#[test]
fn parse_borders() {
let ppr = parse_ppr(
r#"<w:pBdr><w:top w:val="single" w:sz="4" w:space="1" w:color="000000"/><w:bottom w:val="double" w:sz="6"/></w:pBdr>"#,
);
let borders = ppr.borders.unwrap();
assert_eq!(borders.top.as_ref().unwrap().val, ST_Border::Single);
assert_eq!(borders.top.as_ref().unwrap().sz, Some(4));
assert_eq!(borders.bottom.as_ref().unwrap().val, ST_Border::Double);
assert!(borders.left.is_none());
}
#[test]
fn parse_tabs() {
let ppr = parse_ppr(
r#"<w:tabs><w:tab w:val="left" w:pos="720"/><w:tab w:val="right" w:pos="8640" w:leader="dot"/></w:tabs>"#,
);
let tabs = ppr.tabs.unwrap();
assert_eq!(tabs.tabs.len(), 2);
assert_eq!(tabs.tabs[0].pos, Twips(720));
assert_eq!(tabs.tabs[1].leader, Some(crate::shared::ST_TabLeader::Dot));
}
#[test]
fn canonical_ppr_ignores_foreign_same_local_containers() {
let xml = r#"<w:pPr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:ext="urn:producer"><w:tabs><ext:tab ext:val="right" ext:pos="100"/><w:tab w:val="left" w:pos="720"/></w:tabs><ext:tabs><ext:tab ext:val="right" ext:pos="99"/></ext:tabs><ext:rPr><ext:b/></ext:rPr></w:pPr>"#;
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(_)) => break,
Ok(Event::Eof) => panic!("missing pPr start"),
_ => {}
}
buf.clear();
}
let ppr = CT_PPr::from_xml(&mut reader).unwrap();
let tabs = ppr.tabs.unwrap();
assert_eq!(tabs.tabs.len(), 1);
assert_eq!(tabs.tabs[0].pos, Twips(720));
assert_eq!(tabs.tabs[0].source_occurrence, Some(0));
assert!(ppr.rpr.is_none());
}
#[test]
fn parse_shading() {
let ppr = parse_ppr(r#"<w:shd w:val="clear" w:fill="FFFF00"/>"#);
let shd = ppr.shading.unwrap();
assert_eq!(shd.val, "clear");
assert_eq!(shd.fill, Some("FFFF00".to_string()));
}
#[test]
fn parse_basic_rpr() {
let rpr = parse_rpr(r#"<w:b/><w:i/><w:sz w:val="24"/><w:color w:val="FF0000"/>"#);
assert_eq!(rpr.bold, Some(true));
assert_eq!(rpr.italic, Some(true));
assert_eq!(rpr.sz, Some(HalfPoint(24)));
assert_eq!(rpr.color, Some("FF0000".to_string()));
}
#[test]
fn parse_rpr_spacing() {
let rpr = parse_rpr(r#"<w:spacing w:val="20"/><w:w w:val="150"/><w:position w:val="-4"/>"#);
assert_eq!(rpr.spacing, Some(Twips(20)));
assert_eq!(rpr.width_scale, Some(150));
assert_eq!(rpr.position, Some(-4));
}
#[test]
fn round_trip_rpr() {
let original = CT_RPr {
bold: Some(true),
italic: Some(true),
sz: Some(HalfPoint(24)),
color: Some("FF0000".to_string()),
underline: Some(ST_Underline::Single),
spacing: Some(Twips(20)),
..Default::default()
};
let mut output = Vec::new();
let mut writer = Writer::new(&mut output);
original.to_xml(&mut writer).unwrap();
let xml = String::from_utf8(output).unwrap();
let inner = xml
.strip_prefix("<w:rPr>")
.unwrap()
.strip_suffix("</w:rPr>")
.unwrap();
let parsed = parse_rpr(inner);
assert_eq!(parsed.bold, original.bold);
assert_eq!(parsed.italic, original.italic);
assert_eq!(parsed.sz, original.sz);
assert_eq!(parsed.color, original.color);
assert_eq!(parsed.underline, original.underline);
assert_eq!(parsed.spacing, original.spacing);
}
#[test]
fn merge_ppr() {
let mut base = CT_PPr {
jc: Some(ST_Jc::Left),
space_after: Some(Twips(200)),
..Default::default()
};
let override_ppr = CT_PPr {
jc: Some(ST_Jc::Center),
space_before: Some(Twips(120)),
..Default::default()
};
base.merge_from(&override_ppr);
assert_eq!(base.jc, Some(ST_Jc::Center)); assert_eq!(base.space_after, Some(Twips(200))); assert_eq!(base.space_before, Some(Twips(120))); }
#[test]
fn merge_rpr() {
let mut base = CT_RPr {
bold: Some(true),
sz: Some(HalfPoint(24)),
..Default::default()
};
let override_rpr = CT_RPr {
sz: Some(HalfPoint(28)),
italic: Some(true),
..Default::default()
};
base.merge_from(&override_rpr);
assert_eq!(base.bold, Some(true)); assert_eq!(base.sz, Some(HalfPoint(28))); assert_eq!(base.italic, Some(true)); }
}