1 // XML_as.cpp: ActionScript "XMLDocument" class, for Gnash.
3 // Copyright (C) 2009, 2010 Free Software Foundation, Inc.
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "as_function.h" //for as_function
24 #include "Global_as.h"
26 #include "LoadableObject.h"
27 #include "XMLNode_as.h"
29 #include "builtin_function.h"
30 #include "NativeFunction.h"
32 #include "namedStrings.h"
33 #include "StringPredicates.h"
34 #include "smart_ptr.h" // for boost intrusive_ptr
35 #include "GnashException.h" // for ActionException
42 #include <boost/assign/list_of.hpp>
43 #include <boost/algorithm/string/compare.hpp>
44 #include <boost/algorithm/string/replace.hpp>
49 // Forward declarations
52 as_value
xml_new(const fn_call
& fn
);
53 as_value
xml_createElement(const fn_call
& fn
);
54 as_value
xml_createTextNode(const fn_call
& fn
);
55 as_value
xml_parseXML(const fn_call
& fn
);
56 as_value
xml_onData(const fn_call
& fn
);
57 as_value
xml_onLoad(const fn_call
& fn
);
58 as_value
xml_xmlDecl(const fn_call
& fn
);
59 as_value
xml_docTypeDecl(const fn_call
& fn
);
60 as_value
xml_escape(const fn_call
& fn
);
61 as_value
xml_loaded(const fn_call
& fn
);
62 as_value
xml_status(const fn_call
& fn
);
64 typedef XML_as::xml_iterator xml_iterator
;
66 bool textAfterWhitespace(xml_iterator
& it
, xml_iterator end
);
67 bool textMatch(xml_iterator
& it
, xml_iterator end
,
68 const std::string
& match
, bool advance
= true);
69 bool parseNodeWithTerminator( xml_iterator
& it
, xml_iterator end
,
70 const std::string
& terminator
, std::string
& content
);
73 typedef std::map
<std::string
, std::string
> Entities
;
74 const Entities
& getEntities();
76 void attachXMLProperties(as_object
& o
);
77 void attachXMLInterface(as_object
& o
);
82 XML_as::XML_as(as_object
& object
)
84 XMLNode_as(getGlobal(object
)),
85 _loaded(XML_LOADED_UNDEFINED
),
91 // Parse the ASCII XML string into an XMLNode tree
92 XML_as::XML_as(as_object
& object
, const std::string
& xml
)
94 XMLNode_as(getGlobal(object
)),
95 _loaded(XML_LOADED_UNDEFINED
),
103 escapeXML(std::string
& text
)
105 const Entities
& ent
= getEntities();
107 for (Entities::const_iterator i
= ent
.begin(), e
= ent
.end();
110 boost::replace_all(text
, i
->second
, i
->first
);
115 unescapeXML(std::string
& text
)
117 const Entities
& ent
= getEntities();
119 for (Entities::const_iterator i
= ent
.begin(), e
= ent
.end();
121 boost::replace_all(text
, i
->first
, i
->second
);
124 // Additionally, the entity is unescaped (but never escaped).
125 // Note we do this as UTF-8, which is most likely wrong for SWF5.
126 boost::replace_all(text
, " ", "\xc2\xa0");
130 XML_as::toString(std::ostream
& o
, bool encode
) const
132 if (!_xmlDecl
.empty()) o
<< _xmlDecl
;
133 if (!_docTypeDecl
.empty()) o
<< _docTypeDecl
;
135 XMLNode_as::toString(o
, encode
);
139 XML_as::parseAttribute(XMLNode_as
* node
, xml_iterator
& it
,
140 const xml_iterator end
, Attributes
& attributes
)
143 const std::string
terminators("\r\t\n >=");
145 xml_iterator ourend
= std::find_first_of(it
, end
,
146 terminators
.begin(), terminators
.end());
149 _status
= XML_UNTERMINATED_ELEMENT
;
152 std::string
name(it
, ourend
);
155 _status
= XML_UNTERMINATED_ELEMENT
;
159 // Point iterator to the DisplayObject after the name.
162 // Skip any whitespace before the '='. If we reach the end of the string
163 // or don't find an '=', it's a parser error.
164 if (!textAfterWhitespace(it
, end
) || *it
!= '=') {
165 _status
= XML_UNTERMINATED_ELEMENT
;
169 // Point to the DisplayObject after the '='
172 // Skip any whitespace. If we reach the end of the string, or don't find
173 // a " or ', it's a parser error.
174 if (!textAfterWhitespace(it
, end
) || (*it
!= '"' && *it
!= '\'')) {
175 _status
= XML_UNTERMINATED_ELEMENT
;
179 // Find the end of the attribute, looking for the opening DisplayObject,
180 // as long as it's not escaped. We begin one after the present position,
181 // which should be the opening DisplayObject. We want to remember what the
182 // iterator is pointing to for a while, so don't advance it.
186 ourend
= std::find(ourend
, end
, *it
);
187 } while (ourend
!= end
&& *(ourend
- 1) == '\\');
190 _status
= XML_UNTERMINATED_ATTRIBUTE
;
195 std::string
value(it
, ourend
);
197 // Replace entities in the value.
200 // We've already checked that ourend != end, so we can advance at
203 // Advance past the last attribute DisplayObject
206 // Handle namespace. This is set once only for each node, and is also
207 // pushed to the attributes list once.
208 StringNoCaseEqual noCaseCompare
;
209 if (noCaseCompare(name
, "xmlns") || noCaseCompare(name
, "xmlns:")) {
210 if (!node
->getNamespaceURI().empty()) return;
211 node
->setNamespaceURI(value
);
214 // This ensures values are not inserted twice, which is expected
216 attributes
.insert(std::make_pair(name
, value
));
220 /// Parse and set the docTypeDecl. This is stored without any validation and
221 /// with the same case as in the parsed XML.
223 XML_as::parseDocTypeDecl(xml_iterator
& it
, const xml_iterator end
)
227 xml_iterator current
= it
;
229 std::string::size_type count
= 1;
231 // Look for angle brackets in the doctype declaration.
234 // Find the next closing bracket after the current position.
235 ourend
= std::find(current
, end
, '>');
237 _status
= XML_UNTERMINATED_DOCTYPE_DECL
;
242 // Count any opening brackets in between.
243 count
+= std::count(current
, ourend
, '<');
248 const std::string
content(it
, ourend
);
249 std::ostringstream os
;
250 os
<< '<' << content
<< '>';
251 _docTypeDecl
= os
.str();
257 XML_as::parseXMLDecl(xml_iterator
& it
, const xml_iterator end
)
260 if (!parseNodeWithTerminator(it
, end
, "?>", content
))
262 _status
= XML_UNTERMINATED_XML_DECL
;
266 std::ostringstream os
;
267 os
<< "<" << content
<< "?>";
269 // This is appended to any xmlDecl already there.
270 _xmlDecl
+= os
.str();
274 // The iterator should be pointing to the first char after the '<'
276 XML_as::parseTag(XMLNode_as
*& node
, xml_iterator
& it
,
277 const xml_iterator end
)
280 bool closing
= (*it
== '/');
283 // These are for terminating the tag name, not (necessarily) the tag.
284 const std::string
terminators("\r\n\t >");
286 xml_iterator endName
= std::find_first_of(it
, end
, terminators
.begin(),
289 // Check that one of the terminators was found; otherwise it's malformed.
290 if (endName
== end
) {
291 _status
= XML_UNTERMINATED_ELEMENT
;
295 // Knock off the "/>" of a self-closing tag.
296 if (std::equal(endName
- 1, endName
+ 1, "/>")) {
297 // This can leave endName before it, e.g when a self-closing tag is
298 // empty ("</>"). This must be checked before trying to construct
303 // If the tag is empty, the XML counts as malformed.
305 _status
= XML_UNTERMINATED_ELEMENT
;
309 std::string
tagName(it
, endName
);
313 XMLNode_as
* childNode
= new XMLNode_as(_global
);
314 childNode
->nodeNameSet(tagName
);
315 childNode
->nodeTypeSet(Element
);
317 // Skip to the end of any whitespace after the tag name
320 if (!textAfterWhitespace(it
, end
)) {
321 _status
= XML_UNTERMINATED_ELEMENT
;
325 // Parse any attributes in an opening tag only, stopping at "/>" or
327 // Attributes are added in reverse order and without any duplicates.
328 Attributes attributes
;
329 while (it
!= end
&& *it
!= '>' && _status
== XML_OK
)
331 if (end
- it
> 1 && std::equal(it
, it
+ 2, "/>")) break;
333 // This advances the iterator
334 parseAttribute(childNode
, it
, end
, attributes
);
336 // Skip any whitespace. If we reach the end of the string,
338 if (!textAfterWhitespace(it
, end
)) {
339 _status
= XML_UNTERMINATED_ELEMENT
;
344 // Do nothing more if there was an error in attributes parsing.
345 if (_status
!= XML_OK
) return;
347 for (Attributes::const_reverse_iterator i
= attributes
.rbegin(),
348 e
= attributes
.rend(); i
!= e
; ++i
) {
349 childNode
->setAttribute(i
->first
, i
->second
);
352 node
->appendChild(childNode
);
353 if (*it
== '/') ++it
;
354 else node
= childNode
;
356 if (*it
== '>') ++it
;
361 // If we reach here, this is a closing tag.
363 it
= std::find(endName
, end
, '>');
366 _status
= XML_UNTERMINATED_ELEMENT
;
371 StringNoCaseEqual noCaseCompare
;
373 if (node
->getParent() && noCaseCompare(node
->nodeName(), tagName
)) {
374 node
= node
->getParent();
377 // Malformed. Search for the parent node.
378 XMLNode_as
* s
= node
;
379 while (s
&& !noCaseCompare(s
->nodeName(), tagName
)) {
380 //log_debug("parent: %s, this: %s", s->nodeName(), tagName);
384 // If there's a parent, the open tag is orphaned.
385 _status
= XML_MISSING_CLOSE_TAG
;
388 // If no parent, the close tag is orphaned.
389 _status
= XML_MISSING_OPEN_TAG
;
396 XML_as::parseText(XMLNode_as
* node
, xml_iterator
& it
,
397 const xml_iterator end
)
399 xml_iterator ourend
= std::find(it
, end
, '<');
400 std::string
content(it
, ourend
);
405 content
.find_first_not_of("\t\r\n ") == std::string::npos
) return;
407 XMLNode_as
* childNode
= new XMLNode_as(_global
);
409 childNode
->nodeTypeSet(XMLNode_as::Text
);
411 // Replace any entitites.
412 unescapeXML(content
);
414 childNode
->nodeValueSet(content
);
415 node
->appendChild(childNode
);
420 XML_as::parseComment(XMLNode_as
* /*node*/, xml_iterator
& it
,
421 const xml_iterator end
)
426 if (!parseNodeWithTerminator(it
, end
, "-->", content
)) {
427 _status
= XML_UNTERMINATED_COMMENT
;
430 // Comments are discarded at least up to SWF8
434 XML_as::parseCData(XMLNode_as
* node
, xml_iterator
& it
,
435 const xml_iterator end
)
439 if (!parseNodeWithTerminator(it
, end
, "]]>", content
)) {
440 _status
= XML_UNTERMINATED_CDATA
;
444 XMLNode_as
* childNode
= new XMLNode_as(_global
);
445 childNode
->nodeValueSet(content
);
446 childNode
->nodeTypeSet(Text
);
447 node
->appendChild(childNode
);
452 // This parses an XML string into a tree of XMLNodes.
454 XML_as::parseXML(const std::string
& xml
)
458 log_error(_("XML data is empty"));
462 // Clear current data
465 xml_iterator it
= xml
.begin();
466 const xml_iterator end
= xml
.end();
467 XMLNode_as
* node
= this;
469 while (it
!= end
&& _status
== XML_OK
)
474 if (textMatch(it
, end
, "!DOCTYPE", false))
476 // We should not advance past the DOCTYPE label, as
477 // the case is preserved.
478 parseDocTypeDecl(it
, end
);
480 else if (textMatch(it
, end
, "?xml", false))
482 // We should not advance past the xml label, as
483 // the case is preserved.
484 parseXMLDecl(it
, end
);
486 else if (textMatch(it
, end
, "!--"))
488 parseComment(node
, it
, end
);
490 else if (textMatch(it
, end
, "![CDATA["))
492 parseCData(node
, it
, end
);
494 else parseTag(node
, it
, end
);
496 else parseText(node
, it
, end
);
499 // If everything parsed correctly, check that we've got back to the
500 // parent node. If not, there is a missing closing tag.
501 if (_status
== XML_OK
&& node
!= this) {
502 _status
= XML_MISSING_CLOSE_TAG
;
510 // TODO: should set childs's parent to NULL ?
512 _docTypeDecl
.clear();
518 XML_as::ignoreWhite()
522 const ObjectURI
& propnamekey
=
523 getURI(getVM(_global
), "ignoreWhite");
526 as_object
* obj
= object();
528 if (!obj
->get_member(propnamekey
, &val
)) {
531 return toBool(val
, getVM(*obj
));
534 // XML.prototype is assigned after the class has been constructed, so it
535 // replaces the original prototype and does not have a 'constructor'
538 xml_class_init(as_object
& where
, const ObjectURI
& uri
)
541 Global_as
& gl
= getGlobal(where
);
542 as_object
* cl
= gl
.createClass(&xml_new
, 0);
544 as_function
* ctor
= getMember(gl
, NSV::CLASS_XMLNODE
).to_function();
547 // XML.prototype is an XMLNode(1, "");
551 constructInstance(*ctor
, as_environment(getVM(where
)), args
);
552 attachXMLInterface(*proto
);
553 cl
->init_member(NSV::PROP_PROTOTYPE
, proto
);
556 where
.init_member(uri
, cl
, as_object::DefaultFlags
);
561 registerXMLNative(as_object
& where
)
563 VM
& vm
= getVM(where
);
564 vm
.registerNative(xml_escape
, 100, 5);
565 vm
.registerNative(xml_createElement
, 253, 10);
566 vm
.registerNative(xml_createTextNode
, 253, 11);
567 vm
.registerNative(xml_parseXML
, 253, 12);
573 attachXMLProperties(as_object
& o
)
576 as_object
* proto
= o
.get_prototype();
579 proto
->init_member("contentType", "application/x-www-form-urlencoded",
581 proto
->init_property("docTypeDecl", &xml_docTypeDecl
, &xml_docTypeDecl
,
583 proto
->init_member("ignoreWhite", false, flags
);
584 proto
->init_property("loaded", xml_loaded
, xml_loaded
);
585 proto
->init_property("status", xml_status
, xml_status
, flags
);
586 proto
->init_property("xmlDecl", &xml_xmlDecl
, &xml_xmlDecl
, flags
);
592 attachXMLInterface(as_object
& o
)
596 Global_as
& gl
= getGlobal(o
);
601 o
.init_member("createElement", vm
.getNative(253, 10), flags
);
602 o
.init_member("createTextNode", vm
.getNative(253, 11), flags
);
603 o
.init_member("load", vm
.getNative(301, 0), flags
);
605 /// This handles getBytesLoaded, getBytesTotal, and addRequestHeader
606 attachLoadableInterface(o
, flags
);
608 o
.init_member("parseXML", vm
.getNative(253, 12), flags
);
609 o
.init_member("send", vm
.getNative(301, 1), flags
);
610 o
.init_member("sendAndLoad", vm
.getNative(301, 2), flags
);
611 o
.init_member("onData", gl
.createFunction(xml_onData
), flags
);
612 o
.init_member("onLoad", gl
.createFunction(xml_onLoad
), flags
);
617 xml_new(const fn_call
& fn
)
620 as_object
* obj
= ensure
<ValidThis
>(fn
);
622 if (fn
.nargs
&& !fn
.arg(0).is_undefined()) {
624 // Copy constructor clones nodes.
625 if (fn
.arg(0).is_object()) {
626 as_object
* other
= toObject(fn
.arg(0), getVM(fn
));
628 if (isNativeType(other
, xml
)) {
629 as_object
* clone
= xml
->cloneNode(true)->object();
630 attachXMLProperties(*clone
);
631 return as_value(clone
);
635 const int version
= getSWFVersion(fn
);
636 const std::string
& xml_in
= fn
.arg(0).to_string(version
);
637 // It doesn't matter if the string is empty.
638 obj
->setRelay(new XML_as(*obj
, xml_in
));
639 attachXMLProperties(*obj
);
643 obj
->setRelay(new XML_as(*obj
));
644 attachXMLProperties(*obj
);
649 /// This is attached to the prototype (an XMLNode) on construction of XML
651 /// It has the curious effect of giving the XML object an inherited 'loaded'
652 /// property that fails when called on the prototype, because the prototype
653 /// is of type XMLNode.
655 xml_loaded(const fn_call
& fn
)
657 XML_as
* ptr
= ensure
<ThisIsNative
<XML_as
> >(fn
);
660 XML_as::LoadStatus ls
= ptr
->loaded();
661 if (ls
== XML_as::XML_LOADED_UNDEFINED
) return as_value();
662 return as_value(static_cast<bool>(ls
));
665 static_cast<XML_as::LoadStatus
>(toBool(fn
.arg(0), getVM(fn
))));
670 xml_status(const fn_call
& fn
)
672 XML_as
* ptr
= ensure
<ThisIsNative
<XML_as
> >(fn
);
675 return as_value(ptr
->status());
678 const double status
= toNumber(fn
.arg(0), getVM(fn
));
680 status
> std::numeric_limits
<boost::int32_t>::max() ||
681 status
< std::numeric_limits
<boost::int32_t>::min()) {
683 ptr
->setStatus(static_cast<XML_as::ParseStatus
>(
684 std::numeric_limits
<boost::int32_t>::min()));
687 ptr
->setStatus(static_cast<XML_as::ParseStatus
>(int(status
)));
691 /// Only available as ASnative.
693 xml_escape(const fn_call
& fn
)
695 if (!fn
.nargs
) return as_value();
697 std::string escaped
= fn
.arg(0).to_string();
699 return as_value(escaped
);
702 /// \brief create a new XML element
704 /// Method; creates a new XML element with the name specified in the
705 /// parameter. The new element initially has no parent, no children,
706 /// and no siblings. The method returns a reference to the newly
707 /// created XML object that represents the element. This method and
708 /// the XML.createTextNode() method are the constructor methods for
709 /// creating nodes for an XML object.
711 xml_createElement(const fn_call
& fn
)
716 const std::string
& text
= fn
.arg(0).to_string();
717 XMLNode_as
*xml_obj
= new XMLNode_as(getGlobal(fn
));
718 xml_obj
->nodeNameSet(text
);
719 xml_obj
->nodeTypeSet(XMLNode_as::Text
);
721 return as_value(xml_obj
->object());
725 log_error(_("no text for element creation"));
731 /// \brief Create a new XML node
733 /// Method; creates a new XML text node with the specified text. The
734 /// new node initially has no parent, and text nodes cannot have
735 /// children or siblings. This method returns a reference to the XML
736 /// object that represents the new text node. This method and the
737 /// XML.createElement() method are the constructor methods for
738 /// creating nodes for an XML object.
740 xml_createTextNode(const fn_call
& fn
)
744 const std::string
& text
= fn
.arg(0).to_string();
745 XMLNode_as
* xml_obj
= new XMLNode_as(getGlobal(fn
));
746 xml_obj
->nodeValueSet(text
);
747 xml_obj
->nodeTypeSet(XMLNode_as::Text
);
748 return as_value(xml_obj
->object());
751 log_error(_("no text for text node creation"));
758 xml_parseXML(const fn_call
& fn
)
761 XML_as
* ptr
= ensure
<ThisIsNative
<XML_as
> >(fn
);
765 IF_VERBOSE_ASCODING_ERRORS(
766 log_aserror("XML.parseXML() needs one argument");
771 const std::string
& text
= fn
.arg(0).to_string();
778 xml_xmlDecl(const fn_call
& fn
)
780 XML_as
* ptr
= ensure
<ThisIsNative
<XML_as
> >(fn
);
785 const std::string
& xml
= ptr
->getXMLDecl();
786 if (xml
.empty()) return as_value();
787 return as_value(xml
);
792 const std::string
& xml
= fn
.arg(0).to_string();
793 ptr
->setXMLDecl(xml
);
800 xml_docTypeDecl(const fn_call
& fn
)
802 XML_as
* ptr
= ensure
<ThisIsNative
<XML_as
> >(fn
);
807 const std::string
& docType
= ptr
->getDocTypeDecl();
808 if (docType
.empty()) return as_value();
809 return as_value(docType
);
814 const std::string
& docType
= fn
.arg(0).to_string();
815 ptr
->setDocTypeDecl(docType
);
821 /// XML.prototype has an empty onLoad function defined.
823 xml_onLoad(const fn_call
& /*fn*/)
829 xml_onData(const fn_call
& fn
)
832 as_object
* thisPtr
= fn
.this_ptr
;
835 // See http://gitweb.freedesktop.org/?p=swfdec/swfdec.git;
836 // a=blob;f=libswfdec/swfdec_initialize.as
839 if (fn
.nargs
) src
= fn
.arg(0);
841 if (!src
.is_undefined()) {
842 thisPtr
->set_member(NSV::PROP_LOADED
, true);
843 callMethod(thisPtr
, NSV::PROP_PARSE_XML
, src
);
844 callMethod(thisPtr
, NSV::PROP_ON_LOAD
, true);
847 thisPtr
->set_member(NSV::PROP_LOADED
, false);
848 callMethod(thisPtr
, NSV::PROP_ON_LOAD
, false);
854 /// Case insensitive match of a string, returning false if there too few
855 /// DisplayObjects left or if there is no match. If there is a match, and advance
856 /// is not false, the iterator points to the DisplayObject after the match.
858 textMatch(xml_iterator
& it
, const xml_iterator end
,
859 const std::string
& match
, bool advance
)
862 const std::string::size_type len
= match
.length();
864 if (static_cast<size_t>(end
- it
) < len
) return false;
866 if (!std::equal(it
, it
+ len
, match
.begin(), boost::is_iequal())) {
869 if (advance
) it
+= len
;
873 /// Advance past whitespace
875 /// @return true if there is text after the whitespace, false if we
876 /// reach the end of the string.
878 textAfterWhitespace(xml_iterator
& it
, const xml_iterator end
)
880 const std::string
whitespace("\r\t\n ");
881 while (it
!= end
&& whitespace
.find(*it
) != std::string::npos
) ++it
;
885 /// Parse a complete node up to a specified terminator.
887 /// @return false if we reach the end of the text before finding the
889 /// @param it The current position of the iterator. If the return is true,
890 /// this points to the first DisplayObject after the terminator
892 /// @param content If the return is true, this is filled with the content of
894 /// @param xml The complete XML string.
896 parseNodeWithTerminator(xml_iterator
& it
, const xml_iterator end
,
897 const std::string
& terminator
, std::string
& content
)
899 xml_iterator ourend
= std::search(it
, end
, terminator
.begin(),
906 content
= std::string(it
, ourend
);
907 it
= ourend
+ terminator
.length();
916 static const Entities entities
= boost::assign::map_list_of
927 } // anonymous namespace
932 // indent-tabs-mode: t