// XMLDocument_as.cpp:  ActionScript "XMLDocument" class, for Gnash.
//
//   Copyright (C) 2009 Free Software Foundation, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// 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.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#include "log.h"
#include "as_function.h" //for as_function
#include "fn_call.h"
#include "Global_as.h"

#include "LoadableObject.h"
#include "xml/XMLNode_as.h"
#include "xml/XMLDocument_as.h"
#include "builtin_function.h"
#include "NativeFunction.h"
#include "VM.h"
#include "namedStrings.h"
#include "StringPredicates.h"
#include "smart_ptr.h" // for boost intrusive_ptr
#include "GnashException.h" // for ActionException
#include "Object.h"

#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/replace.hpp>


namespace gnash {

// Forward declarations
namespace {

    as_value xml_new(const fn_call& fn);
    as_value xml_createElement(const fn_call& fn);
    as_value xml_createTextNode(const fn_call& fn);
    as_value xml_parseXML(const fn_call& fn);
    as_value xml_onData(const fn_call& fn);
    as_value xml_onLoad(const fn_call& fn);
    as_value xml_xmlDecl(const fn_call& fn);
    as_value xml_docTypeDecl(const fn_call& fn);
    as_value xml_escape(const fn_call& fn);
    as_value xml_loaded(const fn_call& fn);
    as_value xml_status(const fn_call& fn);

    bool textAfterWhitespace(const std::string& xml,
            std::string::const_iterator& it);
    bool textMatch(const std::string& xml, std::string::const_iterator& it,
            const std::string& match, bool advance = true);
    bool parseNodeWithTerminator(const std::string& xml,
            std::string::const_iterator& it, const std::string& terminator,
            std::string& content);
	
	
    typedef std::map<std::string, std::string> Entities;
    const Entities& getEntities();

    void unescapeXML(std::string& text);

    void attachXMLProperties(as_object& o);
	void attachXMLInterface(as_object& o);

}


/// Implements XML (AS2) and flash.xml.XMLDocument (AS3) class.
//
/// This class interface is identical in AS3 and AS2; it is probably 
/// included in AS3 for backward compatibility.
class XMLDocument_as : public XMLNode_as
{
public:

    enum ParseStatus {
            XML_OK = 0,
            XML_UNTERMINATED_CDATA = -2,
            XML_UNTERMINATED_XML_DECL = -3,
            XML_UNTERMINATED_DOCTYPE_DECL = -4,
            XML_UNTERMINATED_COMMENT = -5,
            XML_UNTERMINATED_ELEMENT = -6,
            XML_OUT_OF_MEMORY = -7,
            XML_UNTERMINATED_ATTRIBUTE = -8,
            XML_MISSING_CLOSE_TAG = -9,
            XML_MISSING_OPEN_TAG = -10
    };

    enum LoadStatus {
        XML_LOADED_UNDEFINED = -1,
        XML_LOADED_FALSE = false,
        XML_LOADED_TRUE = true
    };

    /// Create an XML object.
    //
    /// An XMLDocument is always user-created, so always starts with an
    /// associated object.
    XMLDocument_as(as_object& object);

    XMLDocument_as(as_object& object, const std::string& xml);

    ~XMLDocument_as() {};
    
    /// Convert the XML object to a string
    //
    /// This calls XMLNode::toString after adding an xmlDecl and
    /// docTypeDecl
    //
    /// @param o        The ostream to write the string to.
    /// @param encode   Whether to URL encode the node values.
    void toString(std::ostream& o, bool encode) const;

    const std::string& getXMLDecl() const {
        return _xmlDecl;
    }

    void setXMLDecl(const std::string& xml) {
        _xmlDecl = xml;
    }

    const std::string& getDocTypeDecl() const {
        return _docTypeDecl;
    }

    void setDocTypeDecl(const std::string& docType) {
        _docTypeDecl = docType;
    }

    // Methods

    /// Parses an XML document into the specified XML object tree.
    //
    /// This reads in an XML file from disk and parses into into a memory
    /// resident tree which can be walked through later.
    ///
    /// Calls to this function clear any precedently parsed data.
    ///
    void parseXML(const std::string& xml);

    XMLNode_as* createElement(const std::string& name);

