defmodule HL7.Segment do
require Logger
@moduledoc """
Filter, parse and modify individual HL7 segments and their fragments.
"""
@type raw_hl7 :: String.t() | HL7.RawMessage.t()
@type fragment_hl7 :: String.t() | [list() | String.t()]
@type segment_hl7 :: [fragment_hl7()]
@type parsed_hl7 :: [segment_hl7()] | HL7.Message.t()
@type content_hl7 :: raw_hl7() | parsed_hl7()
@doc ~S"""
Updates content within a parsed HL7 segment, returning a modified segment whose data has been transformed at the given
indices (starting at 1 as with HL7's convention). The `transform` can be either a `string`, `list` or `fn old_data -> new_data`.
"""
@spec replace_part(
segment_hl7(),
fragment_hl7() | (fragment_hl7() -> fragment_hl7()),
pos_integer(),
pos_integer() | nil,
pos_integer() | nil,
pos_integer() | nil
) :: segment_hl7() | String.t()
def replace_part(
segment,
transform,
field,
repetition \\ nil,
component \\ nil,
subcomponent \\ nil
)
def replace_part(segment, transform, field, repetition, component, subcomponent)
when is_integer(field) and is_function(transform, 1) do
indices = to_indices(field, repetition, component, subcomponent)
replace_fragment(segment, indices, transform, true)
end
def replace_part(segment, transform, field, repetition, component, subcomponent)
when is_integer(field) and (is_binary(transform) or is_list(transform)) do
indices = to_indices(field, repetition, component, subcomponent)
replace_fragment(segment, indices, fn _data -> transform end, true)
end
@doc false
# used by HL7.Message to update segments with list-based indices
@spec replace_fragment(list() | String.t(), list(), function(), boolean()) ::
list() | String.t()
def replace_fragment(data, [], transform, is_field) when is_function(transform, 1) do
transform.(data) |> unwrap_binary_field(is_field)
end
def replace_fragment(data, [i | remaining_indices], transform, is_field)
when is_binary(data) and is_integer(i) and is_function(transform, 1) do
[data | empty_string_list(i)]
|> List.update_at(0, fn d ->
replace_fragment(d, remaining_indices, transform, false) |> unwrap_binary_field(is_field)
end)
|> Enum.reverse()
end
def replace_fragment(data, [i | remaining_indices], transform, is_field)
when is_list(data) and is_integer(i) and is_function(transform, 1) do
count = Enum.count(data)
case i < count do
true ->
List.update_at(data, i, fn d ->
replace_fragment(d, remaining_indices, transform, false)
|> unwrap_binary_field(is_field)
end)
false ->
data
|> Enum.reverse()
|> empty_string_list(i - count + 1)
|> List.update_at(0, fn d ->
replace_fragment(d, remaining_indices, transform, false)
|> unwrap_binary_field(is_field)
end)
|> Enum.reverse()
end
end
@doc false
def get_part_by_indices(data, []) do
data
end
def get_part_by_indices(data, [i | remaining_indices]) do
case data do
nil ->
data
_ when is_binary(data) ->
data
_ when is_integer(i) and is_list(data) ->
Enum.at(data, i) |> get_part_by_indices(remaining_indices)
end
end
@doc ~S"""
Extracts content from a parsed HL7 segment,
returning nested data by applying each supplied index in turn. Please note that HL7 indices start at 1.
"""
@spec get_part(
segment_hl7() | fragment_hl7(),
field :: pos_integer(),
repetition :: pos_integer() | nil,
component :: pos_integer() | nil,
subcomponent :: pos_integer() | nil
) :: nil | list() | binary()
def get_part(data, field, repetition \\ nil, component \\ nil, subcomponent \\ nil)
when is_integer(field) and
(is_integer(repetition) or is_nil(repetition)) and
(is_integer(component) or is_nil(component)) and
(is_integer(subcomponent) or is_nil(subcomponent)) do
indices = to_indices(field, repetition, component, subcomponent)
get_part_by_indices(data, indices)
end
def leftmost_value([]) do
nil
end
def leftmost_value([h | _]) do
leftmost_value(h)
end
def leftmost_value(d) do
d
end
@spec unwrap_binary_field(list() | String.t(), boolean()) :: list() | String.t()
defp unwrap_binary_field([text], _is_field = true) when is_binary(text) do
text
end
defp unwrap_binary_field(data, is_field) when is_boolean(is_field) do
data
end
@spec empty_string_list(non_neg_integer()) :: [String.t()]
defp empty_string_list(n) when is_integer(n) do
empty_string_list([], n)
end
@spec empty_string_list([String.t()], non_neg_integer()) :: [String.t()]
defp empty_string_list(list, 0) do
list
end
defp empty_string_list(list, n) do
empty_string_list(["" | list], n - 1)
end
defp to_indices(field, repetition, component, subcomponent) do
[field + 1, repetition, component, subcomponent]
|> Enum.take_while(fn i -> i != nil end)
|> Enum.map(fn i -> i - 1 end)
end
end