/**
 * XML.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <yatexml.h>
#include <string.h>

using namespace TelEngine;


const String XmlElement::s_ns = "xmlns";
const String XmlElement::s_nsPrefix = "xmlns:";
static const String s_type("type");
static const String s_name("name");


// Return a replacement char for the given string
char replace(const char* str, const XmlEscape* esc)
{
    if (!str)
	return 0;
    if (esc) {
	for (; esc->value; esc++)
	    if (!::strcmp(str,esc->value))
		return esc->replace;
    }
    return 0;
}

// Return a replacement string for the given char
const char* replace(char replace, const XmlEscape* esc)
{
    if (esc) {
	for (; esc->value; esc++)
	    if (replace == esc->replace)
		return esc->value;
    }
    return 0;
}

// XmlEscape a string or replace it if found in a list of restrictions
static inline void addAuth(String& buf, const String& comp, const String& value,
    bool esc, const String* auth)
{
    if (auth) {
	for (; !auth->null(); auth++)
	    if (*auth == comp) {
		buf << "***";
		return;
	    }
    }
    if (esc)
	XmlSaxParser::escape(buf,value);
    else
	buf << value;
}


/*
 * XmlSaxParser
 */
const TokenDict XmlSaxParser::s_errorString[] = {
	{"No error",                      NoError},
	{"Error",                         Unknown},
	{"Not well formed",               NotWellFormed},
	{"I/O error",                     IOError},
	{"Error parsing Element",         ElementParse},
	{"Failed to read Element name",   ReadElementName},
	{"Bad element name",              InvalidElementName},
	{"Error reading Attributes",      ReadingAttributes},
	{"Error reading end tag",         ReadingEndTag},
	{"Error parsing Comment",         CommentParse},
	{"Error parsing Declaration",     DeclarationParse},
	{"Error parsing Definition",      DefinitionParse},
	{"Error parsing CDATA",           CDataParse},
	{"Incomplete",                    Incomplete},
	{"Invalid encoding",              InvalidEncoding},
	{"Unsupported encoding",          UnsupportedEncoding},
	{"Unsupported version",           UnsupportedVersion},
	{0,0}
};

const XmlEscape XmlSaxParser::s_escape[] = {
	{"&lt;",   '<'},
	{"&gt;",   '>'},
	{"&amp;",  '&'},
	{"&quot;", '\"'},
	{"&apos;", '\''},
	{0,0}
};


XmlSaxParser::XmlSaxParser(const char* name)
    : m_offset(0), m_row(1), m_column(1), m_error(NoError),
    m_parsed(""), m_unparsed(None)
{
    debugName(name);
}

XmlSaxParser::~XmlSaxParser()
{
}

// Parse a given string
bool XmlSaxParser::parse(const char* text)
{
    if (TelEngine::null(text))
	return m_error == NoError;
#ifdef XDEBUG
    String tmp;
    m_parsed.dump(tmp," ");
    if (tmp)
	tmp = " parsed=" + tmp;
    XDebug(this,DebugAll,"XmlSaxParser::parse(%s) unparsed=%u%s buf=%s [%p]",
	text,unparsed(),tmp.safe(),m_buf.safe(),this);
#endif
    char car;
    setError(NoError);
    String auxData;
    m_buf << text;
    if (m_buf.lenUtf8() == -1) {
	//FIXME this should not be here in case we have a different encoding
	DDebug(this,DebugNote,"Request to parse invalid utf-8 data [%p]",this);
	return setError(Incomplete);
    }
    if (unparsed()) {
	if (unparsed() != Text) {
	    if (!auxParse())
		return false;
	}
	else
	    auxData = m_parsed;
	resetParsed();
	setUnparsed(None);
    }
    unsigned int len = 0;
    while (m_buf.at(len) && !error()) {
	car = m_buf.at(len);
	if (car != '<' ) { // We have a new child check what it is
	    if (car == '>' || !checkDataChar(car)) {
		Debug(this,DebugNote,"XML text contains unescaped '%c' character [%p]",
		    car,this);
		return setError(Unknown);
	    }
	    len++; // Append xml Text
	    continue;
	}
	if (len > 0) {
	    auxData << m_buf.substr(0,len);
	}
	if (auxData.c_str()) {  // We have an end of tag or another child is riseing
	    if (!processText(auxData))
		return false;
	    m_buf = m_buf.substr(len);
	    len = 0;
	    auxData = "";
	}
	char auxCar = m_buf.at(1);
	if (!auxCar)
	    return setError(Incomplete);
	if (auxCar == '?') {
	    m_buf = m_buf.substr(2);
	    if (!parseInstruction())
		return false;
	    continue;
	}
	if (auxCar == '!') {
	    m_buf = m_buf.substr(2);
	    if (!parseSpecial())
		return false;
	    continue;
	}
	if (auxCar == '/') {
	    m_buf = m_buf.substr(2);
	    if (!parseEndTag())
		return false;
	    continue;
	}
	// If we are here mens that we have a element
	// process an xml element
	m_buf = m_buf.substr(1);
	if (!parseElement())
	    return false;
    }
    // Incomplete text
    if ((unparsed() == None || unparsed() == Text) && (auxData || m_buf)) {
	if (!auxData)
	    m_parsed.assign(m_buf);
	else {
	    auxData << m_buf;
	    m_parsed.assign(auxData);
	}
	m_buf = "";
	setUnparsed(Text);
	return setError(Incomplete);
    }
    if (error()) {
	DDebug(this,DebugNote,"Got error while parsing %s [%p]",getError(),this);
	return false;
    }
    m_buf = "";
    resetParsed();
    setUnparsed(None);
    return true;
}

// Process incomplete text
bool XmlSaxParser::completeText()
{
    if (!completed() || unparsed() != Text || error() != Incomplete)
	return error() == NoError;
    String tmp = m_parsed;
    return processText(tmp);
}

// Parse an unfinished xml object
bool XmlSaxParser::auxParse()
{
    switch (unparsed()) {
	case Element:
	    return parseElement();
	case CData:
	    return parseCData();
	case Comment:
	    return parseComment();
	case Declaration:
	    return parseDeclaration();
	case Instruction:
	    return parseInstruction();
	case EndTag:
	    return parseEndTag();
	case Special:
	    return parseSpecial();
	default:
	    return false;
    }
}

// Set the error code and destroys a child if error code is not NoError
bool XmlSaxParser::setError(Error error, XmlChild* child)
{
    m_error = error;
    if (child && error)
	TelEngine::destruct(child);
    return m_error == XmlSaxParser::NoError;
}

// Parse an endtag form the main buffer
bool XmlSaxParser::parseEndTag()
{
    bool aux = false;
    String* name = extractName(aux);
    // We don't check aux flag because we don't look for attributes here
    if (!name) {
	if (error() && error() == Incomplete)
	    setUnparsed(EndTag);
	return false;
    }
    if (!aux || m_buf.at(0) == '/') { // The end tag has attributes or contains / char at the end of name
	setError(ReadingEndTag);
	Debug(this,DebugNote,"Got bad end tag </%s/> [%p]",name->c_str(),this);
	setUnparsed(EndTag);
	m_buf = *name + m_buf;
	return false;
    }
    resetError();
    endElement(*name);
    if (error()) {
	setUnparsed(EndTag);
	m_buf = *name + ">";
	TelEngine::destruct(name);
	return false;
    }
    m_buf = m_buf.substr(1);
    TelEngine::destruct(name);
    return true;
}

