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