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