{*****************************************************************************} { } { llHL7Msg.pas - THL7Message and related classes } { } { (c)2013-2022 Illuminated Logic, LLC / Ray Marron (see license.txt) } { } {*****************************************************************************} unit llHL7Msg; { The purpose of these classes is the [de]construction of HL7 messages. They do not deal with content or meaning, except in the case of header segments where some knowledge of the segment's internals is required to parse or build a well-formed HL7 message. The object hierarchy is essentially as follows: THL7Message | +-THL7Segment | +-THL7Field | +-Repetitions (TObjectList) | +-Components (TObjectList) | +-Subcomponents (TStringList) A message can contain 0..n segments, each segment contains 1..n fields, etc. All field values are stored at the subcomponent level. Even though a field may have only a single, uncomplicated value like "foo", that value is stored and accessed as repetition zero, component zero, subcomponent zero. For example, Fld.GetValue(0,0,0) or using the handy defaults, Fld.GetValue. The "Str" functions and properties (MessageStr, SegmentStr, FieldStr, etc.) deal with complete HL7-formatted entities and will automatically parse/build the entity into/from its smaller parts, using HL7 [un]escaping logic where needed. The SetValue/GetValue functions store and retrieve distinct subcomponent values as literals - no [un]escaping takes place. If you call SetValue for a segment/index that does not yet exist, it will be created. If you call GetValue for a non-existent index, a blank will be returned. This makes it easy and safe to get and set values without constantly needing to validate your indexes. Since repeating fields are uncommon, RepetitionIndex has been placed as the last argument in the Get/SetValue functions so the default value of zero can be used most of the time without needing to specify it. A "key" syntax has been described for accessing values using a shorthand notation with indexes separated with periods. This is a very common way of communicating HL7 positions, and includes some additional elements specific to this implementation. The bare minimum key is a segment id and field index, such as "PID.3". Key Syntax: SID[#q].f[~r][.c[.s]] SID = Segment ID q = segment seQuence (1=first (default) 2=second, etc., e.g. IN1.1, IN1#2.1) f = Field index r = Repetition index c = Component index s = Subcomponent index Any omitted index is assumed to be one (1). For example, "PID.3" is exactly the same as if you had fully specified "PID#1.3~1.1.1" While all GetValue/SetValue arguments use the internal zero-based indexes, all key string indexing is one-based to match what one would see in the HL7 specifications and be more end-user friendly/familiar. Because fields are already effectively one-based due to field zero being the segment id, field numbers don't require adjustment. The ParseHL7Key/MakeHL7Key functions do all of this conversion automatically. Example: PID|1||123456^^^ACME^MRN^ACME||Marron^Ray| Msg.GetKeyValue('PID.5.1') -> Marron (one-based, first component) Msg.GetValue('PID', 0, 5, 1) -> Ray (zero-based, second component) The field repetition index uses the tilde (~) as its delimiter, which is the default HL7 repetition separator. By convention of being part of a field, this index should follow the field index, which is where the MakeHL7Key function always puts it. Technically, it could appear anywhere due to the special delimiter, so ParseHL7Key will find it in any location. Example: PID.3~2.1 (second repetition of PID.3, component 1) The segment sequence index separator is the pound/number sign (#). This index indicates the sequence of THIS TYPE of segment. For example, OBX#1 indicates the first OBX segment in the message, not the first segment overall, which would most likely be the MSH segment. IN1#2 would be the second IN1 segment, etc. When omitted, the first such segment is assumed. When using NamedFields, the descriptive names can be used in place of the numeric field/component/subcomponent indexes in keys. Numeric indexes can always be used, even when using NamedFields. The CargoStr and CargoObj properties are a way of attaching general purpose data to a message. For example, HL7Viewer uses CargoStr to store the filename the message came from. These properties are not affected by Clear, Clone, or any other method - their use is strictly left up to the user. The Caching setting toggles caching of SegmentStr and FieldStr values to speed their return if nothing has changed since the last time they were generated. The tradeoff is the additional memory usage for each segment and field. } interface uses SysUtils, Classes, Contnrs, llHL7NamedFields, llHL7Utils; const // ValidateData Flags - combine using OR vfNone = 0; vfRequired = 1; vfRepeat = 2; vfLength = 4; vfDataType = 8; vfTable = 16; vfID = 32; // Not implemented vfAll = $FF; type THL7Message = class; // Forward declarations THL7Segment = class; THL7Field = class(TObject) private (* 11 lines omitted *) public constructor Create(AOwner: THL7Segment); constructor CreateField(AOwner: THL7Segment; const AFieldStr: string); destructor Destroy; override; property Owner: THL7Segment read FOwner; property MyMessage: THL7Message read GetMyMessage; property FieldStr: string read GetFieldStr write SetFieldStr; procedure Clear; procedure Clone(const ASource: THL7Field); procedure CompactData(const Forced: Boolean = False); function GetValue(const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): string; procedure SetValue(const AValue: string; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0); function ValueExists(const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): Boolean; function IsEmpty: Boolean; function IsNull(const ARepetitionIndex: Integer = -1): Boolean; procedure SetNull; // Repetitions function RepeatCount: Integer; procedure AddRepetition(const ARepetitionStr: string); procedure ClearRepetition(const ARepetitionIndex: Integer); procedure DeleteRepetition(const ARepetitionIndex: Integer); procedure InsertRepetition(const ARepetitionIndex: Integer; const ARepetitionStr: string); procedure MoveRepetition(const ACurIndex, ANewIndex: Integer); function RepetitionStr(const ARepetitionIndex: Integer): string; procedure SetRepetitionStr(const ARepetitionIndex: Integer; const ARepetitionStr: string); // Components function ComponentCount(const ARepetitionIndex: Integer = 0): Integer; procedure ClearComponent(const AComponentIndex: Integer; const ARepetitionIndex: Integer = 0); procedure MoveComponent(const ARepetitionIndex, ACurIndex, ANewIndex: Integer); function ComponentStr(const AComponentIndex: Integer; const ARepetitionIndex: Integer = 0): string; procedure SetComponentStr(const AComponentIndex, ARepetitionIndex: Integer; const AComponentStr: string); function SubComponentCount(const AComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): Integer; end; THL7Segment = class(TObject) private (* 12 lines omitted *) public constructor Create(AOwner: THL7Message); constructor CreateSegment(AOwner: THL7Message; const ASegmentStr: string); destructor Destroy; override; property Owner: THL7Message read FOwner; property SegmentStr: string read GetSegmentStr write SetSegmentStr; property SegmentID: string read GetSegmentID; property SegmentIDSequence: Integer read GetSegmentIDSequence; property FieldCount: Integer read GetFieldCount; property Fields[const Index: Integer]: THL7Field read GetFieldByIndex; default; property SegmentKey: string read GetSegKey; function AddField(const AFieldStr: string): THL7Field; function InsertField(const AIndex: Integer; const AFieldStr: string): THL7Field; procedure DeleteField(const AIndex: Integer); procedure MoveField(const ACurIndex, ANewIndex: Integer); function ValueExists(const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): Boolean; function GetValue(const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): string; procedure SetValue(const AValue: string; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0); function IsEmpty: Boolean; procedure Clear; procedure SafeClear; procedure Clone(const ASource: THL7Segment); procedure CompactData(const Forced: Boolean = False); end; THL7Message = class(TObject) private (* 46 lines omitted *) public constructor Create; constructor CreateMessage(const AMessageStr: string); destructor Destroy; override; property SegmentTerminator: string read FSegmentTerminator; property FieldSeparator: string read FFieldSeparator write SetFieldSeparator; property ComponentSeparator: string read FComponentSeparator write SetComponentSeparator; property SubComponentSeparator: string read FSubComponentSeparator write SetSubComponentSeparator; property RepetitionSeparator: string read FRepetitionSeparator write SetRepetitionSeparator; property EscapeCharacter: string read FEscapeCharacter write SetEscapeCharacter; property Caching: Boolean read FCaching write SetCaching; property Compact: Boolean read FCompact write SetCompact; property Strict: Boolean read FStrict write FStrict; property ForceField: Boolean read FForceField write SetForceField; property LowHexOnly: Boolean read FLowHexOnly write SetLowHexOnly; property MinimalHex: Boolean read FMinimalHex write SetMinimalHex; property AutoADD: Boolean read FAutoADD write FAutoADD; property MessageStr: string read GetMessageStr write SetMessageStr; property MessageText: string read GetMessageText; // CRLF segment terminators property SegmentCount: Integer read GetSegmentCount; property Segments[const Index: Integer]: THL7Segment read GetSegmentByIndex; default; property ControlID: string read GetControlID; // MSH.10, FHS.11, or BHS.11 property MessageTime: string read GetMessageTime; // MSH.7, FHS.7, or BHS.7 property MessageType: string read GetMessageType; // ADTA08 property ShortMessageType: string read GetShortMessageType; // ADT property TriggerEvent: string read GetTriggerEvent; // A08 property NamedFields: THL7NamedFields read FNamedFields write FNamedFields; property CargoStr: string read FCargoStr write FCargoStr; property CargoObj: TObject read FCargoObj write FCargoObj; property Encoding: TEncoding read GetEncoding write SetEncoding; function AddSegment(const ASegmentStr: string): THL7Segment; function InsertSegment(const AIndex: Integer; const ASegmentStr: string): THL7Segment; procedure DeleteSegment(const AIndex: Integer); procedure MoveSegment(const ACurIndex, ANewIndex: Integer); function AddStandardMSHSegment: THL7Segment; procedure Clear; procedure Clone(const ASource: THL7Message; const CopyEncodingChars: Boolean = True); procedure CompactData(const Forced: Boolean = False); procedure CopyEncodingCharacters(const AMsg: THL7Message); procedure DefaultEncodingChars; function GetSegmentByID(const ASegmentID: string; const ASegmentSequence: Integer = 0): THL7Segment; function GetSegmentIndex(const ASegmentID: string; const ASegmentSequence: Integer): Integer; function GetSegmentKeyIndex(const AKey: string): Integer; function ValueExists(const ASegmentID: string; const ASegmentSequence: Integer; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): Boolean; function GetValue(const ASegmentIndex: Integer; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): string; overload; function GetValue(const ASegmentID: string; const ASegmentSequence: Integer; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0): string; overload; procedure SetValue(const AValue: string; const ASegmentIndex: Integer; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0); overload; procedure SetValue(const AValue: string; const ASegmentID: string; const ASegmentSequence: Integer; const AFieldIndex: Integer; const AComponentIndex: Integer = 0; const ASubComponentIndex: Integer = 0; const ARepetitionIndex: Integer = 0); overload; function KeyExists(const AKey: string): Boolean; function GetKeyFieldStr(const AKey: string): string; function GetKeyValue(const AKey: string): string; procedure SetKeyFieldStr(const AKey, AValue: string); procedure SetKeyValue(const AKey, AValue: string); function MakeKey(const SID: string; const q, f, c, s, r: Integer; const ALevel: THL7KeyLevel = klCompact): string; function ParseKey(const AKey: string; out SID: string; out q, f, c, s, r: Integer; const RaiseException: Boolean = False): Boolean; function ReEscape(const S: string): string; function UnEscape(const S: string): string; function EscapeRequired(const S: string): Boolean; function IsHeaderSegment(const SID: string): Boolean; procedure LoadFromFile(const AFileName: string); procedure SaveToFile(const AFileName: string; const AsText: Boolean = False); overload; procedure SaveToFile(const AFileName: string; const AEncoding: TEncoding; const AsText: Boolean = False); overload; procedure AppendToFile(const AFileName: string; const AsText: Boolean = False); overload; procedure AppendToFile(const AFileName: string; const AEncoding: TEncoding; const AsText: Boolean = False); overload; procedure CombineADD; // WARNING: NOT COMPATIBLE WITH COMPACT & FORCEFIELD! Most Reliable=AutoADD procedure SplitADD(const AMax: Integer; const AByLength: Boolean = True; const AOnSeparator: Boolean = False); function ValidateData(const AErrors: TStrings = nil; const AFlags: Integer = vfAll): Boolean; function Diff(const Msg2: THL7Message; const DiffCSV: TStrings; const AIgnoreFields: string = ''): Boolean; end; implementation (* 2369 lines omitted *) end.