Skip to content

Commit dd8fa76

Browse files
Add hl7 fingerprinting function.
Signed-off-by: Christopher Schultz <chris@christopherschultz.net>
1 parent 977974d commit dd8fa76

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Fingerprint HL7 Message Fields
2+
Takes a simple fingerprint of an HL7 message, including some fields and optionally ignoring others.
3+
4+
### Examples
5+
Fingerprint some fields:
6+
7+
```javascript
8+
var msg = /* XML object */
9+
var fields = [ 'PID.2', 'PID.3', 'OBX' ];
10+
var ignores = [ 'MSH', 'EVN' ];
11+
12+
var fingerprint = fingerprint(msg, fields, ignores);
13+
```
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Takes a fingerprint of a message.
3+
*
4+
* @param {XML} msg An XML object representing HL7.
5+
* @param {String[]} fields An array of field names to hash (e.g. 'PID', 'SCH.5', or 'MSH.5.1')
6+
* @param {String[]} ignores (optional) An array of fields to *completely* ignore (e.g. 'MSH', 'PID.5')
7+
* NOTE: You can't (currently) ignore a field inside of one of the 'fields' you have already requested
8+
* @param {String} hfunc (optional) The name of the hashing function to use. Valid values are 'hashcode' (which uses a simple Arrays.hashCode call) or any of the function supported by your JVM's MessageDigest class (e.g. MD5, SHA-1, SHA-256, etc.). The default is 'hashcode'.
9+
*
10+
* @return {String} A string fingerprint of the requested fields.
11+
*
12+
* @throws If no requested fields could be found in the message.
13+
*/
14+
function fingerprint(msg, fields, ignores, hfunc) {
15+
if(!hfunc) {
16+
hfunc = 'hashcode';
17+
}
18+
19+
var captures = capture(msg, fields, ignores);
20+
21+
if(0 < captures.length) {
22+
// Convert to a Java array of Java strings
23+
var len = captures.length;
24+
var javaStrings = java.lang.reflect.Array.newInstance(new java.lang.String().getClass(), len);
25+
for(i=0; i<len; ++i) {
26+
javaStrings[i] = captures[i].toString();
27+
}
28+
29+
// Return a js string
30+
if(hfunc == 'hashcode') {
31+
return '' + java.lang.Integer.toHexString(java.util.Arrays.hashCode(javaStrings));
32+
} else {
33+
var md = java.security.MessageDigest.getInstance(hfunc);
34+
for(i=0; i<len; ++i) {
35+
md.update(javaStrings[i].getBytes('UTF-8'));
36+
}
37+
return toHex(md.digest());
38+
}
39+
} else {
40+
throw 'Captured no data from message; cannot take fingerprint';
41+
}
42+
}
43+
44+
/**
45+
* Convert a byte array to a string if hex digits.
46+
*
47+
* For example, toHex([ 0xca, 0xfe ]) would return "cafe".
48+
*
49+
* @param {byte[]} bytes A byte array
50+
*
51+
* @return {String} A js string representation of the bytes in simple hex encoding.
52+
*/
53+
function toHex(bytes) {
54+
var hexString = "";
55+
for (var i = 0; i < bytes.length; i++) {
56+
var byteValue = bytes[i] & 0xFF;
57+
var hex = byteValue.toString(16);
58+
if (hex.length === 1) {
59+
// Ped if necessary
60+
hex = "0" + hex;
61+
}
62+
hexString += hex;
63+
}
64+
return hexString;
65+
}
66+
67+
/**
68+
* Captures field (etc.) values from an HL7 message.
69+
*
70+
* @param {XML} msg An XML object representing HL7.
71+
* @param {String[]} fields An array of field names whose values should be captured (e.g. 'PID', 'SCH.5', or 'MSH.5.1')
72+
* @param {String[]} ignores (optional) An array of fields to *completely* ignore (e.g. 'MSH', 'PID.5')
73+
* NOTE: You can't (currently) ignore a field inside of one of the 'fields' you have already requested
74+
*
75+
* @returns {String[]} An array of captured field values (without any indication of which fields they came from)
76+
*/
77+
function capture(msg, fields, ignores) {
78+
// Clone the incoming 'fields' array; We may mutate it
79+
var fieldNames = fields.map(s => ''+s); // Convert all strings to js strings, just in case
80+
81+
var captures = [];
82+
83+
for each (seg in msg.children()) {
84+
scan_node(seg, fields, captures, ignores);
85+
if(0 == fields.length) {
86+
return captures;
87+
}
88+
}
89+
90+
return captures;
91+
}
92+
93+
/**
94+
* Scans a node and its children for data to capture.
95+
*
96+
* @param {XML} msg An XML object representing HL7.
97+
* @param {String[]} fields An array of field names whose values should be captured (e.g. 'PID', 'SCH.5', or 'MSH.5.1')
98+
* @param {String[]} captures An array to append captured values to
99+
* @param {String[]} ignores (optional) An array of fields to *completely* ignore (e.g. 'MSH', 'PID.5')
100+
* NOTE: You can't (currently) ignore a field inside of one of the 'fields' you have already requested
101+
*
102+
* @returns Nothing
103+
*/
104+
function scan_node(node, fields, captures, ignores) {
105+
var nodeName = node.localName(); // nodeName is a js string
106+
107+
if(fields.includes(nodeName)) {
108+
collect_node(node, captures);
109+
} else if(ignores && ignores.includes(nodeName)) {
110+
// Ignore this whole node and all its children
111+
} else {
112+
if(0 < node.children().length()) {
113+
for each (child in node.children()) {
114+
if('element' == child.nodeKind()) { // Only scan actual elements
115+
scan_node(child, fields, captures, ignores);
116+
117+
if(0 == fields.length) {
118+
// No more fields to find; we are done
119+
return;
120+
}
121+
}
122+
}
123+
}
124+
}
125+
}
126+
127+
/**
128+
* Captures all field values for the current node and its children.
129+
*
130+
* @param {XML} msg An XML object representing HL7.
131+
* @param {String[]} captures An array to append captured values to
132+
*
133+
* @returns Nothing
134+
*/
135+
function collect_node(node, captures) {
136+
if('element' == node.nodeKind() && 0 < node.children().length()) {
137+
for each (child in node.children()) {
138+
if('element' == node.nodeKind()) { // Only process actual elements
139+
collect_node(child, captures);
140+
}
141+
}
142+
} else {
143+
var value = node.toString(); // value is a js string
144+
if(0 < value.length) {
145+
captures.push(value);
146+
}
147+
}
148+
}
149+

0 commit comments

Comments
 (0)