Packages
hl7v2
3.5.0
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/type/nm.ex
defmodule HL7v2.Type.NM do
@moduledoc """
Numeric (NM) -- HL7v2 primitive data type.
Format: `[+|-]digits[.digits]`. Max 16 characters.
Parses to a `%NM{}` struct that holds both a normalized `value` (for
computation) and the `original` wire string (for lossless round-trip).
`encode/1` emits `original` when present so that parse-then-encode
preserves the exact wire format. Programmatically-built structs
(where `original` is nil) fall back to emitting `value`.
"""
@behaviour HL7v2.Type
defstruct [:value, :original]
@type t :: %__MODULE__{
value: binary(),
original: binary() | nil
}
# Pattern: optional sign, at least one digit before optional decimal point
@numeric_pattern ~r/\A[+-]?\d+(\.\d+)?\z/
@doc """
Parses a numeric string into a `%NM{}` struct.
Returns `nil` for empty/nil input. Leading/trailing whitespace is stripped.
The `value` field holds the normalized form (leading zeros, trailing decimal
zeros and bare `+` stripped). The `original` field preserves the raw input
for lossless round-trip encoding.
## Examples
iex> HL7v2.Type.NM.parse("123")
%HL7v2.Type.NM{value: "123", original: "123"}
iex> HL7v2.Type.NM.parse("-45.67")
%HL7v2.Type.NM{value: "-45.67", original: "-45.67"}
iex> HL7v2.Type.NM.parse("+01.20")
%HL7v2.Type.NM{value: "1.2", original: "+01.20"}
iex> HL7v2.Type.NM.parse("")
nil
iex> HL7v2.Type.NM.parse(nil)
nil
"""
@spec parse(binary() | nil) :: t() | nil
def parse(nil), do: nil
def parse(""), do: nil
def parse(value) when is_binary(value) do
trimmed = String.trim(value)
if Regex.match?(@numeric_pattern, trimmed) do
# Preserve the raw wire value (with any whitespace) for lossless round-trip.
# Normalize only the trimmed form for programmatic access.
%__MODULE__{value: normalize(trimmed), original: value}
else
nil
end
end
@doc """
Encodes a numeric value back to a string.
For `%NM{}` structs, emits `original` when present (preserving the wire
format), falling back to `value` when `original` is nil (programmatically
built values).
Also accepts plain strings and numbers for backward compatibility.
## Examples
iex> HL7v2.Type.NM.encode(%HL7v2.Type.NM{value: "1.2", original: "+01.20"})
"+01.20"
iex> HL7v2.Type.NM.encode(%HL7v2.Type.NM{value: "42"})
"42"
iex> HL7v2.Type.NM.encode("123")
"123"
iex> HL7v2.Type.NM.encode(nil)
""
"""
@spec encode(t() | binary() | number() | nil) :: binary()
def encode(nil), do: ""
def encode(%__MODULE__{original: original, value: value}), do: original || value
def encode(value) when is_binary(value), do: value
def encode(value) when is_integer(value), do: Integer.to_string(value)
def encode(value) when is_float(value), do: normalize(Float.to_string(value))
defp normalize(str) do
{sign, rest} =
case str do
"+" <> r -> {"", r}
"-" <> r -> {"-", r}
r -> {"", r}
end
normalized =
case String.split(rest, ".", parts: 2) do
[integer_part] ->
strip_leading_zeros(integer_part)
[integer_part, decimal_part] ->
int = strip_leading_zeros(integer_part)
dec = strip_trailing_zeros(decimal_part)
if dec == "" do
int
else
int <> "." <> dec
end
end
# Drop sign for zero values
if sign == "-" and normalized == "0" do
"0"
else
sign <> normalized
end
end
defp strip_leading_zeros(""), do: "0"
defp strip_leading_zeros("0"), do: "0"
defp strip_leading_zeros(str) do
stripped = String.replace_leading(str, "0", "")
if stripped == "", do: "0", else: stripped
end
defp strip_trailing_zeros(str) do
String.replace_trailing(str, "0", "")
end
end