001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2012
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.hl7;
040
041import java.io.BufferedReader;
042import java.io.IOException;
043import java.io.Reader;
044import java.util.EnumSet;
045import java.util.StringTokenizer;
046
047import org.xml.sax.ContentHandler;
048import org.xml.sax.SAXException;
049import org.xml.sax.helpers.AttributesImpl;
050
051/**
052 * @author Gunter Zeilinger <gunterze@gmail.com>
053 *
054 */
055public class HL7Parser {
056
057    private static final String NAMESPACE = "http://aurora.regenstrief.org/xhl7";
058
059    private String namespace = "";
060    private final ContentHandler ch;
061    private final AttributesImpl atts = new AttributesImpl();
062    private final EnumSet<Delimiter> open = EnumSet.noneOf(Delimiter.class);
063    private String delimiters;
064
065    public HL7Parser(ContentHandler ch) {
066        this.ch = ch;
067    }
068
069    public final boolean isIncludeNamespaceDeclaration() {
070        return namespace == NAMESPACE;
071    }
072
073    public final void setIncludeNamespaceDeclaration(boolean includeNameSpaceDeclaration) {
074        this.namespace = includeNameSpaceDeclaration ? NAMESPACE : "";
075    }
076
077    public void parse(Reader reader) throws IOException, SAXException {
078        parse(reader instanceof BufferedReader
079                ? (BufferedReader) reader
080                : new BufferedReader(reader));
081    }
082
083    public void parse(BufferedReader reader) throws IOException, SAXException {
084        startDocument();
085        delimiters = Delimiter.DEFAULT;
086        String line;
087        while ((line = reader.readLine()) != null) {
088            line = line.trim();
089            if(line.length() == 0)
090                continue;
091
092            if (line.length() < 3)
093                throw new IOException ("Segment to short: " + line);
094
095            String seg = line.substring(0, 3);
096            String[] tks;
097            int tkindex = 0;
098            if (isHeaderSegment(line)) {
099                if (line.length() < 8)
100                    throw new IOException ("Header Segment to short: " + line);
101
102                seg = line.substring(0, 3);
103                setDelimiters(line.substring(3, 8));
104                tks = tokenize(line.substring(8));
105            } else {
106                tks = tokenize(line);
107                seg = tks[tkindex++];
108            }
109            startElement(seg);
110            while (tkindex < tks.length) {
111                String tk = tks[tkindex++];
112                Delimiter d = delimiter(tk);
113                if (d != null) {
114                    if (d != Delimiter.escape) {
115                        endElement(d);
116                        startElement(d);
117                        continue;
118                    }
119                    if (tks.length > tkindex+1 && tks[tkindex+1].equals(tk)) {
120                        tk = tks[tkindex++];
121                        int e = escapeIndex(tk);
122                        if (e >= 0) {
123                            ch.characters(delimiters.toCharArray(), e, 1);
124                        } else {
125                            startElement(Delimiter.escape.name());
126                            ch.characters(tk.toCharArray(), 0, tk.length());
127                            endElement(Delimiter.escape.name());
128                        }
129                        tkindex++;
130                        continue;
131                    }
132                }
133                ch.characters(tk.toCharArray(), 0, tk.length());
134            }
135            endElement(Delimiter.field);
136            endElement(seg);
137        }
138        endDocument();
139    }
140
141    private boolean isHeaderSegment(String line) {
142        return (line.startsWith("MSH")
143             || line.startsWith("BHS")
144             || line.startsWith("FHS"));
145    }
146
147    private void startDocument() throws SAXException {
148        ch.startDocument();
149        addAttribute("xml-space", "preserved");
150        startElement("hl7");
151    }
152
153    private void endDocument() throws SAXException {
154        endElement("hl7");
155        ch.endDocument();
156    }
157
158    private void setDelimiters(String delimiters) {
159        Delimiter[] a = Delimiter.values();
160        for (int i = 0; i < a.length; i++)
161            addAttribute(a[i].attribute(), delimiters.substring(i,i+1));
162        this.delimiters = delimiters;
163    }
164
165    private void addAttribute(String name, String value) {
166        atts.addAttribute(namespace, name, name, "NMTOKEN", value);
167    }
168
169    private Delimiter delimiter(String tk) {
170        if (tk.length() != 1)
171            return null;
172
173        int index = delimiters.indexOf(tk.charAt(0));
174        return index >= 0 ? Delimiter.values()[index] : null;
175    }
176
177    private int escapeIndex(String tk) {
178        return tk.length() != 1 ? Delimiter.ESCAPE.indexOf(tk.charAt(0)) : -1;
179    }
180
181    private String[] tokenize(String s) {
182        StringTokenizer stk = new StringTokenizer(s, delimiters, true);
183        String[] tks = new String[stk.countTokens()];
184        for (int i = 0; i < tks.length; i++)
185            tks[i] = stk.nextToken();
186
187        return tks;
188    }
189
190    private void startElement(Delimiter d) throws SAXException {
191        startElement(d.name());
192        open.add(d);
193    }
194
195    private void startElement(String name) throws SAXException {
196        ch.startElement(namespace, name, name, atts);
197        atts.clear();
198    }
199
200    private void endElement(Delimiter delimiter) throws SAXException {
201        Delimiter d = Delimiter.escape;
202        do
203            if (open.remove(d = d.parent()))
204                endElement(d.name());
205        while (d != delimiter);
206    }
207
208    private void endElement(String name) throws SAXException {
209        ch.endElement(namespace, name, name);
210    }
211
212}