cellrune 0.1.18

Bounded XLSX/XLSM reading, deterministic calculation, editing, and writing for Rust
Documentation
use std::collections::BTreeSet;

use super::{remove_calculation_chain_relationship, remove_content_type_overrides};
use crate::xlsx::package::PartPath;
use crate::{WriteLimits, XlsxWriteErrorCode};

fn part(value: &[u8]) -> PartPath {
    PartPath::from_archive_name(value).expect("valid package part")
}

#[test]
fn non_empty_calculation_chain_relationship_is_removed() {
    let relationships = br#"<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain" Target="calcChain.xml"><extension/></Relationship><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>"#;
    let relationship_part = part(b"xl/_rels/workbook.xml.rels");
    let workbook_part = part(b"xl/workbook.xml");
    let patch = remove_calculation_chain_relationship(
        relationships,
        &relationship_part,
        &workbook_part,
        WriteLimits::default(),
    )
    .expect("relationship patch");
    assert_eq!(
        patch.removed_parts,
        BTreeSet::from([part(b"xl/calcChain.xml")])
    );
    let output = String::from_utf8(patch.relationship_bytes.expect("rewritten relationships"))
        .expect("UTF-8 XML");
    assert!(!output.contains("calcChain"));
    assert!(output.contains("worksheets/sheet1.xml"));
}

#[test]
fn strict_calculation_chain_relationship_is_removed() {
    let relationships = br#"<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://purl.oclc.org/ooxml/officeDocument/relationships/calcChain" Target="calcChain.xml"/></Relationships>"#;
    let relationship_part = part(b"xl/_rels/workbook.xml.rels");
    let workbook_part = part(b"xl/workbook.xml");

    let patch = remove_calculation_chain_relationship(
        relationships,
        &relationship_part,
        &workbook_part,
        WriteLimits::default(),
    )
    .expect("strict relationship patch");

    assert_eq!(
        patch.removed_parts,
        BTreeSet::from([part(b"xl/calcChain.xml")])
    );
}

#[test]
fn custom_external_and_nested_calc_chain_lookalikes_are_preserved() {
    let relationships = br#"<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="https://example.test/custom/calcChain" Target="custom.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain" Target="https://example.test/calcChain.xml" TargetMode="External"/><Extension><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain" Target="nested.xml"/></Extension></Relationships>"#;
    let relationship_part = part(b"xl/_rels/workbook.xml.rels");
    let workbook_part = part(b"xl/workbook.xml");

    let patch = remove_calculation_chain_relationship(
        relationships,
        &relationship_part,
        &workbook_part,
        WriteLimits::default(),
    )
    .expect("lookalike relationships");

    assert!(patch.removed_parts.is_empty());
    assert!(patch.relationship_bytes.is_none());
}

#[test]
fn content_type_count_accepts_the_exact_limit_and_rejects_one_less() {
    let content_types = br#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override PartName="/xl/workbook.xml" ContentType="workbook"/><Override PartName="/xl/calcChain.xml" ContentType="calc-chain"/></Types>"#;
    let content_types_part = part(b"[Content_Types].xml");
    let removals = BTreeSet::from([part(b"xl/calcChain.xml")]);
    let exact = WriteLimits::default()
        .with_max_content_types(2)
        .expect("positive exact limit");
    let output =
        remove_content_type_overrides(content_types, &content_types_part, &removals, exact)
            .expect("exact declaration count is valid");
    assert!(
        !String::from_utf8(output)
            .expect("UTF-8 XML")
            .contains("calcChain")
    );

    let below = WriteLimits::default()
        .with_max_content_types(1)
        .expect("positive lower limit");
    let error = remove_content_type_overrides(content_types, &content_types_part, &removals, below)
        .expect_err("declarations exceed lower limit");
    assert_eq!(error.code(), XlsxWriteErrorCode::ResourceLimitExceeded);
}