Skip to content

Commit

Permalink
cond format: add duplicate/unique conditional format
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Nov 9, 2023
1 parent 53fc027 commit 48bec62
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 14 deletions.
55 changes: 44 additions & 11 deletions examples/app_conditional_formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
//! cells based on certain criteria.
use rust_xlsxwriter::{
ConditionalFormatCell, ConditionalFormatCellCriteria, Format, Workbook, XlsxError,
ConditionalFormatCell, ConditionalFormatCellCriteria, ConditionalFormatDuplicate, Format,
Workbook, XlsxError,
};

fn main() -> Result<(), XlsxError> {
Expand All @@ -28,16 +29,16 @@ fn main() -> Result<(), XlsxError> {

// some sample data to run the conditional formatting against.
let data = [
[90, 80, 50, 10, 20, 90, 40, 90, 30, 40],
[20, 10, 90, 100, 30, 60, 70, 60, 50, 90],
[10, 50, 60, 50, 20, 50, 80, 30, 40, 60],
[10, 90, 20, 40, 10, 40, 50, 70, 90, 50],
[70, 100, 10, 90, 10, 10, 20, 100, 100, 40],
[20, 60, 10, 100, 30, 10, 20, 60, 100, 10],
[10, 60, 10, 80, 100, 80, 30, 30, 70, 40],
[30, 90, 60, 10, 10, 100, 40, 40, 30, 40],
[80, 90, 10, 20, 20, 50, 80, 20, 60, 90],
[60, 80, 30, 30, 10, 50, 80, 60, 50, 30],
[34, 72, 38, 30, 75, 48, 75, 66, 84, 86],
[6, 24, 1, 84, 54, 62, 60, 3, 26, 59],
[28, 79, 97, 13, 85, 93, 93, 22, 5, 14],
[27, 71, 40, 17, 18, 79, 90, 93, 29, 47],
[88, 25, 33, 23, 67, 1, 59, 79, 47, 36],
[24, 100, 20, 88, 29, 33, 38, 54, 54, 88],
[6, 57, 88, 28, 10, 26, 37, 7, 41, 48],
[52, 78, 1, 96, 26, 45, 47, 33, 96, 36],
[60, 54, 81, 66, 81, 90, 80, 93, 12, 55],
[70, 5, 46, 14, 71, 19, 66, 36, 41, 21],
];

// -----------------------------------------------------------------------
Expand Down Expand Up @@ -113,6 +114,38 @@ fn main() -> Result<(), XlsxError> {

worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;

// -----------------------------------------------------------------------
// Example 3. Duplicate and Unique conditional formats.
// -----------------------------------------------------------------------
let caption = "Duplicate values are in light red. Unique values are in light green.";

// Add a worksheet to the workbook.
let worksheet = workbook.add_worksheet();

// Write the caption.
worksheet.write(0, 1, caption)?;

// Write the worksheet data.
worksheet.write_row_matrix(2, 1, data)?;

// Set the column widths for clarity.
for col_num in 1..=10u16 {
worksheet.set_column_width(col_num, 6)?;
}

// Write a conditional format over a range.
let conditional_format = ConditionalFormatDuplicate::new().set_format(&format1);

worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;

// Invert the duplicate conditional format to show uniques values in the
// same range.
let conditional_format = ConditionalFormatDuplicate::new()
.invert()
.set_format(&format2);

worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;

// -----------------------------------------------------------------------
// Save and close the file.
// -----------------------------------------------------------------------
Expand Down
62 changes: 62 additions & 0 deletions examples/doc_conditional_format_duplicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2023, John McNamara, [email protected]

//! Example of how to add a duplicate/unique conditional formatting to a
//! worksheet. Duplicate values are in light red. Unique values are in light
//! green. Note, that we invert the Duplicate rule to get Unique values.
use rust_xlsxwriter::{ConditionalFormatDuplicate, Format, Workbook, XlsxError};

fn main() -> Result<(), XlsxError> {
// Create a new Excel file object.
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

// Add some sample data.
let data = [
[34, 72, 38, 30, 75, 48, 75, 66, 84, 86],
[6, 24, 1, 84, 54, 62, 60, 3, 26, 59],
[28, 79, 97, 13, 85, 93, 93, 22, 5, 14],
[27, 71, 40, 17, 18, 79, 90, 93, 29, 47],
[88, 25, 33, 23, 67, 1, 59, 79, 47, 36],
[24, 100, 20, 88, 29, 33, 38, 54, 54, 88],
[6, 57, 88, 28, 10, 26, 37, 7, 41, 48],
[52, 78, 1, 96, 26, 45, 47, 33, 96, 36],
[60, 54, 81, 66, 81, 90, 80, 93, 12, 55],
[70, 5, 46, 14, 71, 19, 66, 36, 41, 21],
];
worksheet.write_row_matrix(2, 1, data)?;

// Set the column widths for clarity.
for col_num in 1..=10u16 {
worksheet.set_column_width(col_num, 6)?;
}

// Add a format. Light red fill with dark red text.
let format1 = Format::new()
.set_font_color("9C0006")
.set_background_color("FFC7CE");

// Add a format. Green fill with dark green text.
let format2 = Format::new()
.set_font_color("006100")
.set_background_color("C6EFCE");

// Write a conditional format over a range.
let conditional_format = ConditionalFormatDuplicate::new().set_format(format1);

worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;

// Invert the duplicate conditional format to show uniques values.
let conditional_format = ConditionalFormatDuplicate::new()
.invert()
.set_format(format2);

worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;

// Save the file.
workbook.save("conditional_format.xlsx")?;

Ok(())
}
154 changes: 152 additions & 2 deletions src/conditional_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ macro_rules! generate_conditional_format_impls {
}
)*)
}
generate_conditional_format_impls!(ConditionalFormatCell);
generate_conditional_format_impls!(ConditionalFormatCell ConditionalFormatDuplicate);

