Clean up VectorEffects::init
[hiphop-php.git] / hphp / runtime / ext / ext_simplexml.cpp
blob07a864dc6f7c94bea2b63139ee745e3ce320697b
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/ext_simplexml.h"
19 #include "hphp/runtime/ext/ext_file.h"
20 #include "hphp/runtime/ext/ext_class.h"
21 #include "hphp/runtime/ext/ext_domdocument.h"
22 #include "hphp/runtime/base/class_info.h"
23 #include "hphp/runtime/base/util/request_local.h"
24 #include "hphp/system/systemlib.h"
26 #ifndef LIBXML2_NEW_BUFFER
27 # define xmlOutputBufferGetSize(buf) ((buf)->buffer->use)
28 # define xmlOutputBufferGetContent(buf) ((buf)->buffer->content)
29 #endif
31 namespace HPHP {
32 IMPLEMENT_DEFAULT_EXTENSION(SimpleXML);
33 ///////////////////////////////////////////////////////////////////////////////
35 // This is to make sure each node holds one reference of m_doc, so not to let
36 // it go out of scope.
37 class XmlDocWrapper : public SweepableResourceData {
38 public:
39 DECLARE_OBJECT_ALLOCATION_NO_SWEEP(XmlDocWrapper)
41 static StaticString s_class_name;
42 // overriding ResourceData
43 virtual CStrRef o_getClassNameHook() const { return s_class_name; }
45 XmlDocWrapper(xmlDocPtr doc, CStrRef cls, Object domNode = nullptr)
46 : m_doc(doc), m_cls(cls), m_domNode(domNode) {
47 if (!domNode.isNull()) {
48 DEBUG_ONLY c_DOMNode *domnode = domNode.getTyped<c_DOMNode>();
49 assert(!domnode || domnode->m_node == (xmlNodePtr) doc);
53 CStrRef getClass() { return m_cls; }
55 void sweep() {
56 // if m_domNode isn't null, then he owns the m_doc. Otherwise, I own it
57 if (m_doc && m_domNode.isNull()) {
58 xmlFreeDoc(m_doc);
61 ~XmlDocWrapper() { XmlDocWrapper::sweep(); }
62 private:
63 xmlDocPtr m_doc;
64 String m_cls;
65 // Hold onto the original owner of the doc so it doesn't get free()d.
66 Object m_domNode;
68 IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(XmlDocWrapper)
70 StaticString XmlDocWrapper::s_class_name("xmlDoc");
72 ///////////////////////////////////////////////////////////////////////////////
73 // helpers
75 static inline bool match_ns(xmlNodePtr node, CStrRef ns, bool is_prefix) {
76 if (ns.empty()) {
77 return true;
79 if (node->ns == NULL || node->ns->prefix == NULL) {
80 return false;
82 if (node->ns && !xmlStrcmp(is_prefix ? node->ns->prefix : node->ns->href,
83 (const xmlChar *)ns.data())) {
84 return true;
86 return false;
89 static String node_list_to_string(xmlDocPtr doc, xmlNodePtr list) {
90 xmlChar *tmp = xmlNodeListGetString(doc, list, 1);
91 String res((char*) tmp, CopyString);
92 xmlFree(tmp);
93 return res;
96 static Array collect_attributes(xmlNodePtr node, CStrRef ns, bool is_prefix) {
97 assert(node);
98 Array attributes = Array::Create();
99 if (node->type != XML_ENTITY_DECL) {
100 for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
101 if (match_ns((xmlNodePtr)attr, ns, is_prefix)) {
102 String n = String((char*)attr->name, xmlStrlen(attr->name), CopyString);
103 attributes.set(n, node_list_to_string(node->doc, attr->children));
107 return attributes;
110 static void add_property(Array &properties, xmlNodePtr node, Object value) {
111 const char *name = (char *)node->name;
112 if (name) {
113 int namelen = xmlStrlen(node->name);
114 String sname(name, namelen, CopyString);
116 if (properties.exists(sname)) {
117 Variant &existing = properties.lval(sname);
118 if (existing.is(KindOfArray)) {
119 existing.append(value);
120 } else {
121 Array newdata;
122 newdata.append(existing);
123 newdata.append(value);
124 properties.set(sname, newdata);
126 } else {
127 properties.set(sname, value);
132 static Object create_text(CResRef doc, xmlNodePtr node,
133 CStrRef value, CStrRef ns,
134 bool is_prefix, bool free_text) {
135 Object obj = create_object(doc.getTyped<XmlDocWrapper>()->
136 getClass(), Array(), false);
137 c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
138 elem->m_doc = doc;
139 elem->m_node = node->parent; // assign to parent, not node
140 elem->m_children.set(0, value);
141 elem->m_is_text = true;
142 elem->m_free_text = free_text;
143 elem->m_attributes = collect_attributes(node->parent, ns, is_prefix);
144 return obj;
147 static Array create_children(CResRef doc, xmlNodePtr root,
148 CStrRef ns, bool is_prefix);
150 static Object create_element(CResRef doc, xmlNodePtr node,
151 CStrRef ns, bool is_prefix) {
152 Object obj = create_object(doc.getTyped<XmlDocWrapper>()->
153 getClass(), Array(), false);
154 c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
155 elem->m_doc = doc;
156 elem->m_node = node;
157 if (node) {
158 elem->m_children = create_children(doc, node, ns, is_prefix);
159 elem->m_attributes = collect_attributes(node, ns, is_prefix);
161 return obj;
164 static Array create_children(CResRef doc, xmlNodePtr root,
165 CStrRef ns, bool is_prefix) {
166 Array properties = Array::Create();
167 for (xmlNodePtr node = root->children; node; node = node->next) {
168 if (node->children || node->prev || node->next) {
169 if (node->type == XML_TEXT_NODE) {
170 // bad node from parser, ignoring it...
171 continue;
173 } else {
174 if (node->type == XML_TEXT_NODE) {
175 if (node->content && *node->content) {
176 add_property
177 (properties, root,
178 create_text(doc, node, node_list_to_string(root->doc, node),
179 ns, is_prefix, true));
181 continue;
185 if (node->type != XML_ELEMENT_NODE || match_ns(node, ns, is_prefix)) {
186 xmlNodePtr child = node->children;
187 Object sub;
188 if (child && child->type == XML_TEXT_NODE && !xmlIsBlankNode(child)) {
189 sub = create_text(doc, child, node_list_to_string(root->doc, child),
190 ns, is_prefix, false);
191 } else {
192 sub = create_element(doc, node, ns, is_prefix);
194 add_property(properties, node, sub);
197 return properties;
200 static inline void add_namespace_name(Array &out, xmlNsPtr ns) {
201 String prefix = ns->prefix ? String((const char*)ns->prefix) :
202 String(empty_string);
203 if (!out.exists(prefix)) {
204 out.set(prefix, String((char*)ns->href, CopyString));
208 static void add_namespaces(Array &out, xmlNodePtr node, bool recursive) {
209 if (node->ns) {
210 add_namespace_name(out, node->ns);
213 for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
214 if (attr->ns) {
215 add_namespace_name(out, attr->ns);
219 if (recursive) {
220 for (node = node->children; node; node = node->next) {
221 if (node->type == XML_ELEMENT_NODE) {
222 add_namespaces(out, node, true);
228 static void add_registered_namespaces(Array &out, xmlNodePtr node,
229 bool recursive) {
230 if (node->type == XML_ELEMENT_NODE) {
231 for (xmlNsPtr ns = node->nsDef; ns; ns = ns->next) {
232 add_namespace_name(out, ns);
234 if (recursive) {
235 for (node = node->children; node; node = node->next) {
236 add_registered_namespaces(out, node, true);
242 ///////////////////////////////////////////////////////////////////////////////
243 // simplexml
245 Variant f_simplexml_import_dom(CObjRef node,
246 CStrRef class_name /* = "SimpleXMLElement" */) {
248 c_DOMNode *domnode = node.getTyped<c_DOMNode>();
249 xmlNodePtr nodep = domnode->m_node;
251 if (nodep) {
252 if (nodep->doc == nullptr) {
253 raise_warning("Imported Node must have associated Document");
254 return uninit_null();
256 if (nodep->type == XML_DOCUMENT_NODE ||
257 nodep->type == XML_HTML_DOCUMENT_NODE) {
258 nodep = xmlDocGetRootElement((xmlDocPtr) nodep);
262 if (nodep && nodep->type == XML_ELEMENT_NODE) {
263 Resource obj =
264 Resource(NEWOBJ(XmlDocWrapper)(nodep->doc, class_name, node));
265 return create_element(obj, nodep, String(), false);
266 } else {
267 raise_warning("Invalid Nodetype to import");
268 return uninit_null();
272 Variant f_simplexml_load_string(CStrRef data,
273 CStrRef class_name /* = "SimpleXMLElement" */,
274 int64_t options /* = 0 */,
275 CStrRef ns /* = "" */,
276 bool is_prefix /* = false */) {
277 Class* cls;
278 if (!class_name.empty()) {
279 cls = Unit::loadClass(class_name.get());
280 if (!cls) {
281 throw_invalid_argument("class not found: %s", class_name.data());
282 return uninit_null();
284 if (!cls->classof(c_SimpleXMLElement::s_cls)) {
285 throw_invalid_argument(
286 "simplexml_load_string() expects parameter 2 to be a class name "
287 "derived from SimpleXMLElement, '%s' given",
288 class_name.data());
289 return uninit_null();
291 } else {
292 cls = c_SimpleXMLElement::s_cls;
295 xmlDocPtr doc = xmlReadMemory(data.data(), data.size(), NULL, NULL, options);
296 xmlNodePtr root = xmlDocGetRootElement(doc);
297 if (!doc) {
298 return false;
301 return create_element(Resource(NEWOBJ(XmlDocWrapper)(doc, cls->nameRef())),
302 root, ns, is_prefix);
305 Variant f_simplexml_load_file(CStrRef filename,
306 CStrRef class_name /* = "SimpleXMLElement" */,
307 int64_t options /* = 0 */, CStrRef ns /* = "" */,
308 bool is_prefix /* = false */) {
309 String str = f_file_get_contents(filename);
310 return f_simplexml_load_string(str, class_name, options, ns, is_prefix);
313 ///////////////////////////////////////////////////////////////////////////////
314 // SimpleXMLElement
316 c_SimpleXMLElement::c_SimpleXMLElement(Class* cb) :
317 ExtObjectDataFlags<ObjectData::UseGet|
318 ObjectData::UseSet|
319 ObjectData::UseIsset|
320 ObjectData::UseUnset|
321 ObjectData::CallToImpl>(cb),
322 m_node(NULL), m_is_text(false), m_free_text(false),
323 m_is_attribute(false), m_is_children(false), m_is_property(false),
324 m_xpath(NULL) {
325 m_children = Array::Create();
328 c_SimpleXMLElement::~c_SimpleXMLElement() {
329 c_SimpleXMLElement::sweep();
332 void c_SimpleXMLElement::sweep() {
333 if (m_xpath) {
334 xmlXPathFreeContext(m_xpath);
338 void c_SimpleXMLElement::t___construct(CStrRef data, int64_t options /* = 0 */,
339 bool data_is_url /* = false */,
340 CStrRef ns /* = "" */,
341 bool is_prefix /* = false */) {
342 String xml = data;
343 if (data_is_url) {
344 Variant ret = f_file_get_contents(data);
345 if (same(ret, false)) {
346 raise_warning("Unable to retrieve XML content from %s", data.data());
347 return;
349 xml = ret.toString();
352 xmlDocPtr doc = xmlReadMemory(xml.data(), xml.size(), NULL, NULL, options);
353 if (doc) {
354 m_doc =
355 Resource(NEWOBJ(XmlDocWrapper)(doc, c_SimpleXMLElement::s_class_name));
356 m_node = xmlDocGetRootElement(doc);
357 if (m_node) {
358 m_children = create_children(m_doc, m_node, ns, is_prefix);
359 m_attributes = collect_attributes(m_node, ns, is_prefix);
361 } else {
362 throw Object(SystemLib::AllocExceptionObject(
363 "String could not be parsed as XML"));
367 Variant c_SimpleXMLElement::t_xpath(CStrRef path) {
368 if (m_is_attribute || !m_node) {
369 return uninit_null();
372 xmlDocPtr doc = m_node->doc;
374 int nsnbr = 0;
375 xmlNsPtr *ns = xmlGetNsList(doc, m_node);
376 if (ns != NULL) {
377 while (ns[nsnbr] != NULL) {
378 nsnbr++;
382 if (m_xpath == NULL) {
383 m_xpath = xmlXPathNewContext(doc);
385 m_xpath->node = m_node;
386 m_xpath->namespaces = ns;
387 m_xpath->nsNr = nsnbr;
389 xmlXPathObjectPtr retval = xmlXPathEval((xmlChar *)path.data(), m_xpath);
390 if (ns != NULL) {
391 xmlFree(ns);
392 m_xpath->namespaces = NULL;
393 m_xpath->nsNr = 0;
396 if (!retval) {
397 return false;
400 xmlNodeSetPtr result = retval->nodesetval;
401 if (!result) {
402 xmlXPathFreeObject(retval);
403 return false;
406 Array ret = Array::Create();
407 for (int i = 0; i < result->nodeNr; ++i) {
408 xmlNodePtr nodeptr = result->nodeTab[i];
409 Object sub;
411 * Detect the case where the last selector is text(), simplexml
412 * always accesses the text() child by default, therefore we assign
413 * to the parent node.
415 switch (nodeptr->type) {
416 case XML_TEXT_NODE:
417 sub = create_element(m_doc, nodeptr->parent, String(), false);
418 break;
419 case XML_ELEMENT_NODE:
420 sub = create_element(m_doc, nodeptr, String(), false);
421 break;
422 case XML_ATTRIBUTE_NODE:
423 sub = create_element(m_doc, nodeptr->parent, String(), false);
424 break;
425 default:
426 break;
428 ret.append(sub);
431 xmlXPathFreeObject(retval);
432 return ret;
435 bool c_SimpleXMLElement::t_registerxpathnamespace(CStrRef prefix, CStrRef ns) {
436 if (m_node) {
437 if (!m_xpath) {
438 m_xpath = xmlXPathNewContext(m_node->doc);
440 return xmlXPathRegisterNs(m_xpath, (xmlChar *)prefix.data(),
441 (xmlChar *)ns.data()) == 0;
443 return false;
446 Variant c_SimpleXMLElement::t_asxml(CStrRef filename /* = "" */) {
447 if (!m_node) return false;
449 if (!filename.empty()) {
450 std::string translated = File::TranslatePath(filename).data();
452 if (m_node->parent && m_node->parent->type == XML_DOCUMENT_NODE) {
453 int bytes = xmlSaveFile(translated.c_str(), (xmlDocPtr)m_node->doc);
454 return bytes != -1;
457 xmlOutputBufferPtr outbuf =
458 xmlOutputBufferCreateFilename(translated.c_str(), NULL, 0);
459 if (outbuf == NULL) {
460 return false;
462 xmlNodeDumpOutput(outbuf, m_node->doc, m_node, 0, 0,
463 (char*)m_node->doc->encoding);
464 xmlOutputBufferClose(outbuf);
465 return true;
468 xmlChar *strval;
469 int strval_len;
470 if (m_node->parent && m_node->parent->type == XML_DOCUMENT_NODE) {
471 xmlDocDumpMemory(m_node->doc, &strval, &strval_len);
472 String ret((char *)strval, strval_len, CopyString);
473 xmlFree(strval);
474 return ret;
477 xmlOutputBufferPtr outbuf = xmlAllocOutputBuffer(NULL);
478 if (outbuf == NULL) {
479 return false;
481 xmlNodeDumpOutput(outbuf, m_node->doc, m_node, 0, 0,
482 (char*)m_node->doc->encoding);
483 xmlOutputBufferFlush(outbuf);
484 String ret((char *)xmlOutputBufferGetContent(outbuf),
485 xmlOutputBufferGetSize(outbuf), CopyString);
486 xmlOutputBufferClose(outbuf);
487 return ret;
490 Array c_SimpleXMLElement::t_getnamespaces(bool recursive /* = false */) {
491 Array ret = Array::Create();
492 if (m_node) {
493 if (m_node->type == XML_ELEMENT_NODE) {
494 add_namespaces(ret, m_node, recursive);
495 } else if (m_node->type == XML_ATTRIBUTE_NODE && m_node->ns) {
496 add_namespace_name(ret, m_node->ns);
499 return ret;
502 Array c_SimpleXMLElement::t_getdocnamespaces(bool recursive /* = false */) {
503 Array ret = Array::Create();
504 if (m_node) {
505 add_registered_namespaces(ret, xmlDocGetRootElement(m_node->doc),
506 recursive);
508 return ret;
511 Object c_SimpleXMLElement::t_children(CStrRef ns /* = "" */,
512 bool is_prefix /* = false */) {
513 if (m_is_attribute) {
514 return Object();
517 Object obj = create_object(m_doc.getTyped<XmlDocWrapper>()->
518 getClass(), Array(), false);
519 c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
520 elem->m_doc = m_doc;
521 elem->m_node = m_node;
522 elem->m_is_text = m_is_text;
523 elem->m_free_text = m_free_text;
524 elem->m_is_children = true;
525 if (ns.empty()) {
526 elem->m_children.assignRef(m_children);
527 } else {
528 Array props = Array::Create();
529 for (ArrayIter iter(m_children.toArray()); iter; ++iter) {
530 if (iter.second().isObject()) {
531 c_SimpleXMLElement *elem = iter.second().toObject().
532 getTyped<c_SimpleXMLElement>();
533 if (elem->m_node && match_ns(elem->m_node, ns, is_prefix)) {
534 props.set(iter.first(), iter.second());
536 } else {
537 Array subnodes;
538 for (ArrayIter iter2(iter.second().toArray()); iter2; ++iter2) {
539 c_SimpleXMLElement *elem = iter2.second().toObject().
540 getTyped<c_SimpleXMLElement>();
541 if (elem->m_node && match_ns(elem->m_node, ns, is_prefix)) {
542 subnodes.append(iter2.second());
545 if (!subnodes.empty()) {
546 if (subnodes.size() == 1) {
547 props.set(iter.first(), subnodes[0]);
548 } else {
549 props.set(iter.first(), subnodes);
554 elem->m_children = props;
556 return obj;
559 String c_SimpleXMLElement::t_getname() {
560 if (m_is_children) {
561 Variant first;
562 ArrayIter iter(m_children.toArray());
563 if (iter) {
564 return iter.first();
566 } else if (m_node) {
567 int namelen = xmlStrlen(m_node->name);
568 return String((char*)m_node->name, namelen, CopyString);
570 return String();
573 static const StaticString s_attributes("@attributes");
575 Object c_SimpleXMLElement::t_attributes(CStrRef ns /* = "" */,
576 bool is_prefix /* = false */) {
577 if (m_is_attribute) {
578 return Object();
581 Object obj = create_object(m_doc.getTyped<XmlDocWrapper>()->
582 getClass(), Array(), false);
583 c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
584 elem->m_doc = m_doc;
585 elem->m_node = m_node;
586 elem->m_is_attribute = true;
587 if (!m_attributes.toArray().empty()) {
588 if (!ns.empty()) {
589 elem->m_attributes = collect_attributes(m_node, ns, is_prefix);
590 } else {
591 elem->m_attributes.assignRef(m_attributes);
593 elem->m_children.set(s_attributes, elem->m_attributes);
595 return obj;
598 Variant c_SimpleXMLElement::t_addchild(CStrRef qname,
599 CStrRef value /* = null_string */,
600 CStrRef ns /* = null_string */) {
601 if (qname.empty()) {
602 raise_warning("Element name is required");
603 return uninit_null();
605 if (m_is_attribute) {
606 raise_warning("Cannot add element to attributes");
607 return uninit_null();
609 if (!m_node) {
610 raise_warning("Parent is not a permanent member of the XML tree");
611 return uninit_null();
614 xmlChar *prefix = NULL;
615 xmlChar *localname = xmlSplitQName2((xmlChar *)qname.data(), &prefix);
616 if (localname == NULL) {
617 localname = xmlStrdup((xmlChar *)qname.data());
620 xmlNsPtr nsptr = NULL;
621 xmlNodePtr newnode = xmlNewChild(m_node, NULL, localname,
622 (xmlChar *)value.data());
623 if (!ns.isNull()) {
624 if (ns.empty()) {
625 newnode->ns = NULL;
626 nsptr = xmlNewNs(newnode, (xmlChar *)ns.data(), prefix);
627 } else {
628 nsptr = xmlSearchNsByHref(m_node->doc, m_node, (xmlChar *)ns.data());
629 if (nsptr == NULL) {
630 nsptr = xmlNewNs(newnode, (xmlChar *)ns.data(), prefix);
632 newnode->ns = nsptr;
636 String newname((char*)localname, CopyString);
637 String newns((char*)prefix, CopyString);
638 xmlFree(localname);
639 if (prefix) {
640 xmlFree(prefix);
643 Object child = create_element(m_doc, newnode, newns, false);
644 if (m_children.toArray().exists(newname)) {
645 Variant &tmp = m_children.lvalAt(newname);
646 if (tmp.isArray()) {
647 tmp.append(child);
648 } else {
649 Array arr;
650 arr.append(tmp);
651 arr.append(child);
652 m_children.set(newname, arr);
654 } else {
655 m_children.set(newname, child);
657 return child;
660 void c_SimpleXMLElement::t_addattribute(CStrRef qname,
661 CStrRef value /* = null_string */,
662 CStrRef ns /* = null_string */) {
663 if (qname.empty()) {
664 raise_warning("Attribute name is required");
665 return;
668 if (m_node && m_node->type != XML_ELEMENT_NODE) {
669 m_node = m_node->parent;
671 if (!m_node) {
672 raise_warning("Unable to locate parent Element");
673 return;
676 xmlChar *prefix = NULL;
677 xmlChar *localname = xmlSplitQName2((xmlChar *)qname.data(), &prefix);
678 if (localname == NULL) {
679 localname = xmlStrdup((xmlChar *)qname.data());
682 xmlAttrPtr attrp = xmlHasNsProp(m_node, localname, (xmlChar *)ns.data());
683 if (attrp && attrp->type != XML_ATTRIBUTE_DECL) {
684 xmlFree(localname);
685 if (prefix != NULL) {
686 xmlFree(prefix);
688 raise_warning("Attribute already exists");
689 return;
692 xmlNsPtr nsptr = NULL;
693 if (!ns.isNull()) {
694 nsptr = xmlSearchNsByHref(m_node->doc, m_node, (xmlChar *)ns.data());
695 if (nsptr == NULL) {
696 nsptr = xmlNewNs(m_node, (xmlChar *)ns.data(), prefix);
700 attrp = xmlNewNsProp(m_node, nsptr, localname, (xmlChar *)value.data());
701 m_attributes.set(String((char*)localname, CopyString), value);
703 xmlFree(localname);
704 if (prefix != NULL) {
705 xmlFree(prefix);
709 String c_SimpleXMLElement::t___tostring() {
710 Variant prop;
711 ArrayIter iter(m_children.toArray());
712 if (iter) {
713 prop = iter.second();
714 if (prop.isString()) {
715 return prop.toString();
717 if (prop.isObject()) {
718 c_SimpleXMLElement *elem =
719 prop.toObject().getTyped<c_SimpleXMLElement>();
720 if (elem->m_is_text && elem->m_free_text) {
721 return prop.toString();
725 return "";
728 Variant c_SimpleXMLElement::t___get(Variant name) {
729 Variant ret = m_children[name];
730 if (ret.isArray()) {
731 ret = ret[0];
733 if (ret.isObject()) {
734 c_SimpleXMLElement *elem = ret.toObject().getTyped<c_SimpleXMLElement>();
735 Object obj = create_object(m_doc.getTyped<XmlDocWrapper>()->
736 getClass(), Array(), false);
737 c_SimpleXMLElement *e = obj.getTyped<c_SimpleXMLElement>();
738 e->m_doc = elem->m_doc;
739 e->m_node = elem->m_node;
740 e->m_children.assignRef(elem->m_children);
741 e->m_attributes.assignRef(elem->m_attributes);
742 e->m_is_text = elem->m_is_text;
743 e->m_is_property = true;
744 return obj;
746 if (ret.isNull()) {
747 return create_object(o_getClassName(), Array(), false);
749 return ret;
752 Variant c_SimpleXMLElement::t___unset(Variant name) {
753 if (m_node == NULL) return uninit_null();
755 Variant node;
756 if (m_is_attribute) {
757 node = m_attributes[name];
758 } else {
759 node = m_children[name];
762 if (node.isObject()) {
763 c_SimpleXMLElement *elem =
764 node.toObject().getTyped<c_SimpleXMLElement>();
765 if (elem->m_node) {
766 xmlUnlinkNode(elem->m_node);
768 } else if (node.isArray()) {
769 for (ArrayIter iter(node.toArray()); iter; ++iter) {
770 c_SimpleXMLElement *elem = iter.second().toObject().
771 getTyped<c_SimpleXMLElement>();
772 if (elem->m_node) {
773 xmlUnlinkNode(elem->m_node);
778 if (m_is_attribute) {
779 m_attributes.remove(name);
780 } else {
781 m_children.remove(name);
783 return uninit_null();
786 bool c_SimpleXMLElement::t___isset(Variant name) {
787 if (m_node) {
788 if (m_is_attribute) {
789 return m_attributes.toArray().exists(name);
790 } else {
791 return m_children.toArray().exists(name);
794 return false;
797 static void change_node_zval(xmlNodePtr node, CStrRef value) {
798 if (value.empty()) {
799 xmlNodeSetContentLen(node, (xmlChar *)"", 0);
800 } else {
801 xmlChar *buffer =
802 xmlEncodeEntitiesReentrant(node->doc, (xmlChar *)value.data());
803 int buffer_len = xmlStrlen(buffer);
804 if (buffer) {
805 xmlNodeSetContentLen(node, buffer, buffer_len);
806 xmlFree(buffer);
811 Variant c_SimpleXMLElement::t___set(Variant name, Variant value) {
812 if (m_node == NULL) return uninit_null();
814 String svalue = value.toString();
815 xmlChar *sv = svalue.empty() ? NULL : (xmlChar *)svalue.data();
816 String sname = name.toString();
818 Variant node;
819 if (m_is_attribute) {
820 node = m_attributes[name];
821 } else {
822 node = m_children[name];
825 xmlNodePtr newnode = NULL;
826 if (node.isObject()) {
827 c_SimpleXMLElement *elem =
828 node.toObject().getTyped<c_SimpleXMLElement>();
829 if (elem->m_node) {
830 xmlNodePtr tempnode;
831 while ((tempnode = (xmlNodePtr)elem->m_node->children)) {
832 xmlUnlinkNode(tempnode);
834 elem->m_children = Array::Create();
835 change_node_zval(elem->m_node, svalue);
836 newnode = elem->m_node;
838 } else if (node.isArray()) {
839 raise_warning("Cannot assign to an array of nodes "
840 "(duplicate subnodes or attr detected)");
841 } else if (m_is_attribute) {
842 if (name.isInteger()) {
843 raise_warning("Cannot change attribute number %" PRId64
844 " when only %zd attributes exist", name.toInt64(),
845 m_attributes.toArray().size());
846 } else {
847 newnode = (xmlNodePtr)xmlNewProp(m_node, (xmlChar *)sname.data(), sv);
849 } else {
850 if (sname.empty() || name.isInteger()) {
851 newnode = xmlNewTextChild(m_node->parent, m_node->ns,
852 m_node->name, sv);
853 } else {
854 newnode = xmlNewTextChild(m_node, m_node->ns,
855 (xmlChar *)sname.data(), sv);
859 if (newnode) {
860 String ns((char*)m_node->ns, CopyString);
861 Object child = create_element(m_doc, newnode, ns, false);
862 if (m_is_attribute) {
863 m_attributes.set(name, child);
864 m_children.set(s_attributes, m_attributes);
865 } else {
866 m_children.set(name, child);
870 return uninit_null();
873 bool c_SimpleXMLElement::o_toBooleanImpl() const noexcept {
874 return (m_node || getDynProps().size());
877 int64_t c_SimpleXMLElement::o_toInt64Impl() const noexcept {
878 Variant prop;
879 ArrayIter iter(m_children.toArray());
880 if (iter) {
881 prop = iter.second();
883 return prop.toString().toInt64();
886 double c_SimpleXMLElement::o_toDoubleImpl() const noexcept {
887 Variant prop;
888 ArrayIter iter(m_children.toArray());
889 if (iter) {
890 prop = iter.second();
892 return prop.toString().toDouble();
895 Array c_SimpleXMLElement::o_toArray() const {
896 if (m_attributes.toArray().empty()) {
897 return m_children.toArray();
899 Array ret;
900 ret.set(s_attributes, m_attributes);
901 ret += m_children;
902 return ret;
905 Variant c_SimpleXMLElement::t_getiterator() {
906 c_SimpleXMLElementIterator *iter = NEWOBJ(c_SimpleXMLElementIterator)();
907 iter->set_parent(this);
908 return Object(iter);
911 int64_t c_SimpleXMLElement::t_count() {
912 if (m_is_attribute) {
913 return m_attributes.toArray().size();
915 if (m_is_property) {
916 int64_t n = 0; Variant var(this);
917 for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) {
918 ++n;
920 return n;
922 return m_children.toArray().size();
925 ///////////////////////////////////////////////////////////////////////////////
926 // implementing ArrayAccess
928 bool c_SimpleXMLElement::t_offsetexists(CVarRef index) {
929 if (index.isInteger()) {
930 int64_t n = 0; int64_t nIndex = index.toInt64(); Variant var(this);
931 for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) {
932 if (n++ == nIndex) {
933 return true;
936 return false;
938 return m_attributes.toArray().exists(index);
941 Variant c_SimpleXMLElement::t_offsetget(CVarRef index) {
942 if (index.isInteger()) {
943 if (m_is_property) {
944 int64_t n = 0; int64_t nIndex = index.toInt64(); Variant var(this);
945 for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) {
946 if (n++ == nIndex) {
947 return iter.second();
950 return this;
952 return m_children[index];
954 return m_attributes[index];
957 void c_SimpleXMLElement::t_offsetset(CVarRef index, CVarRef newvalue) {
958 if (index.isInteger()) {
959 raise_error("unable to replace a SimpleXMLElement node");
960 return;
962 String name = index.toString();
963 if (name.empty()) {
964 raise_error("cannot create unnamed attribute");
965 return;
968 String sv = newvalue.toString();
969 if (m_attributes.toArray().exists(name)) {
970 t_offsetunset(index);
973 if (m_node == NULL || m_is_text) {
974 raise_error("cannot create attribute on this node");
975 return;
977 xmlNodePtr newnode = (xmlNodePtr)xmlNewProp(m_node, (xmlChar *)name.data(),
978 (xmlChar*)sv.data());
979 if (newnode) {
980 m_attributes.set(name, sv);
984 void c_SimpleXMLElement::t_offsetunset(CVarRef index) {
985 if (index.isInteger()) {
986 raise_error("unable to remove a SimpleXMLElement node");
987 return;
990 String name = index.toString();
991 if (name.empty()) {
992 raise_error("cannot remove unnamed attribute");
993 return;
996 if (m_attributes.toArray().exists(name) && m_node) {
997 for (xmlAttrPtr attr = m_node->properties; attr; attr = attr->next) {
998 if (String((char*)attr->name, xmlStrlen(attr->name), AttachLiteral) ==
999 name) {
1000 xmlUnlinkNode((xmlNodePtr)attr);
1001 break;
1006 m_attributes.remove(name);
1009 ///////////////////////////////////////////////////////////////////////////////
1011 c_SimpleXMLElementIterator::c_SimpleXMLElementIterator(Class* cb) :
1012 ExtObjectData(cb), m_parent(), m_iter1(NULL), m_iter2(NULL) {
1015 c_SimpleXMLElementIterator::~c_SimpleXMLElementIterator() {
1016 c_SimpleXMLElementIterator::sweep();
1019 void c_SimpleXMLElementIterator::sweep() {
1020 delete m_iter1;
1021 delete m_iter2;
1024 void c_SimpleXMLElementIterator::set_parent(c_SimpleXMLElement* parent) {
1025 m_parent = parent;
1026 reset_iterator();
1029 void c_SimpleXMLElementIterator::reset_iterator() {
1030 assert(m_parent.get() != NULL);
1031 delete m_iter1; m_iter1 = NULL;
1032 delete m_iter2; m_iter2 = NULL;
1034 if (m_parent->m_is_attribute) {
1035 m_iter1 = new ArrayIter(m_parent->m_attributes.toArray());
1036 return;
1039 // When I'm a node like $node->name, we iterate through all my siblings with
1040 // same name of mine.
1041 if (m_parent->m_is_property) {
1042 String name = m_parent->t_getname();
1043 Object obj = create_element(m_parent->m_doc, m_parent->m_node->parent,
1044 "", false);
1045 m_parent = obj.getTyped<c_SimpleXMLElement>();
1046 Variant children = m_parent->m_children[name];
1047 m_parent->m_children = CREATE_MAP1(name, children);
1048 // fall through
1051 if (m_parent->m_is_text) {
1052 return;
1055 if (m_parent->m_children.toArray().size() == 1) {
1056 ArrayIter iter(m_parent->m_children.toArray());
1057 if (iter.second().isObject()) {
1058 c_SimpleXMLElement *elem = iter.second().toObject().
1059 getTyped<c_SimpleXMLElement>();
1060 if (elem->m_is_text && elem->m_free_text) {
1061 return;
1066 m_iter1 = new ArrayIter(m_parent->m_children.toArray());
1067 if (!m_iter1->end() && m_iter1->second().isArray()) {
1068 m_iter2 = new ArrayIter(m_iter1->second().toArray());
1072 void c_SimpleXMLElementIterator::t___construct() {
1075 Variant c_SimpleXMLElementIterator::t_current() {
1076 if (m_iter1 == NULL) return uninit_null();
1077 if (m_parent->m_is_attribute) {
1078 return m_iter1->second();
1081 ArrayIter *iter = m_iter2;
1082 if (iter == NULL && m_iter1->second().isObject()) {
1083 iter = m_iter1;
1086 if (iter) {
1087 return iter->second();
1090 assert(false);
1091 return uninit_null();
1094 Variant c_SimpleXMLElementIterator::t_key() {
1095 if (m_iter1) {
1096 return m_iter1->first();
1098 return uninit_null();
1101 Variant c_SimpleXMLElementIterator::t_next() {
1102 if (m_iter1 == NULL) return uninit_null();
1103 if (m_parent->m_is_attribute) {
1104 m_iter1->next();
1105 return uninit_null();
1108 if (m_iter2) {
1109 m_iter2->next();
1110 if (!m_iter2->end()) {
1111 return uninit_null();
1113 delete m_iter2; m_iter2 = NULL;
1115 m_iter1->next();
1116 while (!m_iter1->end()) {
1117 if (m_iter1->second().isArray()) {
1118 m_iter2 = new ArrayIter(m_iter1->second().toArray());
1119 break;
1121 if (m_iter1->second().isObject()) {
1122 break;
1124 m_iter1->next();
1126 return uninit_null();
1129 Variant c_SimpleXMLElementIterator::t_rewind() {
1130 reset_iterator();
1131 return uninit_null();
1134 Variant c_SimpleXMLElementIterator::t_valid() {
1135 return m_iter1 && !m_iter1->end();
1138 ///////////////////////////////////////////////////////////////////////////////
1139 // LibXMLError
1141 c_LibXMLError::c_LibXMLError(Class* cb) :
1142 ExtObjectData(cb) {
1144 c_LibXMLError::~c_LibXMLError() {
1146 void c_LibXMLError::t___construct() {
1149 ///////////////////////////////////////////////////////////////////////////////
1150 // libxml
1152 class xmlErrorVec : public std::vector<xmlError> {
1153 public:
1154 ~xmlErrorVec() {
1155 reset();
1158 void reset() {
1159 for (unsigned int i = 0; i < size(); i++) {
1160 xmlResetError(&at(i));
1162 clear();
1166 class LibXmlErrors : public RequestEventHandler {
1167 public:
1168 virtual void requestInit() {
1169 m_use_error = false;
1170 m_errors.reset();
1171 xmlParserInputBufferCreateFilenameDefault(NULL);
1173 virtual void requestShutdown() {
1174 m_use_error = false;
1175 m_errors.reset();
1178 bool m_use_error;
1179 xmlErrorVec m_errors;
1182 IMPLEMENT_STATIC_REQUEST_LOCAL(LibXmlErrors, s_libxml_errors);
1183 bool libxml_use_internal_error() {
1184 return s_libxml_errors->m_use_error;
1186 extern void libxml_add_error(const std::string &msg) {
1187 xmlErrorVec *error_list = &s_libxml_errors->m_errors;
1189 error_list->resize(error_list->size() + 1);
1190 xmlError &error_copy = error_list->back();
1191 memset(&error_copy, 0, sizeof(xmlError));
1193 error_copy.domain = 0;
1194 error_copy.code = XML_ERR_INTERNAL_ERROR;
1195 error_copy.level = XML_ERR_ERROR;
1196 error_copy.line = 0;
1197 error_copy.node = NULL;
1198 error_copy.int1 = 0;
1199 error_copy.int2 = 0;
1200 error_copy.ctxt = NULL;
1201 error_copy.message = (char*)xmlStrdup((const xmlChar*)msg.c_str());
1202 error_copy.file = NULL;
1203 error_copy.str1 = NULL;
1204 error_copy.str2 = NULL;
1205 error_copy.str3 = NULL;
1208 static void libxml_error_handler(void *userData, xmlErrorPtr error) {
1209 xmlErrorVec *error_list = &s_libxml_errors->m_errors;
1211 error_list->resize(error_list->size() + 1);
1212 xmlError &error_copy = error_list->back();
1213 memset(&error_copy, 0, sizeof(xmlError));
1215 if (error) {
1216 xmlCopyError(error, &error_copy);
1217 } else {
1218 error_copy.code = XML_ERR_INTERNAL_ERROR;
1219 error_copy.level = XML_ERR_ERROR;
1223 static const StaticString s_level("level");
1224 static const StaticString s_code("code");
1225 static const StaticString s_column("column");
1226 static const StaticString s_message("message");
1227 static const StaticString s_file("file");
1228 static const StaticString s_line("line");
1230 static Object create_libxmlerror(xmlError &error) {
1231 Object ret(NEWOBJ(c_LibXMLError)());
1232 ret->o_set(s_level, error.level);
1233 ret->o_set(s_code, error.code);
1234 ret->o_set(s_column, error.int2);
1235 ret->o_set(s_message, String(error.message, CopyString));
1236 ret->o_set(s_file, String(error.file, CopyString));
1237 ret->o_set(s_line, error.line);
1238 return ret;
1241 Variant f_libxml_get_errors() {
1242 xmlErrorVec *error_list = &s_libxml_errors->m_errors;
1243 Array ret = Array::Create();
1244 for (unsigned int i = 0; i < error_list->size(); i++) {
1245 ret.append(create_libxmlerror(error_list->at(i)));
1247 return ret;
1250 Variant f_libxml_get_last_error() {
1251 xmlErrorPtr error = xmlGetLastError();
1252 if (error) {
1253 return create_libxmlerror(*error);
1255 return false;
1258 void f_libxml_clear_errors() {
1259 xmlResetLastError();
1260 s_libxml_errors->m_errors.reset();
1263 bool f_libxml_use_internal_errors(CVarRef use_errors /* = null_variant */) {
1264 bool ret = (xmlStructuredError == libxml_error_handler);
1265 if (!use_errors.isNull()) {
1266 if (!use_errors.toBoolean()) {
1267 xmlSetStructuredErrorFunc(NULL, NULL);
1268 s_libxml_errors->m_use_error = false;
1269 s_libxml_errors->m_errors.reset();
1270 } else {
1271 xmlSetStructuredErrorFunc(NULL, libxml_error_handler);
1272 s_libxml_errors->m_use_error = true;
1275 return ret;
1278 void f_libxml_set_streams_context(CResRef streams_context) {
1279 throw NotImplementedException(__func__);
1282 static xmlParserInputBufferPtr
1283 hphp_libxml_input_buffer_noload(const char *URI, xmlCharEncoding enc) {
1284 return NULL;
1287 bool f_libxml_disable_entity_loader(bool disable /* = true */) {
1288 xmlParserInputBufferCreateFilenameFunc old;
1290 if (disable) {
1291 old = xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer_noload);
1292 } else {
1293 old = xmlParserInputBufferCreateFilenameDefault(NULL);
1295 return (old == hphp_libxml_input_buffer_noload);
1298 ///////////////////////////////////////////////////////////////////////////////