Arquivos
hhvm/hphp/runtime/ext/ext_simplexml.cpp
T
Paul Tarjan 506f21c4b5 Allow extension functions to match zend calling convention
Introducing `ZendParamMode` to as a idl flag. We are not consistent with zend on how they do their params for builtins. We cast to the expected data type. They do some checks, and if the checks don't pass they issue a warning and return (usually) `null`. This diff starts us down that path.

I'm introducing the param and using it in the places where we were emulating the calling convention in the `f_foo` functions. I'm going to follow up with converting as many as I can and then eventually this becomes the default. I also want this to be applied to php files in systemlib.

Many of the conversions are from https://github.com/php/php-src/blob/master/Zend/zend_API.c#L305
2013-06-25 13:19:04 -07:00

1305 linhas
37 KiB
C++

/*
+----------------------------------------------------------------------+
| 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<c_DOMNode>();
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<XmlDocWrapper>()->
getClass(), Array(), false);
c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
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<XmlDocWrapper>()->
getClass(), Array(), false);
c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
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<c_DOMNode>();
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<ObjectData::UseGet|
ObjectData::UseSet|
ObjectData::UseIsset|
ObjectData::UseUnset|
ObjectData::CallToImpl>(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<XmlDocWrapper>()->
getClass(), Array(), false);
c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
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); iter; ++iter) {
if (iter.second().isObject()) {
c_SimpleXMLElement *elem = iter.second().toObject().
getTyped<c_SimpleXMLElement>();
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()); iter2; ++iter2) {
c_SimpleXMLElement *elem = iter2.second().toObject().
getTyped<c_SimpleXMLElement>();
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);
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<XmlDocWrapper>()->
getClass(), Array(), false);
c_SimpleXMLElement *elem = obj.getTyped<c_SimpleXMLElement>();
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);
if (iter) {
prop = iter.second();
if (prop.isString()) {
return prop.toString();
}
if (prop.isObject()) {
c_SimpleXMLElement *elem =
prop.toObject().getTyped<c_SimpleXMLElement>();
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<c_SimpleXMLElement>();
Object obj = create_object(m_doc.getTyped<XmlDocWrapper>()->
getClass(), Array(), false);
c_SimpleXMLElement *e = obj.getTyped<c_SimpleXMLElement>();
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<c_SimpleXMLElement>();
if (elem->m_node) {
xmlUnlinkNode(elem->m_node);
}
} else if (node.isArray()) {
for (ArrayIter iter(node); iter; ++iter) {
c_SimpleXMLElement *elem = iter.second().toObject().
getTyped<c_SimpleXMLElement>();
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<c_SimpleXMLElement>();
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);
if (iter) {
prop = iter.second();
}
return prop.toString().toInt64();
}
double c_SimpleXMLElement::o_toDoubleImpl() const noexcept {
Variant prop;
ArrayIter iter(m_children);
if (iter) {
prop = iter.second();
}
return prop.toString().toDouble();
}
Array c_SimpleXMLElement::o_toArray() const {
if (m_attributes.toArray().empty()) {
return m_children;
}
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);
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<c_SimpleXMLElement>();
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);
if (iter.second().isObject()) {
c_SimpleXMLElement *elem = iter.second().toObject().
getTyped<c_SimpleXMLElement>();
if (elem->m_is_text && elem->m_free_text) {
return;
}
}
}
m_iter1 = new ArrayIter(m_parent->m_children);
if (!m_iter1->end() && m_iter1->second().isArray()) {
m_iter2 = new ArrayIter(m_iter1->second());
}
}
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());
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<xmlError> {
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);
}
///////////////////////////////////////////////////////////////////////////////
}