use insta::assert_snapshot;
use xlsbye_core::rels::{Relationship, TargetMode};
use xlsbye_core::types::{
Alignment, Border, BorderSide, BorderStyle, Cell, CellValue, Color, ColorType, ColumnInfo,
DefinedName, DxfStyle, Fill, Font, FontScheme, HorizontalAlign, MergeCells, NumFmt,
PatternFill, PatternType, Protection, RangeRef, RichTextRun, RowInfo, SharedStringEntry,
SheetMeta, SheetType, SheetVisibility, Stylesheet, SuperSub, UnderlineStyle, VerticalAlign,
WorkbookMeta, Xf,
};
use crate::content_types::write_content_types;
use crate::rels::write_relationships;
use crate::shared_strings::write_shared_strings;
use crate::styles::write_styles;
use crate::workbook::write_workbook;
use crate::worksheet::{write_worksheet, SheetData, SheetRow};
#[test]
fn workbook_xml_snapshot() {
let meta = WorkbookMeta {
sheets: vec![
SheetMeta {
name: "Sheet1".to_string(),
sheet_id: 1,
rel_id: "rId1".to_string(),
state: SheetVisibility::Visible,
sheet_type: SheetType::Worksheet,
},
SheetMeta {
name: "Sheet2".to_string(),
sheet_id: 2,
rel_id: "rId2".to_string(),
state: SheetVisibility::Hidden,
sheet_type: SheetType::Worksheet,
},
],
formula_sheet_names: vec!["Sheet1".to_string(), "Sheet2".to_string()],
defined_names: vec![DefinedName {
name: "MyRange".to_string(),
formula: "Sheet1!$A$1:$B$10".to_string(),
sheet_index: Some(0),
hidden: false,
}],
has_vba: false,
date1904: false,
};
let mut out = Vec::new();
write_workbook(&mut out, &meta, false).expect("write workbook");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<workbookPr date1904="false"/>
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
<sheet name="Sheet2" sheetId="2" r:id="rId2" state="hidden"/>
</sheets>
<definedNames>
<definedName name="MyRange" localSheetId="0">Sheet1!$A$1:$B$10</definedName>
</definedNames>
</workbook>
"#);
}
#[test]
fn workbook_xml_skips_invalid_defined_names() {
let meta = WorkbookMeta {
sheets: vec![SheetMeta {
name: "Sheet1".to_string(),
sheet_id: 1,
rel_id: "rId1".to_string(),
state: SheetVisibility::Visible,
sheet_type: SheetType::Worksheet,
}],
formula_sheet_names: vec!["Sheet1".to_string()],
defined_names: vec![
DefinedName {
name: "ValidName".to_string(),
formula: "1".to_string(),
sheet_index: None,
hidden: false,
},
DefinedName {
name: "_xlfn.IFERROR".to_string(),
formula: "#NAME?".to_string(),
sheet_index: None,
hidden: true,
},
DefinedName {
name: "Rollforward".to_string(),
formula: " ".to_string(),
sheet_index: None,
hidden: true,
},
DefinedName {
name: "BadScope".to_string(),
formula: "1".to_string(),
sheet_index: Some(99),
hidden: false,
},
],
has_vba: false,
date1904: false,
};
let mut out = Vec::new();
write_workbook(&mut out, &meta, false).expect("write workbook");
let xml = String::from_utf8(out).expect("utf8");
assert!(xml.contains("ValidName"));
assert!(!xml.contains("_xlfn.IFERROR"));
assert!(!xml.contains("Rollforward"));
assert!(!xml.contains("BadScope"));
}
#[test]
fn styles_xml_snapshot() {
let stylesheet = Stylesheet {
num_fmts: vec![
NumFmt {
id: 14,
format_code: "mm-dd-yy".to_string(),
},
NumFmt {
id: 164,
format_code: "yyyy-mm-dd hh:mm:ss".to_string(),
},
],
fonts: vec![Font {
size_twips: 220,
bold: true,
italic: true,
underline: UnderlineStyle::Single,
strikethrough: true,
color: Color {
color_type: ColorType::Rgb(0xFF, 0x11, 0x22, 0x33),
},
name: "Calibri".to_string(),
family: 2,
charset: 1,
scheme: FontScheme::Minor,
superscript: SuperSub::Superscript,
}],
fills: vec![
Fill {
pattern: PatternFill::None,
},
Fill {
pattern: PatternFill::Pattern {
pattern_type: PatternType::Gray125,
fg_color: Color {
color_type: ColorType::Theme {
theme: 1,
tint: 0.25,
},
},
bg_color: Color {
color_type: ColorType::Indexed(64),
},
},
},
],
borders: vec![Border {
left: BorderSide {
style: BorderStyle::Thin,
color: Color {
color_type: ColorType::Auto,
},
},
right: BorderSide::default(),
top: BorderSide::default(),
bottom: BorderSide::default(),
diagonal: BorderSide::default(),
diagonal_down: false,
diagonal_up: false,
}],
cell_xfs: vec![Xf {
num_fmt_id: 164,
font_id: 0,
fill_id: 1,
border_id: 0,
xf_id: Some(0),
alignment: Alignment {
horizontal: HorizontalAlign::Center,
vertical: VerticalAlign::Top,
wrap_text: true,
shrink_to_fit: false,
text_rotation: 0,
indent: 0,
reading_order: 0,
},
protection: Protection {
locked: true,
hidden: false,
},
apply_number_format: true,
apply_font: true,
apply_fill: true,
apply_border: true,
apply_alignment: true,
apply_protection: true,
}],
cell_style_xfs: vec![Xf {
num_fmt_id: 0,
font_id: 0,
fill_id: 0,
border_id: 0,
xf_id: None,
alignment: Alignment::default(),
protection: Protection::default(),
apply_number_format: false,
apply_font: false,
apply_fill: false,
apply_border: false,
apply_alignment: false,
apply_protection: false,
}],
dxfs: vec![DxfStyle {
font: None,
fill: None,
border: None,
num_fmt: Some(NumFmt {
id: 170,
format_code: "0.0000".to_string(),
}),
alignment: Some(Alignment {
horizontal: HorizontalAlign::Right,
vertical: VerticalAlign::Bottom,
wrap_text: false,
shrink_to_fit: false,
text_rotation: 0,
indent: 0,
reading_order: 0,
}),
}],
};
let mut out = Vec::new();
write_styles(&mut out, &stylesheet).expect("write styles");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<numFmts count="1">
<numFmt numFmtId="164" formatCode="yyyy-mm-dd hh:mm:ss"/>
</numFmts>
<fonts count="1">
<font>
<b/>
<i/>
<strike/>
<u/>
<vertAlign val="superscript"/>
<sz val="11"/>
<color rgb="FF112233"/>
<name val="Calibri"/>
<family val="2"/>
<charset val="1"/>
<scheme val="minor"/>
</font>
</fonts>
<fills count="2">
<fill>
<patternFill patternType="none"/>
</fill>
<fill>
<patternFill patternType="gray125">
<fgColor theme="1" tint="0.25"/>
</patternFill>
</fill>
</fills>
<borders count="1">
<border>
<left style="thin">
<color auto="1"/>
</left>
<right/>
<top/>
<bottom/>
<diagonal/>
</border>
</borders>
<cellStyleXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
</cellStyleXfs>
<cellXfs count="1">
<xf numFmtId="164" fontId="0" fillId="1" borderId="0" xfId="0" applyNumberFormat="1" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1" applyProtection="1">
<alignment horizontal="center" vertical="top" wrapText="1"/>
<protection locked="1"/>
</xf>
</cellXfs>
<cellStyles count="1">
<cellStyle name="Normal" xfId="0" builtinId="0"/>
</cellStyles>
<dxfs count="1">
<dxf>
<numFmt numFmtId="170" formatCode="0.0000"/>
<alignment horizontal="right"/>
</dxf>
</dxfs>
<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleLight16"/>
</styleSheet>
"#);
}
#[test]
fn styles_xml_emits_all_cell_style_xfs_as_cell_styles() {
let stylesheet = Stylesheet {
num_fmts: Vec::new(),
fonts: Vec::new(),
fills: Vec::new(),
borders: Vec::new(),
cell_xfs: Vec::new(),
cell_style_xfs: vec![
Xf {
num_fmt_id: 0,
font_id: 0,
fill_id: 0,
border_id: 0,
xf_id: None,
alignment: Alignment::default(),
protection: Protection::default(),
apply_number_format: false,
apply_font: false,
apply_fill: false,
apply_border: false,
apply_alignment: false,
apply_protection: false,
},
Xf {
num_fmt_id: 0,
font_id: 0,
fill_id: 0,
border_id: 0,
xf_id: None,
alignment: Alignment::default(),
protection: Protection::default(),
apply_number_format: false,
apply_font: false,
apply_fill: false,
apply_border: false,
apply_alignment: false,
apply_protection: false,
},
],
dxfs: Vec::new(),
};
let mut out = Vec::new();
write_styles(&mut out, &stylesheet).expect("write styles");
let xml = String::from_utf8(out).expect("utf8");
assert!(xml.contains("<cellStyles count=\"2\">"));
assert!(xml.contains("<cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\"/>"));
assert!(xml.contains("<cellStyle name=\"Style 1\" xfId=\"1\"/>"));
}
#[test]
fn shared_strings_xml_snapshot() {
let sst = vec![
SharedStringEntry::Plain("Plain text".to_string()),
SharedStringEntry::Rich(vec![
RichTextRun {
font_index: Some(1),
text: "Bold".to_string(),
},
RichTextRun {
font_index: None,
text: " normal".to_string(),
},
]),
];
let mut out = Vec::new();
write_shared_strings(&mut out, &sst, 10, 5).expect("write shared strings");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="10" uniqueCount="5">
<si>
<t>Plain text</t>
</si>
<si>
<r>
<rPr>
<rFont val="font1"/>
</rPr>
<t>Bold</t>
</r>
<r>
<t xml:space="preserve"> normal</t>
</r>
</si>
</sst>
"#);
}
#[test]
fn worksheet_xml_snapshot() {
let row1 = SheetRow {
info: RowInfo {
row: 1,
height: 15.0,
style_index: 0,
hidden: false,
custom_height: true,
outline_level: 0,
collapsed: false,
thick_top: false,
thick_bottom: false,
},
cells: vec![
Cell {
col: 1,
style_index: 0,
value: CellValue::SharedString(0),
formula: None,
shared_formula_index: None,
shared_formula_ref: None,
},
Cell {
col: 2,
style_index: 1,
value: CellValue::Number(42.5),
formula: None,
shared_formula_index: None,
shared_formula_ref: None,
},
Cell {
col: 3,
style_index: 2,
value: CellValue::String("42.5".to_string()),
formula: Some("SUM(A1:B1)".to_string()),
shared_formula_index: None,
shared_formula_ref: None,
},
Cell {
col: 4,
style_index: 0,
value: CellValue::Bool(true),
formula: None,
shared_formula_index: None,
shared_formula_ref: None,
},
Cell {
col: 5,
style_index: 0,
value: CellValue::Error(xlsbye_core::types::CellError::Ref),
formula: None,
shared_formula_index: None,
shared_formula_ref: None,
},
],
};
let sheet_data = SheetData {
dimension: Some(RangeRef {
first_row: 1,
first_col: 1,
last_row: 10,
last_col: 5,
}),
sheet_views: Vec::new(),
sheet_format: Some(xlsbye_core::types::SheetFormatInfo {
default_row_height: 15.0,
outline_level_row: 0,
outline_level_col: 0,
}),
columns: vec![ColumnInfo {
min: 1,
max: 1,
width: 15.0,
style_index: 0,
hidden: false,
best_fit: false,
custom_width: true,
outline_level: 0,
collapsed: false,
}],
rows: vec![row1],
merge_cells: vec![xlsbye_core::types::MergeCell {
range: RangeRef {
first_row: 1,
first_col: 1,
last_row: 1,
last_col: 3,
},
}]
.into(),
};
let mut out = Vec::new();
write_worksheet(&mut out, &sheet_data).expect("write worksheet");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
<dimension ref="A1:E10"/>
<sheetFormatPr defaultRowHeight="15" x14ac:dyDescent="0.2"/>
<cols>
<col min="1" max="1" width="15" customWidth="1"/>
</cols>
<sheetData>
<row r="1" spans="1:5" ht="15" customHeight="1" x14ac:dyDescent="0.2">
<c r="A1" s="0" t="s">
<v>0</v>
</c>
<c r="B1" s="1">
<v>42.5</v>
</c>
<c r="C1" s="2" t="str">
<f>SUM(A1:B1)</f>
<v>42.5</v>
</c>
<c r="D1" s="0" t="b">
<v>1</v>
</c>
<c r="E1" s="0" t="e">
<v>#REF!</v>
</c>
</row>
</sheetData>
<mergeCells count="1">
<mergeCell ref="A1:C1"/>
</mergeCells>
</worksheet>
"#);
}
#[test]
fn worksheet_xml_writes_shared_formula_cells() {
let sheet_data = SheetData {
dimension: Some(RangeRef {
first_row: 6,
first_col: 4,
last_row: 7,
last_col: 4,
}),
sheet_views: Vec::new(),
sheet_format: Some(xlsbye_core::types::SheetFormatInfo {
default_row_height: 15.0,
outline_level_row: 0,
outline_level_col: 0,
}),
columns: Vec::new(),
rows: vec![
SheetRow {
info: RowInfo {
row: 6,
height: 15.0,
style_index: 0,
hidden: false,
custom_height: false,
outline_level: 0,
collapsed: false,
thick_top: false,
thick_bottom: false,
},
cells: vec![Cell {
col: 4,
style_index: 368,
value: CellValue::Number(-0.039137319960787176),
formula: Some("IFERROR(B6/C6-1,\"n.a.\")".to_string()),
shared_formula_index: Some(0),
shared_formula_ref: Some(RangeRef {
first_row: 6,
first_col: 4,
last_row: 7,
last_col: 4,
}),
}],
},
SheetRow {
info: RowInfo {
row: 7,
height: 15.0,
style_index: 0,
hidden: false,
custom_height: false,
outline_level: 0,
collapsed: false,
thick_top: false,
thick_bottom: false,
},
cells: vec![Cell {
col: 4,
style_index: 371,
value: CellValue::Number(-0.06893622586073733),
formula: None,
shared_formula_index: Some(0),
shared_formula_ref: None,
}],
},
],
merge_cells: MergeCells::new(),
};
let mut out = Vec::new();
write_worksheet(&mut out, &sheet_data).expect("write worksheet");
let xml = String::from_utf8(out).expect("utf8");
assert!(xml.contains("<f t=\"shared\" si=\"0\" ref=\"D6:D7\">IFERROR(B6/C6-1,"n.a.")</f>"));
assert!(xml.contains("<f t=\"shared\" si=\"0\"/>"));
}
#[test]
fn rels_xml_snapshot() {
let rels = vec![
Relationship {
id: "rId1".to_string(),
rel_type:
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
.to_string(),
target: "worksheets/sheet1.xml".to_string(),
target_mode: TargetMode::Internal,
},
Relationship {
id: "rId2".to_string(),
rel_type:
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
.to_string(),
target: "https://example.com".to_string(),
target_mode: TargetMode::External,
},
];
let mut out = Vec::new();
write_relationships(&mut out, &rels).expect("write rels");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="https://example.com" TargetMode="External"/>
</Relationships>
"#);
}
#[test]
fn content_types_xml_snapshot() {
let defaults = vec![
(
"rels".to_string(),
"application/vnd.openxmlformats-package.relationships+xml".to_string(),
),
("xml".to_string(), "application/xml".to_string()),
];
let overrides = vec![
(
"/xl/workbook.xml".to_string(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
.to_string(),
),
(
"/xl/worksheets/sheet1.xml".to_string(),
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
.to_string(),
),
];
let mut out = Vec::new();
write_content_types(&mut out, &defaults, &overrides).expect("write content types");
let xml = String::from_utf8(out).expect("utf8");
assert_snapshot!(&xml, @r#"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
</Types>
"#);
}