#![allow(clippy::unwrap_used)]
use ironcalc_base::colors::get_indexed_color;
use roxmltree::{ExpandedName, Node};
use crate::error::XlsxError;
use ironcalc_base::types::{Color, Theme};
pub(crate) fn get_number(node: Node, s: &str) -> i32 {
node.attribute(s).unwrap_or("0").parse::<i32>().unwrap_or(0)
}
#[inline]
pub(super) fn get_attribute<'a, 'n, 'm, N>(
node: &'a Node,
attr_name: N,
) -> Result<&'a str, XlsxError>
where
N: Into<ExpandedName<'n, 'm>>,
{
let attr_name = attr_name.into();
node.attribute(attr_name)
.ok_or_else(|| XlsxError::Xml(format!("Missing \"{attr_name:?}\" XML attribute")))
}
pub(super) fn get_value_or_default(node: &Node, tag_name: &str, default: &str) -> String {
let application_nodes = node
.children()
.filter(|n| n.has_tag_name(tag_name))
.collect::<Vec<Node>>();
if application_nodes.len() == 1 {
application_nodes[0].text().unwrap_or(default).to_string()
} else {
default.to_string()
}
}
pub(super) fn get_color(node: Node, theme: &Theme) -> Result<Color, XlsxError> {
get_color_indexed(node, theme, None)
}
pub(super) fn get_color_indexed(
node: Node,
_theme: &Theme,
indexed: Option<&[String]>,
) -> Result<Color, XlsxError> {
if node.has_attribute("rgb") {
let raw = node.attribute("rgb").unwrap();
let hex = if raw.len() == 8 {
format!("#{}", raw[2..].to_ascii_uppercase())
} else {
format!("#{}", raw.to_ascii_uppercase())
};
Ok(Color::Rgb(hex))
} else if node.has_attribute("indexed") {
let index = node.attribute("indexed").unwrap().parse::<i32>()?;
if index == 64 {
return Ok(Color::None);
}
if index < 0 {
return Ok(Color::Rgb(get_indexed_color(0)));
}
let rgb = indexed
.and_then(|palette| {
usize::try_from(index)
.ok()
.and_then(|i| palette.get(i))
.filter(|s| !s.is_empty())
.cloned()
})
.unwrap_or_else(|| get_indexed_color(index));
Ok(Color::Rgb(rgb))
} else if node.has_attribute("theme") {
let theme_index = node.attribute("theme").unwrap().parse::<i32>()?;
let tint = match node.attribute("tint") {
Some(t) => t.parse::<f64>().unwrap_or(0.0),
None => 0.0,
};
Ok(Color::Theme(theme_index, tint))
} else if node.has_attribute("auto") {
Ok(Color::None)
} else {
println!("Unexpected color node {node:?}");
Ok(Color::None)
}
}
fn get_bool_with_default(node: Node, s: &str, default: bool) -> bool {
match node.attribute(s) {
Some(value) => {
let value = value.trim();
if value == "1" || value.eq_ignore_ascii_case("true") {
true
} else if value == "0" || value.eq_ignore_ascii_case("false") {
false
} else {
default
}
}
None => default,
}
}
pub(super) fn get_bool(node: Node, s: &str) -> bool {
get_bool_with_default(node, s, true)
}
pub(super) fn get_bool_false(node: Node, s: &str) -> bool {
get_bool_with_default(node, s, false)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use roxmltree::Document;
const XML: &str = r#"<a one="1" zero="0" t="true" f="false" tu="TRUE" fc="False" ws=" true " empty="" yes="yes" on="on" other="x"/>"#;
#[test]
fn get_bool_false_accepts_all_xsd_boolean_forms() {
let doc = Document::parse(XML).unwrap();
let node = doc.root_element();
assert!(get_bool_false(node, "one"));
assert!(get_bool_false(node, "t"));
assert!(get_bool_false(node, "tu"));
assert!(get_bool_false(node, "ws"));
assert!(!get_bool_false(node, "zero"));
assert!(!get_bool_false(node, "f"));
assert!(!get_bool_false(node, "fc"));
assert!(!get_bool_false(node, "missing"));
assert!(!get_bool_false(node, "empty"));
assert!(!get_bool_false(node, "yes"));
assert!(!get_bool_false(node, "on"));
assert!(!get_bool_false(node, "other"));
}
#[test]
fn get_bool_accepts_all_xsd_boolean_forms() {
let doc = Document::parse(XML).unwrap();
let node = doc.root_element();
assert!(get_bool(node, "one"));
assert!(get_bool(node, "t"));
assert!(get_bool(node, "tu"));
assert!(!get_bool(node, "zero"));
assert!(!get_bool(node, "f"));
assert!(!get_bool(node, "fc"));
assert!(get_bool(node, "missing"));
assert!(get_bool(node, "empty"));
assert!(get_bool(node, "yes"));
assert!(get_bool(node, "other"));
}
}