/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.xml;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Map;
import java.util.Stack;

import divconq.io.IReader;
import divconq.io.ReaderWrapper;
import divconq.lang.Memory;
import divconq.util.StringUtil;

public class XmlReader implements IParseHandler {
	/**
	 * Read and parse xml that is held a string
	 * 
	 * @param xml the string that holds the Xml
	 * @return the root xml element
	 */
	public static XElement parse(String xml) {
		try {
			return new XmlReader(new StringReader(xml)).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}

	/**
	 * Read and parse xml that is pointed to by a reader (utf-8)
	 * 
	 * @param in the reader that holds the Xml
	 * @return the root xml element
	 */
	public static XElement parse(Reader in) {
		try {
			return new XmlReader(in).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}

	/**
	 * Read and parse xml that is pointed to by a stream (utf-8)
	 * 
	 * @param in the stream that holds the Xml
	 * @return the root xml element
	 */
	public static XElement parse(InputStream in) {
		try {
			return new XmlReader(new InputStreamReader(in)).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}

	/**
	 * Read and parse xml that is held in memory (utf-8)
	 * 
	 * @param mem the memory that holds the Xml
	 * @return the root xml element
	 */
	public static XElement parse(Memory mem) {
		try {
			return new XmlReader(mem).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}

	/**
	 * Read and parse an xml file
	 * 
	 * @param fullname the file name to read and parse
	 * @return the root xml element
	 */
	public static XElement loadFile(String fullname) {
		try {
			return new XmlReader(new FileReader(fullname)).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}

	/**
	 * Read and parse an xml file
	 * 
	 * @param fl the file to read and parse
	 * @return the root xml element
	 */
	public static XElement loadFile(File fl) {
		try {
			return new XmlReader(new FileReader(fl)).parse();
		} 
		catch (Exception e) {
		}
		
		return null;
	}
	
	// instance
	
	/**
	 * The source of the XML
	 */
	protected IReader input = null;
	/**
	 * The root of the class structure
	 */
	protected XElement top = null;
	/**
	 * The current element being worked on
	 */
	protected XElement element = null;
	/**
	 * Holds all the parent elements of the current element
	 */
	protected Stack<XElement> stack = null;

	/**
	 * Set XML source to be an IReader 
	 * 
	 * @param input the XML source to be parsed
	 */
	public XmlReader(IReader input) {
		this.input = input;
	}
	
	/**
	 * Set XML source to be a Reader 
	 * 
	 * @param input the XML source to be parsed
	 */
	public XmlReader(Reader input) {
		this.input = new ReaderWrapper(input);
	}

	/**
	 * Parses the XML and returns the root element.  Comments and PI
	 * will be missing, this is a really basic and lightweight XML utility.
	 * 
	 * @return the root XML element 
	 * @throws XMLParseException
	 *             if an error occurs when parsing the XML
	 * @throws IOException
	 *             if an error occurs when reading from the input source
	 */
	public XElement parse() throws Exception {
		this.top = null;
		this.element = null;
		this.stack = new Stack<XElement>();
		
		XmlParser.parse(this, this.input);
		
		return this.top;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#startDocument()
	 */
	public void startDocument() throws Exception {
		this.top = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#endDocument()
	 */
	public void endDocument() throws Exception {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#startElement(java.lang.String, java.lang.String, java.util.Map, int, int)
	 */
	public void startElement(String tag, Map<String, String> attributes, int line, int col) throws Exception {
		XElement newElement = new XElement(tag);

		newElement.setLocation(line, col);

		for (Map.Entry<String,String> entry : attributes.entrySet()) 
			newElement.setAttribute((String) entry.getKey(),
					(String) entry.getValue());

		if (this.top == null)
			this.top = newElement;
		else
			this.element.add(newElement);
		
		if (this.element != null)
			this.stack.push(this.element);
		
		this.element = newElement;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#endElement(java.lang.String, java.lang.String)
	 */
	public void endElement(String tag) throws Exception {
		if (!this.stack.isEmpty())
			this.element = this.stack.pop();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#text(java.lang.String, boolean, int, int)
	 */
	public void text(String str, boolean cdata, int line, int col) throws Exception {
		str = str.trim();
		
		// non-cdata text should not keep its extra whitespace
		if (!cdata)
			str = StringUtil.deleteWhitespace(str);
		
		if (StringUtil.isNotEmpty(str) && (this.element != null)) {
			XText text = new XText(str);
			text.setCData(cdata);
			this.element.add(text);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IParseHandler#text(java.lang.String, int, int)
	 */
	public void text(String str, int line, int col) throws Exception {
		this.text(str, false, line, col);
	}
}