// Parse an instruction form the main buffer
bool XmlSaxParser::parseInstruction()
{
    XDebug(this,DebugAll,"XmlSaxParser::parseInstruction() buf len=%u [%p]",m_buf.length(),this);
    setUnparsed(Instruction);
    if (!m_buf.c_str())
	return setError(Incomplete);
    // extract the name
    String name;
    char c;
    int len = 0;
    if (!m_parsed) {
	bool nameComplete = false;
	bool endDecl = false;
	while (0 != (c = m_buf.at(len))) {
	    nameComplete = blank(c);
	    if (!nameComplete) {
		// Check for instruction end: '?>'
		if (c == '?') {
		    char next = m_buf.at(len + 1);
		    if (!next)
			return setError(Incomplete);
		    if (next == '>') {
			nameComplete = endDecl = true;
			break;
		    }
		}
		if (checkNameCharacter(c)) {
		    len++;
		    continue;
		}
		Debug(this,DebugNote,"Instruction name contains bad character '%c' [%p]",c,this);
		return setError(InvalidElementName);
	    }
	    // Blank found
	    if (len)
	        break;
	    Debug(this,DebugNote,"Instruction with empty name [%p]",this);
	    return setError(InvalidElementName);
	}
	if (!len) {
	    if (!endDecl)
		return setError(Incomplete);
	    // Remove instruction end from buffer
	    m_buf = m_buf.substr(2);
	    Debug(this,DebugNote,"Instruction with empty name [%p]",this);
	    return setError(InvalidElementName);
	}
	if (!nameComplete)
	    return setError(Incomplete);
	name = m_buf.substr(0,len);
	m_buf = m_buf.substr(!endDecl ? len : len + 2);
	if (name == YSTRING("xml")) {
	    if (!endDecl)
		return parseDeclaration();
	    resetParsed();
	    resetError();
	    setUnparsed(None);
	    gotDeclaration(NamedList::empty());
	    return error() == NoError;
	}
	// Instruction name can't be xml case insensitive
	if (name.length() == 3 && name.startsWith("xml",false,true)) {
	    Debug(this,DebugNote,"Instruction name '%s' reserved [%p]",name.c_str(),this);
	    return setError(InvalidElementName);
	}
    }
    else {
	name = m_parsed;
	resetParsed();
    }
    // Retrieve instruction content
    skipBlanks();
    len = 0;
    while (0 != (c = m_buf.at(len))) {
	if (c != '?') {
	    if (c == 0x0c) {
		setError(Unknown);
		Debug(this,DebugNote,"Xml instruction with unaccepted character '%c' [%p]",
		    c,this);
		return false;
	    }
	    len++;
	    continue;
	}
	char ch = m_buf.at(len + 1);
	if (!ch)
	    break;
	if (ch == '>') { // end of instruction
	    NamedString inst(name,m_buf.substr(0,len));
	    // Parsed instruction: remove instruction end from buffer and reset parsed
	    m_buf = m_buf.substr(len + 2);
	    resetParsed();
	    resetError();
	    setUnparsed(None);
	    gotProcessing(inst);
	    return error() == NoError;
	}
	len ++;
    }
    // If we are here mens that text has reach his bounds is an error or we need to receive more data
    m_parsed.assign(name);
    return setError(Incomplete);
}

// Parse a declaration form the main buffer
bool XmlSaxParser::parseDeclaration()
{
    XDebug(this,DebugAll,"XmlSaxParser::parseDeclaration() buf len=%u [%p]",m_buf.length(),this);
    setUnparsed(Declaration);
    if (!m_buf.c_str())
	return setError(Incomplete);
    NamedList dc("xml");
    if (m_parsed.count()) {
	dc.copyParams(m_parsed);
	resetParsed();
    }
    char c;
    skipBlanks();
    int len = 0;
    while (m_buf.at(len)) {
	c = m_buf.at(len);
	if (c != '?') {
	    skipBlanks();
	    NamedString* s = getAttribute();
	    if (!s) {
		if (error() == Incomplete)
		    m_parsed = dc;
		return false;
	    }
	    len = 0;
	    if (dc.getParam(s->name())) {
		Debug(this,DebugNote,"Duplicate attribute '%s' in declaration [%p]",
		    s->name().c_str(),this);
		TelEngine::destruct(s);
		return setError(DeclarationParse);
	    }
	    dc.addParam(s);
	    char ch = m_buf.at(len);
	    if (ch && !blank(ch) && ch != '?') {
		Debug(this,DebugNote,"No blanks between attributes in declaration [%p]",this);
		return setError(DeclarationParse);
	    }
	    skipBlanks();
	    continue;
	}
	if (!m_buf.at(++len))
	    break;
	char ch = m_buf.at(len);
	if (ch == '>') { // end of declaration
	    // Parsed declaration: remove declaration end from buffer and reset parsed
	    resetError();
	    resetParsed();
	    setUnparsed(None);
	    m_buf = m_buf.substr(len + 1);
	    gotDeclaration(dc);
	    return error() == NoError;
	}
	Debug(this,DebugNote,"Invalid declaration ending char '%c' [%p]",ch,this);
	return setError(DeclarationParse);
    }
    m_parsed.copyParams(dc);
    setError(Incomplete);
    return false;
}

// Parse a CData section form the main buffer
bool XmlSaxParser::parseCData()
{
    if (!m_buf.c_str()) {
	setUnparsed(CData);
	setError(Incomplete);
	return false;
    }
    String cdata = "";
    if (m_parsed.c_str()) {
	cdata = m_parsed;
	resetParsed();
    }
    char c;
    int len = 0;
    while (m_buf.at(len)) {
	c = m_buf.at(len);
	if (c != ']') {
	    len ++;
	    continue;
	}
	if (m_buf.substr(++len,2) == "]>") { // End of CData section
	    cdata += m_buf.substr(0,len - 1);
	    resetError();
	    gotCdata(cdata);
	    resetParsed();
	    if (error())
		return false;
	    m_buf = m_buf.substr(len + 2);
	    return true;
	}
    }
    cdata += m_buf;
    m_buf = "";
    setUnparsed(CData);
    int length = cdata.length();
    m_buf << cdata.substr(length - 2);
    if (length > 1)
	m_parsed.assign(cdata.substr(0,length - 2));
    setError(Incomplete);
    return false;
}

// Helper method to classify the Xml objects starting with "<!" sequence
bool XmlSaxParser::parseSpecial()
{
    if (m_buf.length() < 2) {
	setUnparsed(Special);
	return setError(Incomplete);
    }
    if (m_buf.startsWith("--")) {
	m_buf = m_buf.substr(2);
	if (!parseComment())
	    return false;
	return true;
    }
    if (m_buf.length() < 7) {
	setUnparsed(Special);
	return setError(Incomplete);
    }
    if (m_buf.startsWith("[CDATA[")) {
	m_buf = m_buf.substr(7);
	if (!parseCData())
	    return false;
	return true;
    }
    if (m_buf.startsWith("DOCTYPE")) {
	m_buf = m_buf.substr(7);
	if (!parseDoctype())
	    return false;
	return true;
    }
    Debug(this,DebugNote,"Can't parse unknown special starting with '%s' [%p]",
	m_buf.c_str(),this);
    setError(Unknown);
    return false;
}


// Extract from the given buffer an comment and check if is valid
bool XmlSaxParser::parseComment()
{
    String comment;
    if (m_parsed.c_str()) {
	comment = m_parsed;
	resetParsed();
    }
    char c;
    int len = 0;
    while (m_buf.at(len)) {
	c = m_buf.at(len);
	if (c != '-') {
	    if (c == 0x0c) {
		Debug(this,DebugNote,"Xml comment with unaccepted character '%c' [%p]",c,this);
		return setError(NotWellFormed);
	    }
	    len++;
	    continue;
	}
	if (m_buf.at(len + 1) == '-' && m_buf.at(len + 2) == '>') { // End of comment
	    comment << m_buf.substr(0,len);
	    m_buf = m_buf.substr(len + 3);
#ifdef DEBUG
	    if (comment.at(0) == '-' || comment.at(comment.length() - 1) == '-')
		DDebug(this,DebugInfo,"Comment starts or ends with '-' character [%p]",this);
	    if (comment.find("--") >= 0)
		DDebug(this,DebugInfo,"Comment contains '--' char sequence [%p]",this);
#endif
	    gotComment(comment);
	    resetParsed();
	    // The comment can apear anywhere sow SaxParser never
	    // sets an error when receive a comment
	    return true;
	}
	len++;
    }
    // If we are here we haven't detect the end of comment
    comment << m_buf;
    int length = comment.length();
    // Keep the last 2 charaters in buffer because if the input buffer ends
    // between "--" and ">"
    m_buf = comment.substr(length - 2);
    setUnparsed(Comment);
    if (length > 1)
	m_parsed.assign(comment.substr(0,length - 2));
    return setError(Incomplete);
}

