patternMinor
hl7 parser with fparsec
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:
A segment has fields or repetitions (repeating fields). A field can have components. A component can have sub-components.
Sample .hl7 message header:
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
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|DateTimeOfMessageI 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 :
Using partial application, it could be written as :
And the one after it:
Also,
On other matters, there is a potential bug in your code : in your
Now you don't have to use extra methods/properties to get the length and and the first item. It is all right there ...
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 |>> idOn 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 |>> idmatch 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.