use crate::Encoding;
use std::borrow::Cow;
pub fn escape<'a>(text: &'a str, enc: Encoding) -> Cow<'a, str> {
let special = [
enc.field,
enc.component,
enc.repetition,
enc.escape,
enc.subcomponent,
b'\r',
b'\n',
];
if !text.bytes().any(|b| special.contains(&b)) {
return Cow::Borrowed(text);
}
let esc = enc.escape as char;
let mut out = String::with_capacity(text.len() + 8);
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
let code = if c.is_ascii() { Some(c as u8) } else { None };
match code {
Some(b) if b == enc.escape => push_seq(&mut out, esc, "E"),
Some(b) if b == enc.field => push_seq(&mut out, esc, "F"),
Some(b) if b == enc.component => push_seq(&mut out, esc, "S"),
Some(b) if b == enc.repetition => push_seq(&mut out, esc, "R"),
Some(b) if b == enc.subcomponent => push_seq(&mut out, esc, "T"),
Some(b'\r') => {
if chars.peek() == Some(&'\n') {
chars.next();
}
push_seq(&mut out, esc, ".br");
}
Some(b'\n') => push_seq(&mut out, esc, ".br"),
_ => out.push(c),
}
}
Cow::Owned(out)
}
fn push_seq(out: &mut String, esc: char, seq: &str) {
out.push(esc);
out.push_str(seq);
out.push(esc);
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Value {
pub repetitions: Vec<Vec<Vec<String>>>,
}
impl Value {
pub fn text(text: impl Into<String>) -> Value {
Value {
repetitions: vec![vec![vec![text.into()]]],
}
}
pub fn components<I, S>(components: I) -> Value
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Value {
repetitions: vec![components.into_iter().map(|c| vec![c.into()]).collect()],
}
}
pub fn repetitions<I, R, S>(reps: I) -> Value
where
I: IntoIterator<Item = R>,
R: IntoIterator<Item = S>,
S: Into<String>,
{
Value {
repetitions: reps
.into_iter()
.map(|r| r.into_iter().map(|c| vec![c.into()]).collect())
.collect(),
}
}
pub fn set(&mut self, r: usize, c: usize, s: usize, text: impl Into<String>) -> &mut Value {
let (r, c, s) = (r.max(1) - 1, c.max(1) - 1, s.max(1) - 1);
if self.repetitions.len() <= r {
self.repetitions.resize(r + 1, Vec::new());
}
let rep = &mut self.repetitions[r];
if rep.len() <= c {
rep.resize(c + 1, Vec::new());
}
let comp = &mut rep[c];
if comp.len() <= s {
comp.resize(s + 1, String::new());
}
comp[s] = text.into();
self
}
fn render(&self, enc: Encoding) -> String {
let sub = (enc.subcomponent as char).to_string();
let comp = (enc.component as char).to_string();
let rep = (enc.repetition as char).to_string();
self.repetitions
.iter()
.map(|r| {
r.iter()
.map(|c| {
c.iter()
.map(|s| escape(s, enc))
.collect::<Vec<_>>()
.join(&sub)
})
.collect::<Vec<_>>()
.join(&comp)
})
.collect::<Vec<_>>()
.join(&rep)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Value {
Value::text(s)
}
}
impl From<String> for Value {
fn from(s: String) -> Value {
Value::text(s)
}
}
impl From<&String> for Value {
fn from(s: &String) -> Value {
Value::text(s.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SegmentBuilder {
name: String,
fields: Vec<Option<Value>>,
}
impl SegmentBuilder {
fn new(name: &str) -> SegmentBuilder {
SegmentBuilder {
name: name.to_ascii_uppercase(),
fields: Vec::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set(&mut self, n: usize, value: impl Into<Value>) -> &mut SegmentBuilder {
if n == 0 || (self.name == "MSH" && n <= 2) {
return self;
}
if self.fields.len() < n {
self.fields.resize(n, None);
}
self.fields[n - 1] = Some(value.into());
self
}
pub fn set_component(
&mut self,
n: usize,
c: usize,
text: impl Into<String>,
) -> &mut SegmentBuilder {
self.set_subcomponent(n, c, 1, text)
}
pub fn set_subcomponent(
&mut self,
n: usize,
c: usize,
s: usize,
text: impl Into<String>,
) -> &mut SegmentBuilder {
if n == 0 || (self.name == "MSH" && n <= 2) {
return self;
}
if self.fields.len() < n {
self.fields.resize(n, None);
}
self.fields[n - 1]
.get_or_insert_with(Value::default)
.set(1, c, s, text);
self
}
pub fn add_repetition<I, S>(&mut self, n: usize, components: I) -> &mut SegmentBuilder
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
if n == 0 || (self.name == "MSH" && n <= 2) {
return self;
}
if self.fields.len() < n {
self.fields.resize(n, None);
}
let value = self.fields[n - 1].get_or_insert_with(Value::default);
if value.repetitions.len() == 1 && value.repetitions[0].is_empty() {
value.repetitions.clear();
}
value
.repetitions
.push(components.into_iter().map(|c| vec![c.into()]).collect());
self
}
fn render(&self, enc: Encoding) -> String {
let fs = enc.field as char;
let mut out = self.name.clone();
let mut fields: Vec<String> = self
.fields
.iter()
.map(|f| f.as_ref().map(|v| v.render(enc)).unwrap_or_default())
.collect();
if self.name == "MSH" {
let mut msh = vec![enc.msh2()];
msh.extend(fields.drain(..).skip(2));
fields = msh;
}
for f in fields {
out.push(fs);
out.push_str(&f);
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Builder {
encoding: Encoding,
segments: Vec<SegmentBuilder>,
}
impl Default for Builder {
fn default() -> Self {
Builder::new()
}
}
impl Builder {
pub fn new() -> Builder {
Builder::with_encoding(Encoding::STANDARD)
}
pub fn with_encoding(encoding: Encoding) -> Builder {
Builder {
encoding,
segments: Vec::new(),
}
}
pub fn encoding(&self) -> Encoding {
self.encoding
}
pub fn segment(&mut self, name: &str) -> &mut SegmentBuilder {
self.segments.push(SegmentBuilder::new(name));
let last = self.segments.len() - 1;
&mut self.segments[last]
}
pub fn segments(&self) -> &[SegmentBuilder] {
&self.segments
}
pub fn segment_at(&mut self, index: usize) -> Option<&mut SegmentBuilder> {
self.segments.get_mut(index)
}
pub fn build(&self) -> String {
let mut out = String::new();
if self.segments.first().map(|s| s.name.as_str()) != Some("MSH") {
out.push_str(&SegmentBuilder::new("MSH").render(self.encoding));
out.push('\r');
}
for seg in &self.segments {
out.push_str(&seg.render(self.encoding));
out.push('\r');
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{unescape, Message};
#[test]
fn escape_roundtrips_through_unescape() {
let enc = Encoding::STANDARD;
let text = "a|b^c~d\\e&f\r\ng\nh";
let escaped = escape(text, enc);
assert_eq!(escaped, "a\\F\\b\\S\\c\\R\\d\\E\\e\\T\\f\\.br\\g\\.br\\h");
assert_eq!(unescape(&escaped, enc), "a|b^c~d\\e&f\ng\nh");
assert!(matches!(escape("plain", enc), Cow::Borrowed(_)));
}
#[test]
fn msh_is_written_from_the_encoding() {
let mut b = Builder::new();
b.segment("MSH")
.set(1, "ignored")
.set(2, "ignored")
.set(3, "APP");
assert_eq!(b.build(), "MSH|^~\\&|APP\r");
let mut c = Builder::with_encoding(Encoding::from_msh2(b'#', b"!*?%").unwrap());
c.segment("MSH").set(9, Value::components(["ADT", "A01"]));
c.segment("PID").set(3, "A#B");
assert_eq!(c.build(), "MSH#!*?%#######ADT!A01\rPID###A?F?B\r");
let msg = Message::parse(c.build()).unwrap();
assert_eq!(msg.get_decoded("PID-3").unwrap(), "A#B");
}
#[test]
fn missing_msh_is_inserted() {
let mut b = Builder::new();
b.segment("PID").set(1, "1");
assert_eq!(b.build(), "MSH|^~\\&\rPID|1\r");
}
#[test]
fn structure_roundtrips_through_parser() {
let mut b = Builder::new();
b.segment("MSH")
.set(9, Value::components(["ORU", "R01"]))
.set(10, "1");
let pid = b.segment("PID");
pid.set(
3,
Value::repetitions([["A", "", "", "H1"], ["B", "", "", "H2"]]),
);
pid.set_subcomponent(3, 4, 2, "sub");
pid.set_component(5, 2, "First");
pid.add_repetition(3, ["C"]);
b.segment("OBX").set(5, "value with | and ^ inside");
let text = b.build();
assert_eq!(
text,
"MSH|^~\\&|||||||ORU^R01|1\rPID|||A^^^H1&sub~B^^^H2~C||^First\rOBX|||||value with \\F\\ and \\S\\ inside\r"
);
let msg = Message::parse(&text).unwrap();
assert_eq!(msg.get("PID-3[2].4"), Some("H2"));
assert_eq!(msg.get("PID-3[3].1"), Some("C"));
assert_eq!(msg.get("PID-3.4.2"), Some("sub"));
assert_eq!(msg.get("PID-5.2"), Some("First"));
assert_eq!(
msg.get_decoded("OBX-5").unwrap(),
"value with | and ^ inside"
);
assert!(msg.warnings().is_empty());
}
#[test]
fn value_set_grows() {
let mut v = Value::default();
v.set(2, 3, 1, "x");
assert_eq!(v.render(Encoding::STANDARD), "~^^x");
assert_eq!(Value::from("t").render(Encoding::STANDARD), "t");
}
}