// Parse an element form the main buffer
bool XmlSaxParser::parseElement()
{
    XDebug(this,DebugAll,"XmlSaxParser::parseElement() buf len=%u [%p]",m_buf.length(),this);
    if (!m_buf.c_str()) {
	setUnparsed(Element);
	return setError(Incomplete);
    }
    bool empty = false;
    if (!m_parsed.c_str()) {
	String* name = extractName(empty);
	if (!name) {
	    if (error() == Incomplete)
		setUnparsed(Element);
	    return false;
	}
#ifdef XML_STRICT
	// http://www.w3.org/TR/REC-xml/
	// Names starting with 'xml' (case insensitive) are reserved
	if (name->startsWith("xml",false,true)) {
	    Debug(this,DebugNote,"Element tag starts with 'xml' [%p]",this);
	    TelEngine::destruct(name);
	    return setError(ReadElementName);
	}
#endif
	m_parsed.assign(*name);
	TelEngine::destruct(name);
    }
    if (empty) { // empty flag means that the element does not have attributes
	// check if the element is empty
	bool aux = m_buf.at(0) == '/';
	if (!processElement(m_parsed,aux))
	    return false;
	if (aux)
	    m_buf = m_buf.substr(2); // go back where we were
	else
	    m_buf = m_buf.substr(1); // go back where we were
	return true;
    }
    char c;
    skipBlanks();
    int len = 0;
    while (m_buf.at(len)) {
	c = m_buf.at(len);
	if (c == '/' || c == '>') { // end of element declaration
	    if (c == '>') {
		if (!processElement(m_parsed,false))
		    return false;
		m_buf = m_buf.substr(1);
		return true;
	    }
	    if (!m_buf.at(++len))
		break;
	    char ch = m_buf.at(len);
	    if (ch != '>') {
		Debug(this,DebugNote,"Element attribute name contains '/' character [%p]",this);
		return setError(ReadingAttributes);
	    }
	    if (!processElement(m_parsed,true))
		return false;
	    m_buf = m_buf.substr(len + 1);
	    return true;
	}
	NamedString* ns = getAttribute();
	if (!ns) { // Attribute is invalid
	    if (error() == Incomplete)
		break;
	    return false;
	}
	if (m_parsed.getParam(ns->name())) {
	    Debug(this,DebugNote,"Duplicate attribute '%s' [%p]",ns->name().c_str(),this);
	    TelEngine::destruct(ns);
	    return setError(NotWellFormed);
	}
	XDebug(this,DebugAll,"Parser adding attribute %s='%s' to '%s' [%p]",
	    ns->name().c_str(),ns->c_str(),m_parsed.c_str(),this);
	m_parsed.setParam(ns);
	char ch = m_buf.at(len);
	if (ch && !blank(ch) && (ch != '/' && ch != '>')) {
	    Debug(this,DebugNote,"Element without blanks between attributes [%p]",this);
	    return setError(NotWellFormed);
	}
	skipBlanks();
    }
    setUnparsed(Element);
    return setError(Incomplete);
}

// Parse a doctype form the main buffer
bool XmlSaxParser::parseDoctype()
{
    if (!m_buf.c_str()) {
	setUnparsed(Doctype);
	setError(Incomplete);
	return false;
    }
    unsigned int len = 0;
    skipBlanks();
    while (m_buf.at(len) && !blank(m_buf.at(len)))
	len++;
    // Use a while() to break to the end
    while (m_buf.at(len)) {
	while (m_buf.at(len) && blank(m_buf.at(len)))
	    len++;
	if (len >= m_buf.length())
	   break;
	if (m_buf[len++] == '[') {
	    while (len < m_buf.length()) {
		if (m_buf[len] != ']') {
		    len ++;
		    continue;
		}
		if (m_buf.at(++len) != '>')
		    continue;
		gotDoctype(m_buf.substr(0,len));
		resetParsed();
		m_buf = m_buf.substr(len + 1);
		return true;
	    }
	    break;
	}
	while (len < m_buf.length()) {
	    if (m_buf[len] != '>') {
		len++;
		continue;
	    }
	    gotDoctype(m_buf.substr(0,len));
	    resetParsed();
	    m_buf = m_buf.substr(len + 1);
	    return true;
	}
	break;
    }
    setUnparsed(Doctype);
    return setError(Incomplete);
}

// Extract the name of tag
String* XmlSaxParser::extractName(bool& empty)
{
    skipBlanks();
    unsigned int len = 0;
    bool ok = false;
    empty = false;
    while (len < m_buf.length()) {
	char c = m_buf[len];
	if (blank(c)) {
	    if (checkFirstNameCharacter(m_buf[0])) {
		ok = true;
		break;
	    }
	    Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
		m_buf[0],this);
	    setError(ReadElementName);
	    return 0;
	}
	if (c == '/' || c == '>') { // end of element declaration
	    if (c == '>') {
		if (checkFirstNameCharacter(m_buf[0])) {
		    empty = true;
		    ok = true;
		    break;
		}
		Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
		    m_buf[0],this);
		setError(ReadElementName);
		return 0;
	    }
	    char ch = m_buf.at(len + 1);
	    if (!ch)
		break;
	    if (ch != '>') {
		Debug(this,DebugNote,"Element tag contains '/' character [%p]",this);
		setError(ReadElementName);
		return 0;
	    }
	    if (checkFirstNameCharacter(m_buf[0])) {
		empty = true;
		ok = true;
		break;
	    }
	    Debug(this,DebugNote,"Element tag starting with invalid char %c [%p]",
		m_buf[0],this);
	    setError(ReadElementName);
	    return 0;
	}
	if (checkNameCharacter(c))
	    len++;
	else {
	    Debug(this,DebugNote,"Element tag contains invalid char %c [%p]",c,this);
	    setError(ReadElementName);
	    return 0;
	}
    }
    if (ok) {
	String* name = new String(m_buf.substr(0,len));
	m_buf = m_buf.substr(len);
	if (!empty) {
	    skipBlanks();
	    empty = (m_buf && m_buf[0] == '>') ||
		(m_buf.length() > 1 && m_buf[0] == '/' && m_buf[1] == '>');
	}
	return name;
    }
    setError(Incomplete);
    return 0;
}

// Extract an attribute
NamedString* XmlSaxParser::getAttribute()
{
    String name = "";
    skipBlanks();
    char c,sep = 0;
    unsigned int len = 0;

    while (len < m_buf.length()) { // Circle until we find attribute value startup character (["]|['])
	c = m_buf[len];
	if (blank(c) || c == '=') {
	    if (!name.c_str())
		name = m_buf.substr(0,len);
	    len++;
	    continue;
	}
	if (!name.c_str()) {
	    if (!checkNameCharacter(c)) {
		Debug(this,DebugNote,"Attribute name contains %c character [%p]",c,this);
		setError(ReadingAttributes);
		return 0;
	    }
	    len++;
	    continue;
	}
	if (c != '\'' && c != '\"') {
	    Debug(this,DebugNote,"Unenclosed attribute value [%p]",this);
	    setError(ReadingAttributes);
	    return 0;
	}
	sep = c;
	break;
    }

    if (!sep) {
	setError(Incomplete);
	return 0;
    }
    if (!checkFirstNameCharacter(name[0])) {
	Debug(this,DebugNote,"Attribute name starting with bad character %c [%p]",
	    name.at(0),this);
	setError(ReadingAttributes);
	return 0;
    }
    int pos = ++len;

    while (len < m_buf.length()) {
	c = m_buf[len];
	if (c != sep && !badCharacter(c)) {
	    len ++;
	    continue;
	}
	if (badCharacter(c)) {
	    Debug(this,DebugNote,"Attribute value with unescaped character '%c' [%p]",
		c,this);
	    setError(ReadingAttributes);
	    return 0;
	}
	NamedString* ns = new NamedString(name,m_buf.substr(pos,len - pos));
	m_buf = m_buf.substr(len + 1);
	// End of attribute value
	unEscape(*ns);
	if (error()) {
	    TelEngine::destruct(ns);
	    return 0;
	}
	return ns;
    }

    setError(Incomplete);
    return 0;
}

// Reset this parser
void XmlSaxParser::reset()
{
    m_offset = 0;
    m_row = 1;
    m_column = 1;
    m_error = NoError;
    m_buf.clear();
    resetParsed();
    m_unparsed = None;
}

