use log::{debug, trace};
use regex::Regex;
use crate::separators::Separators;
use std::borrow::Cow;
pub struct EscapeSequence {
escape_buf: [u8; 1],
field_buf: [u8; 1],
repeat_buf: [u8; 1],
component_buf: [u8; 1],
subcomponent_buf: [u8; 1],
escape_regex: Regex,
}
impl<'a> EscapeSequence {
pub fn new(delims: Separators) -> EscapeSequence {
let regex = if delims.escape_char == '\\' {
Regex::new(r#"\\"#) } else {
Regex::new(String::from(delims.escape_char).as_str()) }
.unwrap();
let mut return_val = EscapeSequence {
escape_buf: [0; 1], field_buf: [0; 1],
repeat_buf: [0; 1],
component_buf: [0; 1],
subcomponent_buf: [0; 1],
escape_regex: regex,
};
let _bytes = delims.escape_char.encode_utf8(&mut return_val.escape_buf);
let _bytes = delims.field.encode_utf8(&mut return_val.field_buf);
let _bytes = delims.repeat.encode_utf8(&mut return_val.repeat_buf);
let _bytes = delims.component.encode_utf8(&mut return_val.component_buf);
let _bytes = delims
.subcomponent
.encode_utf8(&mut return_val.subcomponent_buf);
return_val
}
pub fn decode<S>(&self, input: S) -> Cow<'a, str>
where
S: Into<Cow<'a, str>>,
{
let input = input.into();
let first = self.escape_regex.find(&input);
match first {
Some(first) => {
let first = first.start();
let mut output: Vec<u8> = Vec::with_capacity(input.len());
output.extend_from_slice(input[0..first].as_bytes());
let mut i = first;
debug!("Found first escape char at {}", first);
while i < input.len() {
let start_of_sequence = self.escape_regex.find(&input[i..]);
if start_of_sequence.is_none() {
trace!("No more sequence starts in input, completing...");
output.extend_from_slice(input[i..].as_bytes()); break; }
let start_index = start_of_sequence.unwrap().start() + i; trace!("Found the next escape char at {}", start_index);
let end_of_sequence = self.escape_regex.find(&input[start_index + 1..]);
if end_of_sequence.is_none() {
trace!("No more sequence ends in input, completing...");
output.extend_from_slice(input[start_index..].as_bytes()); break; }
let end_index = end_of_sequence.unwrap().start() + start_index + 1; trace!("Found end of sequence at {}", end_index);
let sequence = &input[start_index + 1..end_index];
trace!("Found escape sequence: '{}'", sequence);
output.extend_from_slice(input[i..start_index].as_bytes());
match sequence {
"E" => output.extend_from_slice(&self.escape_buf),
"F" => output.extend_from_slice(&self.field_buf),
"R" => output.extend_from_slice(&self.repeat_buf),
"S" => output.extend_from_slice(&self.component_buf),
"T" => output.extend_from_slice(&self.subcomponent_buf),
"H" | "N" => {
output.extend_from_slice(&self.escape_buf);
output.extend_from_slice(sequence.as_bytes());
output.extend_from_slice(&self.escape_buf);
}
_ => {
if sequence.starts_with('Z') {
trace!("Into custom escape sequence, ignoring...");
output.extend_from_slice(&self.escape_buf);
output.extend_from_slice(sequence.as_bytes());
output.extend_from_slice(&self.escape_buf);
} else if let Some(hex_code) = sequence.strip_prefix('X') {
let hex = hex::decode(hex_code)
.expect("Unable to parse X-value into valid hex");
println!("Converted hex code {} to {:?}", hex_code, hex);
output.extend_from_slice(&hex);
} else {
trace!("Unknown sequence, extending output...");
output.extend_from_slice(
input[start_index - 1..end_index].as_bytes(),
);
}
}
}
i = end_index + 1; }
Cow::Owned(String::from_utf8(output).unwrap())
}
None => {
input
}
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn test_decode_does_nothing_if_not_required() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = "There are no escape sequences here/there/.";
let output = escaper.decode(input);
assert_eq!(output, input);
}
#[test]
fn test_decode_handles_simple_x_codes() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = "Escape sequence with \\X0D\\.";
let output = escaper.decode(input);
assert_eq!(output, "Escape sequence with \r.");
}
#[test]
fn test_decode_handles_multi_byte_x_codes() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = "Sentence 1.\\X0D0A\\Sentence 2.";
let output = escaper.decode(input);
assert_eq!(output, "Sentence 1.\r\nSentence 2.");
}
#[test]
fn test_decode_does_nothing_if_backslash_is_not_escape_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"There are no escape sequences here\there."#;
let output = escaper.decode(input);
assert_eq!(output, input);
}
#[test]
fn test_decode_handles_field_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this \F\ please"#;
let output = escaper.decode(input);
assert_eq!(output, "Escape this | please");
}
#[test]
fn ensure_decode_does_not_eat_chars_it_shouldnt() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this \F please"#;
let output = escaper.decode(input);
assert_eq!(output, input);
}
#[test]
fn ensure_decode_handles_custom_delims() {
let delims = Separators::from_str("MSH^!@#$").unwrap();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this #F# please"#;
let output = escaper.decode(input);
assert_eq!(output, "Escape this ^ please");
}
#[test]
fn ensure_decode_handles_eescape_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this \E\ please"#; let output = escaper.decode(input);
assert_eq!(output, r#"Escape this \ please"#);
let input = r#"Escape this \E\ pretty \F\ please"#; let output = escaper.decode(input);
assert_eq!(output, r#"Escape this \ pretty | please"#); }
#[test]
fn test_decode_handles_repeat_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this \R\ please"#;
let output = escaper.decode(input);
assert_eq!(output, "Escape this ~ please");
}
#[test]
fn test_decode_handles_component_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Escape this \S\ please"#;
let output = escaper.decode(input);
assert_eq!(output, "Escape this ^ please");
}
#[test]
fn test_decode_handles_subcomponent_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Obstetrician \T\ Gynaecologist"#;
let output = escaper.decode(input);
assert_eq!(output, "Obstetrician & Gynaecologist");
}
#[test]
fn ensure_decode_ignores_highlighting_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Don't escape this \H\highlighted text\N\ please"#;
let output = escaper.decode(input);
assert_eq!(output, input);
}
#[test]
fn ensure_decode_ignores_custom_sequence() {
let delims = Separators::default();
let escaper = EscapeSequence::new(delims);
let input = r#"Don't escape this custom sequence \Z1234\ please"#;
let output = escaper.decode(input);
assert_eq!(output, input);
}
}