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