use crate::extractors::security::{SecurityBudget, SecurityError};
use quick_xml::Reader;
use quick_xml::events::{BytesStart, Event};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct TableProperties {
pub style_id: Option<String>,
pub width: Option<TableWidth>,
pub alignment: Option<String>,
pub layout: Option<String>,
pub look: Option<TableLook>,
pub borders: Option<TableBorders>,
pub cell_margins: Option<CellMargins>,
pub indent: Option<TableWidth>,
pub caption: Option<String>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TableWidth {
pub value: i32,
pub width_type: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct TableLook {
pub first_row: bool,
pub last_row: bool,
pub first_column: bool,
pub last_column: bool,
pub no_h_band: bool,
pub no_v_band: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct TableBorders {
pub top: Option<BorderStyle>,
pub bottom: Option<BorderStyle>,
pub left: Option<BorderStyle>,
pub right: Option<BorderStyle>,
pub inside_h: Option<BorderStyle>,
pub inside_v: Option<BorderStyle>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct BorderStyle {
pub style: String,
pub size: Option<i32>,
pub color: Option<String>,
pub space: Option<i32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct CellMargins {
pub top: Option<i32>,
pub bottom: Option<i32>,
pub left: Option<i32>,
pub right: Option<i32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct RowProperties {
pub height: Option<i32>,
pub height_rule: Option<String>,
pub is_header: bool,
pub cant_split: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct CellProperties {
pub width: Option<TableWidth>,
pub grid_span: Option<u32>,
pub v_merge: Option<VerticalMerge>,
pub borders: Option<CellBorders>,
pub shading: Option<CellShading>,
pub margins: Option<CellMargins>,
pub vertical_align: Option<String>,
pub text_direction: Option<String>,
pub no_wrap: bool,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VerticalMerge {
Restart,
Continue,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct CellBorders {
pub top: Option<BorderStyle>,
pub bottom: Option<BorderStyle>,
pub left: Option<BorderStyle>,
pub right: Option<BorderStyle>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(alef, alef(skip))]
pub struct CellShading {
pub fill: Option<String>,
pub color: Option<String>,
pub val: Option<String>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct TableGrid {
pub columns: Vec<i32>,
}
fn apply_simple_table_property(props: &mut TableProperties, e: &BytesStart) {
match e.local_name().as_ref() {
"tblStyle" => props.style_id = get_attribute(e, "val"),
"tblW" => props.width = parse_width_element(e),
"jc" => props.alignment = get_attribute(e, "val"),
"tblLayout" => props.layout = get_attribute(e, "type"),
"tblLook" => props.look = Some(parse_table_look(e)),
"tblInd" => props.indent = parse_width_element(e),
"tblCaption" => props.caption = get_attribute(e, "val"),
_ => {}
}
}
pub(crate) fn parse_table_properties(
reader: &mut Reader<&[u8]>,
budget: &mut SecurityBudget,
) -> Result<TableProperties, SecurityError> {
let mut props = TableProperties::default();
let mut buf = Vec::new();
loop {
budget.step()?;
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
budget.enter()?;
match e.local_name().as_ref() {
"tblBorders" => {
props.borders = Some(parse_table_borders(reader));
budget.leave();
}
"tblCellMar" => {
props.cell_margins = Some(parse_cell_margins_element(reader));
budget.leave();
}
_ => apply_simple_table_property(&mut props, &e),
}
buf.clear();
}
Ok(Event::Empty(e)) => {
match e.local_name().as_ref() {
"tblBorders" => props.borders = Some(TableBorders::default()),
"tblCellMar" => props.cell_margins = Some(CellMargins::default()),
_ => apply_simple_table_property(&mut props, &e),
}
buf.clear();
}
Ok(Event::End(e)) => {
budget.leave();
if e.local_name().as_ref() == "tblPr" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
Ok(props)
}
pub(crate) fn parse_row_properties(
reader: &mut Reader<&[u8]>,
budget: &mut SecurityBudget,
) -> Result<RowProperties, SecurityError> {
let mut props = RowProperties::default();
let mut buf = Vec::new();
loop {
budget.step()?;
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
budget.enter()?;
let local_name = e.local_name();
match local_name.as_ref() {
"trHeight" => {
props.height = get_attribute_int(&e, "val");
props.height_rule = get_attribute(&e, "hRule");
}
"tblHeader" => {
props.is_header = is_toggle_on(&e);
}
"cantSplit" => {
props.cant_split = is_toggle_on(&e);
}
_ => {}
}
buf.clear();
}
Ok(Event::Empty(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"trHeight" => {
props.height = get_attribute_int(&e, "val");
props.height_rule = get_attribute(&e, "hRule");
}
"tblHeader" => {
props.is_header = is_toggle_on(&e);
}
"cantSplit" => {
props.cant_split = is_toggle_on(&e);
}
_ => {}
}
buf.clear();
}
Ok(Event::End(e)) => {
budget.leave();
if e.local_name().as_ref() == "trPr" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
Ok(props)
}
const MAX_CELL_GRID_SPAN: u32 = 1024;
fn validate_grid_span(span: Option<u32>) -> Option<u32> {
span.filter(|&value| (1..=MAX_CELL_GRID_SPAN).contains(&value))
}
fn apply_simple_cell_property(props: &mut CellProperties, e: &BytesStart) {
match e.local_name().as_ref() {
"tcW" => props.width = parse_width_element(e),
"gridSpan" => props.grid_span = validate_grid_span(get_attribute_u32(e, "val")),
"vMerge" => props.v_merge = Some(parse_vmerge(e)),
"shd" => props.shading = Some(parse_cell_shading(e)),
"vAlign" => props.vertical_align = get_attribute(e, "val"),
"textDirection" => props.text_direction = get_attribute(e, "val"),
"noWrap" => props.no_wrap = is_toggle_on(e),
_ => {}
}
}
pub(crate) fn parse_cell_properties(
reader: &mut Reader<&[u8]>,
budget: &mut SecurityBudget,
) -> Result<CellProperties, SecurityError> {
let mut props = CellProperties::default();
let mut buf = Vec::new();
loop {
budget.step()?;
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
budget.enter()?;
match e.local_name().as_ref() {
"tcBorders" => {
props.borders = Some(parse_cell_borders(reader));
budget.leave();
}
"tcMar" => {
props.margins = Some(parse_cell_margins_element(reader));
budget.leave();
}
_ => apply_simple_cell_property(&mut props, &e),
}
buf.clear();
}
Ok(Event::Empty(e)) => {
match e.local_name().as_ref() {
"tcBorders" => props.borders = Some(CellBorders::default()),
"tcMar" => props.margins = Some(CellMargins::default()),
_ => apply_simple_cell_property(&mut props, &e),
}
buf.clear();
}
Ok(Event::End(e)) => {
budget.leave();
if e.local_name().as_ref() == "tcPr" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
Ok(props)
}
pub(crate) fn parse_table_grid(
reader: &mut Reader<&[u8]>,
budget: &mut SecurityBudget,
) -> Result<TableGrid, SecurityError> {
let mut grid = TableGrid::default();
let mut buf = Vec::new();
loop {
budget.step()?;
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
budget.enter()?;
if e.local_name().as_ref() == "gridCol"
&& let Some(width) = get_attribute_int(&e, "w")
{
grid.columns.push(width);
}
buf.clear();
}
Ok(Event::Empty(e)) => {
if e.local_name().as_ref() == "gridCol"
&& let Some(width) = get_attribute_int(&e, "w")
{
grid.columns.push(width);
}
buf.clear();
}
Ok(Event::End(e)) => {
budget.leave();
if e.local_name().as_ref() == "tblGrid" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
Ok(grid)
}
fn is_toggle_on(e: &BytesStart) -> bool {
!matches!(
get_attribute(e, "val").as_deref(),
Some("0") | Some("false") | Some("off")
)
}
fn parse_border_element(e: &BytesStart) -> BorderStyle {
BorderStyle {
style: get_attribute(e, "val").unwrap_or_default(),
size: get_attribute_int(e, "sz"),
color: get_attribute(e, "color"),
space: get_attribute_int(e, "space"),
}
}
fn parse_width_element(e: &BytesStart) -> Option<TableWidth> {
get_attribute_int(e, "w").map(|value| TableWidth {
value,
width_type: get_attribute(e, "type").unwrap_or_default(),
})
}
fn parse_table_look(e: &BytesStart) -> TableLook {
let mut look = TableLook::default();
let has_individual = get_attribute(e, "firstRow").is_some()
|| get_attribute(e, "lastRow").is_some()
|| get_attribute(e, "firstColumn").is_some()
|| get_attribute(e, "lastColumn").is_some()
|| get_attribute(e, "noHBand").is_some()
|| get_attribute(e, "noVBand").is_some();
if has_individual {
look.first_row = get_attribute(e, "firstRow").as_deref() == Some("1");
look.last_row = get_attribute(e, "lastRow").as_deref() == Some("1");
look.first_column = get_attribute(e, "firstColumn").as_deref() == Some("1");
look.last_column = get_attribute(e, "lastColumn").as_deref() == Some("1");
look.no_h_band = get_attribute(e, "noHBand").as_deref() == Some("1");
look.no_v_band = get_attribute(e, "noVBand").as_deref() == Some("1");
} else if let Some(val_str) = get_attribute(e, "val")
&& let Ok(mask) = i32::from_str_radix(&val_str, 16)
{
look.first_row = (mask & 0x0020) != 0;
look.last_row = (mask & 0x0040) != 0;
look.first_column = (mask & 0x0080) != 0;
look.last_column = (mask & 0x0100) != 0;
look.no_h_band = (mask & 0x0200) != 0;
look.no_v_band = (mask & 0x0400) != 0;
}
look
}
fn parse_vmerge(e: &BytesStart) -> VerticalMerge {
match get_attribute(e, "val") {
Some(val) if val == "restart" => VerticalMerge::Restart,
_ => VerticalMerge::Continue,
}
}
fn parse_cell_shading(e: &BytesStart) -> CellShading {
CellShading {
fill: get_attribute(e, "fill"),
color: get_attribute(e, "color"),
val: get_attribute(e, "val"),
}
}
fn parse_table_borders(reader: &mut Reader<&[u8]>) -> TableBorders {
let mut borders = TableBorders::default();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
borders.top = Some(parse_border_element(&e));
}
"bottom" => {
borders.bottom = Some(parse_border_element(&e));
}
"left" | "start" => {
borders.left = Some(parse_border_element(&e));
}
"right" | "end" => {
borders.right = Some(parse_border_element(&e));
}
"insideH" => {
borders.inside_h = Some(parse_border_element(&e));
}
"insideV" => {
borders.inside_v = Some(parse_border_element(&e));
}
_ => {}
}
buf.clear();
}
Ok(Event::Empty(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
borders.top = Some(parse_border_element(&e));
}
"bottom" => {
borders.bottom = Some(parse_border_element(&e));
}
"left" | "start" => {
borders.left = Some(parse_border_element(&e));
}
"right" | "end" => {
borders.right = Some(parse_border_element(&e));
}
"insideH" => {
borders.inside_h = Some(parse_border_element(&e));
}
"insideV" => {
borders.inside_v = Some(parse_border_element(&e));
}
_ => {}
}
buf.clear();
}
Ok(Event::End(e)) => {
if e.local_name().as_ref() == "tblBorders" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
borders
}
fn parse_cell_borders(reader: &mut Reader<&[u8]>) -> CellBorders {
let mut borders = CellBorders::default();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
borders.top = Some(parse_border_element(&e));
}
"bottom" => {
borders.bottom = Some(parse_border_element(&e));
}
"left" | "start" => {
borders.left = Some(parse_border_element(&e));
}
"right" | "end" => {
borders.right = Some(parse_border_element(&e));
}
_ => {}
}
buf.clear();
}
Ok(Event::Empty(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
borders.top = Some(parse_border_element(&e));
}
"bottom" => {
borders.bottom = Some(parse_border_element(&e));
}
"left" | "start" => {
borders.left = Some(parse_border_element(&e));
}
"right" | "end" => {
borders.right = Some(parse_border_element(&e));
}
_ => {}
}
buf.clear();
}
Ok(Event::End(e)) => {
if e.local_name().as_ref() == "tcBorders" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
borders
}
fn parse_cell_margins_element(reader: &mut Reader<&[u8]>) -> CellMargins {
let mut margins = CellMargins::default();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
margins.top = get_attribute_int(&e, "w");
}
"bottom" => {
margins.bottom = get_attribute_int(&e, "w");
}
"left" | "start" => {
margins.left = get_attribute_int(&e, "w");
}
"right" | "end" => {
margins.right = get_attribute_int(&e, "w");
}
_ => {}
}
buf.clear();
}
Ok(Event::Empty(e)) => {
let local_name = e.local_name();
match local_name.as_ref() {
"top" => {
margins.top = get_attribute_int(&e, "w");
}
"bottom" => {
margins.bottom = get_attribute_int(&e, "w");
}
"left" | "start" => {
margins.left = get_attribute_int(&e, "w");
}
"right" | "end" => {
margins.right = get_attribute_int(&e, "w");
}
_ => {}
}
buf.clear();
}
Ok(Event::End(e)) => {
if e.local_name().as_ref() == "tblCellMar" || e.local_name().as_ref() == "tcMar" {
break;
}
buf.clear();
}
Ok(Event::Eof) => break,
_ => {
buf.clear();
}
}
}
margins
}
fn get_attribute(e: &BytesStart, key: &str) -> Option<String> {
e.attributes()
.flatten()
.find(|attr| attr.key.local_name().as_ref() == key)
.and_then(|attr| {
let raw = attr.value.as_ref();
quick_xml::escape::unescape(raw).ok().map(|s| s.into_owned())
})
}
fn get_attribute_int(e: &BytesStart, key: &str) -> Option<i32> {
get_attribute(e, key).and_then(|s| s.parse().ok())
}
fn get_attribute_u32(e: &BytesStart, key: &str) -> Option<u32> {
get_attribute(e, key).and_then(|s| s.parse().ok())
}
#[cfg(test)]
mod tests;