1 // XML_as.cpp: ActionScript "XMLDocument" class, for Gnash.
2 //
3 // Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
4 //
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.
9 //
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.
14 //
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
18 //
19
20 #include "XMLNode_as.h"
21
22 #include <string>
23 #include <sstream>
24 #include <vector>
25 #include <algorithm>
26 #include <boost/algorithm/string/compare.hpp>
27 #include <boost/algorithm/string/replace.hpp>
28
29 #include "log.h"
30 #include "as_function.h"
31 #include "fn_call.h"
32 #include "Global_as.h"
33 #include "LoadableObject.h"
34 #include "XML_as.h"
35 #include "NativeFunction.h"
36 #include "VM.h"
37 #include "namedStrings.h"
38 #include "StringPredicates.h"
39 #include "Object.h"
40
41 namespace gnash {
42
43 // Forward declarations
44 namespace {
45
46 as_value xml_new(const fn_call& fn);
47 as_value xml_createElement(const fn_call& fn);
48 as_value xml_createTextNode(const fn_call& fn);
49 as_value xml_parseXML(const fn_call& fn);
50 as_value xml_onData(const fn_call& fn);
51 as_value xml_xmlDecl(const fn_call& fn);
52 as_value xml_docTypeDecl(const fn_call& fn);
53 as_value xml_contentType(const fn_call& fn);
54 as_value xml_escape(const fn_call& fn);
55 as_value xml_loaded(const fn_call& fn);
56 as_value xml_status(const fn_call& fn);
57 as_value xml_ignoreWhite(const fn_call& fn);
58
59 typedef XML_as::xml_iterator xml_iterator;
60
61 bool textAfterWhitespace(xml_iterator& it, xml_iterator end);
62 bool textMatch(xml_iterator& it, xml_iterator end,
63 const std::string& match, bool advance = true);
64 bool parseNodeWithTerminator( xml_iterator& it, xml_iterator end,
65 const std::string& terminator, std::string& content);
66
67 void setIdMap(as_object& xml, XMLNode_as& childNode,
68 const std::string& val);
69
70
71 typedef std::map<std::string, std::string> Entities;
72 const Entities& getEntities();
73
74 void attachXMLProperties(as_object& o);
75 void attachXMLInterface(as_object& o);
76 }
77
78
XML_as(as_object & object)79 XML_as::XML_as(as_object& object)
80 :
81 XMLNode_as(getGlobal(object)),
82 _loaded(XML_LOADED_UNDEFINED),
83 _status(XML_OK),
84 _contentType("application/x-www-form-urlencoded"),
85 _ignoreWhite(false)
86 {
87 setObject(&object);
88 }
89
90 // Parse the ASCII XML string into an XMLNode tree
XML_as(as_object & object,const std::string & xml)91 XML_as::XML_as(as_object& object, const std::string& xml)
92 :
93 XMLNode_as(getGlobal(object)),
94 _loaded(XML_LOADED_UNDEFINED),
95 _status(XML_OK),
96 _contentType("application/x-www-form-urlencoded"),
97 _ignoreWhite(false)
98 {
99 setObject(&object);
100 parseXML(xml);
101 }
102
103 void
escapeXML(std::string & text)104 escapeXML(std::string& text)
105 {
106 const Entities& ent = getEntities();
107
108 for (const auto& entity : ent)
109 {
110 boost::replace_all(text, entity.second, entity.first);
111 }
112 }
113
114 void
unescapeXML(std::string & text)115 unescapeXML(std::string& text)
116 {
117 const Entities& ent = getEntities();
118
119 for (const auto& entity : ent) {
120 boost::replace_all(text, entity.first, entity.second);
121 }
122
123 // Additionally, the entity is unescaped (but never escaped).
124 // Note we do this as UTF-8, which is most likely wrong for SWF5.
125 boost::replace_all(text, " ", "\xc2\xa0");
126 }
127
128 void
toString(std::ostream & o,bool encode) const129 XML_as::toString(std::ostream& o, bool encode) const
130 {
131 if (!_xmlDecl.empty()) o << _xmlDecl;
132 if (!_docTypeDecl.empty()) o << _docTypeDecl;
133
134 XMLNode_as* i = firstChild();
135 while (i) {
136 i->XMLNode_as::toString(o, encode);
137 i = i->nextSibling();
138 }
139 }
140
141 void
parseAttribute(XMLNode_as * node,xml_iterator & it,const xml_iterator end,Attributes & attributes)142 XML_as::parseAttribute(XMLNode_as* node, xml_iterator& it,
143 const xml_iterator end, Attributes& attributes)
144 {
145 const std::string terminators("\r\t\n >=");
146
147 xml_iterator ourend = std::find_first_of(it, end,
148 terminators.begin(), terminators.end());
149
150 if (ourend == end) {
151 _status = XML_UNTERMINATED_ELEMENT;
152 return;
153 }
154 std::string name(it, ourend);
155
156 if (name.empty()) {
157 _status = XML_UNTERMINATED_ELEMENT;
158 return;
159 }
160
161 // Point iterator to the DisplayObject after the name.
162 it = ourend;
163
164 // Skip any whitespace before the '='. If we reach the end of the string
165 // or don't find an '=', it's a parser error.
166 if (!textAfterWhitespace(it, end) || *it != '=') {
167 _status = XML_UNTERMINATED_ELEMENT;
168 return;
169 }
170
171 // Point to the DisplayObject after the '='
172 ++it;
173
174 // Skip any whitespace. If we reach the end of the string, or don't find
175 // a " or ', it's a parser error.
176 if (!textAfterWhitespace(it, end) || (*it != '"' && *it != '\'')) {
177 _status = XML_UNTERMINATED_ELEMENT;
178 return;
179 }
180
181 // Find the end of the attribute, looking for the opening DisplayObject,
182 // as long as it's not escaped. We begin one after the present position,
183 // which should be the opening DisplayObject. We want to remember what the
184 // iterator is pointing to for a while, so don't advance it.
185 ourend = it;
186 do {
187 ++ourend;
188 ourend = std::find(ourend, end, *it);
189 } while (ourend != end && *(ourend - 1) == '\\');
190
191 if (ourend == end) {
192 _status = XML_UNTERMINATED_ATTRIBUTE;
193 return;
194 }
195 ++it;
196
197 std::string value(it, ourend);
198
199 // Replace entities in the value.
200 unescapeXML(value);
201
202 // We've already checked that ourend != end, so we can advance at
203 // least once.
204 it = ourend;
205 // Advance past the last attribute DisplayObject
206 ++it;
207
208 // Handle namespace. This is set once only for each node, and is also
209 // pushed to the attributes list once.
210 StringNoCaseEqual noCaseCompare;
211 if (noCaseCompare(name, "xmlns") || noCaseCompare(name, "xmlns:")) {
212 if (!node->getNamespaceURI().empty()) return;
213 node->setNamespaceURI(value);
214 }
215
216 // This ensures values are not inserted twice, which is expected
217 // behaviour
218 attributes.insert(std::make_pair(name, value));
219
220 }
221
222 /// Parse and set the docTypeDecl. This is stored without any validation and
223 /// with the same case as in the parsed XML.
224 void
parseDocTypeDecl(xml_iterator & it,const xml_iterator end)225 XML_as::parseDocTypeDecl(xml_iterator& it, const xml_iterator end)
226 {
227
228 xml_iterator ourend;
229 xml_iterator current = it;
230
231 std::string::size_type count = 1;
232
233 // Look for angle brackets in the doctype declaration.
234 while (count) {
235
236 // Find the next closing bracket after the current position.
237 ourend = std::find(current, end, '>');
238 if (ourend == end) {
239 _status = XML_UNTERMINATED_DOCTYPE_DECL;
240 return;
241 }
242 --count;
243
244 // Count any opening brackets in between.
245 count += std::count(current, ourend, '<');
246 current = ourend;
247 ++current;
248 }
249
250 const std::string content(it, ourend);
251 std::ostringstream os;
252 os << '<' << content << '>';
253 _docTypeDecl = os.str();
254 it = ourend + 1;
255 }
256
257 void
parseXMLDecl(xml_iterator & it,const xml_iterator end)258 XML_as::parseXMLDecl(xml_iterator& it, const xml_iterator end)
259 {
260 std::string content;
261 if (!parseNodeWithTerminator(it, end, "?>", content)) {
262 _status = XML_UNTERMINATED_XML_DECL;
263 return;
264 }
265
266 std::ostringstream os;
267 os << "<" << content << "?>";
268
269 // This is appended to any xmlDecl already there.
270 _xmlDecl += os.str();
271 }
272
273 // The iterator should be pointing to the first char after the '<'
274 void
parseTag(XMLNode_as * & node,xml_iterator & it,const xml_iterator end)275 XML_as::parseTag(XMLNode_as*& node, xml_iterator& it,
276 const xml_iterator end)
277 {
278 bool closing = (*it == '/');
279 if (closing) ++it;
280
281 // These are for terminating the tag name, not (necessarily) the tag.
282 const std::string terminators("\r\n\t >");
283
284 xml_iterator endName = std::find_first_of(it, end, terminators.begin(),
285 terminators.end());
286
287 // Check that one of the terminators was found; otherwise it's malformed.
288 if (endName == end) {
289 _status = XML_UNTERMINATED_ELEMENT;
290 return;
291 }
292
293 // Knock off the "/>" of a self-closing tag.
294 if (std::equal(endName - 1, endName + 1, "/>")) {
295 // This can leave endName before it, e.g when a self-closing tag is
296 // empty ("</>"). This must be checked before trying to construct
297 // a string!
298 --endName;
299 }
300
301 // If the tag is empty, the XML counts as malformed.
302 if (it >= endName) {
303 _status = XML_UNTERMINATED_ELEMENT;
304 return;
305 }
306
307 std::string tagName(it, endName);
308
309 if (!closing) {
310
311 // Skip to the end of any whitespace after the tag name
312 it = endName;
313
314 if (!textAfterWhitespace(it, end)) {
315 _status = XML_UNTERMINATED_ELEMENT;
316 return;
317 }
318
319 XMLNode_as* childNode = new XMLNode_as(_global);
320 childNode->nodeNameSet(tagName);
321 childNode->nodeTypeSet(Element);
322
323 // Parse any attributes in an opening tag only, stopping at "/>" or
324 // '>'
325 // Attributes are added in reverse order and without any duplicates.
326 Attributes attributes;
327 while (it != end && *it != '>' && _status == XML_OK)
328 {
329 if (end - it > 1 && std::equal(it, it + 2, "/>")) break;
330
331 // This advances the iterator
332 parseAttribute(childNode, it, end, attributes);
333
334 // Skip any whitespace. If we reach the end of the string,
335 // it's malformed.
336 if (!textAfterWhitespace(it, end)) {
337 _status = XML_UNTERMINATED_ELEMENT;
338 return;
339 }
340 }
341
342 // Do nothing more if there was an error in attributes parsing.
343 if (_status != XML_OK) {
344 delete childNode;
345 return;
346 }
347
348 // testsuite/swfdec/xml-id-map.as tests that the node is appended
349 // first.
350 node->appendChild(childNode);
351
352 for (Attributes::const_reverse_iterator i = attributes.rbegin(),
353 e = attributes.rend(); i != e; ++i) {
354 childNode->setAttribute(i->first, i->second);
355 if (i->first == "id") setIdMap(*object(), *childNode, i->second);
356 }
357
358 if (*it == '/') ++it;
359 else node = childNode;
360
361 if (*it == '>') ++it;
362
363 return;
364 }
365
366 // If we reach here, this is a closing tag.
367
368 it = std::find(endName, end, '>');
369
370 if (it == end) {
371 _status = XML_UNTERMINATED_ELEMENT;
372 return;
373 }
374 ++it;
375
376 StringNoCaseEqual noCaseCompare;
377
378 if (node->getParent() && noCaseCompare(node->nodeName(), tagName)) {
379 node = node->getParent();
380 }
381 else {
382 // Malformed. Search for the parent node.
383 XMLNode_as* s = node;
384 while (s && !noCaseCompare(s->nodeName(), tagName)) {
385 //log_debug("parent: %s, this: %s", s->nodeName(), tagName);
386 s = s->getParent();
387 }
388 if (s) {
389 // If there's a parent, the open tag is orphaned.
390 _status = XML_MISSING_CLOSE_TAG;
391 }
392 else {
393 // If no parent, the close tag is orphaned.
394 _status = XML_MISSING_OPEN_TAG;
395 }
396 }
397
398 }
399
400 void
parseText(XMLNode_as * node,xml_iterator & it,const xml_iterator end,bool iw)401 XML_as::parseText(XMLNode_as* node, xml_iterator& it,
402 const xml_iterator end, bool iw)
403 {
404 xml_iterator ourend = std::find(it, end, '<');
405 std::string content(it, ourend);
406
407 it = ourend;
408
409 if (iw &&
410 content.find_first_not_of("\t\r\n ") == std::string::npos) return;
411
412 XMLNode_as* childNode = new XMLNode_as(_global);
413
414 childNode->nodeTypeSet(XMLNode_as::Text);
415
416 // Replace any entitites.
417 unescapeXML(content);
418
419 childNode->nodeValueSet(content);
420 node->appendChild(childNode);
421
422 }
423
424 void
parseComment(XMLNode_as *,xml_iterator & it,const xml_iterator end)425 XML_as::parseComment(XMLNode_as* /*node*/, xml_iterator& it,
426 const xml_iterator end)
427 {
428
429 std::string content;
430
431 if (!parseNodeWithTerminator(it, end, "-->", content)) {
432 _status = XML_UNTERMINATED_COMMENT;
433 return;
434 }
435 // Comments are discarded at least up to SWF8
436 }
437
438 void
parseCData(XMLNode_as * node,xml_iterator & it,const xml_iterator end)439 XML_as::parseCData(XMLNode_as* node, xml_iterator& it,
440 const xml_iterator end)
441 {
442 std::string content;
443
444 if (!parseNodeWithTerminator(it, end, "]]>", content)) {
445 _status = XML_UNTERMINATED_CDATA;
446 return;
447 }
448
449 XMLNode_as* childNode = new XMLNode_as(_global);
450 childNode->nodeValueSet(content);
451 childNode->nodeTypeSet(Text);
452 node->appendChild(childNode);
453 }
454
455
456 // This parses an XML string into a tree of XMLNodes.
457 void
parseXML(const std::string & xml)458 XML_as::parseXML(const std::string& xml)
459 {
460 // Clear current data
461 clear();
462
463 if (xml.empty()) {
464 log_error(_("XML data is empty"));
465 return;
466 }
467
468 xml_iterator it = xml.begin();
469 const xml_iterator end = xml.end();
470 XMLNode_as* node = this;
471
472 const bool iw = ignoreWhite();
473
474 while (it != end && _status == XML_OK) {
475 if (*it == '<') {
476 ++it;
477 if (textMatch(it, end, "!DOCTYPE", false)) {
478 // We should not advance past the DOCTYPE label, as
479 // the case is preserved.
480 parseDocTypeDecl(it, end);
481 }
482 else if (textMatch(it, end, "?xml", false)) {
483 // We should not advance past the xml label, as
484 // the case is preserved.
485 parseXMLDecl(it, end);
486 }
487 else if (textMatch(it, end, "!--")) {
488 parseComment(node, it, end);
489 }
490 else if (textMatch(it, end, "![CDATA[")) {
491 parseCData(node, it, end);
492 }
493 else parseTag(node, it, end);
494 }
495 else parseText(node, it, end, iw);
496 }
497
498 // If everything parsed correctly, check that we've got back to the
499 // parent node. If not, there is a missing closing tag.
500 if (_status == XML_OK && node != this) {
501 _status = XML_MISSING_CLOSE_TAG;
502 }
503
504 }
505
506 void
clear()507 XML_as::clear()
508 {
509 clearChildren();
510 _docTypeDecl.clear();
511 _xmlDecl.clear();
512 _status = XML_OK;
513 }
514
515 // XML.prototype is assigned after the class has been constructed, so it
516 // replaces the original prototype and does not have a 'constructor'
517 // property.
518 void
xml_class_init(as_object & where,const ObjectURI & uri)519 xml_class_init(as_object& where, const ObjectURI& uri)
520 {
521 Global_as& gl = getGlobal(where);
522 as_object* cl = gl.createClass(&xml_new, nullptr);
523
524 as_function* ctor = getMember(gl, NSV::CLASS_XMLNODE).to_function();
525
526 if (ctor) {
527 // XML.prototype is an XMLNode(1, "");
528 fn_call::Args args;
529 args += 1, "";
530 as_object* proto =
531 constructInstance(*ctor, as_environment(getVM(where)), args);
532 attachXMLInterface(*proto);
533 cl->init_member(NSV::PROP_PROTOTYPE, proto);
534 }
535
536 where.init_member(uri, cl, as_object::DefaultFlags);
537 }
538
539 void
registerXMLNative(as_object & where)540 registerXMLNative(as_object& where)
541 {
542 VM& vm = getVM(where);
543 vm.registerNative(xml_escape, 100, 5);
544 vm.registerNative(xml_createElement, 253, 10);
545 vm.registerNative(xml_createTextNode, 253, 11);
546 vm.registerNative(xml_parseXML, 253, 12);
547 }
548
549 namespace {
550
551 void
attachXMLProperties(as_object & o)552 attachXMLProperties(as_object& o)
553 {
554 as_object* proto = o.get_prototype();
555 if (!proto) return;
556 const int flags = 0;
557 proto->init_property("docTypeDecl", &xml_docTypeDecl, &xml_docTypeDecl,
558 flags);
559 proto->init_property("contentType", &xml_contentType, &xml_contentType,
560 flags);
561 proto->init_property("ignoreWhite", xml_ignoreWhite, xml_ignoreWhite, flags);
562 proto->init_property("loaded", xml_loaded, xml_loaded);
563 proto->init_property("status", xml_status, xml_status, flags);
564 proto->init_property("xmlDecl", &xml_xmlDecl, &xml_xmlDecl, flags);
565 }
566
567
568 void
attachXMLInterface(as_object & o)569 attachXMLInterface(as_object& o)
570 {
571 VM& vm = getVM(o);
572 Global_as& gl = getGlobal(o);
573
574 const int flags = 0;
575
576 // No flags:
577 o.init_member("createElement", vm.getNative(253, 10), flags);
578 o.init_member("createTextNode", vm.getNative(253, 11), flags);
579 o.init_member("load", vm.getNative(301, 0), flags);
580
581 /// This handles getBytesLoaded, getBytesTotal, and addRequestHeader
582 attachLoadableInterface(o, flags);
583
584 o.init_member("parseXML", vm.getNative(253, 12), flags);
585 o.init_member("send", vm.getNative(301, 1), flags);
586 o.init_member("sendAndLoad", vm.getNative(301, 2), flags);
587 o.init_member("onData", gl.createFunction(xml_onData), flags);
588 o.init_member("onLoad", gl.createFunction(emptyFunction), flags);
589 }
590
591 as_value
xml_new(const fn_call & fn)592 xml_new(const fn_call& fn)
593 {
594 as_object* obj = ensure<ValidThis>(fn);
595
596 if (fn.nargs && !fn.arg(0).is_undefined()) {
597
598 // Copy constructor clones nodes.
599 if (fn.arg(0).is_object()) {
600 as_object* other = toObject(fn.arg(0), getVM(fn));
601 XML_as* xml;
602 if (isNativeType(other, xml)) {
603 as_object* clone = xml->cloneNode(true)->object();
604 attachXMLProperties(*clone);
605 return as_value(clone);
606 }
607 }
608
609 const int version = getSWFVersion(fn);
610 const std::string& xml_in = fn.arg(0).to_string(version);
611 // It doesn't matter if the string is empty.
612 obj->setRelay(new XML_as(*obj, xml_in));
613 attachXMLProperties(*obj);
614 return as_value();
615 }
616
617 obj->setRelay(new XML_as(*obj));
618 attachXMLProperties(*obj);
619
620 return as_value();
621 }
622
623 /// This is attached to the prototype (an XMLNode) on construction of XML
624 //
625 /// It has the curious effect of giving the XML object an inherited 'loaded'
626 /// property that fails when called on the prototype, because the prototype
627 /// is of type XMLNode.
628 as_value
xml_loaded(const fn_call & fn)629 xml_loaded(const fn_call& fn)
630 {
631 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
632
633 if (!fn.nargs) {
634 XML_as::LoadStatus ls = ptr->loaded();
635 if (ls == XML_as::XML_LOADED_UNDEFINED) return as_value();
636 return as_value(static_cast<bool>(ls));
637 }
638 ptr->setLoaded(
639 static_cast<XML_as::LoadStatus>(toBool(fn.arg(0), getVM(fn))));
640 return as_value();
641 }
642
643 as_value
xml_status(const fn_call & fn)644 xml_status(const fn_call& fn)
645 {
646 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
647
648 if (!fn.nargs) {
649 return as_value(ptr->status());
650 }
651
652 if (fn.arg(0).is_undefined()) {
653 return as_value();
654 }
655
656 const double status = toNumber(fn.arg(0), getVM(fn));
657 if (isNaN(status) ||
658 status > std::numeric_limits<std::int32_t>::max() ||
659 status < std::numeric_limits<std::int32_t>::min()) {
660
661 ptr->setStatus(static_cast<XML_as::ParseStatus>(
662 std::numeric_limits<std::int32_t>::min()));
663 }
664 else ptr->setStatus(static_cast<XML_as::ParseStatus>(int(status)));
665 return as_value();
666 }
667
668 as_value
xml_ignoreWhite(const fn_call & fn)669 xml_ignoreWhite(const fn_call& fn)
670 {
671 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
672 if (!fn.nargs) {
673 // Getter
674 return as_value(ptr->ignoreWhite());
675 }
676
677 // Setter
678 if (fn.arg(0).is_undefined()) return as_value();
679 ptr->ignoreWhite(toBool(fn.arg(0), getVM(fn)));
680 return as_value();
681 }
682
683 /// Only available as ASnative.
684 as_value
xml_escape(const fn_call & fn)685 xml_escape(const fn_call& fn)
686 {
687 if (!fn.nargs) return as_value();
688
689 std::string escaped = fn.arg(0).to_string();
690 escapeXML(escaped);
691 return as_value(escaped);
692 }
693
694 /// \brief create a new XML element
695 ///
696 /// Method; creates a new XML element with the name specified in the
697 /// parameter. The new element initially has no parent, no children,
698 /// and no siblings. The method returns a reference to the newly
699 /// created XML object that represents the element. This method and
700 /// the XML.createTextNode() method are the constructor methods for
701 /// creating nodes for an XML object.
702 as_value
xml_createElement(const fn_call & fn)703 xml_createElement(const fn_call& fn)
704 {
705 if (!fn.nargs || fn.arg(0).is_undefined()) {
706 return as_value();
707 }
708
709 const as_value& arg = fn.arg(0);
710
711 const std::string& text = arg.to_string(getSWFVersion(fn));
712 XMLNode_as *xml_obj = new XMLNode_as(getGlobal(fn));
713 xml_obj->nodeNameSet(text);
714 if (!text.empty()) xml_obj->nodeTypeSet(XMLNode_as::Text);
715
716 return as_value(xml_obj->object());
717 }
718
719
720 /// \brief Create a new XML node
721 ///
722 /// Method; creates a new XML text node with the specified text. The
723 /// new node initially has no parent, and text nodes cannot have
724 /// children or siblings. This method returns a reference to the XML
725 /// object that represents the new text node. This method and the
726 /// XML.createElement() method are the constructor methods for
727 /// creating nodes for an XML object.
728 as_value
xml_createTextNode(const fn_call & fn)729 xml_createTextNode(const fn_call& fn)
730 {
731 if (fn.nargs > 0) {
732 const std::string& text = fn.arg(0).to_string();
733 XMLNode_as* xml_obj = new XMLNode_as(getGlobal(fn));
734 xml_obj->nodeValueSet(text);
735 xml_obj->nodeTypeSet(XMLNode_as::Text);
736 return as_value(xml_obj->object());
737 }
738 else {
739 log_error(_("no text for text node creation"));
740 }
741 return as_value();
742 }
743
744
745 as_value
xml_parseXML(const fn_call & fn)746 xml_parseXML(const fn_call& fn)
747 {
748 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
749
750 if (fn.nargs < 1) {
751 IF_VERBOSE_ASCODING_ERRORS(
752 log_aserror(_("XML.parseXML() needs one argument"));
753 );
754 return as_value();
755 }
756
757 const as_value arg = fn.arg(0);
758 if (arg.is_undefined()) return as_value();
759
760 const std::string& text = arg.to_string(getSWFVersion(fn));
761 ptr->parseXML(text);
762
763 return as_value();
764 }
765
766 as_value
xml_xmlDecl(const fn_call & fn)767 xml_xmlDecl(const fn_call& fn)
768 {
769 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
770
771 if (!fn.nargs) {
772 // Getter
773 const std::string& xml = ptr->getXMLDecl();
774 if (xml.empty()) return as_value();
775 return as_value(xml);
776 }
777
778 // Setter
779
780 const std::string& xml = fn.arg(0).to_string();
781 ptr->setXMLDecl(xml);
782
783 return as_value();
784 }
785
786 as_value
xml_contentType(const fn_call & fn)787 xml_contentType(const fn_call& fn)
788 {
789 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
790
791 if (!fn.nargs) {
792 // Getter
793 return as_value(ptr->getContentType());
794 }
795
796 // Setter
797 const std::string& contentType = fn.arg(0).to_string();
798 ptr->setContentType(contentType);
799
800 return as_value();
801 }
802
803 as_value
xml_docTypeDecl(const fn_call & fn)804 xml_docTypeDecl(const fn_call& fn)
805 {
806 XML_as* ptr = ensure<ThisIsNative<XML_as> >(fn);
807
808 if (!fn.nargs) {
809 // Getter
810 const std::string& docType = ptr->getDocTypeDecl();
811 if (docType.empty()) return as_value();
812 return as_value(docType);
813 }
814
815 // Setter
816 const std::string& docType = fn.arg(0).to_string();
817 ptr->setDocTypeDecl(docType);
818
819 return as_value();
820 }
821
822 as_value
xml_onData(const fn_call & fn)823 xml_onData(const fn_call& fn)
824 {
825 as_object* thisPtr = fn.this_ptr;
826 assert(thisPtr);
827
828 // See http://gitweb.freedesktop.org/?p=swfdec/swfdec.git;
829 // a=blob;f=libswfdec/swfdec_initialize.as
830
831 as_value src;
832 if (fn.nargs) src = fn.arg(0);
833
834 if (!src.is_undefined()) {
835 thisPtr->set_member(NSV::PROP_LOADED, true);
836 callMethod(thisPtr, NSV::PROP_PARSE_XML, src);
837 callMethod(thisPtr, NSV::PROP_ON_LOAD, true);
838 }
839 else {
840 thisPtr->set_member(NSV::PROP_LOADED, false);
841 callMethod(thisPtr, NSV::PROP_ON_LOAD, false);
842 }
843
844 return as_value();
845 }
846
847 /// Case insensitive match of a string, returning false if there too few
848 /// DisplayObjects left or if there is no match. If there is a match, and advance
849 /// is not false, the iterator points to the DisplayObject after the match.
850 bool
textMatch(xml_iterator & it,const xml_iterator end,const std::string & match,bool advance)851 textMatch(xml_iterator& it, const xml_iterator end,
852 const std::string& match, bool advance)
853 {
854 const std::string::size_type len = match.length();
855
856 if (static_cast<size_t>(end - it) < len) return false;
857
858 if (!std::equal(it, it + len, match.begin(), boost::is_iequal())) {
859 return false;
860 }
861 if (advance) it += len;
862 return true;
863 }
864
865 /// Advance past whitespace
866 //
867 /// @return true if there is text after the whitespace, false if we
868 /// reach the end of the string.
869 bool
textAfterWhitespace(xml_iterator & it,const xml_iterator end)870 textAfterWhitespace(xml_iterator& it, const xml_iterator end)
871 {
872 const std::string whitespace("\r\t\n ");
873 while (it != end && whitespace.find(*it) != std::string::npos) ++it;
874 return (it != end);
875 }
876
877 /// Parse a complete node up to a specified terminator.
878 //
879 /// @return false if we reach the end of the text before finding the
880 /// terminator.
881 /// @param it The current position of the iterator. If the return is true,
882 /// this points to the first DisplayObject after the terminator
883 /// after return
884 /// @param content If the return is true, this is filled with the content of
885 /// the tag.
886 /// @param xml The complete XML string.
887 bool
parseNodeWithTerminator(xml_iterator & it,const xml_iterator end,const std::string & terminator,std::string & content)888 parseNodeWithTerminator(xml_iterator& it, const xml_iterator end,
889 const std::string& terminator, std::string& content)
890 {
891 xml_iterator ourend = std::search(it, end, terminator.begin(),
892 terminator.end());
893
894 if (ourend == end) {
895 return false;
896 }
897
898 content = std::string(it, ourend);
899 it = ourend + terminator.length();
900
901 return true;
902 }
903
904
905 void
setIdMap(as_object & xml,XMLNode_as & childNode,const std::string & val)906 setIdMap(as_object& xml, XMLNode_as& childNode, const std::string& val)
907 {
908 VM& vm = getVM(xml);
909
910 const ObjectURI& id = getURI(vm, "idMap");
911
912 if (getSWFVersion(xml) < 8) {
913 // In version 7 or below, properties are added to the XML object.
914 xml.set_member(getURI(vm, val), childNode.object());
915 return;
916 }
917
918 // In version 8 or above, properties are added to an idMap member.
919 as_value im;
920 as_object* idMap;
921 if (xml.get_member(id, &im)) {
922 // If it's present but not an object just ignore it
923 // and carry on.
924 if (!im.is_object()) return;
925
926 idMap = toObject(im, vm);
927 assert(idMap);
928 }
929 else {
930 // If it's not there at all create it.
931 idMap = new as_object(getGlobal(xml));
932 xml.set_member(id, idMap);
933 }
934 idMap->set_member(getURI(vm, val), childNode.object());
935 }
936
937 const Entities&
getEntities()938 getEntities()
939 {
940 static const Entities entities = {
941 {"&", "&"},
942 {""", "\""},
943 {"<", "<"},
944 {">", ">"},
945 {"'", "'"}
946 };
947
948 return entities;
949 }
950
951 } // anonymous namespace
952 } // gnash namespace
953
954 // local Variables:
955 // mode: C++
956 // indent-tabs-mode: t
957 // End:
958
959