Packages
hl7v2
3.3.5
3.10.1
3.9.0
3.8.0
3.7.0
3.6.0
3.5.0
3.4.0
3.3.6
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
2.11.0
2.10.0
2.9.1
2.9.0
2.8.2
2.8.1
2.8.0
2.7.1
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
1.4.6
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.6.0
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.1.0
Pure Elixir HL7 v2.x toolkit — schema-driven parsing, typed segments, message builder, MLLP transport
Current section
Files
Jump to
Current section
Files
lib/hl7v2/escape.ex
defmodule HL7v2.Escape do
@moduledoc """
Encodes and decodes HL7v2 escape sequences.
Delimiter characters appearing in field data must be escaped so they are not
confused with structural separators. The standard escape sequences are:
| Sequence | Meaning |
|------------|-----------------------------------|
| `\\F\\` | Field separator (default `|`) |
| `\\S\\` | Component separator (default `^`) |
| `\\T\\` | Sub-component separator (`&`) |
| `\\R\\` | Repetition separator (`~`) |
| `\\E\\` | Escape character (`\\`) |
| `\\Xdd\\` | Hexadecimal data |
| `\\.br\\` | Line break |
Unrecognized escape sequences are passed through unchanged.
"""
alias HL7v2.Separator
@doc """
Decodes HL7v2 escape sequences in `text`, replacing them with literal characters.
## Examples
iex> sep = HL7v2.Separator.default()
iex> HL7v2.Escape.decode("foo\\\\F\\\\bar", sep)
"foo|bar"
iex> sep = HL7v2.Separator.default()
iex> HL7v2.Escape.decode("line1\\\\.br\\\\line2", sep)
"line1\\r\\nline2"
"""
@spec decode(binary(), Separator.t()) :: binary()
def decode(text, %Separator{} = sep) do
esc = sep.escape
do_decode(text, esc, sep, [])
end
defp do_decode(<<>>, _esc, _sep, acc), do: acc |> Enum.reverse() |> IO.iodata_to_binary()
defp do_decode(<<esc, rest::binary>>, esc, sep, acc) do
case extract_sequence(rest, esc) do
{:ok, seq, remainder} ->
decoded = decode_sequence(seq, sep)
do_decode(remainder, esc, sep, [decoded | acc])
:no_closing ->
# No closing escape char — treat the escape literally
do_decode(rest, esc, sep, [<<esc>> | acc])
end
end
defp do_decode(<<byte, rest::binary>>, esc, sep, acc) do
do_decode(rest, esc, sep, [<<byte>> | acc])
end
defp extract_sequence(data, esc) do
case :binary.match(data, <<esc>>) do
{pos, 1} ->
seq = binary_part(data, 0, pos)
remainder = binary_part(data, pos + 1, byte_size(data) - pos - 1)
{:ok, seq, remainder}
:nomatch ->
:no_closing
end
end
defp decode_sequence("F", sep), do: <<sep.field>>
defp decode_sequence("S", sep), do: <<sep.component>>
defp decode_sequence("T", sep), do: <<sep.sub_component>>
defp decode_sequence("R", sep), do: <<sep.repetition>>
defp decode_sequence("E", sep), do: <<sep.escape>>
defp decode_sequence(".br", _sep), do: "\r\n"
defp decode_sequence(<<".sp", rest::binary>>, _sep) do
n = parse_space_count(rest)
String.duplicate(" ", n)
end
defp decode_sequence(<<"X", hex::binary>>, _sep) do
decode_hex(hex)
end
# Unrecognized sequences: pass through with escape delimiters
defp decode_sequence(seq, sep) do
<<sep.escape>> <> seq <> <<sep.escape>>
end
defp parse_space_count(""), do: 1
defp parse_space_count(" " <> rest), do: parse_space_count(rest)
defp parse_space_count(s) do
case Integer.parse(String.trim(s)) do
{n, _} when n > 0 -> n
_ -> 1
end
end
defp decode_hex(hex) do
hex
|> String.upcase()
|> do_decode_hex([])
end
defp do_decode_hex(<<>>, acc), do: acc |> Enum.reverse() |> IO.iodata_to_binary()
defp do_decode_hex(<<hi, lo, rest::binary>>, acc) do
case {hex_to_int(hi), hex_to_int(lo)} do
{nil, _} -> acc |> Enum.reverse() |> IO.iodata_to_binary()
{_, nil} -> acc |> Enum.reverse() |> IO.iodata_to_binary()
{h, l} -> do_decode_hex(rest, [<<h * 16 + l>> | acc])
end
end
# Odd-length hex — ignore trailing nibble
defp do_decode_hex(<<_>>, acc), do: acc |> Enum.reverse() |> IO.iodata_to_binary()
defp hex_to_int(c) when c in ?0..?9, do: c - ?0
defp hex_to_int(c) when c in ?A..?F, do: c - ?A + 10
defp hex_to_int(c) when c in ?a..?f, do: c - ?a + 10
defp hex_to_int(_), do: nil
@doc """
Encodes delimiter characters in `text` as HL7v2 escape sequences.
This is the inverse of `decode/2`. Characters matching any of the separator's
delimiters are replaced with their escape sequence equivalents.
## Examples
iex> sep = HL7v2.Separator.default()
iex> HL7v2.Escape.encode("pipe|here", sep)
"pipe\\\\F\\\\here"
iex> sep = HL7v2.Separator.default()
iex> HL7v2.Escape.encode("no specials", sep)
"no specials"
"""
@spec encode(binary(), Separator.t()) :: binary()
def encode(text, %Separator{} = sep) do
do_encode(text, sep, [])
end
defp do_encode(<<>>, _sep, acc), do: acc |> Enum.reverse() |> IO.iodata_to_binary()
defp do_encode(<<byte, rest::binary>>, sep, acc) do
replacement =
cond do
byte == sep.escape -> [<<sep.escape>>, "E", <<sep.escape>>]
byte == sep.field -> [<<sep.escape>>, "F", <<sep.escape>>]
byte == sep.component -> [<<sep.escape>>, "S", <<sep.escape>>]
byte == sep.sub_component -> [<<sep.escape>>, "T", <<sep.escape>>]
byte == sep.repetition -> [<<sep.escape>>, "R", <<sep.escape>>]
true -> <<byte>>
end
do_encode(rest, sep, [replacement | acc])
end
end