use encoding_rs::WINDOWS_1252;
use indexmap::IndexMap;
use std::fmt;
use crate::io::reader::read_parameters_block;
use crate::io::writer::write_parameters_block;
use crate::traits::{FromBinary, ToBinary};
use crate::types::{Color, Coord, Layer, Unit};
const ENTRY_SEPARATORS: &[char] = &['|', '`'];
const KEY_VALUE_SEPARATOR: char = '=';
const UTF8_PREFIX: &str = "%UTF8%";
const TRUE_VALUES: &[&str] = &["T", "TRUE"];
const FALSE_VALUES: &[&str] = &["F", "FALSE"];
#[derive(Clone, Debug, Default)]
pub struct ParameterValue {
data: String,
level: usize,
}
impl ParameterValue {
pub fn new(data: String, level: usize) -> Self {
ParameterValue { data, level }
}
#[inline]
pub fn as_str(&self) -> &str {
&self.data
}
pub fn as_string_or(&self, default: &str) -> String {
if self.data.is_empty() {
default.to_string()
} else {
self.data.clone()
}
}
pub fn as_int(&self) -> Result<i32, std::num::ParseIntError> {
self.data.trim().parse()
}
pub fn as_int_or(&self, default: i32) -> i32 {
self.data.trim().parse().unwrap_or(default)
}
pub fn as_double(&self) -> Result<f64, std::num::ParseFloatError> {
self.data.trim().parse()
}
pub fn as_double_or(&self, default: f64) -> f64 {
self.data.trim().parse().unwrap_or(default)
}
pub fn as_bool(&self) -> Result<bool, &'static str> {
let s = self.data.trim().to_uppercase();
if TRUE_VALUES.contains(&s.as_str()) {
Ok(true)
} else if FALSE_VALUES.contains(&s.as_str()) || s.is_empty() {
Ok(false)
} else {
Err("Invalid boolean value")
}
}
pub fn as_bool_or(&self, default: bool) -> bool {
self.as_bool().unwrap_or(default)
}
pub fn as_coord(&self) -> Result<Coord, crate::error::AltiumError> {
let (coord, _) = Unit::parse_with_unit(&self.data)?;
Ok(coord)
}
pub fn as_coord_or(&self, default: Coord) -> Coord {
self.as_coord().unwrap_or(default)
}
pub fn as_color(&self) -> Result<Color, std::num::ParseIntError> {
let value: i32 = self.data.trim().parse()?;
Ok(Color::from_win32(value))
}
pub fn as_color_or(&self, default: Color) -> Color {
self.as_color().unwrap_or(default)
}
pub fn as_layer(&self) -> Layer {
if let Ok(value) = self.data.trim().parse::<u8>() {
Layer::from_byte(value)
} else {
Layer::from_name(&self.data).unwrap_or(Layer::UNKNOWN)
}
}
pub fn as_parameters(&self) -> ParameterCollection {
ParameterCollection::from_string_with_level(&self.data, self.level + 1)
}
pub fn as_string_list(&self) -> Vec<String> {
self.as_list_impl().map(|s| s.to_string()).collect()
}
pub fn as_int_list(&self) -> Vec<i32> {
self.as_list_impl()
.filter_map(|s| s.trim().parse().ok())
.collect()
}
pub fn as_double_list(&self) -> Vec<f64> {
self.as_list_impl()
.filter_map(|s| s.trim().parse().ok())
.collect()
}
pub fn as_coord_list(&self) -> Vec<Coord> {
self.as_list_impl()
.filter_map(|s| Unit::parse_with_unit(s).ok().map(|(c, _)| c))
.collect()
}
fn as_list_impl(&self) -> impl Iterator<Item = &str> {
self.data.split(',').filter(|s| !s.is_empty())
}
pub fn is_parameters(&self) -> bool {
let sep = ENTRY_SEPARATORS.get(self.level + 1).copied().unwrap_or('`');
self.data.contains(sep)
}
pub fn is_list(&self) -> bool {
self.data.contains(',')
}
}
impl fmt::Display for ParameterValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.data)
}
}
#[derive(Clone, Debug, Default)]
pub struct ParameterCollection {
#[allow(dead_code)] data: Option<String>,
level: usize,
keys: Vec<String>,
parameters: IndexMap<String, String>,
use_long_booleans: bool,
}
impl ParameterCollection {
pub fn new() -> Self {
ParameterCollection::default()
}
pub fn from_string(data: &str) -> Self {
Self::from_string_with_level(data, 0)
}
pub fn from_string_with_level(data: &str, level: usize) -> Self {
let mut collection = ParameterCollection {
data: Some(data.to_string()),
level,
keys: Vec::new(),
parameters: IndexMap::new(),
use_long_booleans: false,
};
collection.parse_data(data);
collection
}
fn parse_data(&mut self, data: &str) {
let separator = ENTRY_SEPARATORS.get(self.level).copied().unwrap_or('|');
let mut ignored: std::collections::HashSet<String> = std::collections::HashSet::new();
for entry in data.split(separator).filter(|s| !s.is_empty()) {
let entry = entry.trim_end_matches(&['\r', '\n'] as &[char]);
let (key, value) = if let Some(pos) = entry.find(KEY_VALUE_SEPARATOR) {
let (k, v) = entry.split_at(pos);
(k.to_string(), v[1..].to_string())
} else {
(String::new(), entry.to_string())
};
let upper_key = key.to_uppercase();
if ignored.contains(&upper_key) {
continue;
}
let (final_key, final_value) = if let Some(stripped) = key.strip_prefix(UTF8_PREFIX) {
let real_key = stripped.to_string();
let decoded = Self::decode_utf8_from_win1252(&value);
ignored.insert(real_key.to_uppercase());
(real_key, decoded)
} else {
(key, value)
};
self.add_internal(&final_key, &final_value);
}
}
fn decode_utf8_from_win1252(s: &str) -> String {
let (bytes, _, _) = WINDOWS_1252.encode(s);
match std::str::from_utf8(bytes.as_ref()) {
Ok(decoded) => decoded.to_string(),
Err(_) => s.to_string(),
}
}
fn add_internal(&mut self, key: &str, value: &str) {
let upper_key = key.to_uppercase();
if !self.parameters.contains_key(&upper_key) {
self.keys.push(upper_key.clone());
}
self.parameters.insert(upper_key, value.to_string());
}
pub fn contains(&self, key: &str) -> bool {
self.parameters.contains_key(&key.to_uppercase())
}
pub fn get(&self, key: &str) -> Option<ParameterValue> {
self.parameters
.get(&key.to_uppercase())
.map(|v| ParameterValue::new(v.clone(), self.level))
}
pub fn get_or(&self, key: &str, default: &str) -> ParameterValue {
self.get(key)
.unwrap_or_else(|| ParameterValue::new(default.to_string(), self.level))
}
pub fn get_at(&self, index: usize) -> Option<(&str, ParameterValue)> {
self.keys.get(index).and_then(|k| {
self.parameters
.get(k)
.map(|v| (k.as_str(), ParameterValue::new(v.clone(), self.level)))
})
}
pub fn index_of(&self, key: &str) -> Option<usize> {
let upper = key.to_uppercase();
self.keys.iter().position(|k| k == &upper)
}
pub fn add(&mut self, key: &str, value: &str) {
self.add_internal(key, value);
}
pub fn add_int(&mut self, key: &str, value: i32) {
if value != 0 {
self.add_internal(key, &value.to_string());
}
}
pub fn add_double(&mut self, key: &str, value: f64, decimals: usize) {
if value != 0.0 {
let formatted = format!("{:.prec$}", value, prec = decimals);
self.add_internal(key, &formatted);
}
}
pub fn add_bool(&mut self, key: &str, value: bool) {
if value {
let s = if self.use_long_booleans { "TRUE" } else { "T" };
self.add_internal(key, s);
}
}
pub fn add_coord(&mut self, key: &str, value: Coord) {
if value.to_raw() != 0 {
let mils = value.to_mils();
let formatted = format!("{:.5}mil", mils);
self.add_internal(key, &formatted);
}
}
pub fn add_color(&mut self, key: &str, value: Color) {
self.add_int(key, value.to_win32());
}
pub fn remove(&mut self, key: &str) {
let upper = key.to_uppercase();
self.parameters.swap_remove(&upper);
self.keys.retain(|k| k != &upper);
}
pub fn len(&self) -> usize {
self.parameters.len()
}
pub fn is_empty(&self) -> bool {
self.parameters.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, ParameterValue)> {
self.keys.iter().filter_map(move |k| {
self.parameters
.get(k)
.map(|v| (k.as_str(), ParameterValue::new(v.clone(), self.level)))
})
}
pub fn level(&self) -> usize {
self.level
}
pub fn set_use_long_booleans(&mut self, value: bool) {
self.use_long_booleans = value;
}
pub fn to_param_string(&self) -> String {
let separator = ENTRY_SEPARATORS.get(self.level).copied().unwrap_or('|');
let mut result = String::new();
for (key, value) in self.iter() {
result.push(separator);
result.push_str(key);
result.push(KEY_VALUE_SEPARATOR);
result.push_str(&value.data);
}
result
}
}
impl FromBinary for ParameterCollection {
fn read_from<R: std::io::Read>(reader: &mut R) -> crate::error::Result<Self> {
read_parameters_block(reader)
}
}
impl ToBinary for ParameterCollection {
fn write_to<W: std::io::Write>(&self, writer: &mut W) -> crate::error::Result<()> {
write_parameters_block(writer, self)
}
fn binary_size(&self) -> usize {
4 + self.to_param_string().len() + 1
}
}
impl fmt::Display for ParameterCollection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_param_string())
}
}
impl<'a> IntoIterator for &'a ParameterCollection {
type Item = (&'a str, ParameterValue);
type IntoIter = Box<dyn Iterator<Item = (&'a str, ParameterValue)> + 'a>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.keys.iter().filter_map(move |k| {
self.parameters
.get(k)
.map(|v| (k.as_str(), ParameterValue::new(v.clone(), self.level)))
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_parameters() {
let data = "|RECORD=1|LIBREFERENCE=Resistor|VALUE=10k|";
let params = ParameterCollection::from_string(data);
assert_eq!(params.len(), 3);
assert_eq!(params.get("RECORD").unwrap().as_int_or(0), 1);
assert_eq!(params.get("LIBREFERENCE").unwrap().as_str(), "Resistor");
assert_eq!(params.get("VALUE").unwrap().as_str(), "10k");
}
#[test]
fn test_boolean_values() {
let data = "|VISIBLE=T|LOCKED=FALSE|";
let params = ParameterCollection::from_string(data);
assert!(params.get("VISIBLE").unwrap().as_bool_or(false));
assert!(!params.get("LOCKED").unwrap().as_bool_or(true));
}
#[test]
fn test_coordinate_values() {
let data = "|X=100mil|Y=2.54mm|";
let params = ParameterCollection::from_string(data);
let x = params.get("X").unwrap().as_coord_or(Coord::ZERO);
assert!((x.to_mils() - 100.0).abs() < 0.1);
let y = params.get("Y").unwrap().as_coord_or(Coord::ZERO);
assert!((y.to_mils() - 100.0).abs() < 0.2);
}
#[test]
fn test_integer_list() {
let data = "|POINTS=1,2,3,4,5|";
let params = ParameterCollection::from_string(data);
let list = params.get("POINTS").unwrap().as_int_list();
assert_eq!(list, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_to_param_string() {
let mut params = ParameterCollection::new();
params.add("RECORD", "1");
params.add("NAME", "Test");
let s = params.to_param_string();
assert!(s.contains("|RECORD=1"));
assert!(s.contains("|NAME=Test"));
}
}