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