Reference

HL7 paths

The short notation that names one place in a message: PID-5.1, OBX[2]-5, PID-13[2].1.

The notation is a de-facto standard among interface engineers rather than part of HL7 itself, so this crate accepts the two spellings that are common in the field and writes the first.

Grammar

path       = name occurrence? ( ("-" | ".") index occurrence? ( "." index ( "." index )? )? )?
name       = one or more ASCII letters and digits
occurrence = "[" index "]"
index      = a decimal number, 1 or greater

Surrounding whitespace is ignored.

The four levels

PathNamesOn PID|1||9|4|SMITH^JOHN^Q
PIDevery PID segment, wholePID|1||9|4|SMITH^JOHN^Q
PID-5field 5SMITH^JOHN^Q
PID-5.1component 1 of field 5SMITH
PID-5.1.2subcomponent 2 of that component— only one here

A path that stops above the leaf returns that subtree as written, with its structural delimiters intact. Only the leaf text is decoded.

Occurrence indices

There are two, and they mean different things.

PositionSelectsExample
after the segment namewhich segment of that nameOBX[2]-5 — the second OBX
after the field numberwhich repetition of that fieldPID-13[2] — the second phone number

Both are 1-based. Both may be omitted, and omitting one means “every one”. They compose: OBX[2]-5[1].1.2 is subcomponent 2 of component 1 of the first repetition of field 5 of the second OBX.

// Three OBX segments, so three answers.
assert_eq!(message.query_all("OBX-5")?, ["187", "102", ""]);

// One, pinned down.
assert_eq!(message.query_all("OBX[2]-5")?, ["102"]);

Every index is 1-based, and 0 is rejected with an error rather than clamped — it is almost always a caller’s off-by-one, and silently reading it as 1 would return a plausible wrong answer.

Repetitions have a special case

A path that stops at the field returns the whole field, repetition separators included. A path that goes deeper splits into one answer per repetition.

// PID-13 is `555-1111~555-2222`
assert_eq!(message.query("PID-13")?.as_deref(), Some("555-1111~555-2222"));
assert_eq!(message.query_all("PID-13.1")?, ["555-1111", "555-2222"]);
assert_eq!(message.query_all("PID-13[2].1")?, ["555-2222"]);

This is deliberate: PID-13 as a whole field is a meaningful thing to ask for, and joining its repetitions back with ~ is the only honest way to return it as one string.

Both spellings

PID-5.1 and PID.5.1 parse identically. Display writes the first, and round-tripping through it preserves meaning, because occurrence indices the path left open are left out rather than defaulted to 1.

let a: er7::Path = "PID.5.1".parse()?;
let b: er7::Path = "PID-5.1".parse()?;
assert_eq!(a, b);
assert_eq!(a.to_string(), "PID-5.1");

The four query methods

MethodTakesReturnsDecodedNote
query&strResult<Option<String>, Error>yesthe first match
query_all&strResult<Vec<String>, Error>yesevery match
query_path&PathVec<String>yespath parsed once
query_path_raw&PathVec<String>noexactly as sent

The &str forms parse the path each call and can fail with a bad-path error. The &Path forms take an already-parsed path, which is what you want when applying one path to many messages.

let path: er7::Path = "PID-5.1".parse()?;
let names: Vec<String> = messages
    .iter()
    .flat_map(|message| message.query_path(&path))
    .collect();

Path implements Clone, PartialEq, Eq, and Hash, so a set of paths can be map keys or deduplicated.

Two surprises

Header delimiters come back literally

MSH-1 is the field separator and MSH-2 is the encoding characters. They are the delimiters, not values encoded with them, so they are never decoded: MSH-1 gives | and MSH-2 gives ^~\&.

A missing position contributes nothing

Not an empty string — no entry at all. So query_all("OBX-5").len() counts the OBX segments that actually carried a fifth field, which is usually what you want and occasionally a surprise.

Reading paths off the command line

The er7 command’s default output labels every value with the path that names it, and every one of those labels is a valid query.

er7 message.er7 | grep Cholesterol
#=> OBX[1]-3.2  Cholesterol

er7 --query 'OBX[1]-3.2' message.er7
#=> Cholesterol

Quote the path in a shell: [ and ] are glob characters.

Full command-line reference →