// Verify if the given character is in the range allowed
bool XmlSaxParser::checkFirstNameCharacter(unsigned char ch)
{
    return ch == ':' || (ch >= 'A' && ch <= 'Z') || ch == '_' || (ch >= 'a' && ch <= 'z')
	|| (ch >= 0xc0 && ch <= 0xd6) || (ch >= 0xd8 && ch <= 0xf6) || (ch >= 0xf8);
}

// Check if the given character is in the range allowed for an xml char
bool XmlSaxParser::checkDataChar(unsigned char c)
{
    return  c == 0x9 || c == 0xA || c == 0xD || (c >= 0x20);
}

// Verify if the given character is in the range allowed for a xml name
bool XmlSaxParser::checkNameCharacter(unsigned char ch)
{
    return checkFirstNameCharacter(ch) || ch == '-' || ch == '.' || (ch >= '0' && ch <= '9')
	|| ch == 0xB7;
}

// Remove blank characters from the beginning of the buffer
void XmlSaxParser::skipBlanks()
{
    unsigned int len = 0;
    while (len < m_buf.length() && blank(m_buf[len]))
	len++;
    if (len != 0)
	m_buf = m_buf.substr(len);
}

// Obtain a char from an ascii decimal char declaration
inline unsigned char getDec(String& dec)
{
    if (dec.length() > 6) {
	DDebug(DebugNote,"Decimal number '%s' too long",dec.c_str());
	return 0;
    }
    int num = dec.substr(2,dec.length() - 3).toInteger(-1);
    if (num > 0 && num < 256)
	return num;
    DDebug(DebugNote,"Invalid decimal number '%s'",dec.c_str());
    return 0;
}

// Unescape the given text
void XmlSaxParser::unEscape(String& text)
{
    const char* str = text.c_str();
    if (!str)
	return;
    String buf;
    String aux = "&";
    unsigned int len = 0;
    int found = -1;
    while (str[len]) {
	if (str[len] == '&' && found < 0) {
	    found = len++;
	    continue;
	}
	if (found < 0) {
	    len++;
	    continue;
	}
	if (str[len] == '&') {
	    Debug(this,DebugNote,"Unescape. Duplicate '&' in expression [%p]",this);
	    setError(NotWellFormed);
	    return;
	}
	if (str[len] != ';')
	    len++;
	else { // We have a candidate for escaping
	    len += 1; // Append ';' character
	    String aux(str + found,len - found);
	    char re = 0;
	    if (aux.startsWith("&#")) {
		if (aux.at(2) == 'x') {
		    if (aux.length() > 4 && aux.length() <= 12) {
			int esc = aux.substr(3,aux.length() - 4).toInteger(-1,16);
			if (esc != -1) {
			    UChar uc(esc);
			    buf.append(str,found) << uc.c_str();
			    str += len;
			    len = 0;
			    found = -1;
			    continue;
			}
		    }
		} else
		    re = getDec(aux);
	    }
	    if (re == '&') {
		if (str[len] == '#') {
		    aux = String(str + len,4);
		    if (aux == "#60;") {
			re = '<';
			len += 4;
		    }
		    if (aux == "#38;") {
			re = '&';
			len += 4;
		    }
		}
	    }
	    else if (!re)
		re = replace(aux,s_escape);
	    if (re) { // We have an valid escape character
		buf << String(str,found) << re;
		str += len;
		len = 0;
		found = -1;
	    }
	    else {
		Debug(this,DebugNote,"Unescape. No replacement found for '%s' [%p]",
		    String(str + found,len - found).c_str(),this);
		setError(NotWellFormed);
		return;
	    }
	}
    }
    if (found >= 0) {
	Debug(this,DebugNote,"Unescape. Unexpected end of expression [%p]",this);
	setError(NotWellFormed);
	return;
    }
    if (len) {
	if (str != text.c_str()) {
	    buf << String(str,len);
	    text = buf;
	}
    }
    else
	text = buf;
}

// Check if a given string is a valid xml tag name
bool XmlSaxParser::validTag(const String& buf)
{
    if (!(buf && checkFirstNameCharacter(buf[0])))
	return false;
    for (unsigned int i = 1; i < buf.length(); i++)
	if (!checkNameCharacter(buf[i]))
	    return false;
    return true;
}

// XmlEscape the given text
void XmlSaxParser::escape(String& buf, const String& text)
{
    const char* str = text.c_str();
    if (!str)
	return;
    char c;
    while ((c = *str++)) {
	const char* rep = replace(c,XmlSaxParser::s_escape);
	if (!rep) {
	    buf += c;
	    continue;
	}
	buf += rep;
    }
}

// Calls gotElement(). Reset parsed if ok
bool XmlSaxParser::processElement(NamedList& list, bool empty)
{
    gotElement(list,empty);
    if (error() == XmlSaxParser::NoError) {
	resetParsed();
	return true;
    }
    return false;
}

// Calls gotText() and reset parsed on success
bool XmlSaxParser::processText(String& text)
{
    resetError();
    unEscape(text);
    if (!error())
	gotText(text);
    else
	setUnparsed(Text);
    if (!error()) {
	resetParsed();
	setUnparsed(None);
    }
    return error() == NoError;
}


/*
 * XmlDomPareser
 */
XmlDomParser::XmlDomParser(const char* name, bool fragment)
    : XmlSaxParser(name),
    m_current(0), m_data(0), m_ownData(true)
{
    if (fragment)
	m_data = new XmlFragment();
    else
	m_data = new XmlDocument();
}

XmlDomParser::XmlDomParser(XmlParent* fragment, bool takeOwnership)
    : m_current(0), m_data(0), m_ownData(takeOwnership)
{
    m_data = fragment;
}

XmlDomParser::~XmlDomParser()
{
    if (m_ownData) {
	reset();
	if (m_data)
	    delete m_data;
    }
}

// Create a new xml comment and append it in the xml three
void XmlDomParser::gotComment(const String& text)
{
    XmlComment* com = new XmlComment(text);
    if (m_current)
	setError(m_current->addChild(com),com);
    else
	setError(m_data->addChild(com),com);

}

// Append a new xml doctype to main xml parent
void XmlDomParser::gotDoctype(const String& doc)
{
    m_data->addChild(new XmlDoctype(doc));
}

// TODO implement it see what to do
void XmlDomParser::gotProcessing(const NamedString& instr)
{
    DDebug(this,DebugStub,"gotProcessing(%s=%s) not implemented [%p]",
	instr.name().c_str(),instr.safe(),this);
}

// Create a new xml declaration, verifies the version and encoding
// and append it in the main xml parent
void XmlDomParser::gotDeclaration(const NamedList& decl)
{
    if (m_current) {
	setError(DeclarationParse);
	Debug(this,DebugNote,"Received declaration inside element bounds [%p]",this);
	return;
    }
    Error err = NoError;
    while (true) {
	String* version = decl.getParam("version");
	if (version) {
	    int ver = version->substr(0,version->find('.')).toInteger();
	    if (ver != 1) {
		err = UnsupportedVersion;
		break;
	    }
	}
	String* enc = decl.getParam("encoding");
	if (enc && !(*enc &= "utf-8")) {
	    err = UnsupportedEncoding;
	    break;
	}
	break;
    }
    if (err == NoError) {
	XmlDeclaration* dec = new XmlDeclaration(decl);
	setError(m_data->addChild(dec),dec);
    }
    else {
	setError(err);
	Debug(this,DebugNote,
	    "Received unacceptable declaration version='%s' encoding='%s' error '%s' [%p]",
	    decl.getValue("version"),decl.getValue("encoding"),getError(),this);
    }
}

// Create a new xml text and append it in the xml tree
void XmlDomParser::gotText(const String& text)
{
    XmlText* tet = new XmlText(text);
    if (m_current)
	m_current->addChild(tet);
    else
	setError(m_data->addChild(tet),tet);
}

