/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- _status
- _status
- escapeXML
- unescapeXML
- toString
- parseAttribute
- parseDocTypeDecl
- parseXMLDecl
- parseTag
- parseText
- parseComment
- parseCData
- parseXML
- clear
- ignoreWhite
- xml_class_init
- registerXMLNative
- attachXMLProperties
- attachXMLInterface
- xml_new
- xml_loaded
- xml_status
- xml_escape
- xml_createElement
- xml_createTextNode
- xml_parseXML
- xml_xmlDecl
- xml_docTypeDecl
- xml_onLoad
- xml_onData
- textMatch
- textAfterWhitespace
- parseNodeWithTerminator
- getEntities
// XML_as.cpp: ActionScript "XMLDocument" class, for Gnash.
//
// Copyright (C) 2009, 2010, 2011 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
//
#include "log.h"
#include "as_function.h" //for as_function
#include "fn_call.h"
#include "Global_as.h"
#include "LoadableObject.h"
#include "XMLNode_as.h"
#include "XML_as.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);
typedef XML_as::xml_iterator xml_iterator;
bool textAfterWhitespace(xml_iterator& it, xml_iterator end);
bool textMatch(xml_iterator& it, xml_iterator end,
const std::string& match, bool advance = true);
bool parseNodeWithTerminator( xml_iterator& it, xml_iterator end,
const std::string& terminator, std::string& content);
typedef std::map<std::string, std::string> Entities;
const Entities& getEntities();
void attachXMLProperties(as_object& o);
void attachXMLInterface(as_object& o);
}
XML_as::XML_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
XML_as::XML_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
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 entity is unescaped (but never escaped).
// Note we do this as UTF-8, which is most likely wrong for SWF5.
boost::replace_all(text, " ", "\xc2\xa0");
}
void
XML_as::toString(std::ostream& o, bool encode) const
{
if (!_xmlDecl.empty()) o << _xmlDecl;
if (!_docTypeDecl.empty()) o << _docTypeDecl;
XMLNode_as::toString(o, encode);
}
void
XML_as::parseAttribute(XMLNode_as* node, xml_iterator& it,
const xml_iterator end, Attributes& attributes)
{
const std::string terminators("\r\t\n >=");
xml_iterator ourend = std::find_first_of(it, end,
terminators.begin(), terminators.end());
if (ourend == end) {
_status = XML_UNTERMINATED_ELEMENT;
return;
}
std::string name(it, ourend);
if (name.empty()) {
_status = XML_UNTERMINATED_ELEMENT;
return;
}
// Point iterator to the DisplayObject after the name.
it = ourend;
// 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(it, end) || *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(it, end) || (*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.
ourend = it;
do {
++ourend;
ourend = std::find(ourend, end, *it);
} while (ourend != end && *(ourend - 1) == '\\');
if (ourend == end) {
_status = XML_UNTERMINATED_ATTRIBUTE;
return;
}
++it;
std::string value(it, ourend);
// Replace entities in the value.
unescapeXML(value);
// We've already checked that ourend != end, so we can advance at
// least once.
it = ourend;
// 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
XML_as::parseDocTypeDecl(xml_iterator& it, const xml_iterator end)
{
xml_iterator ourend;
xml_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.
ourend = std::find(current, end, '>');
if (ourend == end) {
_status = XML_UNTERMINATED_DOCTYPE_DECL;
return;
}
--count;
// Count any opening brackets in between.
count += std::count(current, ourend, '<');
current = ourend;
++current;
}
const std::string content(it, ourend);
std::ostringstream os;
os << '<' << content << '>';
_docTypeDecl = os.str();
it = ourend + 1;
}
void
XML_as::parseXMLDecl(xml_iterator& it, const xml_iterator end)
{
std::string content;
if (!parseNodeWithTerminator(it, end, "?>", 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
XML_as::parseTag(XMLNode_as*& node, xml_iterator& it,
const xml_iterator end)
{
bool closing = (*it == '/');
if (closing) ++it;
// These are for terminating the tag name, not (necessarily) the tag.
const std::string terminators("\r\n\t >");
xml_iterator endName = std::find_first_of(it, end, terminators.begin(),
terminators.end());
// Check that one of the terminators was found; otherwise it's malformed.
if (endName == 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);
// Skip to the end of any whitespace after the tag name
it = endName;
if (!textAfterWhitespace(it, end)) {
_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 != end && *it != '>' && _status == XML_OK)
{
if (end - it > 1 && std::equal(it, it + 2, "/>")) break;
// This advances the iterator
parseAttribute(childNode, it, end, attributes);
// Skip any whitespace. If we reach the end of the string,
// it's malformed.
if (!textAfterWhitespace(it, end)) {
_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, end, '>');
if (it == 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
XML_as::parseText(XMLNode_as* node, xml_iterator& it,
const xml_iterator end)
{
xml_iterator ourend = std::find(it, end, '<');
std::string content(it, ourend);
it = ourend;
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);
}
void
XML_as::parseComment(XMLNode_as* /*node*/, xml_iterator& it,
const xml_iterator end)
{
std::string content;
if (!parseNodeWithTerminator(it, end, "-->", content)) {
_status = XML_UNTERMINATED_COMMENT;
return;
}
// Comments are discarded at least up to SWF8
}
void
XML_as::parseCData(XMLNode_as* node, xml_iterator& it,
const xml_iterator end)
{
std::string content;
if (!parseNodeWithTerminator(it, end, "]]>", 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
XML_as::parseXML(const std::string& xml)
{
if (xml.empty()) {
log_error(_("XML data is empty"));
return;
}
// Clear current data
clear();
xml_iterator it = xml.begin();
const xml_iterator end = xml.end();
XMLNode_as* node = this;
while (it != end && _status == XML_OK)
{
if (*it == '<')
{
++it;
if (textMatch(it, end, "!DOCTYPE", false))
{
// We should not advance past the DOCTYPE label, as
// the case is preserved.
parseDocTypeDecl(it, end);
}
else if (textMatch(it, end, "?xml", false))
{
// We should not advance past the xml label, as
// the case is preserved.
parseXMLDecl(it, end);
}
else if (textMatch(it, end, "!--"))
{
parseComment(node, it, end);
}
else if (textMatch(it, end, "![CDATA["))
{
parseCData(node, it, end);
}
else parseTag(node, it, end);
}
else parseText(node, it, end);
}
// 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
XML_as::clear()
{
// TODO: should set childs's parent to NULL ?
clearChildren();
_docTypeDecl.clear();
_xmlDecl.clear();
_status = XML_OK;
}
bool
XML_as::ignoreWhite()
{
// TODO: use NSV:
const ObjectURI& propnamekey =
getURI(getVM(_global), "ignoreWhite");
as_value val;
as_object* obj = object();
if (!obj->get_member(propnamekey, &val)) {
return false;
}
return toBool(val, getVM(*obj));
}
// 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 = getMember(gl, NSV::CLASS_XMLNODE).to_function();
if (ctor) {
// XML.prototype is an XMLNode(1, "");
fn_call::Args args;
args += 1, "";
as_object* proto =
constructInstance(*ctor, as_environment(getVM(where)), args);
attachXMLInterface(*proto);
cl->init_member(NSV::PROP_PROTOTYPE, proto);
}
where.init_member(uri, cl, as_object::DefaultFlags);
}
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("contentType", "application/x-www-form-urlencoded",
flags);
proto->init_property("docTypeDecl", &xml_docTypeDecl, &xml_docTypeDecl,
flags);
proto->init_member("ignoreWhite", false, flags);
proto->init_property("loaded", xml_loaded, xml_loaded);
proto->init_property("status", xml_status, xml_status, flags);
proto->init_property("xmlDecl", &xml_xmlDecl, &xml_xmlDecl, flags);
}
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 && !fn.arg(0).is_undefined()) {
// Copy constructor clones nodes.
if (fn.arg(0).is_object()) {
as_object* other = toObject(fn.arg(0), getVM(fn));
XML_as* xml;
if (isNativeType(other, xml)) {
as_object* clone = xml->cloneNode(true)->object();
attachXMLProperties(*clone);
return as_value(clone);
}
}
const int version = getSWFVersion(fn);
const std::string& xml_in = fn.arg(0).to_string(version);
// It doesn't matter if the string is empty.
obj->setRelay(new XML_as(*obj, xml_in));
attachXMLProperties(*obj);
return as_value();
}
obj->setRelay(new XML_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)
{
XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
if (!fn.nargs) {
XML_as::LoadStatus ls = ptr->loaded();
if (ls == XML_as::XML_LOADED_UNDEFINED) return as_value();
return as_value(static_cast<bool>(ls));
}
ptr->setLoaded(
static_cast<XML_as::LoadStatus>(toBool(fn.arg(0), getVM(fn))));
return as_value();
}
as_value
xml_status(const fn_call& fn)
{
XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
if (!fn.nargs) {
return as_value(ptr->status());
}
const double status = toNumber(fn.arg(0), getVM(fn));
if (isNaN(status) ||
status > std::numeric_limits<boost::int32_t>::max() ||
status < std::numeric_limits<boost::int32_t>::min()) {
ptr->setStatus(static_cast<XML_as::ParseStatus>(
std::numeric_limits<boost::int32_t>::min()));
}
ptr->setStatus(static_cast<XML_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)
{
XML_as* ptr = ensure<ThisIsNative<XML_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)
{
XML_as* ptr = ensure<ThisIsNative<XML_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)
{
XML_as* ptr = ensure<ThisIsNative<XML_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(xml_iterator& it, const xml_iterator end,
const std::string& match, bool advance)
{
const std::string::size_type len = match.length();
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(xml_iterator& it, const xml_iterator end)
{
const std::string whitespace("\r\t\n ");
while (it != end && whitespace.find(*it) != std::string::npos) ++it;
return (it != 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(xml_iterator& it, const xml_iterator end,
const std::string& terminator, std::string& content)
{
xml_iterator ourend = std::search(it, end, terminator.begin(),
terminator.end());
if (ourend == end) {
return false;
}
content = std::string(it, ourend);
it = ourend + terminator.length();
return true;
}
const Entities&
getEntities()
{
static const Entities entities = boost::assign::map_list_of
("&", "&")
(""", "\"")
("<", "<")
(">", ">")
("'", "'");
return entities;
}
} // anonymous namespace
} // gnash namespace
// local Variables:
// mode: C++
// indent-tabs-mode: t
// End: