pub fn extract_element<'a>(xml: &'a str, tag: &str) -> Option<&'a str> {
let close = format!("</{tag}>");
let prefix_bare = format!("<{tag}>");
let prefix_attr = format!("<{tag} ");
let tag_start = xml
.find(prefix_bare.as_str())
.or_else(|| xml.find(prefix_attr.as_str()))?;
let gt = xml[tag_start..].find('>')?;
let content_start = tag_start + gt + 1;
let end = xml[content_start..].find(&close)?;
Some(xml[content_start..content_start + end].trim())
}
pub fn extract_all_elements<'a>(xml: &'a str, tag: &str) -> Vec<&'a str> {
let close = format!("</{tag}>");
let prefix_bare = format!("<{tag}>");
let prefix_attr = format!("<{tag} ");
let mut results = Vec::new();
let mut remaining = xml;
loop {
let pos_bare = remaining.find(prefix_bare.as_str());
let pos_attr = remaining.find(prefix_attr.as_str());
let tag_start = match (pos_bare, pos_attr) {
(None, None) => break,
(Some(a), None) => a,
(None, Some(b)) => b,
(Some(a), Some(b)) => a.min(b),
};
let Some(gt) = remaining[tag_start..].find('>') else {
break;
};
let content_start = tag_start + gt + 1;
let tail = &remaining[content_start..];
if let Some(end_pos) = tail.find(&close) {
results.push(tail[..end_pos].trim());
remaining = &tail[end_pos + close.len()..];
} else {
break;
}
}
results
}
pub fn extract_attribute<'a>(xml: &'a str, tag: &str, attr: &str) -> Option<&'a str> {
let prefix = format!("<{tag} ");
let tag_start = xml.find(&prefix)?;
let tag_content = &xml[tag_start + prefix.len()..];
let gt_pos = tag_content.find('>')?;
let attrs_str = &tag_content[..gt_pos];
let attr_prefix = format!("{attr}=\"");
let attr_start = attrs_str.find(&attr_prefix)?;
let value_start = attr_start + attr_prefix.len();
let value_end = attrs_str[value_start..].find('"')?;
Some(&attrs_str[value_start..value_start + value_end])
}
pub fn extract_all_attributes<'a>(xml: &'a str, attr_name: &str) -> Vec<&'a str> {
let needle = format!("{attr_name}=\"");
let mut results = Vec::new();
let mut remaining = xml;
while let Some(pos) = remaining.find(&needle) {
let after_eq = pos + needle.len();
let tail = &remaining[after_eq..];
if let Some(end_pos) = tail.find('"') {
results.push(tail[..end_pos].trim());
remaining = &tail[end_pos + 1..];
} else {
break;
}
}
results
}
pub fn has_element(xml: &str, tag: &str) -> bool {
let bare = format!("<{tag}>");
let self_close = format!("<{tag}/>");
let with_attr = format!("<{tag} ");
xml.contains(&bare) || xml.contains(&self_close) || xml.contains(&with_attr)
}
pub fn xml_byte_size(xml: &str) -> usize {
xml.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_element_simple() {
let xml = "<Root><MsgId>ABC123</MsgId></Root>";
assert_eq!(extract_element(xml, "MsgId"), Some("ABC123"));
}
#[test]
fn extract_element_trims_whitespace() {
let xml = "<Root><MsgId> ABC123 </MsgId></Root>";
assert_eq!(extract_element(xml, "MsgId"), Some("ABC123"));
}
#[test]
fn extract_element_missing_returns_none() {
let xml = "<Root><MsgId>ABC123</MsgId></Root>";
assert_eq!(extract_element(xml, "NbOfTxs"), None);
}
#[test]
fn extract_element_returns_first_match() {
let xml = "<Root><Nm>Alice</Nm><Nm>Bob</Nm></Root>";
assert_eq!(extract_element(xml, "Nm"), Some("Alice"));
}
#[test]
fn extract_all_elements_multiple() {
let xml = "<D><Nm>Alice</Nm><Nm>Bob</Nm><Nm>Carol</Nm></D>";
let result = extract_all_elements(xml, "Nm");
assert_eq!(result, vec!["Alice", "Bob", "Carol"]);
}
#[test]
fn extract_all_elements_none_returns_empty() {
let xml = "<D><MsgId>123</MsgId></D>";
let result = extract_all_elements(xml, "Nm");
assert!(result.is_empty());
}
#[test]
fn extract_attribute_finds_value() {
let xml = r#"<Amount Ccy="USD">100.00</Amount>"#;
assert_eq!(extract_attribute(xml, "Amount", "Ccy"), Some("USD"));
}
#[test]
fn extract_attribute_self_closing() {
let xml = r#"<Amt Ccy="EUR"/>"#;
assert_eq!(extract_attribute(xml, "Amt", "Ccy"), Some("EUR"));
}
#[test]
fn extract_attribute_missing_returns_none() {
let xml = r#"<Amount>100.00</Amount>"#;
assert_eq!(extract_attribute(xml, "Amount", "Ccy"), None);
}
#[test]
fn has_element_present() {
let xml = "<Doc><UETR>uuid-here</UETR></Doc>";
assert!(has_element(xml, "UETR"));
}
#[test]
fn has_element_self_closing() {
let xml = "<Doc><Empty/></Doc>";
assert!(has_element(xml, "Empty"));
}
#[test]
fn has_element_absent() {
let xml = "<Doc><MsgId>123</MsgId></Doc>";
assert!(!has_element(xml, "AppHdr"));
}
#[test]
fn xml_byte_size_ascii() {
assert_eq!(xml_byte_size("hello"), 5);
}
#[test]
fn xml_byte_size_utf8_multibyte() {
assert_eq!(xml_byte_size("é"), 2);
}
}