use crate::com::dispatch::Dispatch;
use crate::com::variant::Variant;
use crate::config::Config;
use crate::error::OaResult;
use crate::shapes::inventory::SlideInventory;
use crate::utils::color::hex_to_bgr;
pub fn parse_numeric(text: &str) -> Option<f64> {
let mut s = text.trim().to_string();
if s.ends_with('%') {
s.pop();
s = s.trim().to_string();
}
if s.starts_with('+') {
s.remove(0);
s = s.trim().to_string();
}
s.parse::<f64>().ok()
}
pub fn sign_category(value: f64) -> SignCategory {
if value > 0.0 {
SignCategory::Positive
} else if value < 0.0 {
SignCategory::Negative
} else {
SignCategory::Neutral
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignCategory {
Positive,
Negative,
Neutral,
}
pub fn apply_color_coding(
inventory: &SlideInventory,
config: &Config,
) -> OaResult<usize> {
if inventory.ccst_tables.is_empty() {
return Ok(0);
}
let positive_color = hex_to_bgr(&config.ccst.positive_color);
let negative_color = hex_to_bgr(&config.ccst.negative_color);
let neutral_color = hex_to_bgr(&config.ccst.neutral_color);
let positive_prefix = &config.ccst.positive_prefix;
let symbol_removal = &config.ccst.symbol_removal;
let mut total_tables = 0;
for shape_ref in &inventory.ccst_tables {
let mut shape = shape_ref.dispatch.clone();
let mut table = match shape.get("Table") {
Ok(v) => match v.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => continue,
},
Err(_) => continue,
};
let rows = table.get("Rows")
.and_then(|v| v.as_dispatch().map_err(|e| e))
.and_then(|d| Dispatch::new(d).get("Count"))
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
let cols = table.get("Columns")
.and_then(|v| v.as_dispatch().map_err(|e| e))
.and_then(|d| Dispatch::new(d).get("Count"))
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
for row in 1..=rows {
for col in 1..=cols {
let cell_variant = match table.call("Cell", &[Variant::from(row), Variant::from(col)]) {
Ok(v) => v,
Err(_) => continue,
};
let mut cell = match cell_variant.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => continue,
};
let mut cell_shape = match cell.get("Shape") {
Ok(v) => match v.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => continue,
},
Err(_) => continue,
};
let cell_text = cell_shape.nav("TextFrame.TextRange")
.and_then(|mut tr| tr.get("Text"))
.and_then(|v| v.as_string().map_err(|e| e))
.unwrap_or_default()
.trim()
.to_string();
let parsed = parse_numeric(&cell_text);
let (color, final_text) = match parsed {
Some(value) => {
let category = sign_category(value);
let color = match category {
SignCategory::Positive => positive_color,
SignCategory::Negative => negative_color,
SignCategory::Neutral => neutral_color,
};
let mut text = cell_text.clone();
if category == SignCategory::Positive
&& !positive_prefix.is_empty()
&& !text.starts_with(positive_prefix.as_str())
{
text = format!("{positive_prefix}{text}");
}
if !symbol_removal.is_empty() {
if symbol_removal.contains('%') && text.ends_with('%') {
text.pop(); }
if symbol_removal.contains('+') && text.starts_with('+') {
text.remove(0);
}
if symbol_removal.contains('-') && text.starts_with('-') {
text.remove(0);
}
}
(color, text)
}
None => {
(neutral_color, cell_text.clone())
}
};
if final_text != cell_text {
let _ = cell_shape.nav("TextFrame.TextRange")
.and_then(|mut tr| tr.put("Text", Variant::from(final_text.as_str())).map_err(|e| e));
}
let _ = cell_shape.nav("TextFrame.TextRange.Font.Color")
.and_then(|mut fc| fc.put("RGB", Variant::from(color)).map_err(|e| e));
}
}
total_tables += 1;
super::verbose::detail(
shape_ref.slide_index,
&shape_ref.name,
&format!("{rows}×{cols} cells"),
);
}
Ok(total_tables)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_numeric_positive() {
assert_eq!(parse_numeric("1.5"), Some(1.5));
}
#[test]
fn test_parse_numeric_negative() {
assert_eq!(parse_numeric("-0.3"), Some(-0.3));
}
#[test]
fn test_parse_numeric_zero() {
assert_eq!(parse_numeric("0"), Some(0.0));
}
#[test]
fn test_parse_numeric_with_percent() {
assert_eq!(parse_numeric("1.5%"), Some(1.5));
}
#[test]
fn test_parse_numeric_with_plus() {
assert_eq!(parse_numeric("+1.5"), Some(1.5));
}
#[test]
fn test_parse_numeric_with_plus_percent() {
assert_eq!(parse_numeric("+1.5%"), Some(1.5));
}
#[test]
fn test_parse_numeric_text() {
assert_eq!(parse_numeric("N/A"), None);
}
#[test]
fn test_parse_numeric_empty() {
assert_eq!(parse_numeric(""), None);
}
#[test]
fn test_parse_numeric_whitespace() {
assert_eq!(parse_numeric(" 1.5 "), Some(1.5));
}
#[test]
fn test_sign_category() {
assert_eq!(sign_category(1.5), SignCategory::Positive);
assert_eq!(sign_category(-0.3), SignCategory::Negative);
assert_eq!(sign_category(0.0), SignCategory::Neutral);
assert_eq!(sign_category(-0.0), SignCategory::Neutral);
}
}