Skip to content

Visitor utility

Visitor for traversing HL7v2 AST trees with ancestor context, depth, and HL7v2-aware sequence numbers.

Visitor for traversing HL7v2 AST trees with ancestor context.

What it does

Walks an HL7v2 AST from any starting node — Root, Group, Segment, Field, Component, or Subcomponent — calling a visitor function for each matching node with the node and its ancestors, from the node the walk starts at to the node's parent. Supports filtering by type string, property object, or predicate, and exposes SKIP/EXIT control actions. Delegates traversal to unist-util-visit-parents, with types from @glion/ast.

Install

npm install @glion/util-visit

Use

import { parseHL7v2 } from "@glion/parser";
import { SKIP, visit } from "@glion/util-visit";

const tree = parseHL7v2(
  "MSH|^~\\&|LAB|FAC|EMR|RFAC|20250601120000||ORU^R01^ORU_R01|MSG00001|P|2.5\rPID|1||12345\rNTE|1||note"
);

// Visit every segment
visit(tree, "segment", (segment) => {
  console.log(segment.name); // "MSH", "PID", "NTE"
});

// Skip the fields of NTE segments
visit(tree, (node) => {
  if (node.type === "segment" && node.name === "NTE") {
    return SKIP;
  }
});

API

visit(tree, visitor)

visit(tree, test, visitor)

Visit nodes in an HL7v2 AST tree, in document order.

Parameters

  • tree (Nodes) — Tree to traverse. Can be any node type, not just Root.
  • test (string | Partial<Nodes> | Test, optional) — Filter:
    • string — Match nodes by type (e.g., "segment").
    • Partial<Nodes> — Match nodes whose own properties equal (===) each value (e.g., { id: "PATIENT" }). A property the node does not own reads as undefined: an undefined value matches an absent property, and a key naming an inherited member (constructor, toString) matches no node.
    • Test — Custom function (node, ancestors) => boolean.
  • visitor (Visitor) — Function called for each matching node.

Returns

void.

Test vs visitor functions

A function passed as the second argument is always the visitor, never a test. A test function needs the three-argument form:

visit(
  tree,
  (node) => node.type === "segment",
  (segment) => {
    console.log(segment.type); // "segment"
  }
);

Visitor function

type Visitor<T extends Nodes = Nodes> = (
  node: T,
  ancestors: Nodes[]
) => VisitorResult;

The visitor receives:

  • node — Current AST node.
  • ancestors — Ancestor nodes from the node the walk starts at to the parent, not including the current node. Empty for the starting node.

The visitor can return:

  • undefined or CONTINUE — Continue traversal, into the node's children.
  • SKIP — Skip the children of the current node.
  • EXIT — Stop traversal.
  • An index — Continue at that index among the current node's siblings.
  • [action, index] — Both.

A visitor that removes the current node returns its former index to continue with the sibling that moved into its place:

visit(tree, { type: "segment", name: "NTE" }, (segment, ancestors) => {
  const parent = ancestors.at(-1) as Root | Group;
  const index = parent.children.indexOf(segment);
  parent.children.splice(index, 1);
  return index;
});

indexOf scans the parent's children, so removing many children of one parent this way costs time in the square of their number. To remove many, filter parent.children once from the parent's visitor instead.

Exports

import {
  visit, // Traversal function
  EXIT, // Return to stop traversal
  SKIP, // Return to skip children
} from "@glion/util-visit";

import type {
  Visitor, // (node, ancestors) => VisitorResult
  VisitorResult, // Return type of a visitor
  Test, // Filter: type string, property object, predicate, or null
  Predicate, // (node, ancestors) => boolean
} from "@glion/util-visit";

Examples

Filter by properties

// A group's `id` is its key in paths; its `name` is the title for display
visit(tree, { type: "group", id: "PATIENT" }, (group) => {
  console.log(group.name); // "Patient"
});

Read the ancestors

visit(tree, "component", (component, ancestors) => {
  const parent = ancestors.at(-1); // the field repetition
  const segment = ancestors.findLast((node) => node.type === "segment");
});

Read the segment groups above a segment

visit(tree, "segment", (segment, ancestors) => {
  const groups = ancestors
    .filter((node): node is Group => node.type === "group")
    .map((group) => group.id);
  console.log(`${segment.name}: ${groups.join(" > ")}`); // "PID: PATIENT_RESULT > PATIENT"
});

Stop at the first match

import { EXIT } from "@glion/util-visit";

let first: Segment | undefined;
visit(tree, { type: "segment", name: "OBX" }, (segment) => {
  first = segment as Segment;
  return EXIT;
});

Start from any node

import { c, f, s } from "@glion/builder";

const segment = s("PID", f(c("value1")), f(c("value2")));

visit(segment, "field", (field, ancestors) => {
  console.log(ancestors.length); // 1: the segment
});