// -----------------------------------------------------------------------
// ConditionalFormatCell
Expand Down Expand Up @@ -498,6 +498,156 @@ impl ConditionalFormatCell {
}
}

// -----------------------------------------------------------------------
// ConditionalFormatDuplicate
// -----------------------------------------------------------------------

/// The `ConditionalFormatDuplicate` struct represents a Duplicate/Unique
/// conditional format.
///
/// `ConditionalFormatDuplicate` is used to represent a Duplicate or Unique
/// style conditional format in Excel. Duplicate conditional formats show
/// duplicated values in a range while Unique conditional formats show unique
/// values.
///
/// <img
/// src="https://rustxlsxwriter.github.io/images/conditional_format_duplicate_intro.png">
///
///
/// # Examples
///
/// Example of how to add a duplicate/unique conditional formatting to a
/// worksheet. Duplicate values are in light red. Unique values are in light
/// green. Note, that we invert the Duplicate rule to get Unique values.
///
/// ```
/// # // This code is available in examples/doc_conditional_format_duplicate.rs
/// #
/// # use rust_xlsxwriter::{ConditionalFormatDuplicate, Format, Workbook, XlsxError};
/// #
/// # fn main() -> Result<(), XlsxError> {
/// # // Create a new Excel file object.
/// # let mut workbook = Workbook::new();
/// # let worksheet = workbook.add_worksheet();
/// #
/// # // Add some sample data.
/// # let data = [
/// # [34, 72, 38, 30, 75, 48, 75, 66, 84, 86],
/// # [6, 24, 1, 84, 54, 62, 60, 3, 26, 59],
/// # [28, 79, 97, 13, 85, 93, 93, 22, 5, 14],
/// # [27, 71, 40, 17, 18, 79, 90, 93, 29, 47],
/// # [88, 25, 33, 23, 67, 1, 59, 79, 47, 36],
/// # [24, 100, 20, 88, 29, 33, 38, 54, 54, 88],
/// # [6, 57, 88, 28, 10, 26, 37, 7, 41, 48],
/// # [52, 78, 1, 96, 26, 45, 47, 33, 96, 36],
/// # [60, 54, 81, 66, 81, 90, 80, 93, 12, 55],
/// # [70, 5, 46, 14, 71, 19, 66, 36, 41, 21],
/// # ];
/// # worksheet.write_row_matrix(2, 1, data)?;
/// #
/// # // Set the column widths for clarity.
/// # for col_num in 1..=10u16 {
/// # worksheet.set_column_width(col_num, 6)?;
/// # }
/// #
/// # // Add a format. Light red fill with dark red text.
/// # let format1 = Format::new()
/// # .set_font_color("9C0006")
/// # .set_background_color("FFC7CE");
/// #
/// # // Add a format. Green fill with dark green text.
/// # let format2 = Format::new()
/// # .set_font_color("006100")
/// # .set_background_color("C6EFCE");
/// #
/// // Write a conditional format over a range.
/// let conditional_format = ConditionalFormatDuplicate::new().set_format(format1);
///
/// worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;
///
/// // Invert the duplicate conditional format to show uniques values.
/// let conditional_format = ConditionalFormatDuplicate::new()
/// .invert()
/// .set_format(format2);
///
/// worksheet.add_conditional_format(2, 1, 11, 10, &conditional_format)?;
///
/// # // Save the file.
/// # workbook.save("conditional_format.xlsx")?;
/// #
/// # Ok(())
/// # }
/// ```
///
/// Output file:
///
/// <img src="https://rustxlsxwriter.github.io/images/conditional_format_duplicate.png">
///
#[derive(Clone)]
pub struct ConditionalFormatDuplicate {
is_unique: bool,
multi_range: String,
stop_if_true: bool,
pub(crate) format: Option<Format>,
}

