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