001/*******************************************************************************
002 * Crown Copyright (c) 2006 - 2014, Copyright (c) 2006 - 2014 Kestral Computing P/L.
003 * All rights reserved. This program and the accompanying materials
004 * are made available under the terms of the Eclipse Public License v1.0
005 * which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v10.html
007 * 
008 * Contributors:
009 *    Kestral Computing P/L - initial implementation
010 *******************************************************************************/
011
012package org.hl7.fhir.utilities.ucum;
013
014import java.io.File;
015import java.io.FileInputStream;
016import java.io.IOException;
017import java.io.InputStream;
018import java.text.DateFormat;
019import java.text.ParseException;
020import java.text.SimpleDateFormat;
021import java.util.Date;
022
023import org.hl7.fhir.exceptions.UcumException;
024import org.hl7.fhir.utilities.Utilities;
025import org.xmlpull.v1.XmlPullParser;
026import org.xmlpull.v1.XmlPullParserException;
027import org.xmlpull.v1.XmlPullParserFactory;
028
029
030/**
031 * Parses the file ucum-essense.xml
032 * 
033 * @author Grahame Grieve
034 *
035 */
036
037public class DefinitionParser {
038
039        public UcumModel parse(String filename) throws UcumException, XmlPullParserException, IOException, ParseException  {
040                return parse(new FileInputStream(new File(filename)));
041        }
042
043        public UcumModel parse(InputStream stream) throws XmlPullParserException, IOException, ParseException, UcumException  {
044                XmlPullParserFactory factory = XmlPullParserFactory.newInstance(
045                                System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
046                factory.setNamespaceAware(true);
047                XmlPullParser xpp = factory.newPullParser();
048
049                xpp.setInput(stream, null);
050
051                int eventType = xpp.next();
052                if (eventType != XmlPullParser.START_TAG)
053                        throw new XmlPullParserException("Unable to process XML document");
054                if (!xpp.getName().equals("root")) 
055                        throw new XmlPullParserException("Unable to process XML document: expected 'root' but found '"+xpp.getName()+"'");
056                DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss' 'Z");
057                Date date = fmt.parse(xpp.getAttributeValue(null, "revision-date").substring(7, 32));        
058                UcumModel root = new UcumModel(xpp.getAttributeValue(null, "version"), xpp.getAttributeValue(null, "revision"), date);
059                xpp.next();
060                while (xpp.getEventType() != XmlPullParser.END_TAG) {
061                        if (xpp.getEventType() == XmlPullParser.TEXT) {
062                                if (Utilities.isWhitespace(xpp.getText()))
063                                        xpp.next();
064                                else
065                                        throw new XmlPullParserException("Unexpected text "+xpp.getText());
066                        } else if (xpp.getName().equals("prefix")) 
067                                root.getPrefixes().add(parsePrefix(xpp));
068                        else if (xpp.getName().equals("base-unit")) 
069                                root.getBaseUnits().add(parseBaseUnit(xpp));
070                        else if (xpp.getName().equals("unit")) 
071                                root.getDefinedUnits().add(parseUnit(xpp));
072                        else 
073                                throw new XmlPullParserException("unknown element name "+xpp.getName());
074                }
075                return root;
076        }
077
078        private DefinedUnit parseUnit(XmlPullParser xpp) throws XmlPullParserException, IOException, UcumException  {
079                DefinedUnit unit = new DefinedUnit(xpp.getAttributeValue(null, "Code"), xpp.getAttributeValue(null, "CODE"));
080                unit.setMetric("yes".equals(xpp.getAttributeValue(null, "isMetric")));
081                unit.setSpecial("yes".equals(xpp.getAttributeValue(null, "isSpecial")));
082                unit.setClass_(xpp.getAttributeValue(null, "class"));
083                xpp.next();
084                skipWhitespace(xpp);
085                while (xpp.getEventType() == XmlPullParser.START_TAG && "name".equals(xpp.getName()))
086                        unit.getNames().add(readElement(xpp, "name", "unit "+unit.getCode(), false));
087                if (xpp.getEventType() == XmlPullParser.START_TAG && "printSymbol".equals(xpp.getName()))
088                        unit.setPrintSymbol(readElement(xpp, "printSymbol", "unit "+unit.getCode(), true));
089                unit.setProperty(readElement(xpp, "property", "unit "+unit.getCode(), false));
090                unit.setValue(parseValue(xpp, "unit "+unit.getCode()));
091                xpp.next();
092                skipWhitespace(xpp);
093                return unit;
094        }
095
096        private Value parseValue(XmlPullParser xpp, String context) throws XmlPullParserException, UcumException, IOException  {
097                checkAtElement(xpp, "value", context);
098                Decimal val = null;
099                if (xpp.getAttributeValue(null, "value") != null) 
100                        try {
101                                if (xpp.getAttributeValue(null, "value").contains("."))
102                                        val = new Decimal(xpp.getAttributeValue(null, "value"), 24); // unlimited precision for these
103                                else
104                                        val = new Decimal(xpp.getAttributeValue(null, "value"));
105                        } catch (NumberFormatException e) {
106                                throw new XmlPullParserException("Error reading "+context+": "+e.getMessage());
107                        }
108                Value value = new Value(xpp.getAttributeValue(null, "Unit"), xpp.getAttributeValue(null, "UNIT"), val);
109                value.setText(readElement(xpp, "value", context, true));
110                return value;
111        }
112
113        private BaseUnit parseBaseUnit(XmlPullParser xpp) throws XmlPullParserException, IOException {
114                BaseUnit base = new BaseUnit(xpp.getAttributeValue(null, "Code"), xpp.getAttributeValue(null, "CODE"));
115                base.setDim(xpp.getAttributeValue(null, "dim").charAt(0));
116                xpp.next();
117                skipWhitespace(xpp);
118                base.getNames().add(readElement(xpp, "name", "base-unit "+base.getCode(), false));
119                base.setPrintSymbol(readElement(xpp, "printSymbol", "base-unit "+base.getCode(), false));
120                base.setProperty(readElement(xpp, "property", "base-unit "+base.getCode(), false));
121                xpp.next();
122                skipWhitespace(xpp);
123                return base;
124        }
125
126        private Prefix parsePrefix(XmlPullParser xpp) throws XmlPullParserException, IOException, UcumException  {
127                Prefix prefix = new Prefix(xpp.getAttributeValue(null, "Code"), xpp.getAttributeValue(null, "CODE"));
128                xpp.next();
129                skipWhitespace(xpp);
130                prefix.getNames().add(readElement(xpp, "name", "prefix "+prefix.getCode(), false));
131                prefix.setPrintSymbol(readElement(xpp, "printSymbol", "prefix "+prefix.getCode(), false));
132                checkAtElement(xpp, "value", "prefix "+prefix.getCode());
133                prefix.setValue(new Decimal(xpp.getAttributeValue(null, "value"), 24));
134                readElement(xpp, "value", "prefix "+prefix.getCode(), true);
135                xpp.next();
136                skipWhitespace(xpp);
137                return prefix;
138        }
139
140        private String readElement(XmlPullParser xpp, String name, String context, boolean complex) throws XmlPullParserException, IOException {
141                checkAtElement(xpp, name, context);
142                xpp.next();
143                skipWhitespace(xpp);
144                String val = null;
145                if (complex) {
146                        val = readText(xpp);
147                } else if (xpp.getEventType() == XmlPullParser.TEXT) {
148                        val = xpp.getText();
149                        xpp.next();
150                        skipWhitespace(xpp);
151                }
152                if (xpp.getEventType() != XmlPullParser.END_TAG) {
153                        throw new XmlPullParserException("Unexpected content reading "+context);
154                }
155                xpp.next();
156                skipWhitespace(xpp);
157                return val;
158        }
159
160        private String readText(XmlPullParser xpp) throws XmlPullParserException, IOException {
161                StringBuilder bldr = new StringBuilder();
162                while (xpp.getEventType() != XmlPullParser.END_TAG) {
163                        if (xpp.getEventType() == XmlPullParser.TEXT) {
164                                bldr.append(xpp.getText());
165                                xpp.next();
166                        } else {
167                                xpp.next();
168                                bldr.append(readText(xpp));
169                                xpp.next();
170                                skipWhitespace(xpp);
171                        }
172                }
173                return bldr.toString();
174        }
175
176        private void skipWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException {
177                while (xpp.getEventType() == XmlPullParser.TEXT && Utilities.isWhitespace(xpp.getText())) 
178                        xpp.next();             
179        }
180
181        private void checkAtElement(XmlPullParser xpp, String name, String context) throws XmlPullParserException {
182                if (xpp.getEventType() != XmlPullParser.START_TAG)
183                        throw new XmlPullParserException("Unexpected state looking for "+name+": at "+Integer.toString(xpp.getEventType())+"  reading "+context);
184                if (!xpp.getName().equals(name))
185                        throw new XmlPullParserException("Unexpected element looking for "+name+": found "+xpp.getName()+"  reading "+context);         
186        }
187}