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