// Create a new xml Cdata and append it in the xml tree
void XmlDomParser::gotCdata(const String& data)
{
    XmlCData* cdata = new XmlCData(data);
    if (!m_current) {
	if (m_data->document()) {
	    Debug(this,DebugNote,"Document got CDATA outside element [%p]",this);
	    setError(NotWellFormed);
	    TelEngine::destruct(cdata);
	    return;
	}
	setError(m_data->addChild(cdata),cdata);
	return;
    }
    setError(m_current->addChild(cdata),cdata);
}

// Create a new xml element and append it in the xml tree
void XmlDomParser::gotElement(const NamedList& elem, bool empty)
{
    XmlElement* element = 0;
    if (!m_current) {
	// If we don't have curent element menns that the main fragment
	// should hold it
	element = new XmlElement(elem,empty);
	setError(m_data->addChild(element),element);
	if (!empty && error() == XmlSaxParser::NoError)
	    m_current = element;
    }
    else {
	if (empty) {
	    element = new XmlElement(elem,empty);
	    setError(m_current->addChild(element),element);
	}
	else {
	    element = new XmlElement(elem,empty,m_current);
	    setError(m_current->addChild(element),element);
	    if (error() == XmlSaxParser::NoError)
		m_current = element;
	}
    }
}

// Verify if is the closeing tag for the current element
// Complete th current element and make current the current parent
void XmlDomParser::endElement(const String& name)
{
    if (!m_current) {
	setError(ReadingEndTag);
	Debug(this,DebugNote,"Unexpected element end tag %s [%p]",name.c_str(),this);
	return;
    }
    if (m_current->getName() != name) {
	setError(ReadingEndTag);
	DDebug(this,DebugNote,
	    "Received end element for %s, but the expected one is for %s [%p]",
	    name.c_str(),m_current->getName().c_str(),this);
	return;
    }
    m_current->setCompleted();
    XDebug(this,DebugInfo,"End element for %s [%p]",m_current->getName().c_str(),this);
    m_current = static_cast<XmlElement*>(m_current->getParent());
}

// Reset this parser
void XmlDomParser::reset()
{
    m_data->reset();
    m_current = 0;
    XmlSaxParser::reset();
}


/*
 * XmlDeclaration
 */
// Create a new XmlDeclaration from version and encoding
XmlDeclaration::XmlDeclaration(const char* version, const char* enc)
    : m_declaration("")
{
    XDebug(DebugAll,"XmlDeclaration::XmlDeclaration(%s,%s) [%p]",version,enc,this);
    if (!TelEngine::null(version))
	m_declaration.addParam("version",version);
    if (!TelEngine::null(enc))
	m_declaration.addParam("encoding",enc);
}

// Constructor
XmlDeclaration::XmlDeclaration(const NamedList& decl)
    : m_declaration(decl)
{
    XDebug(DebugAll,"XmlDeclaration::XmlDeclaration(%s) [%p]",m_declaration.c_str(),this);
}

// Copy Constructor
XmlDeclaration::XmlDeclaration(const XmlDeclaration& decl)
    : m_declaration(decl.getDec())
{
}

// Destructor
XmlDeclaration::~XmlDeclaration()
{
    XDebug(DebugAll,"XmlDeclaration::~XmlDeclaration() ( %s| %p )",
	m_declaration.c_str(),this);
}

// Create a String from this Xml Declaration
void XmlDeclaration::toString(String& dump, bool esc) const
{
    dump << "<?" << "xml";
    int n = m_declaration.count();
    for (int i = 0;i < n;i ++) {
	NamedString* ns = m_declaration.getParam(i);
	if (!ns)
	    continue;
	dump += " ";
	dump += ns->name();
	dump << "=\"";
	if (esc)
	    XmlSaxParser::escape(dump,*ns);
	else
	    dump += *ns;
	dump << "\"";
    }
    dump << "?>";
}


/*
 * XmlFragment
 */
// Constructor
XmlFragment::XmlFragment()
    : m_list()
{
    XDebug(DebugAll,"XmlFragment::XmlFragment() ( %p )",this);
}

// Copy Constructor
XmlFragment::XmlFragment(const XmlFragment& orig)
{
    ObjList* ob = orig.getChildren().skipNull();
    for (;ob;ob = ob->skipNext()) {
	XmlChild* obj = static_cast<XmlChild*>(ob->get());
	if (obj->xmlElement()) {
	    XmlElement* el = obj->xmlElement();
	    if (el)
		addChild(new XmlElement(*el));
	    continue;
	}
	else if (obj->xmlCData()) {
	    XmlCData* cdata = obj->xmlCData();
	    if (cdata)
		addChild(new XmlCData(*cdata));
	    continue;
	}
	else if (obj->xmlText()) {
	    const XmlText* text = obj->xmlText();
	    if (text)
		addChild(new XmlText(*text));
	    continue;
	}
	else if (obj->xmlComment()) {
	    XmlComment* comm = obj->xmlComment();
	    if (comm)
		addChild(new XmlComment(*comm));
	    continue;
	}
	else if (obj->xmlDeclaration()) {
	    XmlDeclaration* decl = obj->xmlDeclaration();
	    if (decl)
		addChild(new XmlDeclaration(*decl));
	    continue;
	}
	else if (obj->xmlDoctype()) {
	    XmlDoctype* doctype = obj->xmlDoctype();
	    if (doctype)
		addChild(new XmlDoctype(*doctype));
	    continue;
	}
    }
}

// Destructor
XmlFragment::~XmlFragment()
{
    m_list.clear();
    XDebug(DebugAll,"XmlFragment::~XmlFragment() ( %p )",this);
}

// Reset. Clear children list
void XmlFragment::reset()
{
    m_list.clear();
}

// Append a new child
XmlSaxParser::Error XmlFragment::addChild(XmlChild* child)
{
    if (child)
	m_list.append(child);
    return XmlSaxParser::NoError;
}

// Remove the first XmlElement from list and returns it if completed
XmlElement* XmlFragment::popElement()
{
    for (ObjList* o = m_list.skipNull(); o; o = o->skipNext()) {
	XmlChild* c = static_cast<XmlChild*>(o->get());
	XmlElement* x = c->xmlElement();
	if (x) {
	     if (x->completed()) {
		o->remove(false);
		return x;
	     }
	     return 0;
	}
    }
    return 0;
}

// Remove a child
XmlChild* XmlFragment::removeChild(XmlChild* child, bool delObj)
{
    XmlChild* ch = static_cast<XmlChild*>(m_list.remove(child,delObj));
    if (ch && ch->xmlElement())
	ch->xmlElement()->setParent(0);
    return ch;
}

// Create a String from this XmlFragment
void XmlFragment::toString(String& dump, bool escape, const String& indent,
    const String& origIndent, bool completeOnly, const String* auth,
    const XmlElement* parent) const
{
    ObjList* ob = m_list.skipNull();
    if (!ob)
	return;
    ObjList buffers;
    for (;ob;ob = ob->skipNext()) {
	String* s = new String;
	XmlChild* obj = static_cast<XmlChild*>(ob->get());
	if (obj->xmlElement())
	    obj->xmlElement()->toString(*s,escape,indent,origIndent,completeOnly,auth);
	else if (obj->xmlText())
	    obj->xmlText()->toString(*s,escape,indent,auth,parent);
	else if (obj->xmlCData())
	    obj->xmlCData()->toString(*s,indent);
	else if (obj->xmlComment())
	    obj->xmlComment()->toString(*s,indent);
	else if (obj->xmlDeclaration())
	    obj->xmlDeclaration()->toString(*s,escape);
	else if (obj->xmlDoctype())
	    obj->xmlDoctype()->toString(*s,origIndent);
	else
	    Debug(DebugStub,"XmlFragment::toString() unhandled element type!");
	if (!TelEngine::null(s))
	    buffers.append(s);
	else
	    TelEngine::destruct(s);
    }
    dump.append(buffers);
}

// Find a completed xml element in a list
XmlElement* XmlFragment::findElement(ObjList* list, const String* name, const String* ns,
    bool noPrefix)
{
    XmlElement* e = 0;
    for (; list; list = list->skipNext()) {
	e = (static_cast<XmlChild*>(list->get()))->xmlElement();
	if (!(e && e->completed()))
	    continue;
	if (name || ns) {
	    if (!ns) {
		if (noPrefix) {
		    if (*name == e->unprefixedTag())
			break;
		}
		else if (*name == e->toString())
		    break;
	    }
	    else if (name) {
		const String* t = 0;
		const String* n = 0;
		if (e->getTag(t,n) && *t == *name && n && *n == *ns)
		    break;
	    }
	    else {
		const String* n = e->xmlns();
		if (n && *n == *ns)
		    break;
	    }
	}
	else
	    break;
	e = 0;
    }
    return e;
}