/// The following methods are specific to `ConditionalFormatDuplicate`.
impl ConditionalFormatDuplicate {
/// Create a new Duplicate conditional format struct.
#[allow(clippy::new_without_default)]
pub fn new() -> ConditionalFormatDuplicate {
ConditionalFormatDuplicate {
is_unique: false,
multi_range: String::new(),
stop_if_true: false,
format: None,
}
}

/// Invert the functionality of the conditional format to get unique values
/// instead of duplicate values.
///
/// See the example above.
///
pub fn invert(mut self) -> ConditionalFormatDuplicate {
self.is_unique = true;
self
}

// Validate the conditional format.
#[allow(clippy::unnecessary_wraps)]
#[allow(clippy::unused_self)]
pub(crate) fn validate(&self) -> Result<(), XlsxError> {
Ok(())
}

// Return the conditional format rule as an XML string.
pub(crate) fn get_rule_string(&self, dxf_index: Option<u32>, priority: u32) -> String {
let mut writer = XMLWriter::new();
let mut attributes = vec![];

if self.is_unique {
attributes.push(("type", "uniqueValues".to_string()));
} else {
attributes.push(("type", "duplicateValues".to_string()));
}

if let Some(dxf_index) = dxf_index {
attributes.push(("dxfId", dxf_index.to_string()));
}

attributes.push(("priority", priority.to_string()));

if self.stop_if_true {
attributes.push(("stopIfTrue", "1".to_string()));
}

writer.xml_empty_tag("cfRule", &attributes);

writer.read_to_string()
}
}

// -----------------------------------------------------------------------
// ConditionalFormatValue
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -746,4 +896,4 @@ macro_rules! generate_conditional_common_methods {
}
)*)
}
generate_conditional_common_methods!(ConditionalFormatCell);
generate_conditional_common_methods!(ConditionalFormatCell ConditionalFormatDuplicate);
67 changes: 67 additions & 0 deletions src/conditional_format/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod conditional_format_tests {
use crate::worksheet::*;
use crate::ConditionalFormatCell;
use crate::ConditionalFormatCellCriteria;
use crate::ConditionalFormatDuplicate;
use crate::ExcelDateTime;
use crate::Formula;
use crate::XlsxError;
Expand Down Expand Up @@ -285,6 +286,72 @@ mod conditional_format_tests {
Ok(())
}

#[test]
fn conditional_format_04() -> Result<(), XlsxError> {
let mut worksheet = Worksheet::new();
worksheet.set_selected(true);

worksheet.write(0, 0, 10)?;
worksheet.write(1, 0, 20)?;
worksheet.write(2, 0, 30)?;
worksheet.write(3, 0, 40)?;

let conditional_format = ConditionalFormatDuplicate::new();
worksheet.add_conditional_format(0, 0, 3, 0, &conditional_format)?;

let conditional_format = ConditionalFormatDuplicate::new().invert();
worksheet.add_conditional_format(0, 0, 3, 0, &conditional_format)?;

worksheet.assemble_xml_file();

let got = worksheet.writer.read_to_str();
let got = xml_to_vec(got);

let expected = xml_to_vec(
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">
<dimension ref="A1:A4"/>
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0"/>
</sheetViews>
<sheetFormatPr defaultRowHeight="15"/>
<sheetData>
<row r="1" spans="1:1">
<c r="A1">
<v>10</v>
</c>
</row>
<row r="2" spans="1:1">
<c r="A2">
<v>20</v>
</c>
</row>
<row r="3" spans="1:1">
<c r="A3">
<v>30</v>
</c>
</row>
<row r="4" spans="1:1">
<c r="A4">
<v>40</v>
</c>
</row>
</sheetData>
<conditionalFormatting sqref="A1:A4">
<cfRule type="duplicateValues" priority="1"/>
<cfRule type="uniqueValues" priority="2"/>
</conditionalFormatting>
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
</worksheet>
"#,
);

assert_eq!(expected, got);

Ok(())
}

#[test]
fn conditional_format_10() -> Result<(), XlsxError> {
let mut worksheet = Worksheet::new();
Expand Down
Loading

0 comments on commit 48bec62

Please sign in to comment.