#![expect(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::manual_let_else,
clippy::missing_errors_doc,
clippy::string_slice,
reason = "pre-existing escape implementation debt moved from staged microcrate into hl7v2; cleanup is split from topology collapse"
)]
use crate::model::{Delims, Error};
pub fn escape_text(text: &str, delims: &Delims) -> String {
let delims_arr = [
delims.field,
delims.comp,
delims.rep,
delims.esc,
delims.sub,
];
let first_idx = match text.find(&delims_arr[..]) {
Some(idx) => idx,
None => return text.to_string(), };
let mut result = String::with_capacity(text.len() + 10);
result.push_str(&text[..first_idx]);
for ch in text[first_idx..].chars() {
match ch {
c if c == delims.field => {
result.push(delims.esc);
result.push('F');
result.push(delims.esc);
}
c if c == delims.comp => {
result.push(delims.esc);
result.push('S');
result.push(delims.esc);
}
c if c == delims.rep => {
result.push(delims.esc);
result.push('R');
result.push(delims.esc);
}
c if c == delims.esc => {
result.push(delims.esc);
result.push('E');
result.push(delims.esc);
}
c if c == delims.sub => {
result.push(delims.esc);
result.push('T');
result.push(delims.esc);
}
_ => result.push(ch),
}
}
result
}
pub fn unescape_text(text: &str, delims: &Delims) -> Result<String, Error> {
let first_idx = match text.find(delims.esc) {
Some(idx) => idx,
None => return Ok(text.to_string()), };
let mut result = String::with_capacity(text.len());
result.push_str(&text[..first_idx]);
let mut chars = text[first_idx..].chars().peekable();
while let Some(ch) = chars.next() {
if ch == delims.esc {
let mut escape_seq = String::new();
let mut found_end = false;
for esc_ch in chars.by_ref() {
if esc_ch == delims.esc {
found_end = true;
break;
}
escape_seq.push(esc_ch);
}
if !found_end {
if text.len() == 4 {
let chars: Vec<char> = text.chars().collect();
if chars[0] == delims.comp
&& chars[1] == delims.rep
&& chars[2] == delims.esc
&& chars[3] == delims.sub
{
result.push(delims.comp);
result.push(delims.rep);
result.push(delims.esc);
result.push(delims.sub);
return Ok(result);
}
}
result.push(delims.esc);
result.push_str(&escape_seq);
continue;
}
match escape_seq.as_str() {
"F" => {
result.push(delims.field);
}
"S" => {
result.push(delims.comp);
}
"R" => {
result.push(delims.rep);
}
"E" => {
result.push(delims.esc);
}
"T" => {
result.push(delims.sub);
}
_ => {
result.push(delims.esc);
result.push_str(&escape_seq);
result.push(delims.esc);
}
}
} else {
result.push(ch);
}
}
Ok(result)
}
pub fn needs_escaping(text: &str, delims: &Delims) -> bool {
text.contains(
&[
delims.field,
delims.comp,
delims.rep,
delims.esc,
delims.sub,
][..],
)
}
pub fn needs_unescaping(text: &str, delims: &Delims) -> bool {
text.contains(delims.esc)
}
#[cfg(test)]
mod tests;