Current section

29 Versions

Jump to

Compare versions

7 files changed
+385 additions
-329 deletions
  @@ -1,213 +1,34 @@
1 1 # Elixir HL7
2 +
2 3 [![hex.pm version](https://img.shields.io/hexpm/v/elixir_hl7.svg)](https://hex.pm/packages/elixir_hl7)
3 4 [![hex.pm downloads](https://img.shields.io/hexpm/dt/elixir_hl7.svg)](https://hex.pm/packages/elixir_hl7)
4 5 [![hex.pm license](https://img.shields.io/hexpm/l/elixir_hl7.svg)](https://hex.pm/packages/elixir_hl7)
5 6
6 - An Elixir library for working with HL7 v2.x healthcare data
7 + An Elixir library for working with HL7 v2.x healthcare data.
7 8
8 9 Elixir HL7 provides functions to parse, query and modify healthcare data that conforms to the HL7 v2.x standards.
9 - It should be able to reconstruct any HL7 Message without data loss or corruption.
10 + It should be able to reconstruct any HL7 message without data loss or corruption.
10 11
11 12 It also provides basic support for reading HL7 file streams with configurable delimiters (MLLP included).
12 13
13 - This library has been tested on a fairly wide variety of real-world HL7 messages to ensure correctness and flexibility.
14 + This library has been tested on a wide variety of real-world HL7 messages to ensure correctness and flexibility.
15 +
16 + Since many HL7 messages do not strictly conform to the standards specified for each version, the library does not
17 + attempt to enforce limits such as character counts or structural expectations.
18 +
19 + In fact, HL7 uses implicit hierarchies within segments (by leaving out certain separators) and to group segments
20 + (via expected patterns only known to the consuming application).
21 +
22 + The majority of the API currently resides in the eponymous `HL7` module.
14 23
15 24 You can learn more about HL7 here:
25 + * Wikipedia's [HL7 article](https://en.wikipedia.org/wiki/Health_Level_7)
16 26 * The official HL7 website ([hl7.org](http://www.hl7.org/index.cfm))
17 - * Wikipedia's [HL7 article](https://en.wikipedia.org/wiki/Health_Level_7)
27 +
28 + Full documentation is available at [hex.pm](https://hexdocs.pm/elixir_hl7/readme.html).
18 29
19 30 Please [report an issue](https://github.com/HCA-Healthcare/elixir-hl7/issues) if something appears to be handled incorrectly.
20 31
21 - > ### Note {: .warning}
22 - >
23 - >
24 - > We are building a simpler and more Elixir-friendly API for this library, centered on
25 - > the `HL7` and `HL7.Path` modules.
26 - >
27 - > The new API will lead to the deprecation of `HL7.Query` and related code.
28 - >
29 - > These will likely not be removed for some time, and their
30 - > removal will coincide with a major version release. For now, the two systems can exchange data when needed.
31 -
32 - ## Getting started
33 -
34 - Add this library to your mix.exs file:
35 -
36 - ```elixir
37 - defp deps do
38 - [{:elixir_hl7, "~> 0.9.2"}]
39 - end
40 - ```
41 -
42 - ## Use the Tabs Below to Explore the Library
43 -
44 - <!-- tabs-open -->
45 -
46 - ### Examples
47 -
48 - The `HL7.Examples` module contains functions with sample data that you can use in unit tests or to explore the API,
49 -
50 - iex> hl7_text = HL7.Examples.wikipedia_sample_hl7()
51 - ...> to_string()
52 - ...> Enum.take(30)
53 - "ADT"
54 -
55 - ### Route
56 -
57 - HL7 messages can be minimally validated to gather header (MSH segment) information for quick routing and acknowledgements.
58 -
59 - iex> hl7_text = HL7.Examples.wikipedia_sample_hl7()
60 - ...> raw_msg = HL7.Message.raw(hl7_text)
61 - ...> raw_msg.header.message_type
62 - "ADT"
63 -
64 - See the `HL7.RawMessage` and `HL7.Header` modules for more information.
65 -
66 - ### Parse
67 -
68 - HL7 messages can be fully parsed into lists of lists and strings to provide a compact representation of the message structure.
69 -
70 - One could grab the 2nd segment in a message:
71 -
72 - iex> HL7.Examples.wikipedia_sample_hl7()
73 - ...> |> HL7.Message.to_list()
74 - ...> |> Enum.at(1)
75 - ["EVN", "", "200605290901", "", "", "", "200605290900"]
76 -
77 - Or get the field value of RXA-5.2 (finding the first RXA segment's 5th field, 1st repetition, 2nd component):
78 -
79 - iex> HL7.Examples.nist_immunization_hl7()
80 - ...> |> HL7.Message.find("RXA")
81 - ...> |> HL7.Segment.get_part(5, 1, 2)
82 - "Influenza"
83 -
84 - It's also possible to modify the data within a segment (but it is much easier to manipulate messages using the `HL7.Query` module):
85 -
86 - iex> HL7.Examples.wikipedia_sample_hl7()
87 - ...> |> HL7.Message.find("EVN")
88 - ...> |> HL7.Segment.replace_part("extra data", 5, 2, 1)
89 - [
90 - "EVN",
91 - "",
92 - "200605290901",
93 - "",
94 - "",
95 - ["", "", ["", "extra data"]],
96 - "200605290900"
97 - ]
98 -
99 - See the `HL7.Message` and `HL7.Segment` modules for more information.
100 -
101 - ### Query
102 -
103 - Advanced manipulation and analysis of HL7 messages can be performed with the `HL7.Query` module.
104 -
105 - It supports a pipeline-friendly API modeled after jQuery and D3, allowing set-based document operations and queries.
106 -
107 - Messages can be broken into groups using a segment selector (similar to a CSS selector string) that denotes optional and repeating segments in potentially nested hierarchies.
108 -
109 - Individual segments can be decomposed using a field selector to reference specific field, repetition, component and subcomponent indices.
110 -
111 - Note that all `HL7.Query` selections implicitly retain the entire message structure such that elements can be selected, modified and then used to reconstruct a new HL7 message.
112 -
113 - The act of selecting something never modifies the content of a message. Applying other methods to a selection, such as delete, filter, append, etc., does modify the actual message content.
114 -
115 - For instance, this would select all textual diagnoses (DG1-3.2) associated with a patient visit (PV1):
116 -
117 - iex> import HL7.Query
118 - iex> HL7.Examples.nist_syndromic_hl7()
119 - ...> |> select("PV1 [{DG1}]")
120 - ...> |> select("DG1")
121 - ...> |> find_all(~p"3.2")
122 - ["Cryptosporidiosis", "Dehydration", "Diarrhea"]
123 -
124 - The statement `select("PV1 [{DG1}]")` grabs a list of segment groups containing a PV1 segment and any DG1 segments that might follow it.
125 - Thus, we would select one group containing a PV1 segment and three DG1 segments.
126 -
127 - ```elixir
128 - [
129 - [ # 1 group of 4 segments
130 - ["PV1", "1", "(data truncated to fit)"],
131 - ["DG1", "1", "", [["0074", "Cryptosporidiosis", "I9CDX"]], "", "", "F"],
132 - ["DG1", "2", "", [["27651", "Dehydration", "I9CDX"]], "", "", "F"],
133 - ["DG1", "3", "", [["78791", "Diarrhea", "I9CDX"]], "", "", "F"]
134 - ]
135 - ]
136 - ```
137 -
138 - Then `select("DG1")` creates three groups of individual DG1 segments by searching within the confines of the prior selection.
139 -
140 - ```elixir
141 - [ # 3 groups of 1 segment each
142 - [["DG1", "1", "", [["0074", "Cryptosporidiosis", "I9CDX"]], "", "", "F"]],
143 - [["DG1", "2", "", [["27651", "Dehydration", "I9CDX"]], "", "", "F"]],
144 - [["DG1", "3", "", [["78791", "Diarrhea", "I9CDX"]], "", "", "F"]]
145 - ]
146 - ```
147 -
148 - Finally, `find_all(~p"3.2")` will return a flattened list of data containing the 3rd field, 1st repetition (the default), 2nd component for each
149 - selected segment.
150 -
151 - ```elixir
152 - ["Cryptosporidiosis", "Dehydration", "Diarrhea"]
153 - ```
154 -
155 - Alternately, one could select and remove every diagnosis tied to a patient visit and then output a modified HL7 message:
156 -
157 - iex> import HL7.Query
158 - iex> HL7.Examples.nist_syndromic_hl7()
159 - ...> |> select("PV1 [{DG1}]")
160 - ...> |> select("DG1")
161 - ...> |> delete()
162 - ...> |> to_string()
163 - "MSH|^~\\&||LakeMichMC^9879874000^NPI|||201204020040||ADT^A03^ADT_A03|NIST-SS-003.32|P|2.5.1|||||||||PH_SS-NoAck^SS Sender^2.16.840.1.114222.4.10.3^ISO\rEVN||201204020030|||||LakeMichMC^9879874000^NPI\rPID|1||33333^^^^MR||^^^^^^~^^^^^^S|||F||2106-3^^CDCREC|^^^^53217^^^^55089|||||||||||2186-5^^CDCREC\rPV1|1||||||||||||||||||33333_001^^^^VN|||||||||||||||||09||||||||201204012130\rOBX|1|CWE|SS003^^PHINQUESTION||261QE0002X^Emergency Care^NUCC||||||F\rOBX|2|NM|21612-7^^LN||45|a^^UCUM|||||F\rOBX|3|CWE|8661-1^^LN||^^^^^^^^Diarrhea, stomach pain, dehydration||||||F\r"
164 -
165 - The following query extracts each Common Order (ORC) group's OBX segments and outputs a list of each order's associated vaccine types.
166 -
167 - iex> import HL7.Query
168 - iex> HL7.Examples.nist_immunization_hl7()
169 - ...> |> select("ORC [RXA] [RXR] {OBX}")
170 - ...> |> filter(fn q -> find_first(q, ~p"3.2") == "vaccine type" end)
171 - ...> |> map(fn q -> find_all(q, ~p"5.2") end)
172 - [
173 - ["Influenza, unspecified formulation"],
174 - ["DTaP", "Polio", "Hep B, unspecified formulation"]
175 - ]
176 -
177 -
178 - See the `HL7.Query` module for more information.
179 -
180 -
181 -
182 - ## Create
183 -
184 - HL7 messages can be constructed from scratch with the `HL7.Message` module. Passing an `HL7.Header` struct to
185 - `HL7.Message.new/1` will produce a base message upon which you can add additional segments. These can be appended as list data.
186 -
187 - The final raw message can be produced by invoking the `to_string/1` protocol on either the `HL7.Query` or `HL7.Message` structs.
188 -
189 - <!-- tabs-close -->
190 -
191 -
192 - ## Files
193 -
194 - The `HL7` module contains utility functions to open file streams of HL7 message content with support for MLLP and standard `:line` storage.
195 - Other formats are somewhat supported by specifying expected prefix and suffix delimiters between messages.
196 -
197 - ## Sockets
198 -
199 - A separate library, Elixir-MLLP, exists to manage MLLP connections. MLLP is a simple protocol on top of TCP that is commonly used for sending and receiving HL7 messages.
200 -
201 - ## Status
202 -
203 - This project is approaching a v1.0 release. The API is mostly stable at this point.
204 -
205 - Also, please be aware of the details of the license (Apache 2.0).
206 -
207 - # Roadmap
208 -
209 - Extending the `HL7` base namespace to provide a simpler and more powerful API.
210 -
211 32 # License
212 33
213 - Elixir-HL7 source code is released under Apache 2 License. Check the LICENSE file for more information.
34 + Elixir HL7 source code is released under Apache 2 License. Check the LICENSE file for more information.
  @@ -1 +1 @@
1 - 0.9.3
\ No newline at end of file
1 + 0.10.0
\ No newline at end of file
  @@ -1,10 +1,10 @@
1 1 {<<"links">>,
2 2 [{<<"GitHub">>,<<"https://github.com/HCA-Healthcare/elixir-hl7">>}]}.
3 3 {<<"name">>,<<"elixir_hl7">>}.
4 - {<<"version">>,<<"0.9.3">>}.
4 + {<<"version">>,<<"0.10.0">>}.
5 5 {<<"description">>,
6 6 <<"An Elixir library for working with HL7 v2.x healthcare data">>}.
7 - {<<"elixir">>,<<"~> 1.14">>}.
7 + {<<"elixir">>,<<"~> 1.18">>}.
8 8 {<<"app">>,<<"elixir_hl7">>}.
9 9 {<<"files">>,
10 10 [<<"lib">>,<<"lib/hl7.ex">>,<<"lib/hl7">>,<<"lib/hl7/invalid_header.ex">>,
  @@ -1,50 +1,109 @@
1 1 defmodule HL7 do
2 2 @moduledoc """
3 - Functions to manipulate and query HL7 documents -- plus a way to handle HL7 file streams.
3 +
4 + Struct and functions for parsing and manipulating HL7 messages.
5 +
6 + This library specifically handles version 2.x of HL7 as it is by far the most prevalent format in production.
7 +
8 + Check out [HL7 on Wikipedia](https://en.wikipedia.org/wiki/Health_Level_7) for a decent overview of the format.
9 +
10 + Since HL7 messages often lack critical contextual metadata, this struct also contains a `tags` field for metadata support.
11 +
12 + Use `new/2` or `new!/2` to convert HL7 text to a parsed HL7 struct.
13 + This struct supports the `String.Chars` protocol such that `to_string/1` can be used to format the HL7 message text.
14 +
15 + To see the parsed representation, call `get_segments/1`.
16 +
17 + To query or update HL7 data, use the `sigil_p/2` macro to provide an `HL7.Path` with compile-time guarantees.
18 + For dynamic path access, use `HL7.Path.new/1` to construct paths on the fly.
19 + Note that HL7 path formats have been designed to reflect common industry usage.
20 +
21 + The `get/2`, `put/3`, and `update/4` functions are designed to query and manipulate HL7 data as an HL7 struct (containing a message),
22 + a list of segments, a single segment, a list of repetitions, or a single repetition. These should handle data as
23 + nested lists (automatically converted to 1-indexed maps), nested sparse maps (1-indexed), simple strings, or mixes of each.
24 +
25 + Use `set_segments/2` to fully replace the content an HL7 message.
26 +
27 + > ### Migrating from HL7.Message, HL7.Segment and HL7.Query {: .tip}
28 + > To migrate from the deprecated `HL7.Message` struct, use `HL7.new!/2` and `HL7.Message.new/2` to transform from one
29 + > struct to the other while preserving associated metadata tags.
30 + >
31 + > You can use `chunk_by_lead_segment/3` to generate segment groups to update code that relies on `HL7.Query` groupings.
32 + >
33 + > Any operations to otherwise query or modify HL7 data should be possible using
34 + > the `get/2`, `put/3`, `update!/3` and `update/4` functions.
35 + >
36 + > If you encounter other issues with feature parity, please open an issue!
37 +
38 + > ### String.Chars Protocol {: .tip}
39 + > You can use the `to_string()` implementation of the `String.Chars` protocol to quickly render HL7 structs as text.
40 +
4 41 """
5 42
6 - defstruct segments: []
43 + defstruct segments: [], tags: %{}
7 44
8 45 @buffer_size 32768
9 46 @type file_type_hl7 :: :mllp | :line | nil
10 47
11 48 @type hl7_map_data() :: %{optional(non_neg_integer) => hl7_map_data() | String.t()}
12 - @type hl7_list_data() :: String.t() | [hl7_list_data]
49 + @type hl7_list_data() :: String.t() | [hl7_list_data()]
13 50
14 51 @type segment() :: %{
15 52 0 => String.t(),
16 53 optional(pos_integer) => hl7_map_data() | String.t()
17 54 }
18 55
19 - @type t() :: %__MODULE__{segments: [segment()]}
56 + @type t() :: %__MODULE__{tags: map(), segments: [segment()]}
20 57
21 - @type parsed_hl7_segments :: t() | [segment()]
22 58 @type parsed_hl7 :: t() | segment() | [segment()] | hl7_map_data()
23 59
24 60 alias HL7.Path
25 61
26 62 @doc ~S"""
27 63 The `~p` sigil encodes an HL7 path into a struct at compile-time to guarantee correctness and speed.
28 - It is designed to work with data returned by `HL7.new!/1`.
64 + It is designed to work with data returned by `HL7.new!/1`, providing a standard way to get and update
65 + HL7 message content.
66 +
67 + > ### Importing Just the Sigil {: .tip}
68 + > Use `import HL7, only: :sigils` to access the `~p` sigil without importing the other `HL7` functions.
29 69
30 70 The full path structure in HL7 is expressed as:
31 71
32 72 `~p"SEGMENT_NAME[SEGMENT_NUMBER]-FIELD[REPETITION].COMPONENT.SUBCOMPONENT`
33 73
34 - `SEGMENT_NAME` -- 3 character string
35 - `SEGMENT_NUMBER` -- Positive integer in brackets, defaulting to 1. All segments of the name can be accessed with `[*]`
36 - `FIELD` -- Positive integer
37 - `REPETITION` -- Positive integer in brackets, defaulting to 1. All repetitions of the field can be accessed with `[*]`
38 - `COMPONENT` -- Positive integer
39 - `SUBCOMPONENT` -- Positive integer
74 + A trailing exclamation mark can be used in the path to the return only the leftmost text at the given level.
75 +
76 + Position Name | Valid Values
77 + ------------ | -------------
78 + `SEGMENT_NAME` | 3 character string
79 + `SEGMENT_NUMBER` | Positive integer in square brackets, defaults to 1. All segments of `SEGMENT_NAME` can be accessed with `[*]`
80 + `FIELD` | Positive integer
81 + `REPETITION` | Positive integer in square brackets, defaults to 1. All repetitions of the `FIELD` can be accessed with `[*]`
82 + `COMPONENT` | Positive integer
83 + `SUBCOMPONENT` | Positive integer
84 +
85 + > ### 1-based Indexes {: .warning}
86 + > To match industry expectations, HL7 uses 1-based indexes.
87 + > As noted above, it also includes defaults whereby all paths refer to the 1st segment and/or 1st repetition
88 + > of any query unless explicitly specified.
40 89
41 90 Example paths:
42 91
43 - `~p"OBX"` refers to the 1st OBX segment in its entirety, the same as `~p"OBX[1]"`.
44 - `~p"OBX-5"` means the 1st repetition of the 5th field of the 1st OBX segment.
45 - `~p"OBX[1]-5[1]"` is equivalent, the numbers in brackets specifying the default repetition and segment number values.
46 - `~p"OBX[2]-5"` thus means 1st repetition of the 5th field of the 2nd OBX segment.
47 - `~p"OBX[*]-5"` would get the 1st repetition of the 5th field of every OBX segment, returning a list of results.
92 + HL7 Path | Description
93 + `~p"OBX"` | The 1st OBX segment in its entirety, the same as `~p"OBX[1]"`.
94 + `~p"OBX-5"` | The 1st repetition of the 5th field of the 1st OBX segment.
95 + `~p"OBX[1]-5[1]"` | Same as above. The numbers in brackets specify the default repetition and segment number values.
96 + `~p"OBX-5[*]"` | Every repetition of the 5th field of the 1st OBX segment, returning a list of results.
97 + `~p"OBX[2]-5"` | The 1st repetition of the 5th field of the 2nd OBX segment.
98 + `~p"OBX[*]-5"` | The 1st repetition of the 5th field of every OBX segment, returning a list of results.
99 + `~p"OBX[*]-5[*]"` | Every repetition of the 5th field of every OBX segment, returning a nested list of results.
100 + `~p"OBX-5.2"` | The 2nd component of the 1st repetition of the 5th field of the 1st OBX segment.
101 + `~p"OBX-5.2.3"` | The 3rd subcomponent of the 2nd component of the 1st repetition of the 5th field of the 1st OBX segment.
102 + `~p"OBX[*]-5.2.3"` | Same as above, but returned as a list with a value for each OBX segment.
103 + `~p"OBX[*]-5[*].2.3"` | Same as above, but now a nested list of results for each repetition within each segment.
104 + `~p"OBX-5!"` | The `!` will take the first text (leftmost value) at whatever level is specified. Thus, `p"OBX-5!"` is equivalent to `p"OBX-5.1.1"`.
105 + `~p"5"` | The fifth field of a segment (parsed as an ordinal map starting with 0)
106 + `~p".2"` | The second component of a repetition (parsed as an ordinal map starting with 1)
48 107
49 108 Note that repetitions are uncommon in HL7 and the default of a 1st repetition is often just assumed.
50 109 `~p"PID-3"` is equivalent to `~p"PID-3[1]"` and is the most standard representation.
  @@ -53,61 +112,63 @@ defmodule HL7 do
53 112 be produced by selecting multiple segments and multiple repetitions with `~p"PID[*]-11[*]"`.
54 113
55 114 Components and subcomponents can also be accessed with the path structures.
56 - `h"OBX-2.3.1"` would return the 1st subcomponent of the 3rd component of the 2nd field of the 1st OBX.
115 + `p"OBX-2.3.1"` would return the 1st subcomponent of the 3rd component of the 2nd field of the 1st OBX.
116 +
117 + If dealing with segments or repetitions extracted from parsed HL7, you can use partial paths that
118 + lack the segment name and/or field like `~p"5"` for the fifth field of a segment or `~p".3"` for the 3rd
119 + component of a repetition.
57 120
58 121 Additionally, if a path might have additional data such that a string might be found at either
59 - `~p"OBX-2"` or `~p"OBX-2.1"` or even `~p"OBX-2.1.1"`, there is truncation character (the bang symbol) that
122 + `~p"OBX-2"` or `~p"OBX-2.1"` or even `~p"OBX-2.1.1"`, there is truncation character (`!`) that
60 123 will return the first element found in the HL7 text at the target specificity. Thus, `~p"OBX[*]-2!"`
61 124 would get the 1st piece of data in the 2nd field of every OBX whether it is a string or nested map.
62 125
63 - Lastly, when accessing data from a specific repetition, the path should begin with `.`; for example,
64 - when working with the Nth OBX segment and using `HL7.get/2` to access that segment's second field, first
65 - component, use the path `~p".2.1"`
126 + > ### Nil vs Empty String {: .tip}
127 + > Paths that query beyond the content of an HL7 document, e.g. asking for the 10th field of a segment with five fields,
128 + > will return `nil` as opposed to an empty string to indicate that the data does not exist. Adding the
129 + > trailing `!` to an `HL7.Path` will force all return values to be simple strings in cases where `nil` is not desired.
130 + > Note that it also returns the leftmost text at the path level, discarding extra data as noted above.
66 131
67 132 ## Examples
68 133
69 - iex> import HL7
70 - iex> HL7.Examples.wikipedia_sample_hl7()
71 - ...> |> HL7.new!()
72 - ...> |> HL7.get(~p"OBX-5")
134 + iex> import HL7, only: :sigils
135 + iex> HL7.Examples.wikipedia_sample_hl7() |> HL7.new!() |> HL7.get(~p"OBX-5")
73 136 "1.80"
74 137
75 138 iex> import HL7
76 - iex> HL7.Examples.wikipedia_sample_hl7()
77 - ...> |> HL7.new!()
78 - ...> |> HL7.get(~p"OBX[*]-5")
139 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"OBX-3")
140 + %{2 => "Body Height"}
141 +
142 + iex> import HL7
143 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"PID-11!")
144 + "260 GOODWIN CREST DRIVE"
145 +
146 + iex> import HL7
147 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"OBX[*]-5")
79 148 ["1.80", "79"]
80 149
81 150 iex> import HL7
82 - iex> HL7.Examples.wikipedia_sample_hl7()
83 - ...> |> HL7.new!()
84 - ...> |> HL7.get(~p"OBX[*]-2!")
151 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"OBX[*]-2!")
85 152 ["N", "NM"]
86 153
87 154 iex> import HL7
88 - iex> HL7.Examples.wikipedia_sample_hl7()
89 - ...> |> HL7.new!()
90 - ...> |> HL7.get(~p"PID-11[*].5")
155 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"PID-11[*].5")
91 156 ["35209", "35200"]
92 157
93 158 iex> import HL7
94 - iex> HL7.Examples.wikipedia_sample_hl7()
95 - ...> |> HL7.new!()
96 - ...> |> HL7.get(~p"PID[*]-11[*].5")
159 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"PID[*]-11[*].5")
97 160 [["35209", "35200"]]
98 161
99 162 iex> import HL7
100 - iex> HL7.Examples.wikipedia_sample_hl7()
101 - ...> |> HL7.new!()
102 - ...> |> HL7.get(~p"PID-11[2].1")
163 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"PID-11[2].1")
103 164 "NICKELL’S PICKLES"
104 165
105 166 iex> import HL7
106 167 iex> HL7.Examples.wikipedia_sample_hl7()
107 - ...> |> HL7.new!()
108 - ...> |> HL7.get(~p"PID-11[*]")
168 + ...> |> new!()
169 + ...> |> get(~p"PID-11[*]")
109 170 ...> |> List.last()
110 - ...> |> HL7.get(~p".1")
171 + ...> |> get(~p".1")
111 172 "NICKELL’S PICKLES"
112 173 """
113 174 defmacro sigil_p({:<<>>, _, [path]}, _modifiers) do
  @@ -117,10 +178,23 @@ defmodule HL7 do
117 178 end
118 179
119 180 @doc ~S"""
120 - Creates an HL7 struct from HL7 data (accepting text, lists or the deprecated `HL7.Message` struct).
181 + Creates an HL7 struct from valid HL7 data (accepting text, lists or the deprecated `HL7.Message` struct).
182 + Raises with a `RuntimeError` if the data is not valid HL7.
183 +
184 + ## Examples
185 +
186 + iex> import HL7
187 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!()
188 + #HL7<with 8 segments>
189 +
121 190 """
122 - @spec new!(hl7_list_data() | String.t() | HL7.Message.t() | [segment()]) :: t()
123 - def new!(segments) when is_list(segments) do
191 + @spec new!(list() | String.t() | HL7.Message.t(), Keyword.t()) :: t()
192 + def new!(message_content, options \\ []) do
193 + do_new!(message_content, apply_default_options(message_content, options))
194 + end
195 +
196 + @spec do_new!(list() | String.t() | HL7.Message.t(), Keyword.t()) :: t()
197 + defp do_new!(segments, options) when is_list(segments) do
124 198 segments =
125 199 Enum.map(
126 200 segments,
  @@ -130,25 +204,95 @@ defmodule HL7 do
130 204 end
131 205 )
132 206
133 - %__MODULE__{segments: segments}
207 + %__MODULE__{segments: segments, tags: options[:tags] || %{}}
134 208 end
135 209
136 - def new!(text) when is_binary(text) do
137 - text |> HL7.Message.to_list() |> new!()
210 + defp do_new!(text, options) when is_binary(text) do
211 + text |> HL7.Message.to_list() |> new!(options)
138 212 end
139 213
140 - def new!(%HL7.Message{} = message) do
141 - message |> HL7.Message.to_list() |> new!()
214 + defp do_new!(%HL7.Message{} = message, options) do
215 + message |> HL7.Message.to_list() |> new!(options)
142 216 end
143 217
144 - @spec new(String.t()) :: {:ok, t()} | {:error, HL7.InvalidMessage.t()}
145 - def new(text) when is_binary(text) do
146 - case HL7.Message.new(text) do
147 - %HL7.Message{} = message -> {:ok, message |> HL7.Message.to_list() |> new!()}
148 - invalid_message -> {:error, invalid_message}
218 + @doc ~S"""
219 + Creates an HL7 struct from valid HL7 data (accepting text, lists or the deprecated `HL7.Message` struct).
220 + Returns `{:ok, HL7.t()}` if successful, `{:error, HL7.InvalidMessage.t()}` otherwise.
221 + """
222 + @spec new(String.t(), Keyword.t()) :: {:ok, t()} | {:error, HL7.InvalidMessage.t()}
223 + def new(text, options \\ []) when is_binary(text) do
224 + case HL7.Message.new(text, Keyword.take(options, [:copy, :validate_string]) |> Map.new()) do
225 + %HL7.Message{} = message ->
226 + {:ok, HL7.new!(message, options)}
227 +
228 + invalid_message ->
229 + {:error, invalid_message}
149 230 end
150 231 end
151 232
233 + @doc ~S"""
234 + Puts data within an `HL7` struct, parsed segments or repetitions
235 + using an `HL7.Path` struct (see `sigil_p/2`).
236 +
237 + ## Examples
238 +
239 + Put field data as a string.
240 +
241 + iex> import HL7
242 + iex> HL7.Examples.wikipedia_sample_hl7()
243 + ...> |> new!()
244 + ...> |> put(~p"PID-8", "F")
245 + ...> |> get(~p"PID-8")
246 + "F"
247 +
248 +
249 + Put field data as a string overwriting all repetitions.
250 +
251 + iex> import HL7
252 + iex> HL7.Examples.wikipedia_sample_hl7()
253 + ...> |> new!()
254 + ...> |> put(~p"PID-11[*]", "SOME_ID")
255 + ...> |> get(~p"PID-11[*]")
256 + ["SOME_ID"]
257 +
258 + Put field data into a single repetition.
259 +
260 + iex> import HL7
261 + iex> HL7.Examples.wikipedia_sample_hl7()
262 + ...> |> new!()
263 + ...> |> put(~p"PID-3[2]", ["a", "b", "c"])
264 + ...> |> get(~p"PID-3[2].3")
265 + "c"
266 +
267 + Put component data across multiple repetitions.
268 +
269 + iex> import HL7
270 + iex> HL7.Examples.wikipedia_sample_hl7()
271 + ...> |> new!()
272 + ...> |> put(~p"PID-11[*].3", "SOME_PLACE")
273 + ...> |> get(~p"PID-11[*].3")
274 + ["SOME_PLACE", "SOME_PLACE"]
275 +
276 + Put data in a segment using just the path to a field.
277 +
278 + iex> import HL7
279 + iex> HL7.Examples.wikipedia_sample_hl7()
280 + ...> |> new!()
281 + ...> |> get(~p"PID")
282 + ...> |> put(~p"3", "SOME_ID")
283 + ...> |> get(~p"3")
284 + "SOME_ID"
285 +
286 + Put data across multiple segments
287 +
288 + iex> import HL7
289 + iex> HL7.Examples.wikipedia_sample_hl7()
290 + ...> |> new!()
291 + ...> |> put(~p"OBX[*]-5", "REDACTED")
292 + ...> |> get(~p"OBX[*]-5")
293 + ["REDACTED", "REDACTED"]
294 +
295 + """
152 296 @spec put(parsed_hl7(), Path.t(), String.t() | nil | hl7_map_data()) :: parsed_hl7()
153 297 def put(%HL7{segments: segments} = hl7, %Path{} = path, value) do
154 298 %HL7{hl7 | segments: put(segments, path, value)}
  @@ -158,6 +302,13 @@ defmodule HL7 do
158 302 segment_data |> do_put(path, value)
159 303 end
160 304
305 + @doc ~S"""
306 + Updates data within an `HL7` struct, parsed segments, or repetitions
307 + using an `HL7.Path` struct (see `sigil_p/2`).
308 + """
309 + @spec update(parsed_hl7(), Path.t(), String.t() | nil | hl7_map_data(), (hl7_map_data() ->
310 + hl7_map_data())) ::
311 + parsed_hl7()
161 312 def update(%HL7{segments: segments} = hl7, %Path{} = path, default, fun) do
162 313 %HL7{hl7 | segments: update(segments, path, default, fun)}
163 314 end
  @@ -166,6 +317,12 @@ defmodule HL7 do
166 317 segment_data |> do_put(path, {default, fun})
167 318 end
168 319
320 + @doc ~S"""
321 + Updates data within an `HL7` struct, parsed segments, or repetitions
322 + using an `HL7.Path` struct (see `sigil_p/2`). Raises a `RuntimeError`
323 + if the path is not present in the source data.
324 + """
325 + @spec update!(parsed_hl7(), Path.t(), (hl7_map_data() -> hl7_map_data())) :: parsed_hl7()
169 326 def update!(%HL7{segments: segments} = hl7, %Path{} = path, fun) do
170 327 %HL7{hl7 | segments: update!(segments, path, fun)}
171 328 end
  @@ -174,16 +331,55 @@ defmodule HL7 do
174 331 segment_data |> do_put(path, {fun})
175 332 end
176 333
334 + @doc ~S"""
335 + Returns a list of sparse maps (with ordinal indices and strings) representing
336 + the parsed segments of an HL7 message stored in an `HL7` struct.
337 + """
177 338 def get_segments(%HL7{segments: segments}) do
178 339 segments
179 340 end
180 341
342 + @doc ~S"""
343 + Sets a list of sparse maps (with ordinal indices and strings) representing
344 + the parsed segments of an HL7 message to define the content of an `HL7` struct.
345 + """
181 346 def set_segments(%HL7{} = hl7, segments) do
182 347 %HL7{hl7 | segments: segments}
183 348 end
184 349
185 350 @doc ~S"""
186 - Labels source data (a segment map or list of segment maps) by using `HL7.Path` sigils in a labeled
351 + Returns a map of custom metadata associated with the `HL7` struct.
352 + """
353 + def get_tags(%HL7{tags: tags}) do
354 + tags
355 + end
356 +
357 + @doc ~S"""
358 + Sets a map of custom metadata associated with the `HL7` struct.
359 + """
360 + def set_tags(%HL7{} = hl7, tags) when is_map(tags) do
361 + %HL7{hl7 | tags: tags}
362 + end
363 +
364 + @doc ~S"""
365 + Creates a minimal map representing an empty HL7 segment that can
366 + be modified via this module.
367 + """
368 + @spec new_segment(String.t()) :: segment()
369 + def new_segment(<<_::binary-size(3)>> = segment_name) do
370 + %{0 => segment_name}
371 + end
372 +
373 + @doc ~S"""
374 + Converts an `HL7` struct into its string representation.
375 + Convenience options will be added in the future.
376 + """
377 + def format(%HL7{} = hl7, _options \\ []) do
378 + to_string(hl7)
379 + end
380 +
381 + @doc ~S"""
382 + Labels source data (a segment map or list of segment maps) by using `HL7.Path`s in a labeled
187 383 output template.
188 384
189 385 One-arity functions placed as output template values will be called with the source data.
  @@ -205,14 +401,16 @@ defmodule HL7 do
205 401 end
206 402
207 403 @doc ~S"""
208 - Finds data within an `HL7.t()` and from within the data that `get/2` returns (segments and repetitions)
209 - using an `HL7.Path` sigil.
404 + Finds data within an `HL7` struct, parsed segments or repetitions
405 + using an `HL7.Path` struct (see `sigil_p/2`).
210 406
211 407 Selecting data across multiple segments or repetitions with the wildcard `[*]` pattern
212 408 will return a list of results.
213 409
214 - Repetition data can be search using a partial path containing ony the component and/or
215 - subcomponent with the preceding period, e.g. `~p".2.3"`.
410 + Repetition data can be searched using a partial path containing ony the component and/or
411 + subcomponent with a preceding period, e.g. `~p".2.3"`.
412 +
413 + See the `sigil_p/2` docs and tests for more examples!
216 414
217 415 ## Examples
218 416
  @@ -246,17 +444,16 @@ defmodule HL7 do
246 444 ...> |> HL7.get(~p"PID-11[2].1")
247 445 "NICKELL’S PICKLES"
248 446
249 - iex> import HL7, only: :sigils
447 + iex> import HL7
250 448 iex> HL7.Examples.wikipedia_sample_hl7()
251 - ...> |> HL7.new!()
252 - ...> |> HL7.get(~p"PID-11[*]")
253 - ...> |> HL7.get(~p".1")
449 + ...> |> new!()
450 + ...> |> get(~p"PID")
451 + ...> |> get(~p"11[*]")
452 + ...> |> get(~p".1")
254 453 ["260 GOODWIN CREST DRIVE", "NICKELL’S PICKLES"]
255 454
256 455 iex> import HL7
257 - iex> HL7.Examples.wikipedia_sample_hl7()
258 - ...> |> HL7.new!()
259 - ...> |> HL7.get(~p"OBX[*]")
456 + iex> HL7.Examples.wikipedia_sample_hl7() |> new!() |> get(~p"OBX[*]")
260 457 [
261 458 %{
262 459 0 => "OBX",
  @@ -278,10 +475,7 @@ defmodule HL7 do
278 475 }
279 476 ]
280 477 """
281 -
282 - @spec get(parsed_hl7(), %Path{}) ::
283 - hl7_map_data() | [hl7_map_data()] | String.t() | nil
284 -
478 + @spec get(parsed_hl7(), Path.t()) :: hl7_map_data() | [hl7_map_data()] | String.t() | nil
285 479 def get(data, path) do
286 480 data
287 481 |> do_get(path)
  @@ -303,19 +497,11 @@ defmodule HL7 do
303 497 end
304 498
305 499 @doc """
306 - Converts a segment map (or lists of segments maps) or HL7 struct into a raw Elixir list.
500 + Converts an HL7 message struct into a nested list of strings.
307 501 """
308 - @spec to_list(t() | hl7_map_data()) :: hl7_list_data()
502 + @spec to_list(t()) :: hl7_list_data()
309 503 def to_list(%HL7{segments: segments}) do
310 - to_list(segments)
311 - end
312 -
313 - def to_list(map_data) when is_list(map_data) do
314 - Enum.map(map_data, fn segment_map -> do_to_list(segment_map) end)
315 - end
316 -
317 - def to_list(map_data) when is_map(map_data) do
318 - do_to_list(map_data)
504 + Enum.map(segments, fn segment_map -> do_to_list(segment_map) end)
319 505 end
320 506
321 507 @doc """
  @@ -343,11 +529,11 @@ defmodule HL7 do
343 529 |> case do
344 530 {:ok, :line} ->
345 531 file_path
346 - |> File.stream!([], @buffer_size)
532 + |> File.stream!(:line)
347 533
348 534 {:ok, :mllp} ->
349 535 file_path
350 - |> File.stream!([], @buffer_size)
536 + |> File.stream!(@buffer_size)
351 537 |> HL7.MLLPStream.raw_to_messages()
352 538
353 539 {:error, reason} ->
  @@ -381,11 +567,11 @@ defmodule HL7 do
381 567 |> to_map(index + 1, t)
382 568 end
383 569
384 - def do_to_list(hl7_map_data) when is_binary(hl7_map_data) do
570 + defp do_to_list(hl7_map_data) when is_binary(hl7_map_data) do
385 571 hl7_map_data
386 572 end
387 573
388 - def do_to_list(hl7_map_data) do
574 + defp do_to_list(hl7_map_data) do
389 575 do_to_list([], hl7_map_data, get_max_index(hl7_map_data))
390 576 end
391 577
  @@ -463,6 +649,10 @@ defmodule HL7 do
463 649 data
464 650 end
465 651
652 + defp maybe_truncate(nil, %Path{truncate: true}) do
653 + ""
654 + end
655 +
466 656 defp maybe_truncate(segment_data, %Path{truncate: true}) do
467 657 truncate(segment_data)
468 658 end
  @@ -485,11 +675,11 @@ defmodule HL7 do
485 675 end
486 676
487 677 defp resolve_placement_value(_field_data = nil, {default, _fun}, _path) do
488 - default
678 + default |> lists_become_maps()
489 679 end
490 680
491 681 defp resolve_placement_value(field_data, {_default, fun}, _path) do
492 - fun.(field_data)
682 + fun.(field_data) |> lists_become_maps()
493 683 end
494 684
495 685 defp resolve_placement_value(_field_data = nil, {_fun}, path) do
  @@ -497,13 +687,21 @@ defmodule HL7 do
497 687 end
498 688
499 689 defp resolve_placement_value(field_data, {fun}, _path) do
500 - fun.(field_data)
690 + fun.(field_data) |> lists_become_maps()
501 691 end
502 692
503 693 defp resolve_placement_value(_field_data, value, _path) do
504 - value
694 + value |> lists_become_maps()
505 695 end
506 696
697 + defp lists_become_maps(value) when is_list(value) do
698 + value
699 + |> Enum.with_index()
700 + |> Map.new(fn {v, i} -> {i + 1, lists_become_maps(v)} end)
701 + end
702 +
703 + defp lists_become_maps(value), do: value
704 +
507 705 defp do_get(%HL7{} = hl7, %Path{} = path) do
508 706 do_get(hl7.segments, path)
509 707 end
  @@ -512,17 +710,19 @@ defmodule HL7 do
512 710 []
513 711 end
514 712
515 - defp do_get([%{0 => _} | _] = _segments, %Path{segment: nil} = path) do
516 - raise RuntimeError,
517 - "`HL7.Path` to get data across segments must begin with a segment name, not #{inspect(path)}"
713 + # from multiple segments, return a result for each segment in a list
714 + defp do_get([%{0 => _} | _] = segments, %Path{segment: nil} = path) do
715 + Enum.map(segments, &do_get_in_segment(&1, path))
518 716 end
519 717
718 + # from multiple segments, filter by name and return a result for each segment in a list
520 719 defp do_get([%{0 => _} | _] = segments, %Path{segment: name, segment_number: "*"} = path) do
521 720 segments
522 721 |> Stream.filter(&(&1[0] == name))
523 722 |> Enum.map(&do_get_in_segment(&1, path))
524 723 end
525 724
725 + # from multiple segments, return a result for a specific segment
526 726 defp do_get([%{0 => _} | _] = segments, %Path{segment: name, segment_number: n} = path) do
527 727 segments
528 728 |> Stream.filter(&(&1[0] == name))
  @@ -531,35 +731,38 @@ defmodule HL7 do
531 731 |> do_get_in_segment(path)
532 732 end
533 733
534 - defp do_get(nil = _segment_data, _path) do
535 - nil
734 + defp do_get(nil = _segment_data, path) do
735 + nil |> maybe_truncate(path)
536 736 end
537 737
738 + # for a single segment
538 739 defp do_get(%{0 => _} = segment_data, path) do
539 740 do_get_in_segment(segment_data, path)
540 741 end
541 742
743 + # for a list of repetitions (generally a repeating field), return a result for each
542 744 defp do_get(repetition_list, %{field: nil, repetition: nil} = path)
543 745 when is_list(repetition_list) do
544 746 Enum.map(repetition_list, &do_get_in_repetition(&1, path))
545 747 end
546 748
749 + # for a single repetition in a field
547 750 defp do_get(repetition_data, %{field: nil, repetition: nil} = path) do
548 751 do_get_in_repetition(repetition_data, path)
549 752 end
550 753
551 754 defp do_get(_repetition_data, path) do
552 755 raise RuntimeError,
553 - "HL7.Path to directly access repetitions should be begin with `.`, not #{inspect(path)}"
756 + "HL7.Path #{inspect(path)} could not work with the given data."
554 757 end
555 758
556 - defp do_get_in_segment(segment_data, %{field: nil, component: nil} = _path) do
557 - segment_data
759 + defp do_get_in_segment(segment_data, %{field: nil, component: nil} = path) do
760 + segment_data |> maybe_truncate(path)
558 761 end
559 762
560 763 defp do_get_in_segment(_segment_data, %{field: nil} = path) do
561 764 raise RuntimeError,
562 - "HL7.Path to access components within segments requires field number, not #{inspect(path)}"
765 + "HL7.Path #{inspect(path)} requires field number."
563 766 end
564 767
565 768 defp do_get_in_segment(segment_data, %{field: f} = path) do
  @@ -577,8 +780,8 @@ defmodule HL7 do
577 780 end)
578 781 end
579 782
580 - defp do_get_in_field(field_data, %{repetition: "*"}) do
581 - [field_data]
783 + defp do_get_in_field(field_data, %{repetition: "*"} = path) do
784 + [field_data |> maybe_truncate(path)]
582 785 end
583 786
584 787 defp do_get_in_field(field_data, %{repetition: r} = path) do
  @@ -587,8 +790,8 @@ defmodule HL7 do
587 790 |> do_get_in_repetition(path)
588 791 end
589 792
590 - defp do_get_in_repetition(repetition_data, %{component: nil}) do
591 - repetition_data
793 + defp do_get_in_repetition(repetition_data, %{component: nil} = path) do
794 + repetition_data |> maybe_truncate(path)
592 795 end
593 796
594 797 defp do_get_in_repetition(repetition_data, %{component: c} = path) do
  @@ -597,15 +800,17 @@ defmodule HL7 do
597 800 |> do_get_in_component(path)
598 801 end
599 802
600 - defp do_get_in_component(component_data, %{subcomponent: nil}) do
601 - component_data
803 + defp do_get_in_component(component_data, %{subcomponent: nil} = path) do
804 + component_data |> maybe_truncate(path)
602 805 end
603 806
604 - defp do_get_in_component(component_data, %{subcomponent: s}) do
807 + defp do_get_in_component(component_data, %{subcomponent: s} = path) do
605 808 component_data
606 809 |> get_value_at_index(s)
810 + |> maybe_truncate(path)
607 811 end
608 812
813 + # put across multiple segments
609 814 defp do_put([%{0 => _} | _] = segments, %Path{segment: name, segment_number: "*"} = path, value) do
610 815 segments
611 816 |> Enum.map(fn segment ->
  @@ -613,6 +818,7 @@ defmodule HL7 do
613 818 end)
614 819 end
615 820
821 + # put within a specific segment
616 822 defp do_put([%{0 => _} | _] = segments, %Path{segment: name, segment_number: n} = path, value) do
617 823 segments
618 824 |> Stream.with_index()
  @@ -643,22 +849,34 @@ defmodule HL7 do
643 849
644 850 defp do_put(_repetition_data, path, _value) do
645 851 raise RuntimeError,
646 - "HL7.Path to directly update repetitions should be begin with `.`, not #{inspect(path)}"
852 + "HL7.Path #{inspect(path)} to update repetition data should be begin with `.`"
853 + end
854 +
855 + defp do_put_in_segment(segment_data, value, %{field: nil} = path) do
856 + resolve_placement_value(segment_data, value, path)
647 857 end
648 858
649 859 defp do_put_in_segment(segment_data, value, %{field: f} = path) do
650 860 Map.put(segment_data, f, do_put_in_field(segment_data[f], value, path))
651 861 end
652 862
653 - # fields can contain either simple strings or maps of repetitions by ordinal index
654 - # if a map of repetitions contains only one string, it is reduced here to the string value alone
655 - defp simplify_string_fields(%{1 => value} = field_data)
656 - when map_size(field_data) == 1 and is_binary(value) do
863 + # data can contain either simple strings or maps by ordinal index
864 + # if a map contains only one string, it is reduced here to the string value alone
865 +
866 + defp simplify_string_fields(value) do
867 + do_simplify_string_fields(value, value)
868 + end
869 +
870 + defp do_simplify_string_fields(original, %{1 => value} = map) when map_size(map) == 1 do
871 + do_simplify_string_fields(original, value)
872 + end
873 +
874 + defp do_simplify_string_fields(_original, value) when is_binary(value) do
657 875 value
658 876 end
659 877
660 - defp simplify_string_fields(field_data) do
661 - field_data
878 + defp do_simplify_string_fields(original, _value) do
879 + original
662 880 end
663 881
664 882 defp do_put_in_field(field_data, value, %{repetition: "*", component: nil} = path) do
  @@ -683,6 +901,7 @@ defmodule HL7 do
683 901
684 902 defp do_put_in_repetition(repetition_data, value, %{component: nil} = path) do
685 903 resolve_placement_value(repetition_data, value, path)
904 + |> simplify_string_fields()
686 905 end
687 906
688 907 defp do_put_in_repetition(repetition_data, value, %{component: c} = path) do
  @@ -692,6 +911,7 @@ defmodule HL7 do
692 911
693 912 defp do_put_in_component(component_data, value, %{subcomponent: nil} = path) do
694 913 resolve_placement_value(component_data, value, path)
914 + |> simplify_string_fields()
695 915 end
696 916
697 917 defp do_put_in_component(subcomponent_data, value, %{subcomponent: s} = path) do
  @@ -730,6 +950,15 @@ defmodule HL7 do
730 950 defp nil_for_empty(""), do: nil
731 951 defp nil_for_empty(value), do: value
732 952
953 + @spec apply_default_options(list() | String.t() | HL7.Message.t(), Keyword.t()) :: Keyword.t()
954 + defp apply_default_options(%HL7.Message{tag: tags} = _message_content, options) do
955 + [tags: tags, copy: options[:copy]]
956 + end
957 +
958 + defp apply_default_options(_message_content, options) do
959 + [tags: options[:tags] || %{}, copy: options[:copy]]
960 + end
961 +
733 962 @spec infer_file_type(String.t()) :: {:ok, :line} | {:ok, :mllp} | {:error, atom()}
734 963 defp infer_file_type(file_path) do
735 964 File.open(file_path, [:read])
  @@ -757,15 +986,9 @@ end
757 986
758 987 defimpl Inspect, for: HL7 do
759 988 def inspect(%HL7{segments: segments} = _hl7, _opts) do
760 - count = Enum.count(segments)
761 - names = segments |> Enum.take(5) |> Enum.map(&to_string(&1[0]))
762 - label = Enum.join(names, ", ")
763 - over = count - Enum.count(names)
764 -
765 - if over > 0 do
766 - "#HL7[" <> label <> ", +" <> to_string(over) <> "]"
767 - else
768 - "#HL7[" <> label <> "]"
989 + case Enum.count(segments) do
990 + 1 -> "#HL7<with 1 segment>"
991 + c -> "#HL7<with #{c} segments>"
769 992 end
770 993 end
771 994 end
  @@ -157,6 +157,10 @@ defmodule HL7.Message do
157 157 msg
158 158 end
159 159
160 + def new(%HL7{} = msg, _options) do
161 + HL7.to_list(msg) |> new() |> Map.put(:tag, msg.tags)
162 + end
163 +
160 164 def new(<<"MSH|^~\\&", _rest::binary>> = raw_text, options) do
161 165 with {:ok, text} <- validate_text(raw_text, options) do
162 166 copy = options[:copy] == true
  @@ -228,6 +232,10 @@ defmodule HL7.Message do
228 232 segments
229 233 end
230 234
235 + def to_list(%HL7.InvalidMessage{}) do
236 + raise RuntimeError, "invalid HL7 data"
237 + end
238 +
231 239 def to_list(%HL7.RawMessage{} = msg) do
232 240 msg |> HL7.Message.new() |> to_list()
233 241 end
Loading more files…