/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | | Copyright (c) 1997-2010 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/ext/ext_simplexml.h" #include "hphp/runtime/ext/ext_file.h" #include "hphp/runtime/ext/ext_class.h" #include "hphp/runtime/ext/ext_domdocument.h" #include "hphp/runtime/base/class_info.h" #include "hphp/runtime/base/util/request_local.h" #include "hphp/system/systemlib.h" #ifndef LIBXML2_NEW_BUFFER # define xmlOutputBufferGetSize(buf) ((buf)->buffer->use) # define xmlOutputBufferGetContent(buf) ((buf)->buffer->content) #endif namespace HPHP { IMPLEMENT_DEFAULT_EXTENSION(SimpleXML); /////////////////////////////////////////////////////////////////////////////// // This is to make sure each node holds one reference of m_doc, so not to let // it go out of scope. class XmlDocWrapper : public SweepableResourceData { public: DECLARE_OBJECT_ALLOCATION_NO_SWEEP(XmlDocWrapper) static StaticString s_class_name; // overriding ResourceData virtual CStrRef o_getClassNameHook() const { return s_class_name; } XmlDocWrapper(xmlDocPtr doc, CStrRef cls, Object domNode = nullptr) : m_doc(doc), m_cls(cls), m_domNode(domNode) { if (!domNode.isNull()) { DEBUG_ONLY c_DOMNode *domnode = domNode.getTyped(); assert(!domnode || domnode->m_node == (xmlNodePtr) doc); } } CStrRef getClass() { return m_cls; } void sweep() { // if m_domNode isn't null, then he owns the m_doc. Otherwise, I own it if (m_doc && m_domNode.isNull()) { xmlFreeDoc(m_doc); } } ~XmlDocWrapper() { XmlDocWrapper::sweep(); } private: xmlDocPtr m_doc; String m_cls; // Hold onto the original owner of the doc so it doesn't get free()d. Object m_domNode; }; IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(XmlDocWrapper) StaticString XmlDocWrapper::s_class_name("xmlDoc"); /////////////////////////////////////////////////////////////////////////////// // helpers static inline bool match_ns(xmlNodePtr node, CStrRef ns, bool is_prefix) { if (ns.empty()) { return true; } if (node->ns == NULL || node->ns->prefix == NULL) { return false; } if (node->ns && !xmlStrcmp(is_prefix ? node->ns->prefix : node->ns->href, (const xmlChar *)ns.data())) { return true; } return false; } static String node_list_to_string(xmlDocPtr doc, xmlNodePtr list) { xmlChar *tmp = xmlNodeListGetString(doc, list, 1); String res((char*) tmp, CopyString); xmlFree(tmp); return res; } static Array collect_attributes(xmlNodePtr node, CStrRef ns, bool is_prefix) { assert(node); Array attributes = Array::Create(); if (node->type != XML_ENTITY_DECL) { for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { if (match_ns((xmlNodePtr)attr, ns, is_prefix)) { String n = String((char*)attr->name, xmlStrlen(attr->name), CopyString); attributes.set(n, node_list_to_string(node->doc, attr->children)); } } } return attributes; } static void add_property(Array &properties, xmlNodePtr node, Object value) { const char *name = (char *)node->name; if (name) { int namelen = xmlStrlen(node->name); String sname(name, namelen, CopyString); if (properties.exists(sname)) { Variant &existing = properties.lval(sname); if (existing.is(KindOfArray)) { existing.append(value); } else { Array newdata; newdata.append(existing); newdata.append(value); properties.set(sname, newdata); } } else { properties.set(sname, value); } } } static Object create_text(CObjRef doc, xmlNodePtr node, CStrRef value, CStrRef ns, bool is_prefix, bool free_text) { Object obj = create_object(doc.getTyped()-> getClass(), Array(), false); c_SimpleXMLElement *elem = obj.getTyped(); elem->m_doc = doc; elem->m_node = node->parent; // assign to parent, not node elem->m_children.set(0, value); elem->m_is_text = true; elem->m_free_text = free_text; elem->m_attributes = collect_attributes(node->parent, ns, is_prefix); return obj; } static Array create_children(CObjRef doc, xmlNodePtr root, CStrRef ns, bool is_prefix); static Object create_element(CObjRef doc, xmlNodePtr node, CStrRef ns, bool is_prefix) { Object obj = create_object(doc.getTyped()-> getClass(), Array(), false); c_SimpleXMLElement *elem = obj.getTyped(); elem->m_doc = doc; elem->m_node = node; if (node) { elem->m_children = create_children(doc, node, ns, is_prefix); elem->m_attributes = collect_attributes(node, ns, is_prefix); } return obj; } static Array create_children(CObjRef doc, xmlNodePtr root, CStrRef ns, bool is_prefix) { Array properties = Array::Create(); for (xmlNodePtr node = root->children; node; node = node->next) { if (node->children || node->prev || node->next) { if (node->type == XML_TEXT_NODE) { // bad node from parser, ignoring it... continue; } } else { if (node->type == XML_TEXT_NODE) { if (node->content && *node->content) { add_property (properties, root, create_text(doc, node, node_list_to_string(root->doc, node), ns, is_prefix, true)); } continue; } } if (node->type != XML_ELEMENT_NODE || match_ns(node, ns, is_prefix)) { xmlNodePtr child = node->children; Object sub; if (child && child->type == XML_TEXT_NODE && !xmlIsBlankNode(child)) { sub = create_text(doc, child, node_list_to_string(root->doc, child), ns, is_prefix, false); } else { sub = create_element(doc, node, ns, is_prefix); } add_property(properties, node, sub); } } return properties; } static inline void add_namespace_name(Array &out, xmlNsPtr ns) { String prefix = ns->prefix ? String((const char*)ns->prefix) : String(empty_string); if (!out.exists(prefix)) { out.set(prefix, String((char*)ns->href, CopyString)); } } static void add_namespaces(Array &out, xmlNodePtr node, bool recursive) { if (node->ns) { add_namespace_name(out, node->ns); } for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { if (attr->ns) { add_namespace_name(out, attr->ns); } } if (recursive) { for (node = node->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { add_namespaces(out, node, true); } } } } static void add_registered_namespaces(Array &out, xmlNodePtr node, bool recursive) { if (node->type == XML_ELEMENT_NODE) { for (xmlNsPtr ns = node->nsDef; ns; ns = ns->next) { add_namespace_name(out, ns); } if (recursive) { for (node = node->children; node; node = node->next) { add_registered_namespaces(out, node, true); } } } } /////////////////////////////////////////////////////////////////////////////// // simplexml static StaticString s_SimpleXMLElement("SimpleXMLElement"); Variant f_simplexml_import_dom(CObjRef node, CStrRef class_name /* = "SimpleXMLElement" */) { c_DOMNode *domnode = node.getTyped(); xmlNodePtr nodep = domnode->m_node; if (nodep) { if (nodep->doc == nullptr) { raise_warning("Imported Node must have associated Document"); return uninit_null(); } if (nodep->type == XML_DOCUMENT_NODE || nodep->type == XML_HTML_DOCUMENT_NODE) { nodep = xmlDocGetRootElement((xmlDocPtr) nodep); } } if (nodep && nodep->type == XML_ELEMENT_NODE) { Object obj = Object(NEWOBJ(XmlDocWrapper)(nodep->doc, class_name, node)); return create_element(obj, nodep, String(), false); } else { raise_warning("Invalid Nodetype to import"); return uninit_null(); } } Variant f_simplexml_load_string(CStrRef data, CStrRef class_name /* = "SimpleXMLElement" */, int64_t options /* = 0 */, CStrRef ns /* = "" */, bool is_prefix /* = false */) { if (!f_class_exists(class_name)) { throw_invalid_argument("class %s does not exist", class_name.data()); return uninit_null(); } Class* cls; if (!class_name.empty()) { cls = Unit::lookupClass(class_name.get()); if (!cls) { throw_invalid_argument("class not found: %s", class_name.data()); return uninit_null(); } if (!cls->classof(c_SimpleXMLElement::s_cls)) { throw_invalid_argument( "simplexml_load_string() expects parameter 2 to be a class name " "derived from SimpleXMLElement, '%s' given", class_name.data()); return uninit_null(); } } else { cls = c_SimpleXMLElement::s_cls; } xmlDocPtr doc = xmlReadMemory(data.data(), data.size(), NULL, NULL, options); xmlNodePtr root = xmlDocGetRootElement(doc); if (!doc) { return false; } return create_element(Object(NEWOBJ(XmlDocWrapper)(doc, cls->nameRef())), root, ns, is_prefix); } Variant f_simplexml_load_file(CStrRef filename, CStrRef class_name /* = "SimpleXMLElement" */, int64_t options /* = 0 */, CStrRef ns /* = "" */, bool is_prefix /* = false */) { String str = f_file_get_contents(filename); return f_simplexml_load_string(str, class_name, options, ns, is_prefix); } /////////////////////////////////////////////////////////////////////////////// // SimpleXMLElement c_SimpleXMLElement::c_SimpleXMLElement(Class* cb) : ExtObjectDataFlags(cb), m_node(NULL), m_is_text(false), m_free_text(false), m_is_attribute(false), m_is_children(false), m_is_property(false), m_xpath(NULL) { m_children = Array::Create(); } c_SimpleXMLElement::~c_SimpleXMLElement() { c_SimpleXMLElement::sweep(); } void c_SimpleXMLElement::sweep() { if (m_xpath) { xmlXPathFreeContext(m_xpath); } } void c_SimpleXMLElement::t___construct(CStrRef data, int64_t options /* = 0 */, bool data_is_url /* = false */, CStrRef ns /* = "" */, bool is_prefix /* = false */) { String xml = data; if (data_is_url) { Variant ret = f_file_get_contents(data); if (same(ret, false)) { raise_warning("Unable to retrieve XML content from %s", data.data()); return; } xml = ret.toString(); } xmlDocPtr doc = xmlReadMemory(xml.data(), xml.size(), NULL, NULL, options); if (doc) { m_doc = Object(NEWOBJ(XmlDocWrapper)(doc, s_SimpleXMLElement)); m_node = xmlDocGetRootElement(doc); if (m_node) { m_children = create_children(m_doc, m_node, ns, is_prefix); m_attributes = collect_attributes(m_node, ns, is_prefix); } } else { throw Object(SystemLib::AllocExceptionObject( "String could not be parsed as XML")); } } Variant c_SimpleXMLElement::t_xpath(CStrRef path) { if (m_is_attribute || !m_node) { return uninit_null(); } xmlDocPtr doc = m_node->doc; int nsnbr = 0; xmlNsPtr *ns = xmlGetNsList(doc, m_node); if (ns != NULL) { while (ns[nsnbr] != NULL) { nsnbr++; } } if (m_xpath == NULL) { m_xpath = xmlXPathNewContext(doc); } m_xpath->node = m_node; m_xpath->namespaces = ns; m_xpath->nsNr = nsnbr; xmlXPathObjectPtr retval = xmlXPathEval((xmlChar *)path.data(), m_xpath); if (ns != NULL) { xmlFree(ns); m_xpath->namespaces = NULL; m_xpath->nsNr = 0; } if (!retval) { return false; } xmlNodeSetPtr result = retval->nodesetval; if (!result) { xmlXPathFreeObject(retval); return false; } Array ret = Array::Create(); for (int i = 0; i < result->nodeNr; ++i) { xmlNodePtr nodeptr = result->nodeTab[i]; Object sub; /** * Detect the case where the last selector is text(), simplexml * always accesses the text() child by default, therefore we assign * to the parent node. */ switch (nodeptr->type) { case XML_TEXT_NODE: sub = create_element(m_doc, nodeptr->parent, String(), false); break; case XML_ELEMENT_NODE: sub = create_element(m_doc, nodeptr, String(), false); break; case XML_ATTRIBUTE_NODE: sub = create_element(m_doc, nodeptr->parent, String(), false); break; default: break; } ret.append(sub); } xmlXPathFreeObject(retval); return ret; } bool c_SimpleXMLElement::t_registerxpathnamespace(CStrRef prefix, CStrRef ns) { if (m_node) { if (!m_xpath) { m_xpath = xmlXPathNewContext(m_node->doc); } return xmlXPathRegisterNs(m_xpath, (xmlChar *)prefix.data(), (xmlChar *)ns.data()) == 0; } return false; } Variant c_SimpleXMLElement::t_asxml(CStrRef filename /* = "" */) { if (!m_node) return false; if (!filename.empty()) { std::string translated = File::TranslatePath(filename).data(); if (m_node->parent && m_node->parent->type == XML_DOCUMENT_NODE) { int bytes = xmlSaveFile(translated.c_str(), (xmlDocPtr)m_node->doc); return bytes != -1; } xmlOutputBufferPtr outbuf = xmlOutputBufferCreateFilename(translated.c_str(), NULL, 0); if (outbuf == NULL) { return false; } xmlNodeDumpOutput(outbuf, m_node->doc, m_node, 0, 0, (char*)m_node->doc->encoding); xmlOutputBufferClose(outbuf); return true; } xmlChar *strval; int strval_len; if (m_node->parent && m_node->parent->type == XML_DOCUMENT_NODE) { xmlDocDumpMemory(m_node->doc, &strval, &strval_len); String ret((char *)strval, strval_len, CopyString); xmlFree(strval); return ret; } xmlOutputBufferPtr outbuf = xmlAllocOutputBuffer(NULL); if (outbuf == NULL) { return false; } xmlNodeDumpOutput(outbuf, m_node->doc, m_node, 0, 0, (char*)m_node->doc->encoding); xmlOutputBufferFlush(outbuf); String ret((char *)xmlOutputBufferGetContent(outbuf), xmlOutputBufferGetSize(outbuf), CopyString); xmlOutputBufferClose(outbuf); return ret; } Array c_SimpleXMLElement::t_getnamespaces(bool recursive /* = false */) { Array ret = Array::Create(); if (m_node) { if (m_node->type == XML_ELEMENT_NODE) { add_namespaces(ret, m_node, recursive); } else if (m_node->type == XML_ATTRIBUTE_NODE && m_node->ns) { add_namespace_name(ret, m_node->ns); } } return ret; } Array c_SimpleXMLElement::t_getdocnamespaces(bool recursive /* = false */) { Array ret = Array::Create(); if (m_node) { add_registered_namespaces(ret, xmlDocGetRootElement(m_node->doc), recursive); } return ret; } Object c_SimpleXMLElement::t_children(CStrRef ns /* = "" */, bool is_prefix /* = false */) { if (m_is_attribute) { return Object(); } Object obj = create_object(m_doc.getTyped()-> getClass(), Array(), false); c_SimpleXMLElement *elem = obj.getTyped(); elem->m_doc = m_doc; elem->m_node = m_node; elem->m_is_text = m_is_text; elem->m_free_text = m_free_text; elem->m_is_children = true; if (ns.empty()) { elem->m_children.assignRef(m_children); } else { Array props = Array::Create(); for (ArrayIter iter(m_children.toArray()); iter; ++iter) { if (iter.second().isObject()) { c_SimpleXMLElement *elem = iter.second().toObject(). getTyped(); if (elem->m_node && match_ns(elem->m_node, ns, is_prefix)) { props.set(iter.first(), iter.second()); } } else { Array subnodes; for (ArrayIter iter2(iter.second().toArray()); iter2; ++iter2) { c_SimpleXMLElement *elem = iter2.second().toObject(). getTyped(); if (elem->m_node && match_ns(elem->m_node, ns, is_prefix)) { subnodes.append(iter2.second()); } } if (!subnodes.empty()) { if (subnodes.size() == 1) { props.set(iter.first(), subnodes[0]); } else { props.set(iter.first(), subnodes); } } } } elem->m_children = props; } return obj; } String c_SimpleXMLElement::t_getname() { if (m_is_children) { Variant first; ArrayIter iter(m_children.toArray()); if (iter) { return iter.first(); } } else if (m_node) { int namelen = xmlStrlen(m_node->name); return String((char*)m_node->name, namelen, CopyString); } return String(); } static const StaticString s_attributes("@attributes"); Object c_SimpleXMLElement::t_attributes(CStrRef ns /* = "" */, bool is_prefix /* = false */) { if (m_is_attribute) { return Object(); } Object obj = create_object(m_doc.getTyped()-> getClass(), Array(), false); c_SimpleXMLElement *elem = obj.getTyped(); elem->m_doc = m_doc; elem->m_node = m_node; elem->m_is_attribute = true; if (!m_attributes.toArray().empty()) { if (!ns.empty()) { elem->m_attributes = collect_attributes(m_node, ns, is_prefix); } else { elem->m_attributes.assignRef(m_attributes); } elem->m_children.set(s_attributes, elem->m_attributes); } return obj; } Variant c_SimpleXMLElement::t_addchild(CStrRef qname, CStrRef value /* = null_string */, CStrRef ns /* = null_string */) { if (qname.empty()) { raise_warning("Element name is required"); return uninit_null(); } if (m_is_attribute) { raise_warning("Cannot add element to attributes"); return uninit_null(); } if (!m_node) { raise_warning("Parent is not a permanent member of the XML tree"); return uninit_null(); } xmlChar *prefix = NULL; xmlChar *localname = xmlSplitQName2((xmlChar *)qname.data(), &prefix); if (localname == NULL) { localname = xmlStrdup((xmlChar *)qname.data()); } xmlNsPtr nsptr = NULL; xmlNodePtr newnode = xmlNewChild(m_node, NULL, localname, (xmlChar *)value.data()); if (!ns.isNull()) { if (ns.empty()) { newnode->ns = NULL; nsptr = xmlNewNs(newnode, (xmlChar *)ns.data(), prefix); } else { nsptr = xmlSearchNsByHref(m_node->doc, m_node, (xmlChar *)ns.data()); if (nsptr == NULL) { nsptr = xmlNewNs(newnode, (xmlChar *)ns.data(), prefix); } newnode->ns = nsptr; } } String newname((char*)localname, CopyString); String newns((char*)prefix, CopyString); xmlFree(localname); if (prefix) { xmlFree(prefix); } Object child = create_element(m_doc, newnode, newns, false); if (m_children.toArray().exists(newname)) { Variant &tmp = m_children.lvalAt(newname); if (tmp.isArray()) { tmp.append(child); } else { Array arr; arr.append(tmp); arr.append(child); m_children.set(newname, arr); } } else { m_children.set(newname, child); } return child; } void c_SimpleXMLElement::t_addattribute(CStrRef qname, CStrRef value /* = null_string */, CStrRef ns /* = null_string */) { if (qname.empty()) { raise_warning("Attribute name is required"); return; } if (m_node && m_node->type != XML_ELEMENT_NODE) { m_node = m_node->parent; } if (!m_node) { raise_warning("Unable to locate parent Element"); return; } xmlChar *prefix = NULL; xmlChar *localname = xmlSplitQName2((xmlChar *)qname.data(), &prefix); if (localname == NULL) { localname = xmlStrdup((xmlChar *)qname.data()); } xmlAttrPtr attrp = xmlHasNsProp(m_node, localname, (xmlChar *)ns.data()); if (attrp && attrp->type != XML_ATTRIBUTE_DECL) { xmlFree(localname); if (prefix != NULL) { xmlFree(prefix); } raise_warning("Attribute already exists"); return; } xmlNsPtr nsptr = NULL; if (!ns.isNull()) { nsptr = xmlSearchNsByHref(m_node->doc, m_node, (xmlChar *)ns.data()); if (nsptr == NULL) { nsptr = xmlNewNs(m_node, (xmlChar *)ns.data(), prefix); } } attrp = xmlNewNsProp(m_node, nsptr, localname, (xmlChar *)value.data()); m_attributes.set(String((char*)localname, CopyString), value); xmlFree(localname); if (prefix != NULL) { xmlFree(prefix); } } String c_SimpleXMLElement::t___tostring() { Variant prop; ArrayIter iter(m_children.toArray()); if (iter) { prop = iter.second(); if (prop.isString()) { return prop.toString(); } if (prop.isObject()) { c_SimpleXMLElement *elem = prop.toObject().getTyped(); if (elem->m_is_text && elem->m_free_text) { return prop.toString(); } } } return ""; } Variant c_SimpleXMLElement::t___get(Variant name) { Variant ret = m_children[name]; if (ret.isArray()) { ret = ret[0]; } if (ret.isObject()) { c_SimpleXMLElement *elem = ret.toObject().getTyped(); Object obj = create_object(m_doc.getTyped()-> getClass(), Array(), false); c_SimpleXMLElement *e = obj.getTyped(); e->m_doc = elem->m_doc; e->m_node = elem->m_node; e->m_children.assignRef(elem->m_children); e->m_attributes.assignRef(elem->m_attributes); e->m_is_text = elem->m_is_text; e->m_is_property = true; return obj; } if (ret.isNull()) { return create_object(o_getClassName(), Array(), false); } return ret; } Variant c_SimpleXMLElement::t___unset(Variant name) { if (m_node == NULL) return uninit_null(); Variant node; if (m_is_attribute) { node = m_attributes[name]; } else { node = m_children[name]; } if (node.isObject()) { c_SimpleXMLElement *elem = node.toObject().getTyped(); if (elem->m_node) { xmlUnlinkNode(elem->m_node); } } else if (node.isArray()) { for (ArrayIter iter(node.toArray()); iter; ++iter) { c_SimpleXMLElement *elem = iter.second().toObject(). getTyped(); if (elem->m_node) { xmlUnlinkNode(elem->m_node); } } } if (m_is_attribute) { m_attributes.remove(name); } else { m_children.remove(name); } return uninit_null(); } bool c_SimpleXMLElement::t___isset(Variant name) { if (m_node) { if (m_is_attribute) { return m_attributes.toArray().exists(name); } else { return m_children.toArray().exists(name); } } return false; } static void change_node_zval(xmlNodePtr node, CStrRef value) { if (value.empty()) { xmlNodeSetContentLen(node, (xmlChar *)"", 0); } else { xmlChar *buffer = xmlEncodeEntitiesReentrant(node->doc, (xmlChar *)value.data()); int buffer_len = xmlStrlen(buffer); if (buffer) { xmlNodeSetContentLen(node, buffer, buffer_len); xmlFree(buffer); } } } Variant c_SimpleXMLElement::t___set(Variant name, Variant value) { if (m_node == NULL) return uninit_null(); String svalue = value.toString(); xmlChar *sv = svalue.empty() ? NULL : (xmlChar *)svalue.data(); String sname = name.toString(); Variant node; if (m_is_attribute) { node = m_attributes[name]; } else { node = m_children[name]; } xmlNodePtr newnode = NULL; if (node.isObject()) { c_SimpleXMLElement *elem = node.toObject().getTyped(); if (elem->m_node) { xmlNodePtr tempnode; while ((tempnode = (xmlNodePtr)elem->m_node->children)) { xmlUnlinkNode(tempnode); } elem->m_children = Array::Create(); change_node_zval(elem->m_node, svalue); newnode = elem->m_node; } } else if (node.isArray()) { raise_warning("Cannot assign to an array of nodes " "(duplicate subnodes or attr detected)"); } else if (m_is_attribute) { if (name.isInteger()) { raise_warning("Cannot change attribute number %" PRId64 " when only %" PRId64 " attributes exist", name.toInt64(), m_attributes.toArray().size()); } else { newnode = (xmlNodePtr)xmlNewProp(m_node, (xmlChar *)sname.data(), sv); } } else { if (sname.empty() || name.isInteger()) { newnode = xmlNewTextChild(m_node->parent, m_node->ns, m_node->name, sv); } else { newnode = xmlNewTextChild(m_node, m_node->ns, (xmlChar *)sname.data(), sv); } } if (newnode) { String ns((char*)m_node->ns, CopyString); Object child = create_element(m_doc, newnode, ns, false); if (m_is_attribute) { m_attributes.set(name, child); m_children.set(s_attributes, m_attributes); } else { m_children.set(name, child); } } return uninit_null(); } bool c_SimpleXMLElement::o_toBooleanImpl() const noexcept { return m_node != NULL || getProperties().size(); } int64_t c_SimpleXMLElement::o_toInt64Impl() const noexcept { Variant prop; ArrayIter iter(m_children.toArray()); if (iter) { prop = iter.second(); } return prop.toString().toInt64(); } double c_SimpleXMLElement::o_toDoubleImpl() const noexcept { Variant prop; ArrayIter iter(m_children.toArray()); if (iter) { prop = iter.second(); } return prop.toString().toDouble(); } Array c_SimpleXMLElement::o_toArray() const { if (m_attributes.toArray().empty()) { return m_children.toArray(); } Array ret; ret.set(s_attributes, m_attributes); ret += m_children; return ret; } Variant c_SimpleXMLElement::t_getiterator() { c_SimpleXMLElementIterator *iter = NEWOBJ(c_SimpleXMLElementIterator)(); iter->set_parent(this); return Object(iter); } int64_t c_SimpleXMLElement::t_count() { if (m_is_attribute) { return m_attributes.toArray().size(); } if (m_is_property) { int64_t n = 0; Variant var(this); for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) { ++n; } return n; } return m_children.toArray().size(); } /////////////////////////////////////////////////////////////////////////////// // implementing ArrayAccess bool c_SimpleXMLElement::t_offsetexists(CVarRef index) { if (index.isInteger()) { int64_t n = 0; int64_t nIndex = index.toInt64(); Variant var(this); for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) { if (n++ == nIndex) { return true; } } return false; } return m_attributes.toArray().exists(index); } Variant c_SimpleXMLElement::t_offsetget(CVarRef index) { if (index.isInteger()) { if (m_is_property) { int64_t n = 0; int64_t nIndex = index.toInt64(); Variant var(this); for (ArrayIter iter = var.begin(); !iter.end(); iter.next()) { if (n++ == nIndex) { return iter.second(); } } return this; } return m_children[index]; } return m_attributes[index]; } void c_SimpleXMLElement::t_offsetset(CVarRef index, CVarRef newvalue) { if (index.isInteger()) { raise_error("unable to replace a SimpleXMLElement node"); return; } String name = index.toString(); if (name.empty()) { raise_error("cannot create unnamed attribute"); return; } String sv = newvalue.toString(); if (m_attributes.toArray().exists(name)) { t_offsetunset(index); } if (m_node == NULL || m_is_text) { raise_error("cannot create attribute on this node"); return; } xmlNodePtr newnode = (xmlNodePtr)xmlNewProp(m_node, (xmlChar *)name.data(), (xmlChar*)sv.data()); if (newnode) { m_attributes.set(name, sv); } } void c_SimpleXMLElement::t_offsetunset(CVarRef index) { if (index.isInteger()) { raise_error("unable to remove a SimpleXMLElement node"); return; } String name = index.toString(); if (name.empty()) { raise_error("cannot remove unnamed attribute"); return; } if (m_attributes.toArray().exists(name) && m_node) { for (xmlAttrPtr attr = m_node->properties; attr; attr = attr->next) { if (String((char*)attr->name, xmlStrlen(attr->name), AttachLiteral) == name) { xmlUnlinkNode((xmlNodePtr)attr); break; } } } m_attributes.remove(name); } /////////////////////////////////////////////////////////////////////////////// c_SimpleXMLElementIterator::c_SimpleXMLElementIterator(Class* cb) : ExtObjectData(cb), m_parent(), m_iter1(NULL), m_iter2(NULL) { } c_SimpleXMLElementIterator::~c_SimpleXMLElementIterator() { c_SimpleXMLElementIterator::sweep(); } void c_SimpleXMLElementIterator::sweep() { delete m_iter1; delete m_iter2; } void c_SimpleXMLElementIterator::set_parent(c_SimpleXMLElement* parent) { m_parent = parent; reset_iterator(); } void c_SimpleXMLElementIterator::reset_iterator() { assert(m_parent.get() != NULL); delete m_iter1; m_iter1 = NULL; delete m_iter2; m_iter2 = NULL; if (m_parent->m_is_attribute) { m_iter1 = new ArrayIter(m_parent->m_attributes.toArray()); return; } // When I'm a node like $node->name, we iterate through all my siblings with // same name of mine. if (m_parent->m_is_property) { String name = m_parent->t_getname(); Object obj = create_element(m_parent->m_doc, m_parent->m_node->parent, "", false); m_parent = obj.getTyped(); Variant children = m_parent->m_children[name]; m_parent->m_children = CREATE_MAP1(name, children); // fall through } if (m_parent->m_is_text) { return; } if (m_parent->m_children.toArray().size() == 1) { ArrayIter iter(m_parent->m_children.toArray()); if (iter.second().isObject()) { c_SimpleXMLElement *elem = iter.second().toObject(). getTyped(); if (elem->m_is_text && elem->m_free_text) { return; } } } m_iter1 = new ArrayIter(m_parent->m_children.toArray()); if (!m_iter1->end() && m_iter1->second().isArray()) { m_iter2 = new ArrayIter(m_iter1->second().toArray()); } } void c_SimpleXMLElementIterator::t___construct() { } Variant c_SimpleXMLElementIterator::t_current() { if (m_iter1 == NULL) return uninit_null(); if (m_parent->m_is_attribute) { return m_iter1->second(); } ArrayIter *iter = m_iter2; if (iter == NULL && m_iter1->second().isObject()) { iter = m_iter1; } if (iter) { return iter->second(); } assert(false); return uninit_null(); } Variant c_SimpleXMLElementIterator::t_key() { if (m_iter1) { return m_iter1->first(); } return uninit_null(); } Variant c_SimpleXMLElementIterator::t_next() { if (m_iter1 == NULL) return uninit_null(); if (m_parent->m_is_attribute) { m_iter1->next(); return uninit_null(); } if (m_iter2) { m_iter2->next(); if (!m_iter2->end()) { return uninit_null(); } delete m_iter2; m_iter2 = NULL; } m_iter1->next(); while (!m_iter1->end()) { if (m_iter1->second().isArray()) { m_iter2 = new ArrayIter(m_iter1->second().toArray()); break; } if (m_iter1->second().isObject()) { break; } m_iter1->next(); } return uninit_null(); } Variant c_SimpleXMLElementIterator::t_rewind() { reset_iterator(); return uninit_null(); } Variant c_SimpleXMLElementIterator::t_valid() { return m_iter1 && !m_iter1->end(); } /////////////////////////////////////////////////////////////////////////////// // LibXMLError c_LibXMLError::c_LibXMLError(Class* cb) : ExtObjectData(cb) { } c_LibXMLError::~c_LibXMLError() { } void c_LibXMLError::t___construct() { } /////////////////////////////////////////////////////////////////////////////// // libxml class xmlErrorVec : public std::vector { public: ~xmlErrorVec() { reset(); } void reset() { for (unsigned int i = 0; i < size(); i++) { xmlResetError(&at(i)); } clear(); } }; class LibXmlErrors : public RequestEventHandler { public: virtual void requestInit() { m_use_error = false; m_errors.reset(); xmlParserInputBufferCreateFilenameDefault(NULL); } virtual void requestShutdown() { m_use_error = false; m_errors.reset(); } bool m_use_error; xmlErrorVec m_errors; }; IMPLEMENT_STATIC_REQUEST_LOCAL(LibXmlErrors, s_libxml_errors); bool libxml_use_internal_error() { return s_libxml_errors->m_use_error; } extern void libxml_add_error(const std::string &msg) { xmlErrorVec *error_list = &s_libxml_errors->m_errors; error_list->resize(error_list->size() + 1); xmlError &error_copy = error_list->back(); memset(&error_copy, 0, sizeof(xmlError)); error_copy.domain = 0; error_copy.code = XML_ERR_INTERNAL_ERROR; error_copy.level = XML_ERR_ERROR; error_copy.line = 0; error_copy.node = NULL; error_copy.int1 = 0; error_copy.int2 = 0; error_copy.ctxt = NULL; error_copy.message = (char*)xmlStrdup((const xmlChar*)msg.c_str()); error_copy.file = NULL; error_copy.str1 = NULL; error_copy.str2 = NULL; error_copy.str3 = NULL; } static void libxml_error_handler(void *userData, xmlErrorPtr error) { xmlErrorVec *error_list = &s_libxml_errors->m_errors; error_list->resize(error_list->size() + 1); xmlError &error_copy = error_list->back(); memset(&error_copy, 0, sizeof(xmlError)); if (error) { xmlCopyError(error, &error_copy); } else { error_copy.code = XML_ERR_INTERNAL_ERROR; error_copy.level = XML_ERR_ERROR; } } static const StaticString s_level("level"); static const StaticString s_code("code"); static const StaticString s_column("column"); static const StaticString s_message("message"); static const StaticString s_file("file"); static const StaticString s_line("line"); static Object create_libxmlerror(xmlError &error) { Object ret(NEWOBJ(c_LibXMLError)()); ret->o_set(s_level, error.level); ret->o_set(s_code, error.code); ret->o_set(s_column, error.int2); ret->o_set(s_message, String(error.message, CopyString)); ret->o_set(s_file, String(error.file, CopyString)); ret->o_set(s_line, error.line); return ret; } Variant f_libxml_get_errors() { xmlErrorVec *error_list = &s_libxml_errors->m_errors; Array ret = Array::Create(); for (unsigned int i = 0; i < error_list->size(); i++) { ret.append(create_libxmlerror(error_list->at(i))); } return ret; } Variant f_libxml_get_last_error() { xmlErrorPtr error = xmlGetLastError(); if (error) { return create_libxmlerror(*error); } return false; } void f_libxml_clear_errors() { xmlResetLastError(); s_libxml_errors->m_errors.reset(); } bool f_libxml_use_internal_errors(CVarRef use_errors /* = null_variant */) { bool ret = (xmlStructuredError == libxml_error_handler); if (!use_errors.isNull()) { if (!use_errors.toBoolean()) { xmlSetStructuredErrorFunc(NULL, NULL); s_libxml_errors->m_use_error = false; s_libxml_errors->m_errors.reset(); } else { xmlSetStructuredErrorFunc(NULL, libxml_error_handler); s_libxml_errors->m_use_error = true; } } return ret; } void f_libxml_set_streams_context(CObjRef streams_context) { throw NotImplementedException(__func__); } static xmlParserInputBufferPtr hphp_libxml_input_buffer_noload(const char *URI, xmlCharEncoding enc) { return NULL; } bool f_libxml_disable_entity_loader(bool disable /* = true */) { xmlParserInputBufferCreateFilenameFunc old; if (disable) { old = xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer_noload); } else { old = xmlParserInputBufferCreateFilenameDefault(NULL); } return (old == hphp_libxml_input_buffer_noload); } /////////////////////////////////////////////////////////////////////////////// }