use std::ops::Range;
pub(super) fn find_tag(source: &str, designator: &str) -> Option<Range<usize>> {
let needle = format!("name=\"{designator}\"");
let mut hits = source.match_indices(&needle).map(|(at, _)| at);
let at = hits.next()?;
if hits.next().is_some() {
return None;
}
let start = source[..at].rfind('<')?;
let end = tag_end(source, start)?;
Some(start..end)
}
fn tag_end(source: &str, start: usize) -> Option<usize> {
let mut depth = 0usize;
let mut quote: Option<char> = None;
for (i, c) in source[start..].char_indices() {
match (quote, c) {
(Some(q), c) if c == q => quote = None,
(Some(_), _) => {}
(None, '"' | '\'') => quote = Some(c),
(None, '{') => depth += 1,
(None, '}') => depth = depth.checked_sub(1)?,
(None, '>') if depth == 0 => return Some(start + i + 1),
_ => {}
}
}
None
}
pub(super) fn with_number(tag: &str, prop: &str, value: f64) -> Option<String> {
let (open, body) = prop_body(tag, prop)?;
let digits = tag[body.clone()].trim_start();
let number_len = digits
.find(|c: char| !(c.is_ascii_digit() || matches!(c, '-' | '+' | '.')))
.unwrap_or(digits.len());
digits[..number_len].parse::<f64>().ok()?;
let unit = &digits[number_len..];
if open == '{' && !unit.trim().is_empty() {
return None;
}
let (head, tail) = (&tag[..body.start], &tag[body.end..]);
Some(format!("{head}{}{unit}{tail}", format_mm(value)))
}
fn prop_body(tag: &str, prop: &str) -> Option<(char, Range<usize>)> {
let key = format!("{prop}=");
let at = tag
.match_indices(&key)
.find(|(i, _)| tag[..*i].ends_with(char::is_whitespace))?
.0
+ key.len();
let (open, close) = match tag[at..].chars().next()? {
'{' => ('{', '}'),
'"' => ('"', '"'),
_ => return None,
};
let start = at + 1;
let end = start + tag[start..].find(close)?;
Some((open, start..end))
}
fn format_mm(value: f64) -> String {
let text = format!("{value:.3}");
let trimmed = text.trim_end_matches('0').trim_end_matches('.');
match trimmed {
"-0" | "" => "0".to_string(),
other => other.to_string(),
}
}