/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.13 $
 *********************************************************************/

#include "XMLParser.h"

#include "../String.h"
#include "../SmartPtr.h"
#include <sstream>

using namespace std;

namespace util { namespace xml {

	typedef StreamTokenizer::Token Token;
	typedef StreamTokenizer::SectionToken SectionToken;
	typedef StreamTokenizer::WordToken WordToken;
	typedef StreamTokenizer::NormalToken NormalToken;

	XMLParser::XMLParser() : _sctText("text","","<"), _tokenizer(StreamTokenizer::SYNTAX_XML)
	{
		_sctText.addReplacement("&lt;","<");
		_sctText.addReplacement("&gt;",">");
		_sctText.addReplacement("&amp;","&");
		_sctText.addReplacement("&apos;","'");
		_sctText.addReplacement("&quot;","\"");
		_sctText.addReplacement("\r\n","\n");
	}
	
	Node* XMLParser::getNextNode(istream& in) throw (util::io::IOException&, XMLException&)
	{
		//Try to read a text section
		if (in.peek() != '<')
			{
				string text(_sctText.readSection(in));
				in.putback('<');
				if (text.size())
					return new Text(text);
			}

		SmartPtr<Token> token(_tokenizer.getNextToken(in));
		
		if (token->getType() != TOKEN_SECTION)
			throw XMLException("XMLParser::getNextNode(istream&): expecting a tag.");

		SectionToken* section = (SectionToken*)token.get();

		string sectionName(section->getName());
		if (sectionName == "tag")
			return constructTag(section->getContents());
		else if (sectionName == "comment")
			return new Comment(section->getContents());
		else if (sectionName == "instruction")
			{
				List<string> parts(2);
				String::tokenize(section->getContents(),"\t\n\r ",parts,2);
				return new ProcessingInstruction(parts[0],parts[1]);
			}
		else if (sectionName == "declaration")
			{
				List<string> parts(2);
				String::tokenize(section->getContents(),"\t\n\r ",parts,2);
				return new Declaration(parts[0],parts[1]);
			}
		else if (sectionName == "cdata")
			return new CDATASection(section->getContents());
		else
			throw XMLException("XMLParser::getNextNode(istream&): unknown section: " + section->getContents());
	}

	Node* XMLParser::constructTag(string contents) throw (util::io::IOException&, XMLException&)
	{
		int len = contents.size();
		//cerr << "Constructing tag from '" << contents << "'" << endl;
		if (!len)
			throw XMLException("XMLParser::constructTag(string): no tag name in a tag.");

		Element::Type type = Element::TAG_OPENING;
		if (contents[0] == '/')
			{
				if (len < 2)
					throw XMLException("XMLParser::constructTag(string): no tag name in a closing tag.");
				type = Element::TAG_CLOSING;
				return new Element(String::fix(contents.substr(1)),type);
			}
		else if (contents[contents.size()-1] == '/') //empty tag
			{
				contents.resize(len-1);
				type = Element::TAG_EMPTY;
			}
		istringstream sin(string(contents.c_str(),contents.size()));
		string name;

		SmartPtr<Token> token(_tokenizer.getNextToken(sin));
		if (token->getType() != TOKEN_WORD)
			throw util::io::IOException("XMLParser::constructTag(string): not a valid name for a tag.");

		WordToken* word = (WordToken*)token.get();

		SmartPtr<Element> pResult(new Element(word->getWord(),type));
		try
			{
				while (true)
					{
						SmartPtr<Token> attr(_tokenizer.getNextToken(sin));
						if (attr->getType() != TOKEN_WORD)
							throw XMLException("XMLParser::constructTag(string): invalid attribute for '" +
																 pResult->tagName + "'.");
						string attribute(((WordToken*)attr.get())->getWord());
						SmartPtr<Token> chr(_tokenizer.getNextToken(sin));
						if (chr->getType() != TOKEN_NORMAL ||
								((NormalToken*)chr.get())->getCharacter() != '=')
							throw XMLException("XMLParser::constructTag(string): expecting '=' after '" +
																 attribute + "' in tag '" + pResult->tagName + "'.");
						SmartPtr<Token> section(_tokenizer.getNextToken(sin));
						if (section->getType() != TOKEN_SECTION ||
								((SectionToken*)section.get())->getName() != "string")
							throw XMLException("XMLParser::constructTag(string): expecting a quoted value after '" +
																 attribute + "=' in tag '" + pResult->tagName + "'.");
						pResult->setAttribute(attribute,((SectionToken*)section.get())->getContents());
						//cerr << "Put " << attribute << " = " << _tokenizer.wordValue << endl;
					}
			}
		catch (io::EOFException& eofe)
			{
				//cerr << eofe->getMessage() << endl;
			}
		return pResult.release();
	}
	
	Document* XMLParser::readDocument(istream& in) throw (util::io::IOException&, XMLException&)
	{
		in >> ws;
		Node* start = getNextNode(in);
		ProcessingInstruction* instruction = dynamic_cast<ProcessingInstruction*>(start);
		if (!instruction || instruction->name != "xml")
			{
				delete start;
				throw XMLException("XMLParser::readDocument(istream&): An XML document must start with a '<?xml ?>' tag.");
			}
		delete start;
		in >> ws;
		Node* root = readFragment(in);
		Element* rootElement = dynamic_cast<Element*>(root);
		if (!rootElement || rootElement->tagType == Element::TAG_CLOSING)
			{
				delete root;
				throw XMLException("XMLParser::readDocument(istream&): An XML document must have a root element.");
			}
		return new Document(rootElement);
	}

	Node* XMLParser::readFragment(istream& in) throw (util::io::IOException&, XMLException&)
	{
		Node* node = getNextNode(in);
		Element* elem = dynamic_cast<Element*>(node);
		if (!elem || elem->tagType != Element::TAG_OPENING) return node;

		SmartPtr<Element> element(elem);

		//if (element->tagType == Element::TAG_CLOSING)
		//	throw XMLException("XMLParser::readFragment(istream&): Excepting an opening tag, read '</" + element->tagName + ">'.");


		while (true)
			{
				Node* child = readFragment(in);
				SmartPtr<Element> childElement(dynamic_cast<Element*>(child));
				if (childElement.get())
					{
						if (childElement->tagType == Element::TAG_CLOSING)
							{
								if (childElement->tagName == element->tagName)
									break;
								else
									throw XMLException("XMLParser::readFragment(istream&): Excepting '</" + element->tagName +
																				 ">', read '</" + childElement->tagName + ">'.");
							}
						else
							element->appendChild(childElement.release());
					}
				else
					element->appendChild(child);
			}
		return element.release();
	}
}}