    XMLNode_as* createTextNode(const std::string& name);

    ParseStatus status() const {
        return _status;
    }

    void setStatus(ParseStatus st) {
        _status = st;
    }

    LoadStatus loaded() const {
        return _loaded;
    }

    void setLoaded(LoadStatus st) {
        _loaded = st;
    }

private:

    typedef std::map<std::string, std::string, StringNoCaseLessThan> Attributes;

    void parseTag(XMLNode_as*& node, const std::string& xml, 
            std::string::const_iterator& it);

    void parseAttribute(XMLNode_as* node, const std::string& xml, 
            std::string::const_iterator& it, Attributes& attributes);

    void parseDocTypeDecl(const std::string& xml, 
            std::string::const_iterator& it);

    void parseText(XMLNode_as* node, const std::string& xml, 
            std::string::const_iterator& it);

    void parseXMLDecl(const std::string& xml, 
            std::string::const_iterator& it);

    void parseComment(XMLNode_as* node, const std::string& xml, 
            std::string::const_iterator& it);

    void parseCData(XMLNode_as* node, const std::string& xml, 
            std::string::const_iterator& it);
 
    /// Clear all properties.
    //
    /// This removes all children, resets doctype and xml decls, and
    /// sets status to XML.
    void clear();
  
    /// \brief
    /// Return true if ignoreWhite property was set to anything evaluating
    /// to true.
    bool ignoreWhite();

    // -1 if never asked to load anything
    //  0 if asked to load but not yet loaded (or failure)
    //  1 if successfully loaded
    LoadStatus _loaded;

    ParseStatus _status;	
 
    std::string _docTypeDecl;

