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