Packages
hl7v2
3.3.3
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/separator.ex
defmodule HL7v2.Separator do
@moduledoc """
Detects and manages HL7v2 message delimiters from MSH-1/MSH-2.
Every HL7v2 message declares its delimiter set in the first 9 characters of the
MSH segment: `MSH` (3 chars) + field separator (MSH-1, 1 char) + encoding
characters (MSH-2, 4 or 5 chars). The defaults are `|^~\\&` but the standard
allows any single-byte characters.
HL7 v2.7+ allows an optional 5th encoding character: the **truncation character**.
When present (e.g., `^~\\&#`), it indicates that field values ending with this
character were truncated. It is a display hint, not a delimiter.
## Examples
iex> HL7v2.Separator.default()
%HL7v2.Separator{field: ?|, component: ?^, repetition: ?~, escape: ?\\\\, sub_component: ?&, truncation: nil, segment: ?\\r}
iex> {:ok, sep} = HL7v2.Separator.from_msh("MSH|^~\\\\&|SENDING_APP|")
iex> sep.field
?|
iex> {:ok, sep} = HL7v2.Separator.from_msh("MSH|^~\\\\&#|SENDING_APP|")
iex> sep.truncation
?#
"""
defstruct field: ?|,
component: ?^,
repetition: ?~,
escape: ?\\,
sub_component: ?&,
truncation: nil,
segment: ?\r
@type t :: %__MODULE__{
field: non_neg_integer(),
component: non_neg_integer(),
repetition: non_neg_integer(),
escape: non_neg_integer(),
sub_component: non_neg_integer(),
truncation: non_neg_integer() | nil,
segment: non_neg_integer()
}
@doc """
Returns the default HL7v2 delimiter set (`|^~\\&` with CR segment terminator, no truncation character).
"""
@spec default() :: t()
def default, do: %__MODULE__{}
@doc """
Extracts delimiters from an MSH header binary.
MSH-1 is the single character immediately after `"MSH"` (the field separator).
MSH-2 is the next 4 characters (component, repetition, escape, sub-component),
written as a literal string and NOT delimited. In HL7 v2.7+, a 5th character
(truncation) may follow. The truncation character is distinguished from the
start of the next field by checking whether the 5th byte equals the field
separator: if it does not, it is the truncation character.
Returns `{:ok, separator}` or `{:error, reason}`.
## Examples
iex> {:ok, sep} = HL7v2.Separator.from_msh("MSH|^~\\\\&|SendApp|")
iex> sep.field
?|
iex> sep.component
?^
iex> {:ok, sep} = HL7v2.Separator.from_msh("MSH|^~\\\\&#|SendApp|")
iex> sep.truncation
?#
"""
@spec from_msh(binary()) :: {:ok, t()} | {:error, term()}
# Extended: 5 encoding chars with truncation character (v2.7+).
# The 5th byte after MSH-1 is a truncation char if it is NOT the field separator.
# The byte after the truncation char must be the field separator or end of input.
def from_msh(<<"MSH", field, c, r, e, s, t, rest::binary>>) when t != field do
case rest do
<<next, _::binary>> when next != field ->
# 6+ encoding characters — overlong, reject
{:error, :invalid_encoding_characters}
_ ->
build_separator(field, c, r, e, s, t)
end
end
# Standard: 4 encoding chars (MSH-2). The next byte must be the field separator
# or the message ends right after the encoding characters.
def from_msh(<<"MSH", field, c, r, e, s, rest::binary>>) do
case rest do
"" ->
build_separator(field, c, r, e, s, nil)
<<^field, _::binary>> ->
build_separator(field, c, r, e, s, nil)
_ ->
# Next byte is not the field separator and was not caught by the
# truncation clause above — means it equals the field separator in
# a duplicate-delimiter scenario, or something else is wrong.
# This case is unreachable in practice because the truncation clause
# above handles t != field, but we keep it for safety.
{:error, :invalid_encoding_characters}
end
end
def from_msh(<<"MSH", _::binary>>) do
{:error, :insufficient_encoding_characters}
end
def from_msh(_) do
{:error, :not_msh}
end
defp build_separator(field, c, r, e, s, t) do
encoding = [c, r, e, s | if(t, do: [t], else: [])]
cond do
field in encoding ->
# An encoding character equals the field separator — this means
# the MSH-2 declaration is effectively too short or malformed.
{:error, :invalid_encoding_characters}
length(encoding) != length(Enum.uniq(encoding)) ->
{:error, :duplicate_delimiters}
true ->
{:ok,
%__MODULE__{
field: field,
component: c,
repetition: r,
escape: e,
sub_component: s,
truncation: t,
segment: ?\r
}}
end
end
@doc """
Returns the encoding characters string (MSH-2 value) for this separator set.
When a truncation character is present (v2.7+), the returned string is 5 characters.
## Examples
iex> HL7v2.Separator.encoding_characters(HL7v2.Separator.default())
"^~\\\\&"
"""
@spec encoding_characters(t()) :: binary()
def encoding_characters(%__MODULE__{truncation: nil} = sep) do
<<sep.component, sep.repetition, sep.escape, sep.sub_component>>
end
def encoding_characters(%__MODULE__{truncation: t} = sep) when not is_nil(t) do
<<sep.component, sep.repetition, sep.escape, sep.sub_component, t>>
end
end