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}