use pptx_parse::{GraphicFrameData, ShapeNode, Table, parse_pptx};
const FIXTURE: &[u8] = include_bytes!("../../pptx-render/tests/fixtures/table-basic.pptx");
fn table() -> Table {
let package = parse_pptx(FIXTURE).unwrap();
let ShapeNode::GraphicFrame(frame) = &package.slides[0].shapes[0] else {
panic!("expected a graphic frame");
};
let GraphicFrameData::Table(table) = &frame.data else {
panic!("expected a table");
};
table.clone()
}
#[test]
fn parses_table_cell_picture_fills_with_relationships_and_insets() {
let package = parse_pptx(include_bytes!(
"../../pptx-render/tests/fixtures/table-picture-fill.pptx"
))
.unwrap();
let ShapeNode::GraphicFrame(frame) = &package.slides[0].shapes[0] else {
panic!("expected a table frame")
};
let GraphicFrameData::Table(table) = &frame.data else {
panic!("expected a table")
};
let cell = serde_json::to_value(&table.rows[0].cells[0]).unwrap();
let picture = &cell["pictureFill"];
assert_eq!(picture["relationshipId"], "rId2");
assert_eq!(picture["mediaPartPath"], "ppt/media/image1.png");
assert_eq!(
picture["fillRect"],
serde_json::json!({"left": -142558, "top": -214634, "right": -101860, "bottom": -1946341})
);
assert_eq!(
picture["effects"],
serde_json::json!([{"type": "grayscale"}])
);
let json = serde_json::to_string(table).unwrap();
assert_eq!(serde_json::from_str::<Table>(&json).unwrap(), *table);
assert!(
package
.media
.iter()
.any(|part| part.part_path == "ppt/media/image1.png")
);
}
#[test]
fn a_picture_only_cell_keeps_its_full_json_encoding() {
let mut value = serde_json::to_value(pptx_parse::TableCell::default()).unwrap();
value["pictureFill"] = serde_json::json!({"mediaPartPath": "ppt/media/image1.png"});
let cell = serde_json::from_value(value).unwrap();
let table = Table {
rows: vec![pptx_parse::TableRow {
cells: vec![cell],
..Default::default()
}],
..Default::default()
};
let encoded = serde_json::to_value(&table).unwrap();
assert_eq!(
encoded["rows"][0]["cells"][0]["pictureFill"]["mediaPartPath"],
"ppt/media/image1.png"
);
}
#[test]
fn parses_the_grid_row_heights_and_table_properties() {
let table = table();
assert_eq!(table.grid, [1_828_800, 1_219_200, 2_667_000]);
assert_eq!(
table.rows.iter().map(|row| row.height).collect::<Vec<_>>(),
[370_840, 370_840, 370_840]
);
assert!(table.properties.first_row);
assert!(table.properties.band_row);
assert!(!table.properties.last_row);
assert!(!table.properties.first_col);
assert!(!table.properties.band_col);
assert_eq!(
table.properties.style_id.as_deref(),
Some("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}")
);
}
#[test]
fn parses_spans_and_their_continuation_cells_in_place() {
let table = table();
let text = |row: usize, cell: usize| {
table.rows[row].cells[cell].text.paragraphs[0].runs[0]
.text
.clone()
};
assert_eq!(
table
.rows
.iter()
.map(|row| row.cells.len())
.collect::<Vec<_>>(),
[3, 3, 3]
);
assert_eq!(text(0, 0), "Header spans two");
assert_eq!(table.rows[0].cells[0].grid_span, 2);
assert!(!table.rows[0].cells[0].merged);
assert!(table.rows[0].cells[1].merged);
assert_eq!(table.rows[0].cells[1].grid_span, 1);
assert_eq!(text(0, 2), "Third");
assert_eq!(text(1, 0), "Tall");
assert_eq!(table.rows[1].cells[0].row_span, 2);
assert!(table.rows[2].cells[0].merged);
assert_eq!(text(2, 1), "Below");
}
#[test]
fn parses_direct_cell_fills_and_borders() {
let table = table();
let fill = table.rows[1].cells[0].fill.as_ref().unwrap();
assert_eq!(fill.fill_type, "solid");
assert_eq!(fill.color.as_ref().unwrap().rgb.as_deref(), Some("DDEBF7"));
assert!(table.rows[1].cells[1].fill.is_none());
let bottom = table.rows[1].cells[2].borders.bottom.as_ref().unwrap();
assert_eq!(bottom.width, Some(19_050.0));
assert_eq!(
bottom.color.as_ref().unwrap().rgb.as_deref(),
Some("C00000")
);
assert!(table.rows[1].cells[2].borders.left.is_none());
assert!(table.rows[1].cells[1].borders.is_empty());
}
#[test]
fn folds_cell_properties_into_the_cell_text_body() {
let table = table();
let text = &table.rows[0].cells[0].text;
assert_eq!(text.anchor.as_deref(), Some("ctr"));
assert_eq!(text.inset_left, Some(91_440));
assert_eq!(text.inset_right, Some(91_440));
assert_eq!(text.inset_top, None);
assert_eq!(text.vertical, None);
}
#[test]
fn a_text_only_table_round_trips_through_the_released_json() {
let legacy = r#"{"type":"table","rows":[[{"anchor":"ctr","vertical":null,"autofit":null,"insetLeft":null,"insetTop":null,"insetRight":null,"insetBottom":null,"paragraphs":[]}]]}"#;
let data: GraphicFrameData = serde_json::from_str(legacy).unwrap();
let GraphicFrameData::Table(stored) = &data else {
panic!("expected a table");
};
assert!(stored.grid.is_empty());
assert_eq!(stored.rows[0].cells[0].grid_span, 1);
assert_eq!(stored.rows[0].cells[0].text.anchor.as_deref(), Some("ctr"));
assert_eq!(serde_json::to_string(&data).unwrap(), legacy);
let parsed = serde_json::to_string(&GraphicFrameData::Table(table())).unwrap();
assert!(parsed.contains("\"grid\":[1828800,1219200,2667000]"));
assert!(parsed.contains("\"gridSpan\":2"));
}
#[test]
fn a_row_or_cell_carrying_anything_beyond_text_keeps_the_full_encoding() {
let text = pptx_parse::TextBody::default();
let borders = pptx_parse::TableCellBorders {
bottom: Some(ooxml_drawingml::ShapeOutline::default()),
..pptx_parse::TableCellBorders::default()
};
for (height, cell) in [
(370_840, pptx_parse::TableCell::from_text(text.clone())),
(
0,
pptx_parse::TableCell {
grid_span: 2,
..pptx_parse::TableCell::from_text(text.clone())
},
),
(
0,
pptx_parse::TableCell {
row_span: 2,
..pptx_parse::TableCell::from_text(text.clone())
},
),
(
0,
pptx_parse::TableCell {
merged: true,
..pptx_parse::TableCell::from_text(text.clone())
},
),
(
0,
pptx_parse::TableCell {
fill: Some(ooxml_drawingml::ShapeFill::named("solid")),
..pptx_parse::TableCell::from_text(text.clone())
},
),
(
0,
pptx_parse::TableCell {
borders,
..pptx_parse::TableCell::from_text(text.clone())
},
),
] {
let data = GraphicFrameData::Table(pptx_parse::Table {
rows: vec![pptx_parse::TableRow {
height,
cells: vec![cell],
}],
..pptx_parse::Table::default()
});
let json = serde_json::to_string(&data).unwrap();
assert!(
json.contains("\"cells\""),
"a row or cell with formatting must not serialize as a bare text list: {json}"
);
assert_eq!(
serde_json::from_str::<GraphicFrameData>(&json).unwrap(),
data
);
}
}