Class UTIL.Ens.HL7Validator Extends EnsLib.HL7.Util.Validator { /// Adds option 'q' (quick) to perform a minimal, DocType-agnostic HL7 syntax check: /// - message must start with MSH /// - MSH-1 must exist, be exactly one char, and equal the message FS /// - each segment name must match [A-Z[A-Z0-9]{2} /// - each segment must support retrieving field 1 (implies FS present) /// - all segments use the same FS as MSH /// /// Behavior: /// - If pValSpec contains only 'q' (optionally with 'x'/'-x'), only quick-check runs. /// - If pValSpec contains 'q' alongside other flas, quick-check runs first, /// then remaining flas run via superclass validator. /// - Honors 'x' (stop on first error) if present (unless overriden with '-x'). ClassMethod Validate(pDoc As EnsLib.HL7.Message, pValSpec As %String = 1) As %Status { #dim tSC As %Status = $$$OK #dim scQuick As %Status = $$$OK #dim scOther As %Status = $$$OK // Normalize stop-on-first flag the same way base code expects: 'x' means stop early (default) // '-x' means scan all. For our purposes, we treat presence of "-x" as scall all. #dim stopOnFirst As %Boolean = 1 If (pValSpec["-x") Set StopOnFirst = 0 Else If (pValSpec["x") Set stopOnFirst = 1 #dim doQuick As %Boolean = (pValSpec["q")!(pValSpec["Q") If doQuick { Set scQuick = ..QuickSyntax(pDoc, stopOnFirst) // If the spec is only 'q' (+optional x/-x), exit now #dim trimmed As %String = $TR(pValSpec,"qQxX-") if trimmed="" Quit scQuick // Otherwise, remove q/Q and continue with remaining specs Set pValSpec = $TR(pValSpec,"qQ") If $$$ISERR(scQuick) & stopOnFirst Quit scQuick } // If anything remains, run the base validator on the rest If pValSpec'="" { Set scOther = ##super(pDoc, pValSpec) } Quit $$$ADDSC(scOther, scQuick) } /// Minimal HL7 syntax checks ClassMethod QuickSyntax(pDoc As EnsLib.HL7.Message, stopOnFirst As %Boolean = 1) As %Status { #dim tSC As %Status = $$$OK #dim s As %Status = $$$OK #dim seg As EnsLib.HL7.Segment #dim i As %Integer #dim fs, fsSeg, mshi, name as %String // 1) Must have at least one segment and it must be MSH Set seg = pDoc.GetSegmentAt(1, .s) If $$$ISERR(s) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: message contains no segments (missing MSH).")) Quit tSC } If seg.Name'="MSH" { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: message must begin with MSH segment. Found '"_seg.Name_"'.")) Quit tSC } // 2) MSH-1 must exist, be exactly 1 char, and equal FS Set fs = $$$FSSEP(seg.Separators) Set msh1 = seg.GetValueAt(1, seg.Separators, .s) If $$$ISERR(s) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: unable to read MSH-1 (Field Separator).")) Quit tSC } If (msh1="")!(+$L(msh1)'=1) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: MSH-1 (Field Separator) must be exactly one character.")) Quit tSC } If (msh1'=fs) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: MSH-1 ('"_msh1_"') must match the message field separator ('"_fs_"').")) Quit tSC } // 3) Every segment: // - name matches [A-Z[A-Z0-9]{2} // - field 1 can be accessed (imples FS present after name) For i=1:1:pDoc.SegCount { Set seg = pDoc.GetSegmentAt(i, .s) If $$$ISERR(s) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: unable to retrieve segment #"_i_".")) If stopOnFirst { Break } } Set name = seg.Name If '(name?1U1(1U,1N)1(1(1U,1N))) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: invalid segment name '"_name_"' at position "_i_"; expected pattern [A-Z][A-Z0-9]{2}.")) If stopOnFirst { Break } } // Attempt to get field 1; if this fails, it's effectively missing FS after tag #dim dummy As %String Set dummy = seg.GetValueAt(1, seg.Separators, .s) If $$$ISERR(s) { Set tSC = $$$ADDSC(tSC, $$$ERROR($$$EnsErrGeneral, "Basic HL7 syntax: no field separator after segment '"_name_"' as position "_i_".")) If stopOnFirst { Break } } } Quit tSC } }