pub fn extract_field_content(input: &str, tag: &str) -> Option<(String, usize)> {
let field_marker = format!(":{}:", tag);
let field_start = input.find(&field_marker)?;
let content_start = field_start + field_marker.len();
let remaining = &input[content_start..];
let content_end = find_next_field_boundary(remaining);
let (raw_content, has_trailing_newline) = if let Some(end) = content_end {
let has_newline = remaining.as_bytes().get(end) == Some(&b'\n');
(remaining[..end].to_string(), has_newline)
} else {
if let Some(end_pos) = remaining.find("\n-}") {
(remaining[..end_pos].to_string(), true)
} else if let Some(end_pos) = remaining.find("\n-\n") {
(remaining[..end_pos].to_string(), true)
} else if let Some(end_pos) = remaining.find("\n-") {
let after_marker = end_pos + 2; if after_marker >= remaining.len() || remaining[after_marker..].starts_with('}') {
(remaining[..end_pos].to_string(), true)
} else {
(remaining.to_string(), false)
}
} else if let Some(end_pos) = remaining.find("-}") {
(remaining[..end_pos].to_string(), false)
} else {
(remaining.to_string(), false)
}
};
let raw_content_len = raw_content.len();
let content = raw_content.trim_end_matches('\n').trim_end_matches('\r');
let consumed = field_start
+ field_marker.len()
+ raw_content_len
+ if has_trailing_newline { 1 } else { 0 };
Some((content.to_string(), consumed))
}
fn find_next_field_boundary(input: &str) -> Option<usize> {
let mut chars = input.char_indices();
while let Some((i, ch)) = chars.next() {
if ch == '\n' {
if let Some((_, ':')) = chars.next() {
let rest = &input[i + 1..];
if is_field_marker(rest) {
return Some(i);
}
}
}
}
None
}
fn is_field_marker(input: &str) -> bool {
if !input.starts_with(':') {
return false;
}
if let Some(close) = input[1..].find(':') {
let tag = &input[1..close + 1];
if (2..=4).contains(&tag.len()) {
return tag.chars().all(|c| c.is_alphanumeric());
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_simple_field() {
let input = ":20:REF123\n:21:RELREF\n-";
let (content, consumed) = extract_field_content(input, "20").unwrap();
assert_eq!(content, "REF123");
assert_eq!(consumed, 11); }
#[test]
fn test_extract_multiline_field() {
let input = ":70:LINE1\nLINE2\nLINE3\n:71A:SHA\n-";
let (content, _consumed) = extract_field_content(input, "70").unwrap();
assert_eq!(content, "LINE1\nLINE2\nLINE3");
}
#[test]
fn test_extract_field_with_variant() {
let input = ":50K:JOHN DOE\n123 MAIN ST\n:59:BENEFICIARY\n-";
let (content, _) = extract_field_content(input, "50K").unwrap();
assert_eq!(content, "JOHN DOE\n123 MAIN ST");
}
#[test]
fn test_extract_last_field() {
let input = ":20:REF123\n:71A:SHA\n-";
let (content, _) = extract_field_content(input, "71A").unwrap();
assert_eq!(content, "SHA");
}
#[test]
fn test_field_not_found() {
let input = ":20:REF123\n:21:RELREF\n-";
let result = extract_field_content(input, "32A");
assert!(result.is_none());
}
#[test]
fn test_field_marker_detection() {
assert!(is_field_marker(":20:"));
assert!(is_field_marker(":32A:"));
assert!(is_field_marker(":50K:"));
assert!(!is_field_marker(":12345:")); assert!(!is_field_marker(":X:")); assert!(!is_field_marker("20:")); }
}