Drop them
Loses information the sender sent.
Reference
How a value carries characters that would otherwise be read as structure — and, just as importantly, which sequences are deliberately left alone.
A sequence is the message’s escape character, a body, and the escape character again.
\F\ \X0D\ \.br\ \Z0102\
^ ^ ^ ^
| body | body
escape escape The escape character is whatever MSH-2 position 3 declared — conventionally \,
but a message using ? writes ?F?. Every function takes the
message’s delimiter set for exactly that reason.
| Sequence | Meaning | Decoded? |
|---|---|---|
\F\ | the field separator as data | yes |
\S\ | the component separator as data | yes |
\T\ | the subcomponent separator as data | yes |
\R\ | the repetition separator as data | yes |
\E\ | the escape character as data | yes |
\Xdd..\ | hexadecimal data; pairs of digits, each pair one byte | yes |
\H\ | start highlighting | no — kept literally |
\N\ | normal text, ending highlighting | no — kept literally |
\Zdd..\ | locally defined, agreed between the two ends | no — kept literally |
\Cxxyy\ | switch to a single-byte character set | no — kept literally |
\Mxxyyzz\ | switch to a multi-byte character set | no — kept literally |
\.br\, \.sp 2\, … | formatted-text display commands | no — kept literally |
Used inside formatted-text (FT) fields.
| Command | Meaning |
|---|---|
.sp <n> | end the line and skip n vertical spaces |
.br | begin a new output line |
.fi | begin word wrap (the default) |
.nf | begin no-wrap |
.in <n> | indent by n spaces |
.ti <n> | temporarily indent n spaces |
.sk <n> | skip n spaces to the right |
.ce | centre the next line |
The sequences marked “no” say something about presentation or encoding that a plain string cannot carry. There are three options and only one of them is honest.
Loses information the sender sent.
Invents information the sender did not send.
The caller sees exactly what arrived. A caller who understands display commands can act on them; a caller who does not sees text that is at worst ugly rather than wrong.
use er7::{Separators, escape::unescape};
let separators = Separators::default();
// Sequences that stand for characters decode.
assert_eq!(unescape(r"Smith \T\ Jones", &separators), "Smith & Jones");
assert_eq!(unescape(r"a\F\b", &separators), "a|b");
assert_eq!(unescape(r"\X0D\", &separators), "\r");
assert_eq!(unescape(r"\X4142\", &separators), "AB");
// Everything else is kept exactly as written, and not damaged.
assert_eq!(unescape(r"line\.br\next", &separators), r"line\.br\next");
assert_eq!(unescape(r"\H\loud\N\", &separators), r"\H\loud\N\");
assert_eq!(unescape(r"a\Fb", &separators), r"a\Fb"); // unterminated \X..\ decodes only when its body is whole pairs of ASCII hexadecimal digits; \XZZ\ and \X123\ are kept literally, because a body that is not hex
was not hex data. Bytes are read as UTF-8 with the usual lossy replacement, which is the best a
receiver can do for a sender that meant some other repertoire.
unescape returns a borrowed string when the text contains no escape character at
all — the overwhelmingly common case, and free.
use er7::escape::escape;
assert_eq!(escape("Smith & Jones", &separators), r"Smith \T\ Jones");
assert_eq!(escape("a|b^c~d&e", &separators), r"a\F\b\S\c\R\d\T\e");
assert_eq!(escape(r"a\b", &separators), r"a\E\b");
assert_eq!(escape("line\r\nnext", &separators), r"line\X0D\\X0A\next"); Two details worth knowing:
\r and \n become \X0D\ and \X0A\. A literal carriage return in a value would end the segment and
truncate the message — the one corruption an ER7 writer must never commit.The truncation character (#, HL7 v2.7) is not encoded: it is structural
only inside MSH-2, so a # in a value is just a #.
Always encode when you write. Subcomponent::set does it for you.
Assigning the raw text directly with an unescaped & silently splits the
component the next time the message is parsed, shifting every value after it.
escapes is the layer both functions above are built on. It turns text into a
stream of classified tokens, which is what you want when you need more than “decode or
don’t”.
use er7::escape::{escapes, Escape};
let tokens: Vec<_> = escapes(r"Dr\S\Who\.br\", &separators).collect();
assert_eq!(tokens, vec![
Escape::Text("Dr"),
Escape::Component,
Escape::Text("Who"),
Escape::Formatting("br"),
]); Two properties make it usable as a foundation:
Unknown or Unterminated, not an error.The crate will not render \.br\ for you — that is a presentation concern and out
of scope — but the token stream gives you everything needed to do it yourself.
HL7 scopes escaping to ST, TX, and FT fields and to the
fourth component of ED. This crate decodes sequences wherever they
appear, because applying the standard’s scope requires knowing each
field’s data type, and that requires a dictionary this crate deliberately does not have.
The risk is a false positive on a value that legitimately contains a backslash in a field where escaping does not apply. The mitigations: unrecognized sequences stay literal, so such a value usually round-trips unchanged anyway; and the raw text is always available, so a caller who knows the data type can override.