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