//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;
}
}