/*
 * XmlDocument
 */
// Constructor
XmlDocument::XmlDocument()
    : m_root(0)
{

}

// Destructor
XmlDocument::~XmlDocument()
{
    reset();
}

// Append a new child to this document
// Set the root to an XML element if not already set. If we already have a completed root
//  the element will be added to the root, otherwise an error will be returned.
// If we don't have a root non xml elements (other then text) will be added the list
//  of elements before root
XmlSaxParser::Error XmlDocument::addChild(XmlChild* child)
{
    if (!child)
	return XmlSaxParser::NoError;

    XmlElement* element = child->xmlElement();
    if (!m_root) {
	if (element) {
	    m_root = element;
	    return XmlSaxParser::NoError;
	}
	XmlDeclaration* decl = child->xmlDeclaration();
	if (decl && declaration()) {
	    DDebug(DebugNote,"XmlDocument. Request to add duplicate declaration [%p]",this);
	    return XmlSaxParser::NotWellFormed;
	}
	// Text outside root: ignore empty, raise error otherwise
	XmlText* text = child->xmlText();
	if (text) {
	    if (text->onlySpaces()) {
		m_beforeRoot.addChild(text);
		return XmlSaxParser::NoError;
	    }
	    Debug(DebugNote,"XmlDocument. Got text outside element [%p]",this);
	    return XmlSaxParser::NotWellFormed;
	}
	return m_beforeRoot.addChild(child);
    }
    // We have a root
    if (element) {
	if (m_root->completed())
	    return m_root->addChild(child);
	DDebug(DebugStub,"XmlDocument. Request to add xml element child to incomplete root [%p]",this);
	return XmlSaxParser::NotWellFormed;
    }
    if ((child->xmlText() && child->xmlText()->onlySpaces()) || child->xmlComment())
	return m_afterRoot.addChild(child);
    // TODO: check what xml we can add after the root or if we can add
    //  anything after an incomplete root
    Debug(DebugStub,"XmlDocument. Request to add non element while having a root [%p]",this);
    return XmlSaxParser::NotWellFormed;
}

// Retrieve the document declaration
XmlDeclaration* XmlDocument::declaration() const
{
    for (ObjList* o = m_beforeRoot.getChildren().skipNull(); o; o = o->skipNext()) {
	XmlDeclaration* d = (static_cast<XmlChild*>(o->get()))->xmlDeclaration();
	if (d)
	    return d;
    }
    return 0;
}

// Obtain root element completed ot not
XmlElement* XmlDocument::root(bool completed) const
{
    return (m_root && (m_root->completed() || !completed)) ? m_root : 0;
}

void XmlDocument::toString(String& dump, bool escape, const String& indent, const String& origIndent) const
{
    m_beforeRoot.toString(dump,escape,indent,origIndent);
    if (m_root) {
	dump << origIndent;
	m_root->toString(dump,escape,indent,origIndent);
    }
    m_afterRoot.toString(dump,escape,indent,origIndent);
}

// Reset this XmlDocument. Destroys root and clear the others xml objects
void XmlDocument::reset()
{
    TelEngine::destruct(m_root);
    m_beforeRoot.clearChildren();
    m_afterRoot.clearChildren();
    m_file.clear();
}

// Load this document from data stream and parse it
XmlSaxParser::Error XmlDocument::read(Stream& in, int* error)
{
    XmlDomParser parser(static_cast<XmlParent*>(this),false);
    char buf[8096];
    bool start = true;
    while (true) {
	int rd = in.readData(buf,sizeof(buf) - 1);
	if (rd > 0) {
	    buf[rd] = 0;
	    const char* text = buf;
	    if (start) {
		String::stripBOM(text);
		start = false;
	    }
	    if (parser.parse(text) || parser.error() == XmlSaxParser::Incomplete)
		continue;
	    break;
	}
	break;
    }
    parser.completeText();
    if (parser.error() != XmlSaxParser::NoError) {
	DDebug(DebugNote,"XmlDocument error loading stream. Parser error %d '%s' [%p]",
	    parser.error(),parser.getError(),this);
	return parser.error();
    }
    if (in.error()) {
	if (error)
	    *error = in.error();
#ifdef DEBUG
	String tmp;
	Thread::errorString(tmp,in.error());
	Debug(DebugNote,"XmlDocument error loading stream. I/O error %d '%s' [%p]",
	    in.error(),tmp.c_str(),this);
#endif
	return XmlSaxParser::IOError;
    }
    return XmlSaxParser::NoError;
}

// Write this document to a data stream
int XmlDocument::write(Stream& out, bool escape, const String& indent,
    const String& origIndent, bool completeOnly) const
{
    String dump;
    m_beforeRoot.toString(dump,escape,indent,origIndent);
    if (m_root)
	m_root->toString(dump,escape,indent,origIndent,completeOnly);
    m_afterRoot.toString(dump,escape,indent,origIndent);
    return out.writeData(dump);
}

// Load a file and parse it
XmlSaxParser::Error XmlDocument::loadFile(const char* file, int* error)
{
    reset();
    if (TelEngine::null(file))
	return XmlSaxParser::NoError;
    m_file = file;
    File f;
    if (f.openPath(file))
	return read(f,error);
    if (error)
	*error = f.error();
#ifdef DEBUG
    String tmp;
    Thread::errorString(tmp,f.error());
    Debug(DebugNote,"XmlDocument error opening file '%s': %d '%s' [%p]",
	file,f.error(),tmp.c_str(),this);
#endif
    return XmlSaxParser::IOError;
}

// Save this xml document in a file
int XmlDocument::saveFile(const char* file, bool esc, const String& indent,
    bool completeOnly) const
{
    if (!file)
	file = m_file;
    if (!file)
	return 0;
    File f;
    int err = 0;
    if (f.openPath(file,true,false,true,false)) {
	String eol("\r\n");
	write(f,esc,eol,indent,completeOnly);
	err = f.error();
	// Add an empty line
	if (err >= 0)
	    f.writeData((void*)eol.c_str(),eol.length());
    }
    else
	err = f.error();
    if (!err) {
	XDebug(DebugAll,"XmlDocument saved file '%s' [%p]",file,this);
	return 0;
    }
#ifdef DEBUG
    String error;
    Thread::errorString(error,err);
    Debug(DebugNote,"Error saving XmlDocument to file '%s'. %d '%s' [%p]",
	file,err,error.c_str(),this);
#endif
    return f.error();
}


/*
 * XmlChild
 */
XmlChild::XmlChild()
{
}


/*
 * XmlElement
 */
XmlElement::XmlElement(const NamedList& element, bool empty, XmlParent* parent)
    : m_element(element), m_prefixed(0),
    m_parent(0), m_inheritedNs(0),
    m_empty(empty), m_complete(empty)
{
    XDebug(DebugAll,"XmlElement::XmlElement(%s,%u,%p) [%p]",
	element.c_str(),empty,parent,this);
    setPrefixed();
    setParent(parent);
}

// Copy constructor
XmlElement::XmlElement(const XmlElement& el)
    : m_children(el.m_children),
    m_element(el.getElement()), m_prefixed(0),
    m_parent(0), m_inheritedNs(0),
    m_empty(el.empty()), m_complete(el.completed())
{
    setPrefixed();
    setInheritedNs(&el,true);
}

// Create an empty xml element
XmlElement::XmlElement(const char* name, bool complete)
    : m_element(name), m_prefixed(0),
    m_parent(0), m_inheritedNs(0),
    m_empty(true), m_complete(complete)
{
    setPrefixed();
    XDebug(DebugAll,"XmlElement::XmlElement(%s) [%p]",
	m_element.c_str(),this);
}

