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