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