use crate::types;
#[cfg(feature = "extra-children")]
use ooxml_xml::PositionedNode;
use ooxml_xml::{RawXmlElement, RawXmlNode};
impl types::Body {
pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
self.block_content
.push(types::BlockContent::P(Box::default()));
match self.block_content.last_mut().unwrap() {
types::BlockContent::P(p) => p.as_mut(),
_ => unreachable!(),
}
}
#[cfg(feature = "wml-tables")]
pub fn add_table(&mut self) -> &mut types::Table {
let table = types::Table {
range_markup: Vec::new(),
table_properties: Box::new(types::TableProperties::default()),
tbl_grid: Box::new(types::TableGrid::default()),
rows: Vec::new(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
self.block_content
.push(types::BlockContent::Tbl(Box::new(table)));
match self.block_content.last_mut().unwrap() {
types::BlockContent::Tbl(t) => t.as_mut(),
_ => unreachable!(),
}
}
#[cfg(feature = "wml-layout")]
pub fn set_section_properties(&mut self, sect_pr: types::SectionProperties) {
self.sect_pr = Some(Box::new(sect_pr));
}
}
impl types::Paragraph {
pub fn add_run(&mut self) -> &mut types::Run {
self.paragraph_content
.push(types::ParagraphContent::R(Box::default()));
match self.paragraph_content.last_mut().unwrap() {
types::ParagraphContent::R(r) => r.as_mut(),
_ => unreachable!(),
}
}
#[cfg(feature = "wml-hyperlinks")]
pub fn add_hyperlink(&mut self) -> &mut types::Hyperlink {
self.paragraph_content
.push(types::ParagraphContent::Hyperlink(Box::default()));
match self.paragraph_content.last_mut().unwrap() {
types::ParagraphContent::Hyperlink(h) => h.as_mut(),
_ => unreachable!(),
}
}
pub fn add_bookmark_start_u32(&mut self, id: u32, name: &str) {
self.add_bookmark_start(id as i64, name);
}
pub fn add_bookmark_end_u32(&mut self, id: u32) {
self.add_bookmark_end(id as i64);
}
pub fn add_bookmark_start(&mut self, id: i64, name: &str) {
let bookmark = types::Bookmark {
id,
name: name.to_string(),
#[cfg(feature = "wml-settings")]
displaced_by_custom_xml: None,
#[cfg(feature = "wml-tables")]
col_first: None,
#[cfg(feature = "wml-tables")]
col_last: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
};
self.paragraph_content
.push(types::ParagraphContent::BookmarkStart(Box::new(bookmark)));
}
pub fn add_bookmark_end(&mut self, id: i64) {
let range = types::CTMarkupRange {
id,
#[cfg(feature = "wml-settings")]
displaced_by_custom_xml: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
};
self.paragraph_content
.push(types::ParagraphContent::BookmarkEnd(Box::new(range)));
}
pub fn add_comment_range_start(&mut self, id: u32) {
let range = types::CTMarkupRange {
id: id as i64,
#[cfg(feature = "wml-settings")]
displaced_by_custom_xml: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
};
self.paragraph_content
.push(types::ParagraphContent::CommentRangeStart(Box::new(range)));
}
pub fn add_comment_range_end(&mut self, id: u32) {
let range = types::CTMarkupRange {
id: id as i64,
#[cfg(feature = "wml-settings")]
displaced_by_custom_xml: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
};
self.paragraph_content
.push(types::ParagraphContent::CommentRangeEnd(Box::new(range)));
}
#[cfg(feature = "wml-styling")]
pub fn set_properties(&mut self, props: types::ParagraphProperties) {
self.p_pr = Some(Box::new(props));
}
#[cfg(feature = "wml-styling")]
pub fn set_numbering(&mut self, num_id: u32, ilvl: u32) {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.num_pr = Some(Box::new(types::NumberingProperties {
ilvl: Some(Box::new(types::CTDecimalNumber {
value: ilvl as i64,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})),
num_id: Some(Box::new(types::CTDecimalNumber {
value: num_id as i64,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})),
numbering_change: None,
ins: None,
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_alignment(&mut self, alignment: types::STJc) {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.justification = Some(Box::new(types::CTJc {
value: alignment,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_spacing(&mut self, before: Option<u32>, after: Option<u32>) {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.spacing = Some(Box::new(types::CTSpacing {
before: before.map(|b| b.to_string()),
after: after.map(|a| a.to_string()),
..Default::default()
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_indent(&mut self, left: Option<u32>, first_line: Option<u32>) {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.indentation = Some(Box::new(types::CTInd {
left: left.map(|l| l.to_string()),
first_line: first_line.map(|fl| fl.to_string()),
..Default::default()
}));
}
pub fn add_page_break(&mut self) -> &mut Self {
let mut run = types::Run::default();
run.run_content
.push(types::RunContent::Br(Box::new(types::CTBr {
r#type: Some(types::STBrType::Page),
clear: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})));
self.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
self
}
pub fn add_column_break(&mut self) -> &mut Self {
let mut run = types::Run::default();
run.run_content
.push(types::RunContent::Br(Box::new(types::CTBr {
r#type: Some(types::STBrType::Column),
clear: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})));
self.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
self
}
#[cfg(feature = "wml-styling")]
pub fn set_space_before(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let spacing = ppr
.spacing
.get_or_insert_with(|| Box::new(types::CTSpacing::default()));
spacing.before = Some(twips.to_string());
self
}
#[cfg(feature = "wml-styling")]
pub fn set_space_after(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let spacing = ppr
.spacing
.get_or_insert_with(|| Box::new(types::CTSpacing::default()));
spacing.after = Some(twips.to_string());
self
}
#[cfg(feature = "wml-styling")]
pub fn set_line_spacing(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let spacing = ppr
.spacing
.get_or_insert_with(|| Box::new(types::CTSpacing::default()));
spacing.line = Some(twips.to_string());
spacing.line_rule = Some(types::STLineSpacingRule::Auto);
self
}
#[cfg(feature = "wml-styling")]
pub fn set_indent_left(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let ind = ppr
.indentation
.get_or_insert_with(|| Box::new(types::CTInd::default()));
ind.left = Some(twips.to_string());
self
}
#[cfg(feature = "wml-styling")]
pub fn set_indent_right(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let ind = ppr
.indentation
.get_or_insert_with(|| Box::new(types::CTInd::default()));
ind.right = Some(twips.to_string());
self
}
#[cfg(feature = "wml-styling")]
pub fn set_indent_first_line(&mut self, twips: u32) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
let ind = ppr
.indentation
.get_or_insert_with(|| Box::new(types::CTInd::default()));
ind.first_line = Some(twips.to_string());
self
}
#[cfg(feature = "wml-styling")]
pub fn set_outline_level(&mut self, level: u8) -> &mut Self {
let ppr = self
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.outline_lvl = Some(Box::new(types::CTDecimalNumber {
value: level as i64,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
self
}
}
impl types::Run {
pub fn set_text(&mut self, text: impl Into<String>) {
let t = types::Text {
text: Some(text.into()),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
self.run_content.push(types::RunContent::T(Box::new(t)));
}
#[cfg(feature = "wml-styling")]
pub fn set_bold(&mut self, bold: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
if bold {
rpr.bold = Some(Box::new(types::OnOffElement {
value: None, #[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
} else {
rpr.bold = None;
}
}
#[cfg(feature = "wml-styling")]
pub fn set_italic(&mut self, italic: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
if italic {
rpr.italic = Some(Box::new(types::OnOffElement {
value: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
} else {
rpr.italic = None;
}
}
pub fn set_page_break(&mut self) {
self.run_content
.push(types::RunContent::Br(Box::new(types::CTBr {
r#type: Some(types::STBrType::Page),
clear: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})));
}
#[cfg(feature = "wml-styling")]
pub fn set_color(&mut self, hex: &str) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
rpr.color = Some(Box::new(types::CTColor {
value: hex.to_string(),
theme_color: None,
theme_tint: None,
theme_shade: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_font_size(&mut self, half_points: i64) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
rpr.size = Some(Box::new(types::HpsMeasureElement {
value: half_points.to_string(),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_strikethrough(&mut self, strike: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
if strike {
rpr.strikethrough = Some(Box::new(types::OnOffElement {
value: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
} else {
rpr.strikethrough = None;
}
}
#[cfg(feature = "wml-styling")]
pub fn set_underline(&mut self, style: types::STUnderline) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
rpr.underline = Some(Box::new(types::CTUnderline {
value: Some(style),
color: None,
theme_color: None,
theme_tint: None,
theme_shade: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-styling")]
pub fn set_fonts(&mut self, fonts: types::Fonts) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
rpr.fonts = Some(Box::new(fonts));
}
#[cfg(feature = "wml-styling")]
pub fn set_properties(&mut self, props: types::RunProperties) {
self.r_pr = Some(Box::new(props));
}
pub fn add_drawing(&mut self, drawing: types::CTDrawing) {
self.run_content
.push(types::RunContent::Drawing(Box::new(drawing)));
}
pub fn add_footnote_ref(&mut self, id: i64) {
self.run_content
.push(types::RunContent::FootnoteReference(Box::new(
types::FootnoteEndnoteRef {
#[cfg(feature = "wml-comments")]
custom_mark_follows: None,
id,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
},
)));
}
pub fn add_endnote_ref(&mut self, id: i64) {
self.run_content
.push(types::RunContent::EndnoteReference(Box::new(
types::FootnoteEndnoteRef {
#[cfg(feature = "wml-comments")]
custom_mark_follows: None,
id,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
},
)));
}
pub fn add_comment_ref(&mut self, id: i64) {
self.run_content
.push(types::RunContent::CommentReference(Box::new(
types::CTMarkup {
id,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
},
)));
}
#[cfg(feature = "wml-styling")]
fn set_on_off(field: &mut Option<Box<types::OnOffElement>>, on: bool) {
if on {
*field = Some(Box::new(types::OnOffElement {
value: None, #[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
} else {
*field = None;
}
}
#[cfg(feature = "wml-styling")]
pub fn set_shadow(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.shadow, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_outline(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.outline, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_emboss(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.emboss, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_imprint(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.imprint, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_small_caps(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.small_caps, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_all_caps(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.caps, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_vanish(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.vanish, on);
}
#[cfg(feature = "wml-styling")]
pub fn set_double_strike(&mut self, on: bool) {
let rpr = self
.r_pr
.get_or_insert_with(|| Box::new(types::RunProperties::default()));
Self::set_on_off(&mut rpr.dstrike, on);
}
}
#[cfg(feature = "wml-hyperlinks")]
impl types::Hyperlink {
pub fn add_run(&mut self) -> &mut types::Run {
self.paragraph_content
.push(types::ParagraphContent::R(Box::default()));
match self.paragraph_content.last_mut().unwrap() {
types::ParagraphContent::R(r) => r.as_mut(),
_ => unreachable!(),
}
}
pub fn set_rel_id(&mut self, rel_id: &str) {
self.id = Some(rel_id.to_string());
}
pub fn set_anchor(&mut self, anchor: &str) {
self.anchor = Some(anchor.to_string());
}
}
#[cfg(feature = "wml-tables")]
impl types::Table {
pub fn add_row(&mut self) -> &mut types::CTRow {
self.rows.push(types::RowContent::Tr(Box::default()));
match self.rows.last_mut().unwrap() {
types::RowContent::Tr(r) => r.as_mut(),
_ => unreachable!(),
}
}
}
#[cfg(feature = "wml-tables")]
impl types::CTRow {
pub fn add_cell(&mut self) -> &mut types::TableCell {
self.cells.push(types::CellContent::Tc(Box::default()));
match self.cells.last_mut().unwrap() {
types::CellContent::Tc(c) => c.as_mut(),
_ => unreachable!(),
}
}
}
#[cfg(feature = "wml-tables")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VMergeType {
Restart,
Continue,
}
#[cfg(feature = "wml-tables")]
impl types::TableCell {
pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
self.block_content
.push(types::BlockContent::P(Box::default()));
match self.block_content.last_mut().unwrap() {
types::BlockContent::P(p) => p.as_mut(),
_ => unreachable!(),
}
}
pub fn set_grid_span(&mut self, n: u32) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
tcpr.grid_span = Some(Box::new(types::CTDecimalNumber {
value: n as i64,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
pub fn set_vertical_merge(&mut self, merge_type: VMergeType) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
tcpr.vertical_merge = Some(Box::new(types::CTVMerge {
value: match merge_type {
VMergeType::Restart => Some(types::STMerge::Restart),
VMergeType::Continue => None,
},
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
}
#[cfg(feature = "wml-tables")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorderStyle {
None,
Single,
Double,
Dashed,
Dotted,
Thick,
}
#[cfg(feature = "wml-tables")]
impl BorderStyle {
fn to_st_border(self) -> types::STBorder {
match self {
BorderStyle::None => types::STBorder::None,
BorderStyle::Single => types::STBorder::Single,
BorderStyle::Double => types::STBorder::Double,
BorderStyle::Dashed => types::STBorder::Dashed,
BorderStyle::Dotted => types::STBorder::Dotted,
BorderStyle::Thick => types::STBorder::Thick,
}
}
}
#[cfg(feature = "wml-tables")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableWidthUnit {
Dxa,
Pct,
}
#[cfg(feature = "wml-tables")]
impl types::CTRow {
pub fn set_height(&mut self, twips: u32) {
let row_pr = self
.row_properties
.get_or_insert_with(|| Box::new(types::TableRowProperties::default()));
row_pr.tr_height = Some(Box::new(types::CTHeight {
value: Some(twips.to_string()),
#[cfg(feature = "wml-tables")]
h_rule: Some(types::STHeightRule::Exact),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
}
#[cfg(feature = "wml-tables")]
impl types::TableCell {
pub fn set_background_color(&mut self, rgb: &str) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
tcpr.shading = Some(Box::new(types::CTShd {
value: types::STShd::Clear,
#[cfg(feature = "wml-styling")]
fill: Some(rgb.to_string()),
#[cfg(feature = "wml-styling")]
color: Some("auto".to_string()),
#[cfg(feature = "wml-styling")]
theme_color: None,
#[cfg(feature = "wml-styling")]
theme_tint: None,
#[cfg(feature = "wml-styling")]
theme_shade: None,
#[cfg(feature = "wml-styling")]
theme_fill: None,
#[cfg(feature = "wml-styling")]
theme_fill_tint: None,
#[cfg(feature = "wml-styling")]
theme_fill_shade: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
pub fn set_borders(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
self.set_border_top(style, width_eights, color);
self.set_border_bottom(style, width_eights, color);
self.set_border_left(style, width_eights, color);
self.set_border_right(style, width_eights, color);
}
pub fn set_border_top(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
let borders = tcpr
.tc_borders
.get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
borders.top = Some(Box::new(make_cell_border(style, width_eights, color)));
}
pub fn set_border_bottom(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
let borders = tcpr
.tc_borders
.get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
borders.bottom = Some(Box::new(make_cell_border(style, width_eights, color)));
}
pub fn set_border_left(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
let borders = tcpr
.tc_borders
.get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
borders.left = Some(Box::new(make_cell_border(style, width_eights, color)));
}
pub fn set_border_right(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
let borders = tcpr
.tc_borders
.get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
borders.right = Some(Box::new(make_cell_border(style, width_eights, color)));
}
pub fn set_padding(&mut self, top: u32, bottom: u32, left: u32, right: u32) {
let tcpr = self
.cell_properties
.get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
tcpr.tc_mar = Some(Box::new(types::CTTcMar {
top: Some(Box::new(make_tbl_width(top, types::STTblWidth::Dxa))),
bottom: Some(Box::new(make_tbl_width(bottom, types::STTblWidth::Dxa))),
left: Some(Box::new(make_tbl_width(left, types::STTblWidth::Dxa))),
right: Some(Box::new(make_tbl_width(right, types::STTblWidth::Dxa))),
#[cfg(feature = "wml-tables")]
start: None,
#[cfg(feature = "wml-tables")]
end: None,
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}));
}
}
#[cfg(feature = "wml-tables")]
impl types::Table {
pub fn set_width(&mut self, width: u32, unit: TableWidthUnit) {
let type_ = match unit {
TableWidthUnit::Dxa => types::STTblWidth::Dxa,
TableWidthUnit::Pct => types::STTblWidth::Pct,
};
self.table_properties.tbl_w = Some(Box::new(make_tbl_width(width, type_)));
}
}
#[cfg(feature = "wml-tables")]
fn make_cell_border(style: BorderStyle, width_eights: u32, color: &str) -> types::CTBorder {
types::CTBorder {
value: style.to_st_border(),
#[cfg(feature = "wml-styling")]
color: Some(color.to_string()),
#[cfg(feature = "wml-styling")]
size: Some(width_eights as u64),
#[cfg(feature = "wml-styling")]
space: Some(0u64),
#[cfg(feature = "wml-styling")]
theme_color: None,
#[cfg(feature = "wml-styling")]
theme_tint: None,
#[cfg(feature = "wml-styling")]
theme_shade: None,
#[cfg(feature = "wml-styling")]
shadow: None,
#[cfg(feature = "wml-styling")]
frame: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}
}
#[cfg(feature = "wml-tables")]
fn make_tbl_width(width: u32, type_: types::STTblWidth) -> types::CTTblWidth {
types::CTTblWidth {
width: Some(width.to_string()),
r#type: Some(type_),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}
}
impl types::HeaderFooter {
pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
self.block_content
.push(types::BlockContent::P(Box::default()));
match self.block_content.last_mut().unwrap() {
types::BlockContent::P(p) => p.as_mut(),
_ => unreachable!(),
}
}
}
impl types::Comment {
pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
self.block_content
.push(types::BlockContent::P(Box::default()));
match self.block_content.last_mut().unwrap() {
types::BlockContent::P(p) => p.as_mut(),
_ => unreachable!(),
}
}
}
impl types::FootnoteEndnote {
pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
self.block_content
.push(types::BlockContent::P(Box::default()));
match self.block_content.last_mut().unwrap() {
types::BlockContent::P(p) => p.as_mut(),
_ => unreachable!(),
}
}
}
#[cfg(feature = "wml-track-changes")]
fn make_run_track_change(
id: i64,
author: &str,
date: Option<&str>,
text: &str,
) -> types::CTRunTrackChange {
let t = types::Text {
text: Some(text.to_string()),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let run = types::Run {
#[cfg(feature = "wml-track-changes")]
rsid_r_pr: None,
#[cfg(feature = "wml-track-changes")]
rsid_del: None,
#[cfg(feature = "wml-track-changes")]
rsid_r: None,
#[cfg(feature = "wml-styling")]
r_pr: None,
run_content: vec![types::RunContent::T(Box::new(t))],
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
types::CTRunTrackChange {
id,
author: author.to_string(),
date: date.map(|d| d.to_string()),
run_content: vec![types::RunContentChoice::R(Box::new(run))],
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}
}
#[cfg(feature = "wml-track-changes")]
pub fn ins_run(id: i64, author: &str, date: Option<&str>, text: &str) -> types::ParagraphContent {
types::ParagraphContent::Ins(Box::new(make_run_track_change(id, author, date, text)))
}
#[cfg(feature = "wml-track-changes")]
pub fn del_run(id: i64, author: &str, date: Option<&str>, text: &str) -> types::ParagraphContent {
types::ParagraphContent::Del(Box::new(make_run_track_change(id, author, date, text)))
}
#[cfg(feature = "wml-track-changes")]
impl types::Paragraph {
pub fn add_tracked_insertion(
&mut self,
id: i64,
author: &str,
date: Option<&str>,
text: &str,
) -> &mut types::CTRunTrackChange {
self.paragraph_content.push(ins_run(id, author, date, text));
match self.paragraph_content.last_mut().unwrap() {
types::ParagraphContent::Ins(tc) => tc.as_mut(),
_ => unreachable!(),
}
}
pub fn add_tracked_deletion(
&mut self,
id: i64,
author: &str,
date: Option<&str>,
text: &str,
) -> &mut types::CTRunTrackChange {
self.paragraph_content.push(del_run(id, author, date, text));
match self.paragraph_content.last_mut().unwrap() {
types::ParagraphContent::Del(tc) => tc.as_mut(),
_ => unreachable!(),
}
}
}
#[cfg(feature = "wml-settings")]
#[derive(Debug, Clone)]
pub enum FormFieldType {
PlainText,
RichText,
ComboBox,
DropDownList,
DatePicker,
}
#[cfg(feature = "wml-settings")]
#[derive(Debug, Clone)]
pub struct FormFieldConfig {
pub tag: Option<String>,
pub label: Option<String>,
pub field_type: FormFieldType,
pub default_value: Option<String>,
pub placeholder: Option<String>,
pub list_items: Vec<String>,
pub date_format: Option<String>,
}
#[cfg(feature = "wml-settings")]
impl Default for FormFieldConfig {
fn default() -> Self {
Self {
tag: None,
label: None,
field_type: FormFieldType::PlainText,
default_value: None,
placeholder: None,
list_items: Vec::new(),
date_format: None,
}
}
}
#[cfg(feature = "wml-settings")]
fn make_text_paragraph(text: &str) -> types::BlockContentChoice {
let t = types::Text {
text: Some(text.to_string()),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let mut run = types::Run::default();
run.run_content.push(types::RunContent::T(Box::new(t)));
let mut para = types::Paragraph::default();
para.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
types::BlockContentChoice::P(Box::new(para))
}
#[cfg(feature = "wml-settings")]
fn make_list_item(text: &str) -> types::CTSdtListItem {
types::CTSdtListItem {
display_text: Some(text.to_string()),
value: Some(text.to_string()),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}
}
#[cfg(feature = "wml-settings")]
impl types::Body {
pub fn add_form_field(&mut self, config: FormFieldConfig) -> &mut Self {
let content_text = config
.default_value
.as_deref()
.or(config.placeholder.as_deref())
.unwrap_or("")
.to_string();
let mut sdt_pr = types::CTSdtPr::default();
#[cfg(feature = "wml-settings")]
if let Some(ref tag_val) = config.tag {
sdt_pr.tag = Some(Box::new(types::CTString {
value: tag_val.clone(),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-settings")]
if let Some(ref alias_val) = config.label {
sdt_pr.alias = Some(Box::new(types::CTString {
value: alias_val.clone(),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
#[cfg(feature = "wml-settings")]
match config.field_type {
FormFieldType::PlainText => {
sdt_pr.text = Some(Box::new(types::CTSdtText {
multi_line: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
FormFieldType::RichText => {
sdt_pr.rich_text = Some(Box::new(types::CTEmpty));
}
FormFieldType::ComboBox => {
let items = config
.list_items
.iter()
.map(|s| make_list_item(s))
.collect();
sdt_pr.combo_box = Some(Box::new(types::CTSdtComboBox {
last_value: None,
list_item: items,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}));
}
FormFieldType::DropDownList => {
let items = config
.list_items
.iter()
.map(|s| make_list_item(s))
.collect();
sdt_pr.drop_down_list = Some(Box::new(types::CTSdtDropDownList {
last_value: None,
list_item: items,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}));
}
FormFieldType::DatePicker => {
let date_format_elem = config.date_format.as_deref().map(|fmt| {
Box::new(types::CTString {
value: fmt.to_string(),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
})
});
sdt_pr.date = Some(Box::new(types::CTSdtDate {
date_format: date_format_elem,
full_date: None,
lid: None,
store_mapped_data_as: None,
calendar: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}));
}
}
let sdt_content = types::CTSdtContentBlock {
block_content: vec![make_text_paragraph(&content_text)],
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let sdt = types::CTSdtBlock {
sdt_pr: Some(Box::new(sdt_pr)),
sdt_end_pr: None,
sdt_content: Some(Box::new(sdt_content)),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
self.block_content
.push(types::BlockContent::Sdt(Box::new(sdt)));
self
}
}
#[cfg(feature = "wml-fields")]
#[derive(Debug, Clone)]
pub struct TocOptions {
pub title: Option<String>,
pub max_level: u8,
pub right_align_page_numbers: bool,
pub use_hyperlinks: bool,
}
#[cfg(feature = "wml-fields")]
impl Default for TocOptions {
fn default() -> Self {
Self {
title: None,
max_level: 3,
right_align_page_numbers: true,
use_hyperlinks: true,
}
}
}
#[cfg(feature = "wml-fields")]
fn make_fld_char(fld_char_type: types::STFldCharType) -> types::CTFldChar {
types::CTFldChar {
fld_char_type,
#[cfg(feature = "wml-fields")]
fld_lock: None,
#[cfg(feature = "wml-fields")]
dirty: None,
#[cfg(feature = "wml-fields")]
fld_data: None,
#[cfg(feature = "wml-fields")]
ff_data: None,
#[cfg(feature = "wml-track-changes")]
numbering_change: None,
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
}
}
#[cfg(feature = "wml-fields")]
#[allow(dead_code)]
fn make_fld_char_para(fld_char_type: types::STFldCharType) -> types::Paragraph {
let fld_char = make_fld_char(fld_char_type);
let mut run = types::Run::default();
run.run_content
.push(types::RunContent::FldChar(Box::new(fld_char)));
let mut para = types::Paragraph::default();
para.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
para
}
#[cfg(feature = "wml-fields")]
#[allow(dead_code)]
fn make_instr_text_para(instr: &str) -> types::Paragraph {
let t = types::Text {
text: Some(instr.to_string()),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let mut run = types::Run::default();
run.run_content
.push(types::RunContent::InstrText(Box::new(t)));
let mut para = types::Paragraph::default();
para.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
para
}
#[cfg(feature = "wml-fields")]
impl types::Body {
pub fn add_toc(&mut self, opts: TocOptions) -> &mut Self {
if let Some(ref title_text) = opts.title {
let para = self.add_paragraph();
para.add_run().set_text(title_text.as_str());
#[cfg(feature = "wml-styling")]
{
let ppr = para
.p_pr
.get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
ppr.paragraph_style = Some(Box::new(types::CTString {
value: "TOCHeading".to_string(),
#[cfg(feature = "extra-attrs")]
extra_attrs: Default::default(),
}));
}
}
let mut instr = format!(r#" TOC \o "1-{}" "#, opts.max_level);
if opts.use_hyperlinks {
instr.push_str(r"\h ");
}
if opts.right_align_page_numbers {
instr.push_str(r"\z \u ");
}
let fld_begin = make_fld_char(types::STFldCharType::Begin);
let fld_separate = make_fld_char(types::STFldCharType::Separate);
let fld_end = make_fld_char(types::STFldCharType::End);
let instr_t = types::Text {
text: Some(instr),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let mut run_begin = types::Run::default();
run_begin
.run_content
.push(types::RunContent::FldChar(Box::new(fld_begin)));
let mut run_instr = types::Run::default();
run_instr
.run_content
.push(types::RunContent::InstrText(Box::new(instr_t)));
let mut run_separate = types::Run::default();
run_separate
.run_content
.push(types::RunContent::FldChar(Box::new(fld_separate)));
let mut run_end = types::Run::default();
run_end
.run_content
.push(types::RunContent::FldChar(Box::new(fld_end)));
let toc_para = self.add_paragraph();
toc_para
.paragraph_content
.push(types::ParagraphContent::R(Box::new(run_begin)));
toc_para
.paragraph_content
.push(types::ParagraphContent::R(Box::new(run_instr)));
toc_para
.paragraph_content
.push(types::ParagraphContent::R(Box::new(run_separate)));
toc_para
.paragraph_content
.push(types::ParagraphContent::R(Box::new(run_end)));
let placeholder = self.add_paragraph();
placeholder
.add_run()
.set_text("[Right-click to update field]");
self
}
}
pub const NS_M: &str = "http://schemas.openxmlformats.org/officeDocument/2006/math";
#[derive(Debug, Clone)]
pub struct OMathBuilder {
display: bool,
xml_content: String,
}
impl OMathBuilder {
pub fn plain(text: &str) -> Self {
Self {
display: false,
xml_content: format!("<m:r><m:t>{}</m:t></m:r>", xml_escape(text)),
}
}
pub fn fraction(numerator: &str, denominator: &str) -> Self {
Self {
display: false,
xml_content: format!(
"<m:f><m:num><m:r><m:t>{}</m:t></m:r></m:num>\
<m:den><m:r><m:t>{}</m:t></m:r></m:den></m:f>",
xml_escape(numerator),
xml_escape(denominator)
),
}
}
pub fn superscript(base: &str, exp: &str) -> Self {
Self {
display: false,
xml_content: format!(
"<m:sSup><m:e><m:r><m:t>{}</m:t></m:r></m:e>\
<m:sup><m:r><m:t>{}</m:t></m:r></m:sup></m:sSup>",
xml_escape(base),
xml_escape(exp)
),
}
}
pub fn subscript(base: &str, sub: &str) -> Self {
Self {
display: false,
xml_content: format!(
"<m:sSub><m:e><m:r><m:t>{}</m:t></m:r></m:e>\
<m:sub><m:r><m:t>{}</m:t></m:r></m:sub></m:sSub>",
xml_escape(base),
xml_escape(sub)
),
}
}
pub fn radical(base: &str) -> Self {
Self {
display: false,
xml_content: format!(
"<m:rad><m:radPr><m:degHide m:val=\"1\"/></m:radPr>\
<m:deg/><m:e><m:r><m:t>{}</m:t></m:r></m:e></m:rad>",
xml_escape(base)
),
}
}
pub fn as_display(mut self) -> Self {
self.display = true;
self
}
pub fn build(self) -> RawXmlElement {
let omath = RawXmlElement {
name: "m:oMath".to_string(),
attributes: vec![("xmlns:m".to_string(), NS_M.to_string())],
children: vec![RawXmlNode::Text(self.xml_content)],
self_closing: false,
};
if self.display {
RawXmlElement {
name: "m:oMathPara".to_string(),
attributes: vec![("xmlns:m".to_string(), NS_M.to_string())],
children: vec![RawXmlNode::Element({
RawXmlElement {
name: "m:oMath".to_string(),
attributes: vec![],
children: vec![RawXmlNode::Text({
match omath.children.into_iter().next() {
Some(RawXmlNode::Text(t)) => t,
_ => String::new(),
}
})],
self_closing: false,
}
})],
self_closing: false,
}
} else {
omath
}
}
}
fn xml_escape(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
impl types::Paragraph {
#[cfg(feature = "extra-children")]
pub fn add_math(&mut self, builder: OMathBuilder) -> &mut Self {
let elem = builder.build();
let idx = self.paragraph_content.len();
self.extra_children
.push(PositionedNode::new(idx, RawXmlNode::Element(elem)));
self
}
}
impl types::Paragraph {
#[cfg(feature = "wml-charts")]
pub fn add_inline_chart(&mut self, rel_id: &str, width_emu: i64, height_emu: i64) -> &mut Self {
let drawing_elem = build_chart_inline_element(rel_id, width_emu, height_emu);
let drawing = types::CTDrawing {
#[cfg(feature = "extra-children")]
extra_children: vec![PositionedNode::new(0, RawXmlNode::Element(drawing_elem))],
};
let mut run = types::Run::default();
run.run_content
.push(types::RunContent::Drawing(Box::new(drawing)));
self.paragraph_content
.push(types::ParagraphContent::R(Box::new(run)));
self
}
}
#[cfg(feature = "wml-charts")]
fn build_chart_inline_element(rel_id: &str, width_emu: i64, height_emu: i64) -> RawXmlElement {
let chart_ref = RawXmlElement {
name: "c:chart".to_string(),
attributes: vec![
(
"xmlns:c".to_string(),
"http://schemas.openxmlformats.org/drawingml/2006/chart".to_string(),
),
(
"xmlns:r".to_string(),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships".to_string(),
),
("r:id".to_string(), rel_id.to_string()),
],
children: vec![],
self_closing: true,
};
let graphic_data = RawXmlElement {
name: "a:graphicData".to_string(),
attributes: vec![(
"uri".to_string(),
"http://schemas.openxmlformats.org/drawingml/2006/chart".to_string(),
)],
children: vec![RawXmlNode::Element(chart_ref)],
self_closing: false,
};
let graphic = RawXmlElement {
name: "a:graphic".to_string(),
attributes: vec![(
"xmlns:a".to_string(),
"http://schemas.openxmlformats.org/drawingml/2006/main".to_string(),
)],
children: vec![RawXmlNode::Element(graphic_data)],
self_closing: false,
};
let extent = RawXmlElement {
name: "wp:extent".to_string(),
attributes: vec![
("cx".to_string(), width_emu.to_string()),
("cy".to_string(), height_emu.to_string()),
],
children: vec![],
self_closing: true,
};
let doc_pr = RawXmlElement {
name: "wp:docPr".to_string(),
attributes: vec![
("id".to_string(), "1".to_string()),
("name".to_string(), "Chart 1".to_string()),
],
children: vec![],
self_closing: true,
};
RawXmlElement {
name: "wp:inline".to_string(),
attributes: vec![
(
"xmlns:wp".to_string(),
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
.to_string(),
),
("distT".to_string(), "0".to_string()),
("distB".to_string(), "0".to_string()),
("distL".to_string(), "0".to_string()),
("distR".to_string(), "0".to_string()),
],
children: vec![
RawXmlNode::Element(extent),
RawXmlNode::Element(doc_pr),
RawXmlNode::Element(graphic),
],
self_closing: false,
}
}
#[cfg(test)]
mod feature_tests {
use super::*;
#[test]
#[cfg(feature = "wml-settings")]
fn test_settings_xml_content() {
use crate::writer::DocumentSettingsOptions;
let opts = DocumentSettingsOptions {
default_tab_stop: Some(720),
even_and_odd_headers: true,
track_changes: true,
rsid_root: Some("AB12CD34".to_string()),
compat_mode: true,
};
let _ = opts;
}
#[test]
#[cfg(all(
feature = "wml-settings",
feature = "extra-attrs",
feature = "extra-children"
))]
fn test_settings_roundtrip() {
use crate::Document;
use crate::writer::{DocumentBuilder, DocumentSettingsOptions};
use std::io::Cursor;
let mut builder = DocumentBuilder::new();
builder.set_settings(DocumentSettingsOptions {
default_tab_stop: Some(720),
even_and_odd_headers: true,
track_changes: false,
rsid_root: None,
compat_mode: false,
});
builder.add_paragraph("Hello");
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
buf.set_position(0);
let doc = Document::from_reader(buf).unwrap();
let body = doc.body();
assert!(!body.block_content.is_empty());
}
#[test]
#[cfg(feature = "wml-settings")]
fn test_form_field_plain_text() {
let mut body = types::Body::default();
body.add_form_field(FormFieldConfig {
tag: Some("myTag".to_string()),
label: Some("My Field".to_string()),
field_type: FormFieldType::PlainText,
default_value: Some("default".to_string()),
..Default::default()
});
assert_eq!(body.block_content.len(), 1);
match &body.block_content[0] {
types::BlockContent::Sdt(sdt) => {
let sdt_pr = sdt.sdt_pr.as_ref().expect("sdt_pr should be present");
assert_eq!(sdt_pr.tag.as_ref().unwrap().value, "myTag");
assert_eq!(sdt_pr.alias.as_ref().unwrap().value, "My Field");
assert!(sdt_pr.text.is_some(), "text element should be set");
let content = sdt.sdt_content.as_ref().expect("sdt_content");
assert_eq!(content.block_content.len(), 1);
}
_ => panic!("expected Sdt block content"),
}
}
#[test]
#[cfg(feature = "wml-settings")]
fn test_form_field_dropdown() {
let mut body = types::Body::default();
body.add_form_field(FormFieldConfig {
field_type: FormFieldType::DropDownList,
list_items: vec!["Option A".to_string(), "Option B".to_string()],
..Default::default()
});
match &body.block_content[0] {
types::BlockContent::Sdt(sdt) => {
let sdt_pr = sdt.sdt_pr.as_ref().unwrap();
let dd = sdt_pr.drop_down_list.as_ref().expect("drop_down_list");
assert_eq!(dd.list_item.len(), 2);
assert_eq!(dd.list_item[0].display_text.as_deref(), Some("Option A"));
assert_eq!(dd.list_item[1].display_text.as_deref(), Some("Option B"));
}
_ => panic!("expected Sdt"),
}
}
#[test]
#[cfg(feature = "wml-settings")]
fn test_form_field_date_picker() {
let mut body = types::Body::default();
body.add_form_field(FormFieldConfig {
field_type: FormFieldType::DatePicker,
date_format: Some("MM/dd/yyyy".to_string()),
..Default::default()
});
match &body.block_content[0] {
types::BlockContent::Sdt(sdt) => {
let sdt_pr = sdt.sdt_pr.as_ref().unwrap();
let date = sdt_pr.date.as_ref().expect("date element");
assert_eq!(date.date_format.as_ref().unwrap().value, "MM/dd/yyyy");
}
_ => panic!("expected Sdt"),
}
}
#[test]
#[cfg(feature = "wml-fields")]
fn test_toc_basic() {
let mut body = types::Body::default();
body.add_toc(TocOptions {
title: Some("Contents".to_string()),
max_level: 3,
right_align_page_numbers: true,
use_hyperlinks: true,
});
let paras: Vec<_> = body
.block_content
.iter()
.filter_map(|b| match b {
types::BlockContent::P(p) => Some(p),
_ => None,
})
.collect();
assert_eq!(
paras.len(),
3,
"expected title + field + placeholder paragraphs"
);
let field_para = ¶s[1];
assert_eq!(
field_para.paragraph_content.len(),
4,
"TOC paragraph should have 4 run items"
);
}
#[test]
#[cfg(feature = "wml-fields")]
fn test_toc_no_title() {
let mut body = types::Body::default();
body.add_toc(TocOptions::default());
let paras: Vec<_> = body
.block_content
.iter()
.filter_map(|b| match b {
types::BlockContent::P(p) => Some(p),
_ => None,
})
.collect();
assert_eq!(paras.len(), 2, "expected field + placeholder paragraphs");
}
#[test]
#[cfg(feature = "wml-fields")]
fn test_toc_instr_text_contains_level() {
let mut body = types::Body::default();
body.add_toc(TocOptions {
title: None,
max_level: 2,
right_align_page_numbers: false,
use_hyperlinks: false,
});
let field_para = match &body.block_content[0] {
types::BlockContent::P(p) => p,
_ => panic!("expected paragraph"),
};
match &field_para.paragraph_content[1] {
types::ParagraphContent::R(run) => match &run.run_content[0] {
types::RunContent::InstrText(t) => {
let instr = t.text.as_deref().unwrap_or("");
assert!(
instr.contains("1-2"),
"should contain level range 1-2, got: {}",
instr
);
}
_ => panic!("expected InstrText"),
},
_ => panic!("expected run"),
}
}
#[test]
fn test_omath_plain_build() {
let builder = OMathBuilder::plain("x");
let elem = builder.build();
assert_eq!(elem.name, "m:oMath");
assert!(!elem.attributes.is_empty(), "should have xmlns:m");
assert_eq!(elem.children.len(), 1);
match &elem.children[0] {
RawXmlNode::Text(t) => assert!(t.contains("m:r"), "text should wrap in m:r: {}", t),
_ => panic!("expected text node"),
}
}
#[test]
fn test_omath_fraction_build() {
let builder = OMathBuilder::fraction("a", "b");
let elem = builder.build();
match &elem.children[0] {
RawXmlNode::Text(t) => {
assert!(t.contains("m:f"), "should contain fraction: {}", t);
assert!(t.contains("m:num"), "should contain numerator: {}", t);
assert!(t.contains("m:den"), "should contain denominator: {}", t);
}
_ => panic!("expected text node"),
}
}
#[test]
fn test_omath_superscript_build() {
let builder = OMathBuilder::superscript("x", "2");
let elem = builder.build();
match &elem.children[0] {
RawXmlNode::Text(t) => {
assert!(t.contains("m:sSup"), "should contain sSup: {}", t);
assert!(t.contains("m:e"), "should contain base: {}", t);
assert!(t.contains("m:sup"), "should contain exp: {}", t);
}
_ => panic!("expected text node"),
}
}
#[test]
fn test_omath_subscript_build() {
let builder = OMathBuilder::subscript("x", "i");
let elem = builder.build();
match &elem.children[0] {
RawXmlNode::Text(t) => assert!(t.contains("m:sSub"), "{}", t),
_ => panic!(),
}
}
#[test]
fn test_omath_radical_build() {
let builder = OMathBuilder::radical("x");
let elem = builder.build();
match &elem.children[0] {
RawXmlNode::Text(t) => assert!(t.contains("m:rad"), "{}", t),
_ => panic!(),
}
}
#[test]
fn test_omath_display_wraps_in_para() {
let builder = OMathBuilder::plain("x").as_display();
let elem = builder.build();
assert_eq!(
elem.name, "m:oMathPara",
"display should be wrapped in oMathPara"
);
assert_eq!(elem.children.len(), 1);
match &elem.children[0] {
RawXmlNode::Element(inner) => assert_eq!(inner.name, "m:oMath"),
_ => panic!("expected oMath element child"),
}
}
#[test]
#[cfg(feature = "extra-children")]
fn test_paragraph_add_math() {
let mut para = types::Paragraph::default();
para.add_math(OMathBuilder::plain("y = mx + b"));
assert_eq!(para.extra_children.len(), 1);
}
#[test]
#[cfg(all(
feature = "wml-charts",
feature = "extra-children",
feature = "extra-attrs"
))]
fn test_embed_chart_and_add_inline() {
use crate::writer::DocumentBuilder;
use std::io::Cursor;
let chart_xml = br#"<?xml version="1.0" encoding="UTF-8"?><c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"/>"#;
let mut builder = DocumentBuilder::new();
let rel_id = builder.embed_chart(chart_xml).unwrap();
assert!(
rel_id.starts_with("rId"),
"rel_id should be rId-prefixed: {}",
rel_id
);
{
let body = builder.body_mut();
let para = body.add_paragraph();
para.add_inline_chart(&rel_id, 3000000, 2000000);
}
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
assert!(!buf.get_ref().is_empty(), "output should not be empty");
}
#[test]
#[cfg(feature = "wml-charts")]
fn test_chart_inline_element_structure() {
let elem = build_chart_inline_element("rId5", 3000000, 2000000);
assert_eq!(elem.name, "wp:inline");
assert_eq!(elem.children.len(), 3);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paragraph_add_page_break() {
let mut para = types::Paragraph::default();
para.add_page_break();
assert_eq!(para.paragraph_content.len(), 1);
match ¶.paragraph_content[0] {
types::ParagraphContent::R(run) => {
assert_eq!(run.run_content.len(), 1);
match &run.run_content[0] {
types::RunContent::Br(br) => {
assert_eq!(br.r#type, Some(types::STBrType::Page));
}
_ => panic!("expected Br content"),
}
}
_ => panic!("expected R content"),
}
}
#[test]
fn test_paragraph_add_column_break() {
let mut para = types::Paragraph::default();
para.add_column_break();
assert_eq!(para.paragraph_content.len(), 1);
match ¶.paragraph_content[0] {
types::ParagraphContent::R(run) => {
assert_eq!(run.run_content.len(), 1);
match &run.run_content[0] {
types::RunContent::Br(br) => {
assert_eq!(br.r#type, Some(types::STBrType::Column));
}
_ => panic!("expected Br content"),
}
}
_ => panic!("expected R content"),
}
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_space_before() {
let mut para = types::Paragraph::default();
para.set_space_before(240);
let ppr = para.p_pr.as_ref().unwrap();
let spacing = ppr.spacing.as_ref().unwrap();
assert_eq!(spacing.before.as_deref(), Some("240"));
assert!(spacing.after.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_space_after() {
let mut para = types::Paragraph::default();
para.set_space_after(160);
let ppr = para.p_pr.as_ref().unwrap();
let spacing = ppr.spacing.as_ref().unwrap();
assert_eq!(spacing.after.as_deref(), Some("160"));
assert!(spacing.before.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_line_spacing() {
let mut para = types::Paragraph::default();
para.set_line_spacing(360);
let ppr = para.p_pr.as_ref().unwrap();
let spacing = ppr.spacing.as_ref().unwrap();
assert_eq!(spacing.line.as_deref(), Some("360"));
assert_eq!(spacing.line_rule, Some(types::STLineSpacingRule::Auto));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_spacing_accumulates() {
let mut para = types::Paragraph::default();
para.set_space_before(240);
para.set_space_after(120);
para.set_line_spacing(480);
let ppr = para.p_pr.as_ref().unwrap();
let spacing = ppr.spacing.as_ref().unwrap();
assert_eq!(spacing.before.as_deref(), Some("240"));
assert_eq!(spacing.after.as_deref(), Some("120"));
assert_eq!(spacing.line.as_deref(), Some("480"));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_indent_left() {
let mut para = types::Paragraph::default();
para.set_indent_left(720);
let ppr = para.p_pr.as_ref().unwrap();
let ind = ppr.indentation.as_ref().unwrap();
assert_eq!(ind.left.as_deref(), Some("720"));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_indent_right() {
let mut para = types::Paragraph::default();
para.set_indent_right(360);
let ppr = para.p_pr.as_ref().unwrap();
let ind = ppr.indentation.as_ref().unwrap();
assert_eq!(ind.right.as_deref(), Some("360"));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_indent_first_line() {
let mut para = types::Paragraph::default();
para.set_indent_first_line(180);
let ppr = para.p_pr.as_ref().unwrap();
let ind = ppr.indentation.as_ref().unwrap();
assert_eq!(ind.first_line.as_deref(), Some("180"));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_indentation_accumulates() {
let mut para = types::Paragraph::default();
para.set_indent_left(720);
para.set_indent_right(360);
para.set_indent_first_line(180);
let ppr = para.p_pr.as_ref().unwrap();
let ind = ppr.indentation.as_ref().unwrap();
assert_eq!(ind.left.as_deref(), Some("720"));
assert_eq!(ind.right.as_deref(), Some("360"));
assert_eq!(ind.first_line.as_deref(), Some("180"));
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_set_outline_level() {
let mut para = types::Paragraph::default();
para.set_outline_level(1);
let ppr = para.p_pr.as_ref().unwrap();
let lvl = ppr.outline_lvl.as_ref().unwrap();
assert_eq!(lvl.value, 1);
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_shadow() {
let mut run = types::Run::default();
run.set_shadow(true);
assert!(run.r_pr.as_ref().unwrap().shadow.is_some());
run.set_shadow(false);
assert!(run.r_pr.as_ref().unwrap().shadow.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_outline() {
let mut run = types::Run::default();
run.set_outline(true);
assert!(run.r_pr.as_ref().unwrap().outline.is_some());
run.set_outline(false);
assert!(run.r_pr.as_ref().unwrap().outline.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_emboss() {
let mut run = types::Run::default();
run.set_emboss(true);
assert!(run.r_pr.as_ref().unwrap().emboss.is_some());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_imprint() {
let mut run = types::Run::default();
run.set_imprint(true);
assert!(run.r_pr.as_ref().unwrap().imprint.is_some());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_small_caps() {
let mut run = types::Run::default();
run.set_small_caps(true);
assert!(run.r_pr.as_ref().unwrap().small_caps.is_some());
run.set_small_caps(false);
assert!(run.r_pr.as_ref().unwrap().small_caps.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_all_caps() {
let mut run = types::Run::default();
run.set_all_caps(true);
assert!(run.r_pr.as_ref().unwrap().caps.is_some());
run.set_all_caps(false);
assert!(run.r_pr.as_ref().unwrap().caps.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_vanish() {
let mut run = types::Run::default();
run.set_vanish(true);
assert!(run.r_pr.as_ref().unwrap().vanish.is_some());
run.set_vanish(false);
assert!(run.r_pr.as_ref().unwrap().vanish.is_none());
}
#[test]
#[cfg(feature = "wml-styling")]
fn test_run_set_double_strike() {
let mut run = types::Run::default();
run.set_double_strike(true);
assert!(run.r_pr.as_ref().unwrap().dstrike.is_some());
run.set_double_strike(false);
assert!(run.r_pr.as_ref().unwrap().dstrike.is_none());
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_grid_span() {
let mut cell = types::TableCell::default();
cell.set_grid_span(3);
let tcpr = cell.cell_properties.as_ref().unwrap();
let gs = tcpr.grid_span.as_ref().unwrap();
assert_eq!(gs.value, 3);
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_vertical_merge_restart() {
let mut cell = types::TableCell::default();
cell.set_vertical_merge(VMergeType::Restart);
let tcpr = cell.cell_properties.as_ref().unwrap();
let vm = tcpr.vertical_merge.as_ref().unwrap();
assert_eq!(vm.value, Some(types::STMerge::Restart));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_vertical_merge_continue() {
let mut cell = types::TableCell::default();
cell.set_vertical_merge(VMergeType::Continue);
let tcpr = cell.cell_properties.as_ref().unwrap();
let vm = tcpr.vertical_merge.as_ref().unwrap();
assert_eq!(vm.value, None);
}
#[test]
#[cfg(all(
feature = "wml-styling",
feature = "extra-attrs",
feature = "extra-children"
))]
fn test_roundtrip_page_break() {
use crate::Document;
use crate::writer::DocumentBuilder;
use std::io::Cursor;
let mut builder = DocumentBuilder::new();
let body = builder.body_mut();
let para = body.add_paragraph();
para.add_page_break();
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
buf.set_position(0);
let doc = Document::from_reader(buf).unwrap();
let body_ref = doc.body();
assert!(!body_ref.block_content.is_empty());
}
#[test]
#[cfg(all(
feature = "wml-styling",
feature = "extra-attrs",
feature = "extra-children"
))]
fn test_roundtrip_spacing_and_indent() {
use crate::Document;
use crate::ext::BodyExt;
use crate::writer::DocumentBuilder;
use std::io::Cursor;
let mut builder = DocumentBuilder::new();
{
let body = builder.body_mut();
let para = body.add_paragraph();
para.set_space_before(240);
para.set_space_after(120);
para.set_line_spacing(360);
para.set_indent_left(720);
para.set_outline_level(0);
para.add_run().set_text("test spacing");
}
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
buf.set_position(0);
let doc = Document::from_reader(buf).unwrap();
let body_ref = doc.body();
assert_eq!(body_ref.paragraphs().len(), 1);
let p = body_ref.paragraphs()[0];
let ppr = p.p_pr.as_ref().unwrap();
let spacing = ppr.spacing.as_ref().unwrap();
assert_eq!(spacing.before.as_deref(), Some("240"));
assert_eq!(spacing.after.as_deref(), Some("120"));
}
#[test]
#[cfg(all(
feature = "wml-tables",
feature = "extra-attrs",
feature = "extra-children"
))]
fn test_roundtrip_grid_span_and_vmerge() {
use crate::Document;
use crate::ext::BodyExt;
use crate::writer::DocumentBuilder;
use std::io::Cursor;
let mut builder = DocumentBuilder::new();
{
let body = builder.body_mut();
let tbl = body.add_table();
let row = tbl.add_row();
let cell = row.add_cell();
cell.set_grid_span(2);
cell.set_vertical_merge(VMergeType::Restart);
cell.add_paragraph().add_run().set_text("merged");
}
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
buf.set_position(0);
let doc = Document::from_reader(buf).unwrap();
let body_ref = doc.body();
assert!(!body_ref.tables().is_empty());
let tbl = body_ref.tables()[0];
let row = match &tbl.rows[0] {
crate::types::RowContent::Tr(r) => r,
_ => panic!("expected Tr"),
};
let cell = match &row.cells[0] {
crate::types::CellContent::Tc(c) => c,
_ => panic!("expected Tc"),
};
let tcpr = cell.cell_properties.as_ref().unwrap();
assert_eq!(tcpr.grid_span.as_ref().unwrap().value, 2);
assert_eq!(
tcpr.vertical_merge.as_ref().unwrap().value,
Some(crate::types::STMerge::Restart)
);
}
#[test]
fn test_add_bookmark_start_u32() {
let mut para = types::Paragraph::default();
para.add_bookmark_start_u32(1, "myBookmark");
assert_eq!(para.paragraph_content.len(), 1);
match ¶.paragraph_content[0] {
types::ParagraphContent::BookmarkStart(bm) => {
assert_eq!(bm.id, 1);
assert_eq!(bm.name, "myBookmark");
}
_ => panic!("expected BookmarkStart"),
}
}
#[test]
fn test_add_bookmark_end_u32() {
let mut para = types::Paragraph::default();
para.add_bookmark_end_u32(42);
assert_eq!(para.paragraph_content.len(), 1);
match ¶.paragraph_content[0] {
types::ParagraphContent::BookmarkEnd(bm) => {
assert_eq!(bm.id, 42);
}
_ => panic!("expected BookmarkEnd"),
}
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_row_set_height() {
let mut row = types::CTRow::default();
row.set_height(720);
let row_pr = row.row_properties.as_ref().unwrap();
let height = row_pr.tr_height.as_ref().unwrap();
assert_eq!(height.value.as_deref(), Some("720"));
assert_eq!(height.h_rule, Some(types::STHeightRule::Exact));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_background_color() {
let mut cell = types::TableCell::default();
cell.set_background_color("FF0000");
let tcpr = cell.cell_properties.as_ref().unwrap();
let shd = tcpr.shading.as_ref().unwrap();
assert_eq!(shd.value, types::STShd::Clear);
#[cfg(feature = "wml-styling")]
assert_eq!(shd.fill.as_deref(), Some("FF0000"));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_borders() {
let mut cell = types::TableCell::default();
cell.set_borders(BorderStyle::Single, 4, "000000");
let tcpr = cell.cell_properties.as_ref().unwrap();
let borders = tcpr.tc_borders.as_ref().unwrap();
assert!(borders.top.is_some());
assert!(borders.bottom.is_some());
assert!(borders.left.is_some());
assert!(borders.right.is_some());
let top = borders.top.as_ref().unwrap();
assert_eq!(top.value, types::STBorder::Single);
#[cfg(feature = "wml-styling")]
assert_eq!(top.size, Some(4u64));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_border_top_only() {
let mut cell = types::TableCell::default();
cell.set_border_top(BorderStyle::Dashed, 8, "AABBCC");
let tcpr = cell.cell_properties.as_ref().unwrap();
let borders = tcpr.tc_borders.as_ref().unwrap();
assert!(borders.top.is_some());
assert!(borders.bottom.is_none());
assert!(borders.left.is_none());
assert!(borders.right.is_none());
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_cell_set_padding() {
let mut cell = types::TableCell::default();
cell.set_padding(100, 100, 200, 200);
let tcpr = cell.cell_properties.as_ref().unwrap();
let mar = tcpr.tc_mar.as_ref().unwrap();
let top = mar.top.as_ref().unwrap();
assert_eq!(top.width.as_deref(), Some("100"));
assert_eq!(top.r#type, Some(types::STTblWidth::Dxa));
let left = mar.left.as_ref().unwrap();
assert_eq!(left.width.as_deref(), Some("200"));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_table_set_width_dxa() {
let table = types::Table {
range_markup: Vec::new(),
table_properties: Box::new(types::TableProperties::default()),
tbl_grid: Box::new(types::TableGrid::default()),
rows: Vec::new(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let mut body = types::Body::default();
body.block_content
.push(types::BlockContent::Tbl(Box::new(table)));
let tbl = match body.block_content.last_mut().unwrap() {
types::BlockContent::Tbl(t) => t.as_mut(),
_ => unreachable!(),
};
tbl.set_width(9360, TableWidthUnit::Dxa);
let tbl_w = tbl.table_properties.tbl_w.as_ref().unwrap();
assert_eq!(tbl_w.width.as_deref(), Some("9360"));
assert_eq!(tbl_w.r#type, Some(types::STTblWidth::Dxa));
}
#[test]
#[cfg(feature = "wml-tables")]
fn test_table_set_width_pct() {
let table = types::Table {
range_markup: Vec::new(),
table_properties: Box::new(types::TableProperties::default()),
tbl_grid: Box::new(types::TableGrid::default()),
rows: Vec::new(),
#[cfg(feature = "extra-children")]
extra_children: Vec::new(),
};
let mut body = types::Body::default();
body.block_content
.push(types::BlockContent::Tbl(Box::new(table)));
let tbl = match body.block_content.last_mut().unwrap() {
types::BlockContent::Tbl(t) => t.as_mut(),
_ => unreachable!(),
};
tbl.set_width(5000, TableWidthUnit::Pct);
let tbl_w = tbl.table_properties.tbl_w.as_ref().unwrap();
assert_eq!(tbl_w.width.as_deref(), Some("5000"));
assert_eq!(tbl_w.r#type, Some(types::STTblWidth::Pct));
}
#[test]
#[cfg(all(
feature = "wml-tables",
feature = "wml-styling",
feature = "extra-attrs",
feature = "extra-children"
))]
fn test_roundtrip_cell_background_and_borders() {
use crate::Document;
use crate::ext::BodyExt;
use crate::writer::DocumentBuilder;
use std::io::Cursor;
let mut builder = DocumentBuilder::new();
{
let body = builder.body_mut();
let tbl = body.add_table();
let row = tbl.add_row();
let cell = row.add_cell();
cell.set_background_color("FFFF00");
cell.set_borders(BorderStyle::Single, 4, "000000");
cell.set_padding(72, 72, 144, 144);
cell.add_paragraph().add_run().set_text("styled");
}
let mut buf = Cursor::new(Vec::new());
builder.write(&mut buf).unwrap();
buf.set_position(0);
let doc = Document::from_reader(buf).unwrap();
let body_ref = doc.body();
assert!(!body_ref.tables().is_empty());
let tbl = body_ref.tables()[0];
let row = match &tbl.rows[0] {
crate::types::RowContent::Tr(r) => r,
_ => panic!("expected Tr"),
};
let cell = match &row.cells[0] {
crate::types::CellContent::Tc(c) => c,
_ => panic!("expected Tc"),
};
let tcpr = cell.cell_properties.as_ref().unwrap();
let shd = tcpr.shading.as_ref().unwrap();
assert_eq!(shd.value, crate::types::STShd::Clear);
assert_eq!(shd.fill.as_deref(), Some("FFFF00"));
let borders = tcpr.tc_borders.as_ref().unwrap();
assert!(borders.top.is_some());
let top = borders.top.as_ref().unwrap();
assert_eq!(top.value, crate::types::STBorder::Single);
let mar = tcpr.tc_mar.as_ref().unwrap();
let left = mar.left.as_ref().unwrap();
assert_eq!(left.width.as_deref(), Some("144"));
}
}