    std::string _xmlDecl;

};

XMLDocument_as::XMLDocument_as(as_object& object) 
    :
    XMLNode_as(getGlobal(object)),
    _loaded(XML_LOADED_UNDEFINED), 
    _status(XML_OK)
{
    setObject(&object);
}

// Parse the ASCII XML string into an XMLNode tree
XMLDocument_as::XMLDocument_as(as_object& object, const std::string& xml)
    :
    XMLNode_as(getGlobal(object)),
    _loaded(XML_LOADED_UNDEFINED), 
    _status(XML_OK)
{
    setObject(&object);
    parseXML(xml);
}

void
escapeXML(std::string& text)
{
    const Entities& ent = getEntities();

    for (Entities::const_iterator i = ent.begin(), e = ent.end();
            i != e; ++i)
    {
        boost::replace_all(text, i->second, i->first);
    }
}

void
XMLDocument_as::toString(std::ostream& o, bool encode) const
{
    if (!_xmlDecl.empty()) o << _xmlDecl;
    if (!_docTypeDecl.empty()) o << _docTypeDecl;

    XMLNode_as::toString(o, encode);
}

void
XMLDocument_as::parseAttribute(XMLNode_as* node, const std::string& xml,
        std::string::const_iterator& it, Attributes& attributes)
{

    const std::string terminators("\r\t\n >=");

    std::string::const_iterator end = std::find_first_of(it, xml.end(),
            terminators.begin(), terminators.end());

    if (end == xml.end()) {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }
    std::string name(it, end);
    
    if (name.empty()) {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }

    // Point iterator to the DisplayObject after the name.
    it = end;

    // Skip any whitespace before the '='. If we reach the end of the string
    // or don't find an '=', it's a parser error.
    if (!textAfterWhitespace(xml, it) || *it != '=') {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }

    // Point to the DisplayObject after the '='
    ++it;

    // Skip any whitespace. If we reach the end of the string, or don't find
    // a " or ', it's a parser error.
    if (!textAfterWhitespace(xml, it) || (*it != '"' && *it != '\'')) {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }

    // Find the end of the attribute, looking for the opening DisplayObject,
    // as long as it's not escaped. We begin one after the present position,
    // which should be the opening DisplayObject. We want to remember what the
    // iterator is pointing to for a while, so don't advance it.
    end = it;
    do {
        ++end;
        end = std::find(end, xml.end(), *it);
    } while (end != xml.end() && *(end - 1) == '\\');

    if (end == xml.end()) {
        _status = XML_UNTERMINATED_ATTRIBUTE;
        return;
    }
    ++it;

    std::string value(it, end);

    // Replace entities in the value.
    unescapeXML(value);

    //log_debug("adding attribute to node %s: %s, %s", node->nodeName(),
    //        name, value);

    // We've already checked that end != xml.end(), so we can advance at 
    // least once.
    it = end;
    // Advance past the last attribute DisplayObject
    ++it;

    // Handle namespace. This is set once only for each node, and is also
    // pushed to the attributes list once.
    StringNoCaseEqual noCaseCompare;
    if (noCaseCompare(name, "xmlns") || noCaseCompare(name, "xmlns:")) {
        if (!node->getNamespaceURI().empty()) return;
        node->setNamespaceURI(value);
    }

    // This ensures values are not inserted twice, which is expected
    // behaviour
    attributes.insert(std::make_pair(name, value));

}

/// Parse and set the docTypeDecl. This is stored without any validation and
/// with the same case as in the parsed XML.
void
XMLDocument_as::parseDocTypeDecl(const std::string& xml,
        std::string::const_iterator& it)
{

    std::string::const_iterator end;
    std::string::const_iterator current = it; 

    std::string::size_type count = 1;

    // Look for angle brackets in the doctype declaration.
    while (count) {

        // Find the next closing bracket after the current position.
        end = std::find(current, xml.end(), '>');
        if (end == xml.end()) {
            _status = XML_UNTERMINATED_DOCTYPE_DECL;
            return;
        }
        --count;

        // Count any opening brackets in between.
        count += std::count(current, end, '<');
        current = end;
        ++current;
    }

    const std::string content(it, end);
    std::ostringstream os;
    os << '<' << content << '>';
    _docTypeDecl = os.str();
    it = end + 1;
}


void
XMLDocument_as::parseXMLDecl(const std::string& xml, std::string::const_iterator& it)
{
    std::string content;
    if (!parseNodeWithTerminator(xml, it, "?>", content))
    {
        _status = XML_UNTERMINATED_XML_DECL;
        return;
    }

    std::ostringstream os;
    os << "<" << content << "?>";

    // This is appended to any xmlDecl already there.
    _xmlDecl += os.str();

}

// The iterator should be pointing to the first char after the '<'
void
XMLDocument_as::parseTag(XMLNode_as*& node, const std::string& xml,
    std::string::const_iterator& it)
{
    //log_debug("Processing node: %s", node->nodeName());

    bool closing = (*it == '/');
    if (closing) ++it;

    // These are for terminating the tag name, not (necessarily) the tag.
    const std::string terminators("\r\n\t >");

    std::string::const_iterator endName = std::find_first_of(it, xml.end(),
            terminators.begin(), terminators.end());

    // Check that one of the terminators was found; otherwise it's malformed.
    if (endName == xml.end()) {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }

    // Knock off the "/>" of a self-closing tag.
    if (std::equal(endName - 1, endName + 1, "/>")) {
        // This can leave endName before it, e.g when a self-closing tag is
        // empty ("</>"). This must be checked before trying to construct
        // a string!
        --endName;
    }
    
    // If the tag is empty, the XML counts as malformed. 
    if (it >= endName) {
        _status = XML_UNTERMINATED_ELEMENT;
        return;
    }

    std::string tagName(it, endName);

    if (!closing) {

        XMLNode_as* childNode = new XMLNode_as(_global);
        childNode->nodeNameSet(tagName);
        childNode->nodeTypeSet(Element);

        //log_debug("created childNode with name %s", childNode->nodeName());
        // Skip to the end of any whitespace after the tag name
        it = endName;

        if (!textAfterWhitespace(xml, it)) {
            _status = XML_UNTERMINATED_ELEMENT;
           return;
        }

        // Parse any attributes in an opening tag only, stopping at "/>" or
        // '>'
        // Attributes are added in reverse order and without any duplicates.
        Attributes attributes;
        while (it != xml.end() && *it != '>' && _status == XML_OK)
        {
            if (xml.end() - it > 1 && std::equal(it, it + 2, "/>")) break;

            // This advances the iterator
            parseAttribute(childNode, xml, it, attributes);

            // Skip any whitespace. If we reach the end of the string,
            // it's malformed.
            if (!textAfterWhitespace(xml, it)) {
                _status = XML_UNTERMINATED_ELEMENT;
                return;
            }
        }
        
        // Do nothing more if there was an error in attributes parsing.
        if (_status != XML_OK) return;

        for (Attributes::const_reverse_iterator i = attributes.rbegin(),
                e = attributes.rend(); i != e; ++i) {
            childNode->setAttribute(i->first, i->second);
        }

        node->appendChild(childNode);
        if (*it == '/') ++it;
        else node = childNode;

        if (*it == '>') ++it;

        return;
    }

    // If we reach here, this is a closing tag.

    it = std::find(endName, xml.end(), '>');

    if (it == xml.end())
    {
       _status = XML_UNTERMINATED_ELEMENT;
       return;
    }
    ++it;

    StringNoCaseEqual noCaseCompare;

    if (node->getParent() && noCaseCompare(node->nodeName(), tagName)) {
        node = node->getParent();
    }
    else {
        // Malformed. Search for the parent node.
        XMLNode_as* s = node;
        while (s && !noCaseCompare(s->nodeName(), tagName)) {
            //log_debug("parent: %s, this: %s", s->nodeName(), tagName);
            s = s->getParent();
        }
        if (s) {
            // If there's a parent, the open tag is orphaned.
            _status = XML_MISSING_CLOSE_TAG;
        }
        else {
            // If no parent, the close tag is orphaned.
            _status = XML_MISSING_OPEN_TAG;
        }
    }

}

void
XMLDocument_as::parseText(XMLNode_as* node, const std::string& xml, 
        std::string::const_iterator& it)
{
    std::string::const_iterator end = std::find(it, xml.end(), '<');
    std::string content(it, end);
    
    it = end;

    if (ignoreWhite() && 
        content.find_first_not_of("\t\r\n ") == std::string::npos) return;

    XMLNode_as* childNode = new XMLNode_as(_global);

    childNode->nodeTypeSet(XMLNode_as::Text);

    // Replace any entitites.
    unescapeXML(content);

    childNode->nodeValueSet(content);
    node->appendChild(childNode);

    //log_debug("appended text node: %s", content);
}



void
XMLDocument_as::parseComment(XMLNode_as* /*node*/, const std::string& xml, 
        std::string::const_iterator& it)
{
    //log_debug("discarding comment node");

    std::string content;

    if (!parseNodeWithTerminator(xml, it, "-->", content)) {
        _status = XML_UNTERMINATED_COMMENT;
        return;
    }
    // Comments are discarded at least up to SWF8
    
}

void
XMLDocument_as::parseCData(XMLNode_as* node, const std::string& xml, 
        std::string::const_iterator& it)
{
    std::string content;

    if (!parseNodeWithTerminator(xml, it, "]]>", content)) {
        _status = XML_UNTERMINATED_CDATA;
        return;
    }

    XMLNode_as* childNode = new XMLNode_as(_global);
    childNode->nodeValueSet(content);
    childNode->nodeTypeSet(Text);
    node->appendChild(childNode);
    
}


// This parses an XML string into a tree of XMLNodes.
void
XMLDocument_as::parseXML(const std::string& xml)
{

    if (xml.empty()) {
        log_error(_("XML data is empty"));
        return;
    }

    // Clear current data
    clear(); 

    std::string::const_iterator it = xml.begin();
    XMLNode_as* node = this;

    while (it != xml.end() && _status == XML_OK)
    {
        if (*it == '<')
        {
            ++it;
            if (textMatch(xml, it, "!DOCTYPE", false))
            {
                // We should not advance past the DOCTYPE label, as
                // the case is preserved.
                parseDocTypeDecl(xml, it);
            }
            else if (textMatch(xml, it, "?xml", false))
            {
                // We should not advance past the xml label, as
                // the case is preserved.
                parseXMLDecl(xml, it);
            }
            else if (textMatch(xml, it, "!--"))
            {
                parseComment(node, xml, it);
            }
            else if (textMatch(xml, it, "![CDATA["))
            {
                parseCData(node, xml, it);
            }
            else parseTag(node, xml, it);
        }
        else parseText(node, xml, it);
    }

    // If everything parsed correctly, check that we've got back to the
    // parent node. If not, there is a missing closing tag.
    if (_status == XML_OK && node != this) {
        _status = XML_MISSING_CLOSE_TAG;
    }

}

void
XMLDocument_as::clear()
{
    // TODO: should set childs's parent to NULL ?
    clearChildren();
    _docTypeDecl.clear();
    _xmlDecl.clear();
    _status = XML_OK;
}

bool
XMLDocument_as::ignoreWhite() 
{

    const string_table::key propnamekey =
        getStringTable(_global).find("ignoreWhite");
    as_value val;
    if (!object()->get_member(propnamekey, &val)) {
        return false;
    }
    return val.to_bool();
}

// XML.prototype is assigned after the class has been constructed, so it
// replaces the original prototype and does not have a 'constructor'
// property.
void
xml_class_init(as_object& where, const ObjectURI& uri)
{

    Global_as& gl = getGlobal(where);
    as_object* cl = gl.createClass(&xml_new, 0);

    as_function* ctor = gl.getMember(NSV::CLASS_XMLNODE).to_function();

    if (ctor) {
        // XML.prototype is an XMLNode(1, "");
        fn_call::Args args;
        args += 1, "";
        as_object* proto =
            ctor->constructInstance(as_environment(getVM(where)), args);
        attachXMLInterface(*proto);
        cl->init_member(NSV::PROP_PROTOTYPE, proto);
    }
    
    where.init_member(getName(uri), cl, as_object::DefaultFlags,
            getNamespace(uri));

}

void
registerXMLNative(as_object& where)
{
    VM& vm = getVM(where);
    vm.registerNative(xml_escape, 100, 5);
    vm.registerNative(xml_createElement, 253, 10);
    vm.registerNative(xml_createTextNode, 253, 11);
    vm.registerNative(xml_parseXML, 253, 12);
}

namespace {

void
attachXMLProperties(as_object& o)
{

    as_object* proto = o.get_prototype();
    if (!proto) return;
    const int flags = 0;
    proto->init_member("ignoreWhite", false, flags);
    proto->init_member("contentType", "application/x-www-form-urlencoded",
            flags);
    proto->init_property("xmlDecl", &xml_xmlDecl, &xml_xmlDecl, flags);
    proto->init_property("docTypeDecl", &xml_docTypeDecl, &xml_docTypeDecl,
            flags);
    proto->init_property("loaded", xml_loaded, xml_loaded);
    proto->init_property("status", xml_status, xml_status);

}


void
attachXMLInterface(as_object& o)
{

    VM& vm = getVM(o);
    Global_as& gl = getGlobal(o);

    const int flags = 0;

    // No flags:
    o.init_member("createElement", vm.getNative(253, 10), flags);
    o.init_member("createTextNode", vm.getNative(253, 11), flags);
    o.init_member("load", vm.getNative(301, 0), flags);
    
    /// This handles getBytesLoaded, getBytesTotal, and addRequestHeader
    attachLoadableInterface(o, flags);
    
    o.init_member("parseXML", vm.getNative(253, 12), flags); 
    o.init_member("send", vm.getNative(301, 1), flags);
    o.init_member("sendAndLoad", vm.getNative(301, 2), flags);
    o.init_member("onData", gl.createFunction(xml_onData), flags);
    o.init_member("onLoad", gl.createFunction(xml_onLoad), flags);

}

as_value
xml_new(const fn_call& fn)
{

    as_object* obj = ensure<ValidThis>(fn);

    if (fn.nargs > 0) {

        // Copy constructor clones nodes.
        if (fn.arg(0).is_object()) {
            as_object* other = fn.arg(0).to_object(getGlobal(fn));
            XMLDocument_as* xml;
            if (isNativeType(other, xml)) {
                as_object* clone = xml->cloneNode(true)->object();
                attachXMLProperties(*clone);
                return as_value(clone);
            }
        }

        const std::string& xml_in = fn.arg(0).to_string();
        if (xml_in.empty()) {
            IF_VERBOSE_ASCODING_ERRORS(
            log_aserror(_("First arg given to XML constructor (%s) "
                    "evaluates to the empty string"), fn.arg(0));
            );
        }
        else {
            obj->setRelay(new XMLDocument_as(*obj, xml_in));
            attachXMLProperties(*obj);
            return as_value();
        }
    }

    obj->setRelay(new XMLDocument_as(*obj));
    attachXMLProperties(*obj);

    return as_value();
}

/// This is attached to the prototype (an XMLNode) on construction of XML
//
/// It has the curious effect of giving the XML object an inherited 'loaded'
/// property that fails when called on the prototype, because the prototype
/// is of type XMLNode.
as_value
xml_loaded(const fn_call& fn)
{
    XMLDocument_as* ptr = ensure<ThisIsNative<XMLDocument_as> >(fn);

    if (!fn.nargs) {
        XMLDocument_as::LoadStatus ls = ptr->loaded();
        if (ls == XMLDocument_as::XML_LOADED_UNDEFINED) return as_value();
        return as_value(static_cast<bool>(ls));
    }
    ptr->setLoaded(
            static_cast<XMLDocument_as::LoadStatus>(fn.arg(0).to_bool()));
    return as_value();
}

as_value
xml_status(const fn_call& fn)
{
    XMLDocument_as* ptr = ensure<ThisIsNative<XMLDocument_as> >(fn);
    
    if (!fn.nargs) {
        return as_value(ptr->status());
    }

    const double status = fn.arg(0).to_number();
    if (isNaN(status) ||
            status > std::numeric_limits<boost::int32_t>::max() ||
            status < std::numeric_limits<boost::int32_t>::min()) {

        ptr->setStatus(static_cast<XMLDocument_as::ParseStatus>(
                    std::numeric_limits<boost::int32_t>::min()));
    }

    ptr->setStatus(static_cast<XMLDocument_as::ParseStatus>(int(status)));
    return as_value();
}

/// Only available as ASnative.
as_value
xml_escape(const fn_call& fn)
{
    if (!fn.nargs) return as_value();

    std::string escaped = fn.arg(0).to_string();
    escapeXML(escaped);
    return as_value(escaped);
}

/// \brief create a new XML element
///
/// Method; creates a new XML element with the name specified in the
/// parameter. The new element initially has no parent, no children,
/// and no siblings. The method returns a reference to the newly
/// created XML object that represents the element. This method and
/// the XML.createTextNode() method are the constructor methods for
/// creating nodes for an XML object. 
as_value
xml_createElement(const fn_call& fn)
{
    
    if (fn.nargs > 0)
    {
        const std::string& text = fn.arg(0).to_string();
        XMLNode_as *xml_obj = new XMLNode_as(getGlobal(fn));
        xml_obj->nodeNameSet(text);
        xml_obj->nodeTypeSet(XMLNode_as::Text);

        return as_value(xml_obj->object());
        
    }
    else {
        log_error(_("no text for element creation"));
    }
    return as_value();
}


/// \brief Create a new XML node
/// 
/// Method; creates a new XML text node with the specified text. The
/// new node initially has no parent, and text nodes cannot have
/// children or siblings. This method returns a reference to the XML
/// object that represents the new text node. This method and the
/// XML.createElement() method are the constructor methods for
/// creating nodes for an XML object.
as_value
xml_createTextNode(const fn_call& fn)
{

    if (fn.nargs > 0) {
        const std::string& text = fn.arg(0).to_string();
        XMLNode_as* xml_obj = new XMLNode_as(getGlobal(fn));
        xml_obj->nodeValueSet(text);
        xml_obj->nodeTypeSet(XMLNode_as::Text);
        return as_value(xml_obj->object());
    }
    else {
        log_error(_("no text for text node creation"));
    }
    return as_value();
}


as_value
xml_parseXML(const fn_call& fn)
{

    XMLDocument_as* ptr = ensure<ThisIsNative<XMLDocument_as> >(fn);

    if (fn.nargs < 1)
    {
        IF_VERBOSE_ASCODING_ERRORS(
            log_aserror("XML.parseXML() needs one argument");
        );
        return as_value();
    }

    const std::string& text = fn.arg(0).to_string();
    ptr->parseXML(text);
    
    return as_value();
}

as_value
xml_xmlDecl(const fn_call& fn)
{
    XMLDocument_as* ptr = ensure<ThisIsNative<XMLDocument_as> >(fn);

    if (!fn.nargs)
    {
        // Getter
        const std::string& xml = ptr->getXMLDecl();
        if (xml.empty()) return as_value();
        return as_value(xml);
    }

    // Setter

    const std::string& xml = fn.arg(0).to_string();
    ptr->setXMLDecl(xml);
    
    return as_value();

}

as_value
xml_docTypeDecl(const fn_call& fn)
{
    XMLDocument_as* ptr = ensure<ThisIsNative<XMLDocument_as> >(fn);

    if (!fn.nargs)
    {
        // Getter
        const std::string& docType = ptr->getDocTypeDecl();
        if (docType.empty()) return as_value();
        return as_value(docType);
    }

    // Setter

    const std::string& docType = fn.arg(0).to_string();
    ptr->setDocTypeDecl(docType);
    
    return as_value();

}

/// XML.prototype has an empty onLoad function defined.
as_value
xml_onLoad(const fn_call& /*fn*/)
{
    return as_value();
}

as_value
xml_onData(const fn_call& fn)
{

    as_object* thisPtr = fn.this_ptr;
    assert(thisPtr);

    // See http://gitweb.freedesktop.org/?p=swfdec/swfdec.git;
    // a=blob;f=libswfdec/swfdec_initialize.as

    as_value src;
    if (fn.nargs) src = fn.arg(0);

    if (!src.is_undefined()) {
        thisPtr->set_member(NSV::PROP_LOADED, true);
        callMethod(thisPtr, NSV::PROP_PARSE_XML, src);
        callMethod(thisPtr, NSV::PROP_ON_LOAD, true);
    }
    else {
        thisPtr->set_member(NSV::PROP_LOADED, false);
        callMethod(thisPtr, NSV::PROP_ON_LOAD, false);
    }

    return as_value();
}

/// Case insensitive match of a string, returning false if there too few
/// DisplayObjects left or if there is no match. If there is a match, and advance
/// is not false, the iterator points to the DisplayObject after the match.
bool
textMatch(const std::string& xml, std::string::const_iterator& it,
        const std::string& match, bool advance)
{

    const std::string::size_type len = match.length();
    const std::string::const_iterator end = xml.end();

    if (static_cast<size_t>(end - it) < len) return false;

    if (!std::equal(it, it + len, match.begin(), boost::is_iequal())) {
        return false;
    }
    if (advance) it += len;
    return true;
}

/// Advance past whitespace
//
/// @return true if there is text after the whitespace, false if we 
///         reach the end of the string.
bool
textAfterWhitespace(const std::string& xml, std::string::const_iterator& it)
{
    const std::string whitespace("\r\t\n ");
    while (it != xml.end() && whitespace.find(*it) != std::string::npos) ++it;
    return (it != xml.end());
}

/// Parse a complete node up to a specified terminator.
//
/// @return     false if we reach the end of the text before finding the
///             terminator.
/// @param it   The current position of the iterator. If the return is true,
///             this points to the first DisplayObject after the terminator 
///             after return
/// @param content  If the return is true, this is filled with the content of
///                 the tag.
/// @param xml      The complete XML string.
bool
parseNodeWithTerminator(const std::string& xml,
        std::string::const_iterator& it, const std::string& terminator,
        std::string& content)
{
    std::string::const_iterator end = std::search(it, xml.end(),
            terminator.begin(), terminator.end());

    if (end == xml.end()) {
        return false;
    }

    content = std::string(it, end);
    it = end + terminator.length();

    return true;
}

void
unescapeXML(std::string& text)
{
    const Entities& ent = getEntities();

    for (Entities::const_iterator i = ent.begin(), e = ent.end();
            i != e; ++i) {
        boost::replace_all(text, i->first, i->second);
    }

    // Additionally, the &nbsp; entity is unescaped (but never escaped).
    // Note we do this as UTF-8, which is most likely wrong for SWF5.
    boost::replace_all(text, "&nbsp;", "\xc2\xa0");
}

const Entities&
getEntities()
{

    static const Entities entities = boost::assign::map_list_of
        ("&amp;", "&")
        ("&quot;", "\"")
        ("&lt;", "<")
        ("&gt;", ">")
        ("&apos;", "'");

    return entities;

}

} // anonymous namespace 
} // gnash namespace

// local Variables:
// mode: C++
// indent-tabs-mode: t
// End:

