Crates
hl7-2-from-er7-into-xml
ER7 → the official v2.xml XML representation
What it is
Convert HL7 v2.5 messages from the traditional pipe-delimited ER7 encoding to the official HL7 v2.xml XML representation (urn:hl7-org:v2xml), as a Rust library
and command-line tool.
The ER7 encoding itself comes from the er7 crate. This crate is the layer above
it: the HL7 v2.5 data-type tables that name XML elements, the message-structure grammars that
group segments, and the XML renderer.
Since 0.5.0 those tables and grammars come from the hl7-2 dictionary rather than being hand-written here —
which is also what lets a caller pass --dictionary to convert against a vendor's own
XML Schema instead of the bundled v2.5 release.
What it produces
An ER7 fragment such as:
PID|1||241900||TEST^FOUAZconverts to the v2.xml structure, with components named after their HL7 v2.5 data types:
<PID>
<PID.1>1</PID.1>
<PID.3>
<CX.1>241900</CX.1>
</PID.3>
<PID.5>
<XPN.1>
<FN.1>TEST</FN.1>
</XPN.1>
<XPN.2>FOUAZ</XPN.2>
</PID.5>
</PID>Command line
# From a file to stdout
hl7-2-from-er7-into-xml samples/orm_o01.hl7
# From stdin, to a file
cat samples/oru_r01.hl7 | hl7-2-from-er7-into-xml -o out.xml
# Disable message-structure grouping
hl7-2-from-er7-into-xml --flat samples/orm_o01.hl7
# Convert against a dictionary built from XSDs instead of bundled v2.5
hl7-2-from-er7-into-xml --dictionary my-dialect.json samples/orm_o01.hl7
# Let that dictionary decide the document's exact shape
hl7-2-from-er7-into-xml --dictionary my-dialect.json --schema-shape samples/orm_o01.hl7--dictionary FILE reads a JSON dictionary — such as one built by hl7-2-from-xsd-into-json-dictionary from a directory of XSDs — in place of the bundled HL7 v2.5 tables.
--schema-shape changes how that dictionary is read: instead of only naming what a
field is, it decides what the document contains — required fields are written
even when empty, fields that cannot repeat keep their repetition separator as text, and no field
the dictionary does not declare is written. That is what makes the output validate against the
schemas the dictionary came from.
Input may hold one message, several messages, or an HL7 batch file (FHS/BHS envelopes are dropped); each message becomes one XML document.
Library
let er7 = "MSH|^~\\&|hphis||EPIC||20131011093851||ORM^O01|14AAACVDD|P|2.5\r\
PID|1||241900||MEDIANO^FOUAZ\r\
ORC|NW|ORD1";
let xml = hl7_2_from_er7_into_xml::convert(er7)?;See also convert_with_options (for example Options { flat: true })
and split_messages for batch input:
use hl7_2_from_er7_into_xml::{convert, split_messages};
let batch = "MSH|^~\\&|A||||1||ACK|1|P|2.5\rMSA|AA|1\r\
MSH|^~\\&|B||||2||ACK|2|P|2.5\rMSA|AA|2";
for message in split_messages(batch) {
match convert(&message) {
Ok(xml) => println!("{xml}"),
Err(e) => eprintln!("skipping malformed message: {e}"),
}
}convert and convert_with_options return Result<String, Hl7Error>; an Err only ever means the message has
no usable MSH header — empty input, missing MSH, or a malformed MSH header. Everything below
that always converts, falling back to generic names or a flat layout rather than failing.
What it does
- ER7 parsing at every level: segments, fields, repetitions (
~), components (^), and subcomponents (&). - Dynamic delimiters: the separator set is read from
MSH-1andMSH-2rather than hardcoded, and both are emitted literally per the standard. - Escape sequences:
\F\ \S\ \T\ \R\ \E\decode to the delimiter characters and\Xhh..\decodes hex bytes, before XML escaping. Unrecognized sequences — formatting commands such as\.br\— are kept literally. - Typed element names: the dictionary maps each field of the common segments
(MSH, SFT, EVN, PID, PD1, NK1, PV1, PV2, ROL, DG1, PR1, ORC, OBR, OBX, NTE, AL1, IN1, MRG, MSA,
ERR, DSC, BLG, CTI, SPM) to its data type, and each composite type (CX, XPN, XCN, XAD, CE, CWE,
EI, HD, TS, …) to its component types — producing
<PID.5><XPN.1><FN.1>style nesting. - OBX-5 variable typing: the value type declared in
OBX-2(CE, CX, SN, …) names theOBX-5components. - HL7 null: the explicit null
""keeps its literal text (<PID.2>""</PID.2>). An empty element means “not sent” instead — the XML Encoding Rules give the two opposite meanings, so they do not share an encoding. - Message-structure groups: for known structures the segments are nested into
their official groups, e.g.
<ORM_O01.PATIENT>. Grammars are included for ACK, ADT_A01 (also used by ADT^A04/A08/A13), ORM_O01, and ORU_R01. The root element name comes fromMSH-9.3when present, otherwise fromMSH-9.1andMSH-9.2.
Fallback behavior
Fidelity degrades gracefully instead of failing:
- A message whose segment sequence does not fit its declared structure — it contains Z-segments, or uses a structure with no built-in grammar — renders with all segments flat under the root element.
- Fields of unknown segments, and segment fields beyond the built-in tables, use positional
generic names:
<ZDS.1>,<ZDS.1.1>, and so on.
Limitations
- Only the four message structures listed above are grouped; everything else renders flat.
- ORM_O01 order detail supports the common OBR choice; RQD/RQ1/RXO/ODS/ODT detail segments cause a flat rendering.
- Formatting escape sequences are preserved as literal text rather than mapped to
<escape/>elements.
Round trip with the reverse crate
Because hl7-2-from-xml-into-er7 reads
back exactly what this crate writes, the two compose into a lossless round trip you can run from
the shell:
hl7-2-from-er7-into-xml samples/orm_o01.hl7 \
| hl7-2-from-xml-into-er7The output is the original ER7 message, canonicalized — a good smoke test after changing either crate's naming rules, since a drift in one breaks the other's assumptions. See A lossless round trip.
Related crates
hl7-2-from-xml-into-er7
v2.xml XML → ER7
hl7-2-from-er7-into-json
ER7 → typed JSON
hl7-2-from-xsd-into-json-dictionary
HL7 v2.xml XSDs → the JSON dictionary hl7-2 reads