// Create a new element with a text child
XmlElement::XmlElement(const char* name, const char* value, bool complete)
    : m_element(name), m_prefixed(0),
    m_parent(0), m_inheritedNs(0),
    m_empty(true), m_complete(complete)
{
    setPrefixed();
    addText(value);
    XDebug(DebugAll,"XmlElement::XmlElement(%s) [%p]",
	m_element.c_str(),this);
}

// Destructor
XmlElement::~XmlElement()
{
    setInheritedNs();
    TelEngine::destruct(m_prefixed);
    XDebug(DebugAll,"XmlElement::~XmlElement() ( %s| %p )",
	m_element.c_str(),this);
}

// Set element's unprefixed tag, don't change namespace prefix
void XmlElement::setUnprefixedTag(const String& s)
{
    if (!s || s == unprefixedTag())
	return;
    if (TelEngine::null(m_prefixed))
	m_element.assign(s);
    else
	m_element.assign(*m_prefixed + ":" + s);
    setPrefixed();
}

// Set inherited namespaces from a given element. Reset them anyway
void XmlElement::setInheritedNs(const XmlElement* xml, bool inherit)
{
    XDebug(DebugAll,"XmlElement(%s) setInheritedNs(%p,%s) [%p]",
	tag(),xml,String::boolText(inherit),this);
    TelEngine::destruct(m_inheritedNs);
    if (!xml)
	return;
    addInheritedNs(xml->attributes());
    if (!inherit)
	return;
    XmlElement* p = xml->parent();
    bool xmlAdd = (p == 0);
    while (p) {
	addInheritedNs(p->attributes());
	const NamedList* i = p->inheritedNs();
	p = p->parent();
	if (!p && i)
	    addInheritedNs(*i);
    }
    if (xmlAdd && xml->inheritedNs())
	addInheritedNs(*xml->inheritedNs());
}

// Add inherited namespaces from a list
void XmlElement::addInheritedNs(const NamedList& list)
{
    XDebug(DebugAll,"XmlElement(%s) addInheritedNs(%s) [%p]",tag(),list.c_str(),this);
    unsigned int n = list.count();
    for (unsigned int i = 0; i < n; i++) {
	NamedString* ns = list.getParam(i);
	if (!(ns && isXmlns(ns->name())))
	    continue;
	// Avoid adding already overridden namespaces
	if (m_element.getParam(ns->name()))
	    continue;
	if (m_inheritedNs && m_inheritedNs->getParam(ns->name()))
	    continue;
	// TODO: Check if attribute names are unique after adding the namespace
	//       See http://www.w3.org/TR/xml-names/ Section 6.3
	if (!m_inheritedNs)
	    m_inheritedNs = new NamedList("");
	XDebug(DebugAll,"XmlElement(%s) adding inherited %s=%s [%p]",
	    tag(),ns->name().c_str(),ns->c_str(),this);
	m_inheritedNs->addParam(ns->name(),*ns);
    }
}

// Obtain the first text of this xml element
const String& XmlElement::getText() const
{
    const XmlText* txt = 0;
    for (ObjList* ob = getChildren().skipNull(); ob && !txt; ob = ob->skipNext())
	txt = (static_cast<XmlChild*>(ob->get()))->xmlText();
    return txt ? txt->getText() : String::empty();
}

XmlChild* XmlElement::getFirstChild()
{
    if (!m_children.getChildren().skipNull())
	return 0;
    return static_cast<XmlChild*>(m_children.getChildren().skipNull()->get());
}

XmlText* XmlElement::setText(const char* text)
{
    XmlText* txt = 0;
    for (ObjList* o = getChildren().skipNull(); o; o = o->skipNext()) {
	txt = (static_cast<XmlChild*>(o->get()))->xmlText();
	if (txt)
	    break;
    }
    if (txt) {
	if (!text)
	    return static_cast<XmlText*>(removeChild(txt));
	txt->setText(text);
    }
    else if (text) {
	txt = new XmlText(text);
	addChild(txt);
    }
    return txt;
}

// Add a text child
void XmlElement::addText(const char* text)
{
    if (!TelEngine::null(text))
	addChild(new XmlText(text));
}

// Retrieve the element's tag (without prefix) and namespace
bool XmlElement::getTag(const String*& tag, const String*& ns) const
{
    if (!m_prefixed) {
	tag = &m_element;
	ns = xmlns();
	return true;
    }
    // Prefixed element
    tag = &m_prefixed->name();
    ns = xmlns();
    return ns != 0;
}

// Append a new child
XmlSaxParser::Error XmlElement::addChild(XmlChild* child)
{
    if (!child)
	return XmlSaxParser::NoError;
    // TODO: Check if a child element's attribute names are unique in the new context
    //       See http://www.w3.org/TR/xml-names/ Section 6.3
    XmlSaxParser::Error err = m_children.addChild(child);
    if (err == XmlSaxParser::NoError)
	child->setParent(this);
    return err;
}

// Remove a child
XmlChild* XmlElement::removeChild(XmlChild* child, bool delObj)
{
    return m_children.removeChild(child,delObj);
}

// Set this element's parent. Update inherited namespaces
void XmlElement::setParent(XmlParent* parent)
{
    XDebug(DebugAll,"XmlElement(%s) setParent(%p) element=%s [%p]",
	tag(),parent,String::boolText(parent != 0),this);
    if (m_parent && m_parent->element()) {
	// Reset inherited namespaces if the new parent is an element
	// Otherwise set them from the old parent
	if (parent && parent->element())
	    setInheritedNs(0);
	else
	    setInheritedNs(m_parent->element());
    }
    m_parent = parent;
}

// Obtain a string from this xml element
void XmlElement::toString(String& dump, bool esc, const String& indent,
    const String& origIndent, bool completeOnly, const String* auth) const
{
    XDebug(DebugAll,"XmlElement(%s) toString(%u,%s,%s,%u,%p) complete=%u [%p]",
	tag(),esc,indent.c_str(),origIndent.c_str(),completeOnly,auth,m_complete,this);
    if (!m_complete && completeOnly)
	return;
    String auxDump;
    auxDump << indent << "<" << m_element;
    int n = m_element.count();
    for (int i = 0; i < n; i++) {
	NamedString* ns = m_element.getParam(i);
	if (!ns)
	    continue;
	auxDump << " " << ns->name() << "=\"";
	addAuth(auxDump,ns->name(),*ns,esc,auth);
	auxDump << "\"";
    }
    int m = getChildren().count();
    if (m_complete && !m)
	auxDump << "/";
    auxDump << ">";
    if (m) {
	// Avoid adding text on new line when text is the only child
	XmlText* text = 0;
	if (m == 1)
	    text = static_cast<XmlChild*>(getChildren().skipNull()->get())->xmlText();
	if (!text)
	    m_children.toString(auxDump,esc,indent + origIndent,origIndent,completeOnly,auth,this);
	else
	    text->toString(auxDump,esc,String::empty(),auth,this);
	if (m_complete)
	    auxDump << (!text ? indent : String::empty()) << "</" << getName() << ">";
    }
    dump << auxDump;
}

// Copy element attributes to a list of parameters
unsigned int XmlElement::copyAttributes(NamedList& list, const String& prefix) const
{
    unsigned int copy = 0;
    unsigned int n = m_element.length();
    for (unsigned int i = 0; i < n; i++) {
	NamedString* ns = m_element.getParam(i);
	if (!(ns && ns->name()))
	    continue;
	list.addParam(prefix + ns->name(),*ns);
	copy++;
    }
    return copy;
}

void XmlElement::setAttributes(NamedList& list, const String& prefix, bool skipPrefix)
{
    if (prefix)
	m_element.copySubParams(list,prefix,skipPrefix);
    else
	m_element.copyParams(list);
}

// Retrieve a namespace attribute. Search in parent or inherited for it
String* XmlElement::xmlnsAttribute(const String& name) const
{
    String* tmp = getAttribute(name);
    if (tmp)
	return tmp;
    XmlElement* p = parent();
    if (p)
	return p->xmlnsAttribute(name);
    return m_inheritedNs ? m_inheritedNs->getParam(name) : 0;
}

