//Author: ^-^Veerle^-^ import org.xml.sax.Attributes; import org.xml.sax.XMLReader; import org.xml.sax.SAXException; //Thrown when a syntax error occurs import org.xml.sax.InputSource; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; import java.io.FileReader; import java.io.IOException; import java.util.Hashtable; //Allows conversions to be in any order they want, since rates are found via their associated currency import java.util.Vector; //Stores the original order of the currencies so at the end they can be sorted into that order public class calcReadXML extends DefaultHandler { //Allows the tags to quickly be updated for all the checks private final String strRootTag = "conversions"; private final String strSourceTag = "convertFrom"; private final String strDesTag = "convertTo"; //By using Hashtables, the conversions need not be in order in the xml document private Hashtable[] conversions = new Hashtable[2]; private Vector vecCurrencies; private String strCurrentTag = "START", strCurrentCurrency = ""; private boolean secondChance = false; private String[] strCurrencies; private float[] conversionsFrom, conversionsTo; public void calcReadXML () { } public String[] getCurrencies () { return strCurrencies; } public float[] getConversionsFrom () { return conversionsFrom; } public float[] getConversionsTo () { return conversionsTo; } public boolean getSecondChance () { return secondChance; } public void parseConversionFile (String strFile) throws Exception { XMLReader xr = XMLReaderFactory.createXMLReader(); xr.setContentHandler(this); try { xr.parse(new InputSource(new FileReader(strFile))); } catch (IOException ex) //If the file could not be found { try //Try to create the default document { calcWriteXML.create(strFile); } catch (Exception ex2) { throw new Exception("Could not locate nor create file: " + strFile + ", please check File permissions as the xml file must be created in the same folder as this application"); } try //Should never give an error, but just in case { xr.parse(new InputSource(new FileReader(strFile))); secondChance = true; //By default, when the error message is shown the combo boxes are disabled, this gives the combo boxes a second chance } catch (SAXException ex2) { throw new Exception("If you see this, then I've messed up"); } //Tell the user a default xml document was created throw new Exception("Could not locate: " + strFile + " and so a default file has been created with conversions that were true on the 15th of December 2005"); } catch (SAXException ex) //Any syntax errors occured { throw new Exception(ex.getMessage()); } } public void startDocument () //Save time and memory by only creating these if the document was found { conversions[0] = new Hashtable(); //ConvertFrom conversions[1] = new Hashtable(); //convertTo vecCurrencies = new Vector(); //Keeps the order of the currencies } //Called everytime a start tag is found, e.g. public void startElement (String nameSpaceURI, String strTag, String qName, Attributes attr) throws SAXException { if (strCurrentTag.equalsIgnoreCase("START")) //First structure tag in the xml document { if (strTag.equalsIgnoreCase(strRootTag)) //Being the first tag, it must equal the root tag { strCurrentTag = strRootTag; } else //Display friendly message so that some people can actually fix the error { throw new SAXException("The first structure tag in the xml document must be <" + strRootTag + ">, but <" + strTag + "> was found"); } } else if (strCurrentTag.equalsIgnoreCase("END")) //No tags should appear after the end root tag { throw new SAXException("The last structure tag in the xml document must be <\\" + strRootTag + ">, but <" + strTag + "> was found"); } else if (strTag.equalsIgnoreCase(strRootTag)) //The start tag should not be found anywhere else in the document { throw new SAXException("<" + strRootTag + "> must only appear at the start of the xml document and no where else"); } else if (!strCurrentTag.equalsIgnoreCase(strRootTag)) //When a tag is closed it is set to the root, if this hasn't happened then a tag hasn't be closed { throw new SAXException("Missing end tag or ilegal nesting, was expecting <\\" + strCurrentTag + ">, but <" + strTag + "> was found"); } else { int whichWay = 0; //Direction flag to know if doing a convertFrom or a convertTo strCurrentTag = strTag; if (strCurrentTag.equalsIgnoreCase(strDesTag)) //Since "whichWay" is already 0, don't need to change it for strSourceTag { whichWay = 1; } else if (!strCurrentTag.equalsIgnoreCase(strSourceTag)) //If not any of the valid tags { throw new SAXException("Unknown start xml tag <" + strCurrentTag + ">"); } testAttributeSyntax(attr); strCurrentCurrency = attr.getValue(0); if (conversions[whichWay].containsKey(strCurrentCurrency)) //If currency already exists in that hash table { throw new SAXException("Duplicate <" + strCurrentTag + " currency=\"" + attr.getValue(0) + "\"> were found"); } else if (strTag.equalsIgnoreCase(strSourceTag)) { vecCurrencies.addElement(strCurrentCurrency); } } } private void testAttributeSyntax (Attributes attr) throws SAXException { //Must have the attribute currency="" but nothing else if (!(attr.getLength() == 1 && attr.getLocalName(0).equalsIgnoreCase("currency"))) { throw new SAXException("Every convert xml tag must have a \"currency\" attribute"); } } //Called everytime data is found between a tag pair, e.g. ... public void characters (char[] value, int start, int length) throws SAXException { //The char array stores every single character in the xml document //start says where to start reading, and length says how many characters to read //valueToString gets this and turns it into a string String strData = valueToString(value, start, length); int whichWay = 0; if (strCurrentTag.equalsIgnoreCase(strDesTag)) { whichWay = 1; } else if (!strCurrentTag.equalsIgnoreCase(strSourceTag)) { throw new SAXException("Unspecified data \"" + strData + "\" was found. Only conversion rates between convert tags is allowed"); } try //Error thrown if can not convert into a float { conversions[whichWay].put(strCurrentCurrency, new Float(Float.parseFloat(strData))); } catch (NumberFormatException ex) { throw new SAXException("Error converting conversion rate found at: <" + strCurrentTag + " currency=\"" + strCurrentCurrency + "\">" + strData + "<\\" + strCurrentTag + ">"); } } //Converts the char array for the data into a string private String valueToString (char[] value, int start, int length) { String strBuffer = ""; for (int i = start, counter = 0; counter < length; i++, counter++) { strBuffer = strBuffer + value[i]; } return strBuffer; } //Called everytime an end tag is found, e.g. public void endElement (String nameSpaceURI, String strTag, String qName) throws SAXException { //Tags appear before or after the tags if (strCurrentTag.equalsIgnoreCase("START") || strCurrentTag.equalsIgnoreCase("END")) { throw new SAXException("Tags must not appear before or after the <" + strRootTag + "><\\" + strRootTag + "> tags, but <\\" + strTag + "> was found"); } else if (!strCurrentTag.equalsIgnoreCase(strTag)) //Must close the current open tag { throw new SAXException("Missing start tag, or ilegal nesting, was expecting <" + strCurrentTag + ">, but <\\" + strTag + "> was found"); } else if (strTag.equalsIgnoreCase(strSourceTag) || strTag.equalsIgnoreCase(strDesTag)) //Closes the tag by setting current tag back to root { strCurrentTag = strRootTag; } else if (strTag.equalsIgnoreCase(strRootTag)) //Nothing else can appear after this { strCurrentTag = "END"; } else { throw new SAXException("Unknown end xml tag <\\" + strTag + ">"); } } public void endDocument () throws SAXException { if (!strCurrentTag.equalsIgnoreCase("END")) //If end of document without { throw new SAXException("The last structure tag in the xml document must be <\\" + strRootTag + "> but this was not found"); } else if (conversions[0].size() != conversions[1].size() || conversions[0].size() != vecCurrencies.size()) //Unequal amount of conversions { throw new SAXException("The amount of conversions to and from are not equal. There are " + conversions[0].size() + " from, and " + conversions[1].size() + " to"); } int amountofConverts = conversions[0].size(); Float fromRate = 0.0f; Float toRate = 0.0f; strCurrencies = new String[amountofConverts]; //Store all of the currencies to be used conversionsFrom = new float[amountofConverts]; //Store the conversions from * to pound conversionsTo = new float[amountofConverts]; //Store the conversions from pound to * for (int i = 0; i < amountofConverts; i++) //Populate the conversion arrays in the same order as the currencies appear { strCurrencies[i] = (String)vecCurrencies.elementAt(i); //Everything must now be put in the same order fromRate = (Float)conversions[0].get(strCurrencies[i]); //Tries to find the value associated with the supplied key toRate = (Float)conversions[1].get(strCurrencies[i]); //Returns null if the key was not reconised if (fromRate == null) //Could not the value for the conversion (possible spelling mistake or obmission from xml file) { //Try to give the end user a helpful message and possible solution throw new SAXException("<" + strDesTag + " currency=\"" + strCurrencies[i] + "\"><\\" + strDesTag + "> was found\n\nbut could not find <" + strSourceTag + " currency=\"" + strCurrencies[i] + "\"><\\" + strSourceTag + ">"); } else if (toRate == null) { throw new SAXException("<" + strSourceTag + " currency=\"" + strCurrencies[i] + "\"><\\" + strSourceTag + "> was found\n\nbut could not find <" + strDesTag + " currency=\"" + strCurrencies[i] + "\"><\\" + strDesTag + ">"); } conversionsFrom[i] = fromRate; //Store the conversions in the right order conversionsTo[i] = toRate; } conversions[0] = null; //Making these null means the garbage collector can remove them conversions[1] = null; vecCurrencies = null; } }