2 +----------------------------------------------------------------------+
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)
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
{
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
; }
56 // if m_domNode isn't null, then he owns the m_doc. Otherwise, I own it
57 if (m_doc
&& m_domNode
.isNull()) {
61 ~XmlDocWrapper() { XmlDocWrapper::sweep(); }
65 // Hold onto the original owner of the doc so it doesn't get free()d.
68 IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(XmlDocWrapper
)
70 StaticString
XmlDocWrapper::s_class_name("xmlDoc");
72 ///////////////////////////////////////////////////////////////////////////////
75 static inline bool match_ns(xmlNodePtr node
, CStrRef ns
, bool is_prefix
) {
79 if (node
->ns
== NULL
|| node
->ns
->prefix
== NULL
) {
82 if (node
->ns
&& !xmlStrcmp(is_prefix
? node
->ns
->prefix
: node
->ns
->href
,
83 (const xmlChar
*)ns
.data())) {
89 static String
node_list_to_string(xmlDocPtr doc
, xmlNodePtr list
) {
90 xmlChar
*tmp
= xmlNodeListGetString(doc
, list
, 1);
91 String
res((char*) tmp
, CopyString
);
96 static Array
collect_attributes(xmlNodePtr node
, CStrRef ns
, bool is_prefix
) {
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
));
110 static void add_property(Array
&properties
, xmlNodePtr node
, Object value
) {
111 const char *name
= (char *)node
->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
);
122 newdata
.append(existing
);
123 newdata
.append(value
);
124 properties
.set(sname
, newdata
);
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
>();
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
);
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
>();
158 elem
->m_children
= create_children(doc
, node
, ns
, is_prefix
);
159 elem
->m_attributes
= collect_attributes(node
, ns
, is_prefix
);
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...
174 if (node
->type
== XML_TEXT_NODE
) {
175 if (node
->content
&& *node
->content
) {
178 create_text(doc
, node
, node_list_to_string(root
->doc
, node
),
179 ns
, is_prefix
, true));
185 if (node
->type
!= XML_ELEMENT_NODE
|| match_ns(node
, ns
, is_prefix
)) {
186 xmlNodePtr child
= node
->children
;
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);
192 sub
= create_element(doc
, node
, ns
, is_prefix
);
194 add_property(properties
, node
, sub
);
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
) {
210 add_namespace_name(out
, node
->ns
);
213 for (xmlAttrPtr attr
= node
->properties
; attr
; attr
= attr
->next
) {
215 add_namespace_name(out
, attr
->ns
);
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
,
230 if (node
->type
== XML_ELEMENT_NODE
) {
231 for (xmlNsPtr ns
= node
->nsDef
; ns
; ns
= ns
->next
) {
232 add_namespace_name(out
, ns
);
235 for (node
= node
->children
; node
; node
= node
->next
) {
236 add_registered_namespaces(out
, node
, true);
242 ///////////////////////////////////////////////////////////////////////////////
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
;
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
) {
264 Resource(NEWOBJ(XmlDocWrapper
)(nodep
->doc
, class_name
, node
));
265 return create_element(obj
, nodep
, String(), false);
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 */) {
278 if (!class_name
.empty()) {
279 cls
= Unit::loadClass(class_name
.get());
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",
289 return uninit_null();
292 cls
= c_SimpleXMLElement::s_cls
;
295 xmlDocPtr doc
= xmlReadMemory(data
.data(), data
.size(), NULL
, NULL
, options
);
296 xmlNodePtr root
= xmlDocGetRootElement(doc
);
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 ///////////////////////////////////////////////////////////////////////////////
316 c_SimpleXMLElement::c_SimpleXMLElement(Class
* cb
) :
317 ExtObjectDataFlags
<ObjectData::UseGet
|
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),
325 m_children
= Array::Create();
328 c_SimpleXMLElement::~c_SimpleXMLElement() {
329 c_SimpleXMLElement::sweep();
332 void c_SimpleXMLElement::sweep() {
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 */) {
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());
349 xml
= ret
.toString();
352 xmlDocPtr doc
= xmlReadMemory(xml
.data(), xml
.size(), NULL
, NULL
, options
);
355 Resource(NEWOBJ(XmlDocWrapper
)(doc
, c_SimpleXMLElement::s_class_name
));
356 m_node
= xmlDocGetRootElement(doc
);
358 m_children
= create_children(m_doc
, m_node
, ns
, is_prefix
);
359 m_attributes
= collect_attributes(m_node
, ns
, is_prefix
);
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
;
375 xmlNsPtr
*ns
= xmlGetNsList(doc
, m_node
);
377 while (ns
[nsnbr
] != NULL
) {
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
);
392 m_xpath
->namespaces
= NULL
;
400 xmlNodeSetPtr result
= retval
->nodesetval
;
402 xmlXPathFreeObject(retval
);
406 Array ret
= Array::Create();
407 for (int i
= 0; i
< result
->nodeNr
; ++i
) {
408 xmlNodePtr nodeptr
= result
->nodeTab
[i
];
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
) {
417 sub
= create_element(m_doc
, nodeptr
->parent
, String(), false);
419 case XML_ELEMENT_NODE
:
420 sub
= create_element(m_doc
, nodeptr
, String(), false);
422 case XML_ATTRIBUTE_NODE
:
423 sub
= create_element(m_doc
, nodeptr
->parent
, String(), false);
431 xmlXPathFreeObject(retval
);
435 bool c_SimpleXMLElement::t_registerxpathnamespace(CStrRef prefix
, CStrRef ns
) {
438 m_xpath
= xmlXPathNewContext(m_node
->doc
);
440 return xmlXPathRegisterNs(m_xpath
, (xmlChar
*)prefix
.data(),
441 (xmlChar
*)ns
.data()) == 0;
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
);
457 xmlOutputBufferPtr outbuf
=
458 xmlOutputBufferCreateFilename(translated
.c_str(), NULL
, 0);
459 if (outbuf
== NULL
) {
462 xmlNodeDumpOutput(outbuf
, m_node
->doc
, m_node
, 0, 0,
463 (char*)m_node
->doc
->encoding
);
464 xmlOutputBufferClose(outbuf
);
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
);
477 xmlOutputBufferPtr outbuf
= xmlAllocOutputBuffer(NULL
);
478 if (outbuf
== NULL
) {
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
);
490 Array
c_SimpleXMLElement::t_getnamespaces(bool recursive
/* = false */) {
491 Array ret
= Array::Create();
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
);
502 Array
c_SimpleXMLElement::t_getdocnamespaces(bool recursive
/* = false */) {
503 Array ret
= Array::Create();
505 add_registered_namespaces(ret
, xmlDocGetRootElement(m_node
->doc
),
511 Object
c_SimpleXMLElement::t_children(CStrRef ns
/* = "" */,
512 bool is_prefix
/* = false */) {
513 if (m_is_attribute
) {
517 Object obj
= create_object(m_doc
.getTyped
<XmlDocWrapper
>()->
518 getClass(), Array(), false);
519 c_SimpleXMLElement
*elem
= obj
.getTyped
<c_SimpleXMLElement
>();
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;
526 elem
->m_children
.assignRef(m_children
);
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());
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]);
549 props
.set(iter
.first(), subnodes
);
554 elem
->m_children
= props
;
559 String
c_SimpleXMLElement::t_getname() {
562 ArrayIter
iter(m_children
.toArray());
567 int namelen
= xmlStrlen(m_node
->name
);
568 return String((char*)m_node
->name
, namelen
, CopyString
);
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
) {
581 Object obj
= create_object(m_doc
.getTyped
<XmlDocWrapper
>()->
582 getClass(), Array(), false);
583 c_SimpleXMLElement
*elem
= obj
.getTyped
<c_SimpleXMLElement
>();
585 elem
->m_node
= m_node
;
586 elem
->m_is_attribute
= true;
587 if (!m_attributes
.toArray().empty()) {
589 elem
->m_attributes
= collect_attributes(m_node
, ns
, is_prefix
);
591 elem
->m_attributes
.assignRef(m_attributes
);
593 elem
->m_children
.set(s_attributes
, elem
->m_attributes
);
598 Variant
c_SimpleXMLElement::t_addchild(CStrRef qname
,
599 CStrRef value
/* = null_string */,
600 CStrRef ns
/* = null_string */) {
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();
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());
626 nsptr
= xmlNewNs(newnode
, (xmlChar
*)ns
.data(), prefix
);
628 nsptr
= xmlSearchNsByHref(m_node
->doc
, m_node
, (xmlChar
*)ns
.data());
630 nsptr
= xmlNewNs(newnode
, (xmlChar
*)ns
.data(), prefix
);
636 String
newname((char*)localname
, CopyString
);
637 String
newns((char*)prefix
, CopyString
);
643 Object child
= create_element(m_doc
, newnode
, newns
, false);
644 if (m_children
.toArray().exists(newname
)) {
645 Variant
&tmp
= m_children
.lvalAt(newname
);
652 m_children
.set(newname
, arr
);
655 m_children
.set(newname
, child
);
660 void c_SimpleXMLElement::t_addattribute(CStrRef qname
,
661 CStrRef value
/* = null_string */,
662 CStrRef ns
/* = null_string */) {
664 raise_warning("Attribute name is required");
668 if (m_node
&& m_node
->type
!= XML_ELEMENT_NODE
) {
669 m_node
= m_node
->parent
;
672 raise_warning("Unable to locate parent Element");
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
) {
685 if (prefix
!= NULL
) {
688 raise_warning("Attribute already exists");
692 xmlNsPtr nsptr
= NULL
;
694 nsptr
= xmlSearchNsByHref(m_node
->doc
, m_node
, (xmlChar
*)ns
.data());
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
);
704 if (prefix
!= NULL
) {
709 String
c_SimpleXMLElement::t___tostring() {
711 ArrayIter
iter(m_children
.toArray());
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();
728 Variant
c_SimpleXMLElement::t___get(Variant name
) {
729 Variant ret
= m_children
[name
];
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;
747 return create_object(o_getClassName(), Array(), false);
752 Variant
c_SimpleXMLElement::t___unset(Variant name
) {
753 if (m_node
== NULL
) return uninit_null();
756 if (m_is_attribute
) {
757 node
= m_attributes
[name
];
759 node
= m_children
[name
];
762 if (node
.isObject()) {
763 c_SimpleXMLElement
*elem
=
764 node
.toObject().getTyped
<c_SimpleXMLElement
>();
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
>();
773 xmlUnlinkNode(elem
->m_node
);
778 if (m_is_attribute
) {
779 m_attributes
.remove(name
);
781 m_children
.remove(name
);
783 return uninit_null();
786 bool c_SimpleXMLElement::t___isset(Variant name
) {
788 if (m_is_attribute
) {
789 return m_attributes
.toArray().exists(name
);
791 return m_children
.toArray().exists(name
);
797 static void change_node_zval(xmlNodePtr node
, CStrRef value
) {
799 xmlNodeSetContentLen(node
, (xmlChar
*)"", 0);
802 xmlEncodeEntitiesReentrant(node
->doc
, (xmlChar
*)value
.data());
803 int buffer_len
= xmlStrlen(buffer
);
805 xmlNodeSetContentLen(node
, buffer
, buffer_len
);
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();
819 if (m_is_attribute
) {
820 node
= m_attributes
[name
];
822 node
= m_children
[name
];
825 xmlNodePtr newnode
= NULL
;
826 if (node
.isObject()) {
827 c_SimpleXMLElement
*elem
=
828 node
.toObject().getTyped
<c_SimpleXMLElement
>();
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());
847 newnode
= (xmlNodePtr
)xmlNewProp(m_node
, (xmlChar
*)sname
.data(), sv
);
850 if (sname
.empty() || name
.isInteger()) {
851 newnode
= xmlNewTextChild(m_node
->parent
, m_node
->ns
,
854 newnode
= xmlNewTextChild(m_node
, m_node
->ns
,
855 (xmlChar
*)sname
.data(), sv
);
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
);
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
{
879 ArrayIter
iter(m_children
.toArray());
881 prop
= iter
.second();
883 return prop
.toString().toInt64();
886 double c_SimpleXMLElement::o_toDoubleImpl() const noexcept
{
888 ArrayIter
iter(m_children
.toArray());
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();
900 ret
.set(s_attributes
, m_attributes
);
905 Variant
c_SimpleXMLElement::t_getiterator() {
906 c_SimpleXMLElementIterator
*iter
= NEWOBJ(c_SimpleXMLElementIterator
)();
907 iter
->set_parent(this);
911 int64_t c_SimpleXMLElement::t_count() {
912 if (m_is_attribute
) {
913 return m_attributes
.toArray().size();
916 int64_t n
= 0; Variant
var(this);
917 for (ArrayIter iter
= var
.begin(); !iter
.end(); iter
.next()) {
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()) {
938 return m_attributes
.toArray().exists(index
);
941 Variant
c_SimpleXMLElement::t_offsetget(CVarRef index
) {
942 if (index
.isInteger()) {
944 int64_t n
= 0; int64_t nIndex
= index
.toInt64(); Variant
var(this);
945 for (ArrayIter iter
= var
.begin(); !iter
.end(); iter
.next()) {
947 return iter
.second();
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");
962 String name
= index
.toString();
964 raise_error("cannot create unnamed attribute");
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");
977 xmlNodePtr newnode
= (xmlNodePtr
)xmlNewProp(m_node
, (xmlChar
*)name
.data(),
978 (xmlChar
*)sv
.data());
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");
990 String name
= index
.toString();
992 raise_error("cannot remove unnamed attribute");
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
) ==
1000 xmlUnlinkNode((xmlNodePtr
)attr
);
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() {
1024 void c_SimpleXMLElementIterator::set_parent(c_SimpleXMLElement
* parent
) {
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());
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
,
1045 m_parent
= obj
.getTyped
<c_SimpleXMLElement
>();
1046 Variant children
= m_parent
->m_children
[name
];
1047 m_parent
->m_children
= CREATE_MAP1(name
, children
);
1051 if (m_parent
->m_is_text
) {
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
) {
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()) {
1087 return iter
->second();
1091 return uninit_null();
1094 Variant
c_SimpleXMLElementIterator::t_key() {
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
) {
1105 return uninit_null();
1110 if (!m_iter2
->end()) {
1111 return uninit_null();
1113 delete m_iter2
; m_iter2
= NULL
;
1116 while (!m_iter1
->end()) {
1117 if (m_iter1
->second().isArray()) {
1118 m_iter2
= new ArrayIter(m_iter1
->second().toArray());
1121 if (m_iter1
->second().isObject()) {
1126 return uninit_null();
1129 Variant
c_SimpleXMLElementIterator::t_rewind() {
1131 return uninit_null();
1134 Variant
c_SimpleXMLElementIterator::t_valid() {
1135 return m_iter1
&& !m_iter1
->end();
1138 ///////////////////////////////////////////////////////////////////////////////
1141 c_LibXMLError::c_LibXMLError(Class
* cb
) :
1144 c_LibXMLError::~c_LibXMLError() {
1146 void c_LibXMLError::t___construct() {
1149 ///////////////////////////////////////////////////////////////////////////////
1152 class xmlErrorVec
: public std::vector
<xmlError
> {
1159 for (unsigned int i
= 0; i
< size(); i
++) {
1160 xmlResetError(&at(i
));
1166 class LibXmlErrors
: public RequestEventHandler
{
1168 virtual void requestInit() {
1169 m_use_error
= false;
1171 xmlParserInputBufferCreateFilenameDefault(NULL
);
1173 virtual void requestShutdown() {
1174 m_use_error
= false;
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
));
1216 xmlCopyError(error
, &error_copy
);
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
);
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
)));
1250 Variant
f_libxml_get_last_error() {
1251 xmlErrorPtr error
= xmlGetLastError();
1253 return create_libxmlerror(*error
);
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();
1271 xmlSetStructuredErrorFunc(NULL
, libxml_error_handler
);
1272 s_libxml_errors
->m_use_error
= true;
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
) {
1287 bool f_libxml_disable_entity_loader(bool disable
/* = true */) {
1288 xmlParserInputBufferCreateFilenameFunc old
;
1291 old
= xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer_noload
);
1293 old
= xmlParserInputBufferCreateFilenameDefault(NULL
);
1295 return (old
== hphp_libxml_input_buffer_noload
);
1298 ///////////////////////////////////////////////////////////////////////////////