// Set the element's namespace
bool XmlElement::setXmlns(const String& name, bool addAttr, const String& value)
{
    const String* cmp = name ? &name : &s_ns;
    XDebug(DebugAll,"XmlElement(%s)::setXmlns(%s,%u,%s) [%p]",
	tag(),cmp->c_str(),addAttr,value.c_str(),this);
    if (*cmp == s_ns) {
	if (m_prefixed) {
	    m_element.assign(m_prefixed->name());
	    setPrefixed();
	    // TODO: remove children and attributes prefixes
	}
    }
    else if (!m_prefixed || *m_prefixed != cmp) {
	if (!m_prefixed)
	    m_element.assign(*cmp + ":" + tag());
	else
	    m_element.assign(*cmp + ":" + m_prefixed->name());
	setPrefixed();
	// TODO: change children and attributes prefixes
    }
    if (!(addAttr && value))
	return true;
    String attr;
    if (*cmp == s_ns)
	attr = s_ns;
    else
	attr << s_nsPrefix << *cmp;
    NamedString* ns = m_element.getParam(attr);
    if (!ns && m_inheritedNs && m_inheritedNs->getParam(attr))
	m_inheritedNs->clearParam(attr);
    // TODO: Check if attribute names are unique after adding the namespace
    //       See http://www.w3.org/TR/xml-names/ Section 6.3
    if (!ns)
	m_element.addParam(attr,value);
    else
	*ns = value;
    return true;
}

// Build an XML element from a list parameter
XmlElement* XmlElement::param2xml(NamedString* param, const String& tag, bool copyXml)
{
    if (!(param && param->name() && tag))
	return 0;
    XmlElement* xml = new XmlElement(tag);
    xml->setAttribute(s_name,param->name());
    xml->setAttributeValid(YSTRING("value"),*param);
    NamedPointer* np = YOBJECT(NamedPointer,param);
    if (!(np && np->userData()))
	return xml;
    DataBlock* db = YOBJECT(DataBlock,np->userData());
    if (db) {
	xml->setAttribute(s_type,"DataBlock");
	Base64 b(db->data(),db->length(),false);
	String tmp;
	b.encode(tmp);
	b.clear(false);
	xml->addText(tmp);
	return xml;
    }
    XmlElement* element = YOBJECT(XmlElement,np->userData());
    if (element) {
	xml->setAttribute(s_type,"XmlElement");
	if (!copyXml) {
	    np->takeData();
	    xml->addChild(element);
	}
	else
	    xml->addChild(new XmlElement(*element));
	return xml;
    }
    NamedList* list = YOBJECT(NamedList,np->userData());
    if (list) {
	xml->setAttribute(s_type,"NamedList");
	xml->addText(list->c_str());
	unsigned int n = list->length();
	for (unsigned int i = 0; i < n; i++)
	    xml->addChild(param2xml(list->getParam(i),tag,copyXml));
	return xml;
    }
    return xml;
}

// Build a list parameter from xml element
NamedString* XmlElement::xml2param(XmlElement* xml, const String* tag, bool copyXml)
{
    const char* name = xml ? xml->attribute(s_name) : 0;
    if (TelEngine::null(name))
	return 0;
    GenObject* gen = 0;
    String* type = xml->getAttribute(s_type);
    if (type) {
	if (*type == YSTRING("DataBlock")) {
	    gen = new DataBlock;
	    const String& text = xml->getText();
	    Base64 b((void*)text.c_str(),text.length(),false);
	    b.decode(*(static_cast<DataBlock*>(gen)));
	    b.clear(false);
	}
	else if (*type == YSTRING("XmlElement")) {
	    if (!copyXml)
		gen = xml->pop();
	    else {
		XmlElement* tmp = xml->findFirstChild();
		if (tmp)
		    gen = new XmlElement(*tmp);
	    }
	}
	else if (*type == YSTRING("NamedList")) {
	    gen = new NamedList(xml->getText());
	    xml2param(*(static_cast<NamedList*>(gen)),xml,tag,copyXml);
	}
	else
	    Debug(DebugStub,"XmlElement::xml2param: unhandled type=%s",type->c_str());
    }
    if (!gen)
	return new NamedString(name,xml->attribute(YSTRING("value")));
    return new NamedPointer(name,gen,xml->attribute(YSTRING("value")));
}

// Build and add list parameters from XML element children
void XmlElement::xml2param(NamedList& list, XmlElement* parent, const String* tag,
    bool copyXml)
{
    if (!parent)
	return;
    XmlElement* ch = 0;
    while (0 != (ch = parent->findNextChild(ch,tag))) {
	NamedString* ns = xml2param(ch,tag,copyXml);
	if (ns)
	    list.addParam(ns);
    }
}


/*
 * XmlComment
 */
// Constructor
XmlComment::XmlComment(const String& comm)
    : m_comment(comm)
{
    XDebug(DebugAll,"XmlComment::XmlComment(const String& comm) ( %s| %p )",
	m_comment.c_str(),this);
}

// Copy Constructor
XmlComment::XmlComment(const XmlComment& comm)
    : m_comment(comm.getComment())
{
}

// Destructor
XmlComment::~XmlComment()
{
    XDebug(DebugAll,"XmlComment::~XmlComment() ( %s| %p )",
	m_comment.c_str(),this);
}

// Obtain string representation of this xml comment
void XmlComment::toString(String& dump, const String& indent) const
{
    dump << indent << "<!--" << getComment() << "-->";
}


/*
 * XmlCData
 */
// Constructor
XmlCData::XmlCData(const String& data)
    : m_data(data)
{
    XDebug(DebugAll,"XmlCData::XmlCData(const String& data) ( %s| %p )",
	m_data.c_str(),this);
}

// Copy Constructor
XmlCData::XmlCData(const XmlCData& data)
    : m_data(data.getCData())
{
}

// Destructor
XmlCData::~XmlCData()
{
    XDebug(DebugAll,"XmlCData::~XmlCData() ( %s| %p )",
	m_data.c_str(),this);
}

// Obtain string representation of this xml Cdata
void XmlCData::toString(String& dump, const String& indent) const
{
    dump << indent << "<![CDATA[" << getCData() << "]]>";
}


/*
 * XmlText
 */
// Constructor
XmlText::XmlText(const String& text)
    : m_text(text)
{
    XDebug(DebugAll,"XmlText::XmlText(%s) [%p]",m_text.c_str(),this);
}

// Copy Constructor
XmlText::XmlText(const XmlText& text)
    : m_text(text.getText())
{
    XDebug(DebugAll,"XmlText::XmlText(%p,%s) [%p]",
	&text,TelEngine::c_safe(text.getText()),this);
}

// Destructor
XmlText::~XmlText()
{
    XDebug(DebugAll,"XmlText::~XmlText [%p]",this);
}

// Obtain string representation of this xml text
void XmlText::toString(String& dump, bool esc, const String& indent,
    const String* auth, const XmlElement* parent) const
{
    dump << indent;
    if (auth)
	addAuth(dump,parent ? parent->toString() : String::empty(),m_text,esc,auth);
    else if (esc)
        XmlSaxParser::escape(dump,m_text);
    else
	dump << m_text;
}

bool XmlText::onlySpaces()
{
    if (!m_text)
	return true;
    const char *s = m_text;
    unsigned int i = 0;
    for (;i < m_text.length();i++) {
	if (s[i] == ' ' || s[i] == '\t' || s[i] == '\v' || s[i] == '\f' || s[i] == '\r' || s[i] == '\n')
	    continue;
	return false;
    }
    return true;
}

/*
 * XmlDoctype
 */
// Constructor
XmlDoctype::XmlDoctype(const String& doctype)
    : m_doctype(doctype)
{
    XDebug(DebugAll,"XmlDoctype::XmlDoctype(const String& doctype) ( %s| %p )",
	m_doctype.c_str(),this);
}

// Copy Constructor
XmlDoctype::XmlDoctype(const XmlDoctype& doctype)
    : m_doctype(doctype.getDoctype())
{
}

// Destructor
XmlDoctype::~XmlDoctype()
{
    XDebug(DebugAll,"XmlDoctype::~XmlDoctype() ( %s| %p )",
	m_doctype.c_str(),this);
}

// Obtain string representation of this xml doctype
void XmlDoctype::toString(String& dump, const String& indent) const
{
    dump << indent << "<!DOCTYPE " << m_doctype << ">";
}

/* vi: set ts=8 sw=4 sts=4 noet: */
