HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

hl7 parser with fparsec

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
parserfparsechl7with

Problem

I have a little experience working with .hl7 data (format for transmitting health data), so I decided to try and write an .hl7 parser using fparsec.

A standard .hl7 segment is a single line with:

  • First 3 chars are the header



  • Next 5 chars are the separators (typically |^~\&)



  • Fields / repetitions delimited by pipes



A segment has fields or repetitions (repeating fields). A field can have components. A component can have sub-components.

Sample .hl7 message header:

MSH|^~\\&|Location|SendingApp|DateTimeOfMessage


I am looking for feedback on data design (the types) and the functions.

```
type Subcomponent = {value:string; position:int}
type Component = {subcomponents: Subcomponent list; position:int}
type SingleField = {components: Component list; position:int}
type Field = Repetitions of SingleField list | SingleField of SingleField
type Segment = { name:string; fields:Field list; }
type Hl7Message = { segments:Segment list }

let hl7Seps = "|^~\\&"

let normalChar = noneOf hl7Seps

let unescape c = match c with
| 'F' -> '|'
| 'R' -> '~'
| 'S' -> '^'
| 'T' -> '&'
| 'E' -> '\\'
| c -> c

let escapedChar = attempt (pchar '\\' >>. anyChar |>> unescape .>> skipChar '\\') pchar '\\'

let pHl7Element = manyChars (normalChar escapedChar)

let pcomp = sepBy pHl7Element (pchar '&') |>> (fun vals -> List.mapi (fun i s -> {value = s; position = i}) vals)

let pfield = sepBy pcomp (pchar '^') |>> (fun comps -> List.mapi (fun i c -> {subcomponents = c; position = i}) comps)

let pRepsOrField = sepBy pfield (pchar '~') |>> (fun fields -> match fields.Length with
| 0 | 1 -> SingleField {components = fields.Item 0; position = 0}
| _ -> Repetitions (List.mapi (fun i c -> {components = c; position = i}) fields))

let

Solution

I am not really familiar with Fparsec, but a few of your functions could be simplified, or written better:

For example :

let pcomp = sepBy pHl7Element (pchar '&') |>> (fun vals -> List.mapi (fun i s -> {value = s; position = i}) vals)


Using partial application, it could be written as :

let pcomp = sepBy pHl7Element (pchar '&') |>> (List.mapi (fun i s -> { value = s; position = i }))


And the one after it:

// Before
let pfield = sepBy pcomp (pchar '^') |>> (fun comps -> List.mapi (fun i c -> {subcomponents = c; position = i}) comps)
// After
let pfield = sepBy pcomp (pchar '^') |>> (List.mapi (fun i c -> { subcomponents = c; position = i }))


Also, (fun x -> x) could be simply replaced by id.

let pheader = anyString 3 |>> id


On other matters, there is a potential bug in your code : in your pRepsOrField function, you're matching field.Length with 0 then extracting the first item, which will crash your code. Using the Length property to find out the length is .. unusual. You don't even need it. Pattern matching with lists based on length in F# is usually done this way:

match fields with
| [] -> // do something
| [ c ] ->  SingleField { components = c; position = 0 }
| _ -> Repetitions(List.mapi (fun i c -> { components = c; position = i }) fields))


Now you don't have to use extra methods/properties to get the length and and the first item. It is all right there ...

Code Snippets

let pcomp = sepBy pHl7Element (pchar '&') |>> (fun vals -> List.mapi (fun i s -> {value = s; position = i}) vals)
let pcomp = sepBy pHl7Element (pchar '&') |>> (List.mapi (fun i s -> { value = s; position = i }))
// Before
let pfield = sepBy pcomp (pchar '^') |>> (fun comps -> List.mapi (fun i c -> {subcomponents = c; position = i}) comps)
// After
let pfield = sepBy pcomp (pchar '^') |>> (List.mapi (fun i c -> { subcomponents = c; position = i }))
let pheader = anyString 3 |>> id
match fields with
| [] -> // do something
| [ c ] ->  SingleField { components = c; position = 0 }
| _ -> Repetitions(List.mapi (fun i c -> { components = c; position = i }) fields))

Context

StackExchange Code Review Q#141204, answer score: 2

Revisions (0)

No revisions yet.