001package org.hl7.fhir.r4.elementmodel; 002 003import java.io.BufferedInputStream; 004import java.io.IOException; 005import java.io.InputStream; 006import java.io.InputStreamReader; 007import java.io.OutputStream; 008 009import org.hl7.fhir.r4.context.IWorkerContext; 010import org.hl7.fhir.r4.formats.IParser.OutputStyle; 011import org.hl7.fhir.r4.model.StructureDefinition; 012import org.hl7.fhir.exceptions.DefinitionException; 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.exceptions.FHIRFormatError; 015 016/** 017 * This class provides special support for parsing v2 by the v2 logical model 018 * For the logical model, see the FHIRPath spec 019 * 020 * @author Grahame Grieve 021 * 022 */ 023public class VerticalBarParser extends ParserBase { 024 025 /** 026 * Delimiters for a message. Note that the application rarely needs to concern 027 * itself with this information; it mainly exists for internal use. However if 028 * a message is being written to a spec that calls for non-standard delimiters, 029 * the application can set them here. 030 * 031 * @author Grahame 032 * 033 */ 034 public class Delimiters { 035 036 /** 037 * Hl7 defined default delimiter for a field 038 */ 039 public final static char DEFAULT_DELIMITER_FIELD = '|'; 040 041 /** 042 * Hl7 defined default delimiter for a component 043 */ 044 public final static char DEFAULT_DELIMITER_COMPONENT = '^'; 045 046 /** 047 * Hl7 defined default delimiter for a subcomponent 048 */ 049 public final static char DEFAULT_DELIMITER_SUBCOMPONENT = '&'; 050 051 /** 052 * Hl7 defined default delimiter for a repeat 053 */ 054 public final static char DEFAULT_DELIMITER_REPETITION = '~'; 055 056 /** 057 * Hl7 defined default delimiter for an escape 058 */ 059 public final static char DEFAULT_CHARACTER_ESCAPE = '\\'; 060 061 062 /** 063 * defined escape character for this message 064 */ 065 private char escapeCharacter; 066 067 /** 068 * defined repetition character for this message 069 */ 070 private char repetitionDelimiter; 071 072 /** 073 * defined field character for this message 074 */ 075 private char fieldDelimiter; 076 077 /** 078 * defined subComponent character for this message 079 */ 080 private char subComponentDelimiter; 081 082 /** 083 * defined component character for this message 084 */ 085 private char componentDelimiter; 086 087 /** 088 * create 089 * 090 */ 091 public Delimiters() { 092 super(); 093 reset(); 094 } 095 096 public boolean matches(Delimiters other) { 097 return escapeCharacter == other.escapeCharacter && 098 repetitionDelimiter == other.repetitionDelimiter && 099 fieldDelimiter == other.fieldDelimiter && 100 subComponentDelimiter == other.subComponentDelimiter && 101 componentDelimiter == other.componentDelimiter; 102 } 103 104 /** 105 * get defined component character for this message 106 * @return 107 */ 108 public char getComponentDelimiter() { 109 return componentDelimiter; 110 } 111 112 /** 113 * set defined component character for this message 114 * @param componentDelimiter 115 */ 116 public void setComponentDelimiter(char componentDelimiter) { 117 this.componentDelimiter = componentDelimiter; 118 } 119 120 /** 121 * get defined escape character for this message 122 * @return 123 */ 124 public char getEscapeCharacter() { 125 return escapeCharacter; 126 } 127 128 /** 129 * set defined escape character for this message 130 * @param escapeCharacter 131 */ 132 public void setEscapeCharacter(char escapeCharacter) { 133 this.escapeCharacter = escapeCharacter; 134 } 135 136 /** 137 * get defined field character for this message 138 * @return 139 */ 140 public char getFieldDelimiter() { 141 return fieldDelimiter; 142 } 143 144 /** 145 * set defined field character for this message 146 * @param fieldDelimiter 147 */ 148 public void setFieldDelimiter(char fieldDelimiter) { 149 this.fieldDelimiter = fieldDelimiter; 150 } 151 152 /** 153 * get repeat field character for this message 154 * @return 155 */ 156 public char getRepetitionDelimiter() { 157 return repetitionDelimiter; 158 } 159 160 /** 161 * set repeat field character for this message 162 * @param repetitionDelimiter 163 */ 164 public void setRepetitionDelimiter(char repetitionDelimiter) { 165 this.repetitionDelimiter = repetitionDelimiter; 166 } 167 168 /** 169 * get sub-component field character for this message 170 * @return 171 */ 172 public char getSubComponentDelimiter() { 173 return subComponentDelimiter; 174 } 175 176 /** 177 * set sub-component field character for this message 178 * @param subComponentDelimiter 179 */ 180 public void setSubComponentDelimiter(char subComponentDelimiter) { 181 this.subComponentDelimiter = subComponentDelimiter; 182 } 183 184 /** 185 * reset to default HL7 values 186 * 187 */ 188 public void reset () { 189 fieldDelimiter = DEFAULT_DELIMITER_FIELD; 190 componentDelimiter = DEFAULT_DELIMITER_COMPONENT; 191 subComponentDelimiter = DEFAULT_DELIMITER_SUBCOMPONENT; 192 repetitionDelimiter = DEFAULT_DELIMITER_REPETITION; 193 escapeCharacter = DEFAULT_CHARACTER_ESCAPE; 194 } 195 196 /** 197 * check that the delimiters are valid 198 * 199 * @throws FHIRException 200 */ 201 public void check() throws FHIRException { 202 rule(componentDelimiter != fieldDelimiter, "Delimiter Error: \""+componentDelimiter+"\" is used for both CPComponent and CPField"); 203 rule(subComponentDelimiter != fieldDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPField"); 204 rule(subComponentDelimiter != componentDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPComponent"); 205 rule(repetitionDelimiter != fieldDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPField"); 206 rule(repetitionDelimiter != componentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPComponent"); 207 rule(repetitionDelimiter != subComponentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPSubComponent"); 208 rule(escapeCharacter != fieldDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPField"); 209 rule(escapeCharacter != componentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPComponent"); 210 rule(escapeCharacter != subComponentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPSubComponent"); 211 rule(escapeCharacter != repetitionDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and Repetition"); 212 } 213 214 /** 215 * check to see whether ch is a delimiter character (vertical bar parser support) 216 * @param ch 217 * @return 218 */ 219 public boolean isDelimiter(char ch) { 220 return ch == escapeCharacter || ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; 221 } 222 223 /** 224 * check to see whether ch is a cell delimiter char (vertical bar parser support) 225 * @param ch 226 * @return 227 */ 228 public boolean isCellDelimiter(char ch) { 229 return ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter; 230 } 231 232 /** 233 * get the escape for a character 234 * @param ch 235 * @return 236 */ 237 public String getEscape(char ch) { 238 if (ch == escapeCharacter) 239 return escapeCharacter + "E" + escapeCharacter; 240 else if (ch == fieldDelimiter) 241 return escapeCharacter + "F" + escapeCharacter; 242 else if (ch == componentDelimiter) 243 return escapeCharacter + "S" + escapeCharacter; 244 else if (ch == subComponentDelimiter) 245 return escapeCharacter + "T" + escapeCharacter; 246 else if (ch == repetitionDelimiter) 247 return escapeCharacter + "R" + escapeCharacter; 248 else 249 return null; 250 } 251 252 /** 253 * build the MSH-2 content 254 * @return 255 */ 256 public String forMSH2() { 257 return "" + componentDelimiter + repetitionDelimiter + escapeCharacter + subComponentDelimiter; 258 } 259 260 /** 261 * check to see whether ch represents a delimiter escape 262 * @param ch 263 * @return 264 */ 265 public boolean isDelimiterEscape(char ch) { 266 return ch == 'F' || ch == 'S' || ch == 'E' || ch == 'T' || ch == 'R'; 267 } 268 269 /** 270 * get escape for ch in an escape 271 * @param ch 272 * @return 273 * @throws DefinitionException 274 * @throws FHIRException 275 */ 276 public char getDelimiterEscapeChar(char ch) throws DefinitionException { 277 if (ch == 'E') 278 return escapeCharacter; 279 else if (ch == 'F') 280 return fieldDelimiter; 281 else if (ch == 'S') 282 return componentDelimiter; 283 else if (ch == 'T') 284 return subComponentDelimiter; 285 else if (ch == 'R') 286 return repetitionDelimiter; 287 else 288 throw new DefinitionException("internal error in getDelimiterEscapeChar"); 289 } 290 } 291 292 public class VerticalBarParserReader { 293 294 295 private BufferedInputStream stream; 296 private String charsetName; 297 private InputStreamReader reader = null; 298 private boolean finished; 299 private char peeked; 300 private char lastValue; 301 private int offset; 302 private int lineNumber; 303 304 public VerticalBarParserReader(BufferedInputStream stream, String charsetName) throws FHIRException { 305 super(); 306 setStream(stream); 307 setCharsetName(charsetName); 308 open(); 309 } 310 311 public String getCharsetName() { 312 return charsetName; 313 } 314 315 public void setCharsetName(String charsetName) { 316 this.charsetName = charsetName; 317 } 318 319 public BufferedInputStream getStream() { 320 return stream; 321 } 322 323 public void setStream(BufferedInputStream stream) { 324 this.stream = stream; 325 } 326 327 private void open() throws FHIRException { 328 try { 329 stream.mark(2048); 330 reader = new InputStreamReader(stream, charsetName); 331 offset = 0; 332 lineNumber = 0; 333 lastValue = ' '; 334 next(); 335 } catch (Exception e) { 336 throw new FHIRException(e); 337 } 338 } 339 340 private void next() throws IOException, FHIRException { 341 finished = !reader.ready(); 342 if (!finished) { 343 char[] temp = new char[1]; 344 rule(reader.read(temp, 0, 1) == 1, "unable to read 1 character from the stream"); 345 peeked = temp[0]; 346 } 347 } 348 349 public String read(int charCount) throws FHIRException { 350 String value = ""; 351 for (int i = 0; i < charCount; i++) 352 value = value + read(); 353 return value; 354 } 355 356 public void skipEOL () throws FHIRException { 357 while (!finished && (peek() == '\r' || peek() == '\n')) 358 read(); 359 } 360 361 public char read () throws FHIRException { 362 rule(!finished, "No more content to read"); 363 char value = peek(); 364 offset++; 365 if (value == '\r' || value == '\n') { 366 if (lastValue != '\r' || value != '\n') 367 lineNumber++; 368 } 369 lastValue = value; 370 try { 371 next(); 372 } catch (Exception e) { 373 throw new FHIRException(e); 374 } 375 return value; 376 } 377 378 public boolean isFinished () { 379 return finished; 380 } 381 382 public char peek() throws FHIRException { 383 rule(!finished, "Cannot peek"); 384 return peeked; 385 } 386 387 public void mark() { 388 stream.mark(2048); 389 } 390 391 public void reset() throws FHIRException { 392 try { 393 stream.reset(); 394 } catch (IOException e) { 395 throw new FHIRException(e); 396 } 397 open(); 398 } 399 400 public boolean IsEOL() throws FHIRException { 401 return peek() == '\r' || peek() == '\n'; 402 } 403 404 public int getLineNumber() { 405 return lineNumber; 406 } 407 408 public int getOffset() { 409 return offset; 410 } 411 412 } 413 414 public VerticalBarParser(IWorkerContext context) { 415 super(context); 416 } 417 418 private String charset = "ASCII"; 419 private Delimiters delimiters = new Delimiters(); 420 421 @Override 422 public Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { 423 StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message"); 424 Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd)); 425 VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset); 426 427 preDecode(reader); 428 while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size())) 429 readSegment(message, reader); 430 431 return message; 432 } 433 434 private void preDecode(VerticalBarParserReader reader) throws FHIRException { 435 reader.skipEOL(); 436 String temp = reader.read(3); 437 rule(temp.equals("MSH") || temp.equals("FHS"), "Found '" + temp + "' looking for 'MSH' or 'FHS'"); 438 readDelimiters(reader); 439 // readVersion(message); - probably don't need to do that? 440 // readCharacterSet(); 441 reader.reset(); // ready to read message now 442 } 443 444 private void rule(boolean test, String msg) throws FHIRException { 445 if (!test) 446 throw new FHIRException(msg); 447 } 448 449 private void readDelimiters(VerticalBarParserReader reader) throws FHIRException { 450 delimiters.setFieldDelimiter(reader.read()); 451 if (!(reader.peek() == delimiters.getFieldDelimiter())) 452 delimiters.setComponentDelimiter(reader.read()); 453 if (!(reader.peek() == delimiters.getFieldDelimiter())) 454 delimiters.setRepetitionDelimiter(reader.read()); 455 if (!(reader.peek() == delimiters.getFieldDelimiter())) 456 delimiters.setEscapeCharacter(reader.read()); 457 if (!(reader.peek() == delimiters.getFieldDelimiter())) 458 delimiters.setSubComponentDelimiter(reader.read()); 459 delimiters.check(); 460 } 461 462 private void readSegment(Element message, VerticalBarParserReader reader) throws FHIRException { 463 Element segment = new Element("segment", message.getProperty().getChild("segment")); 464 message.getChildren().add(segment); 465 Element segmentCode = new Element("code", segment.getProperty().getChild("code")); 466 segment.getChildren().add(segmentCode); 467 segmentCode.setValue(reader.read(3)); 468 469 int index = 0; 470 while (!reader.isFinished() && !reader.IsEOL()) { 471 index++; 472 readField(reader, segment, index); 473 if (!reader.isFinished() && !reader.IsEOL()) 474 rule(reader.read() == delimiters.getFieldDelimiter(), "Expected to find field delimiter"); 475 } 476 if (!reader.isFinished()) 477 reader.skipEOL(); 478 } 479 480 481 482 private void readField(VerticalBarParserReader reader, Element segment, int index) { 483 // TODO Auto-generated method stub 484 485 } 486 487 @Override 488 public void compose(Element e, OutputStream destination, OutputStyle style, String base) { 489 // TODO Auto-generated method stub 490 491 } 492 493}