@@ -1425,6 +1425,166 @@ PHP_METHOD(DOMNode, isSameNode)
1425
1425
}
1426
1426
/* }}} end dom_node_is_same_node */
1427
1427
1428
+ static bool php_dom_node_is_content_equal (const xmlNode * this , const xmlNode * other )
1429
+ {
1430
+ xmlChar * this_content = xmlNodeGetContent (this );
1431
+ xmlChar * other_content = xmlNodeGetContent (other );
1432
+ bool result = xmlStrEqual (this_content , other_content );
1433
+ xmlFree (this_content );
1434
+ xmlFree (other_content );
1435
+ return result ;
1436
+ }
1437
+
1438
+ static bool php_dom_node_is_ns_uri_equal (const xmlNode * this , const xmlNode * other )
1439
+ {
1440
+ const xmlChar * this_ns = this -> ns ? this -> ns -> href : NULL ;
1441
+ const xmlChar * other_ns = other -> ns ? other -> ns -> href : NULL ;
1442
+ return xmlStrEqual (this_ns , other_ns );
1443
+ }
1444
+
1445
+ static bool php_dom_node_is_ns_prefix_equal (const xmlNode * this , const xmlNode * other )
1446
+ {
1447
+ const xmlChar * this_ns = this -> ns ? this -> ns -> prefix : NULL ;
1448
+ const xmlChar * other_ns = other -> ns ? other -> ns -> prefix : NULL ;
1449
+ return xmlStrEqual (this_ns , other_ns );
1450
+ }
1451
+
1452
+ static bool php_dom_node_is_equal_node (const xmlNode * this , const xmlNode * other );
1453
+
1454
+ #define PHP_DOM_FUNC_CAT (prefix , suffix ) prefix##_##suffix
1455
+ /* xmlNode and xmlNs have incompatible struct layouts, i.e. the next field is in a different offset */
1456
+ #define PHP_DOM_DEFINE_LIST_EQUALITY_HELPER (type ) \
1457
+ static size_t PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(const type *node) \
1458
+ { \
1459
+ size_t counter = 0; \
1460
+ while (node) { \
1461
+ counter++; \
1462
+ node = node->next; \
1463
+ } \
1464
+ return counter; \
1465
+ } \
1466
+ static bool PHP_DOM_FUNC_CAT(php_dom_node_list_equality_check, type)(const type *list1, const type *list2) \
1467
+ { \
1468
+ size_t count = PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list1); \
1469
+ if (count != PHP_DOM_FUNC_CAT(php_dom_node_count_list_size, type)(list2)) { \
1470
+ return false; \
1471
+ } \
1472
+ for (size_t i = 0; i < count; i++) { \
1473
+ if (!php_dom_node_is_equal_node((const xmlNode *) list1, (const xmlNode *) list2)) { \
1474
+ return false; \
1475
+ } \
1476
+ list1 = list1->next; \
1477
+ list2 = list2->next; \
1478
+ } \
1479
+ return true; \
1480
+ }
1481
+ PHP_DOM_DEFINE_LIST_EQUALITY_HELPER (xmlNode )
1482
+ PHP_DOM_DEFINE_LIST_EQUALITY_HELPER (xmlNs )
1483
+
1484
+ static bool php_dom_node_is_equal_node (const xmlNode * this , const xmlNode * other )
1485
+ {
1486
+ ZEND_ASSERT (this != NULL );
1487
+ ZEND_ASSERT (other != NULL );
1488
+
1489
+ if (this -> type != other -> type ) {
1490
+ return false;
1491
+ }
1492
+
1493
+ /* Notes:
1494
+ * - XML_DOCUMENT_TYPE_NODE is no longer created by libxml2, we only have to support XML_DTD_NODE.
1495
+ * - element and attribute declarations are not exposed as nodes in DOM, so no comparison is needed for those. */
1496
+ if (this -> type == XML_ELEMENT_NODE ) {
1497
+ if (!xmlStrEqual (this -> name , other -> name )
1498
+ || !php_dom_node_is_ns_prefix_equal (this , other )
1499
+ || !php_dom_node_is_ns_uri_equal (this , other )) {
1500
+ return false;
1501
+ }
1502
+ /* Check attributes first, then namespace declarations, then children */
1503
+ return php_dom_node_list_equality_check_xmlNode ((const xmlNode * ) this -> properties , (const xmlNode * ) other -> properties )
1504
+ && php_dom_node_list_equality_check_xmlNs (this -> nsDef , other -> nsDef )
1505
+ && php_dom_node_list_equality_check_xmlNode (this -> children , other -> children );
1506
+ } else if (this -> type == XML_DTD_NODE ) {
1507
+ /* Note: in the living spec entity declarations and notations are no longer compared because they're considered obsolete. */
1508
+ const xmlDtd * this_dtd = (const xmlDtd * ) this ;
1509
+ const xmlDtd * other_dtd = (const xmlDtd * ) other ;
1510
+ return xmlStrEqual (this_dtd -> name , other_dtd -> name )
1511
+ && xmlStrEqual (this_dtd -> ExternalID , other_dtd -> ExternalID )
1512
+ && xmlStrEqual (this_dtd -> SystemID , other_dtd -> SystemID );
1513
+ } else if (this -> type == XML_PI_NODE ) {
1514
+ return xmlStrEqual (this -> name , other -> name ) && xmlStrEqual (this -> content , other -> content );
1515
+ } else if (this -> type == XML_TEXT_NODE || this -> type == XML_COMMENT_NODE || this -> type == XML_CDATA_SECTION_NODE ) {
1516
+ /* Note: spec doesn't explicitly mention it, but JS also checks equality of content for CDATA. */
1517
+ return xmlStrEqual (this -> content , other -> content );
1518
+ } else if (this -> type == XML_ATTRIBUTE_NODE ) {
1519
+ const xmlAttr * this_attr = (const xmlAttr * ) this ;
1520
+ const xmlAttr * other_attr = (const xmlAttr * ) other ;
1521
+ return xmlStrEqual (this_attr -> name , other_attr -> name )
1522
+ && php_dom_node_is_ns_uri_equal (this , other )
1523
+ && php_dom_node_is_content_equal (this , other );
1524
+ } else if (this -> type == XML_ENTITY_REF_NODE ) {
1525
+ return xmlStrEqual (this -> name , other -> name );
1526
+ } else if (this -> type == XML_ENTITY_DECL || this -> type == XML_NOTATION_NODE || this -> type == XML_ENTITY_NODE ) {
1527
+ const xmlEntity * this_entity = (const xmlEntity * ) this ;
1528
+ const xmlEntity * other_entity = (const xmlEntity * ) other ;
1529
+ if (this_entity -> etype != other_entity -> etype
1530
+ || !xmlStrEqual (this_entity -> name , other_entity -> name )
1531
+ || !xmlStrEqual (this_entity -> ExternalID , other_entity -> ExternalID )
1532
+ || !xmlStrEqual (this_entity -> SystemID , other_entity -> SystemID )) {
1533
+ return false;
1534
+ }
1535
+ return php_dom_node_is_content_equal (this , other );
1536
+ } else if (this -> type == XML_NAMESPACE_DECL ) {
1537
+ const xmlNs * this_ns = (const xmlNs * ) this ;
1538
+ const xmlNs * other_ns = (const xmlNs * ) other ;
1539
+ return xmlStrEqual (this_ns -> prefix , other_ns -> prefix ) && xmlStrEqual (this_ns -> href , other_ns -> href );
1540
+ }
1541
+
1542
+ return false;
1543
+ }
1544
+
1545
+ /* {{{ URL: https://dom.spec.whatwg.org/#dom-node-isequalnode (for everything still in the living spec)
1546
+ * URL: https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-Node3-isEqualNode (for old nodes removed from the living spec)
1547
+ Since: DOM Level 3
1548
+ */
1549
+ PHP_METHOD (DOMNode , isEqualNode )
1550
+ {
1551
+ zval * id , * node ;
1552
+ xmlNodePtr otherp , nodep ;
1553
+ dom_object * unused_intern ;
1554
+
1555
+ id = ZEND_THIS ;
1556
+ if (zend_parse_parameters (ZEND_NUM_ARGS (), "O!" , & node , dom_node_class_entry ) == FAILURE ) {
1557
+ RETURN_THROWS ();
1558
+ }
1559
+
1560
+ if (node == NULL ) {
1561
+ RETURN_FALSE ;
1562
+ }
1563
+
1564
+ DOM_GET_THIS_OBJ (nodep , id , xmlNodePtr , unused_intern );
1565
+ DOM_GET_OBJ (otherp , node , xmlNodePtr , unused_intern );
1566
+
1567
+ if (nodep == otherp ) {
1568
+ RETURN_TRUE ;
1569
+ }
1570
+
1571
+ if (nodep -> type == XML_DOCUMENT_FRAG_NODE || nodep -> type == XML_HTML_DOCUMENT_NODE || nodep -> type == XML_DOCUMENT_NODE ) {
1572
+ nodep = nodep -> children ;
1573
+ }
1574
+
1575
+ if (otherp -> type == XML_DOCUMENT_FRAG_NODE || otherp -> type == XML_HTML_DOCUMENT_NODE || otherp -> type == XML_DOCUMENT_NODE ) {
1576
+ otherp = otherp -> children ;
1577
+ }
1578
+
1579
+ /* Empty fragments/documents only match if they're both empty */
1580
+ if (UNEXPECTED (nodep == NULL || otherp == NULL )) {
1581
+ RETURN_BOOL (nodep == NULL && otherp == NULL );
1582
+ }
1583
+
1584
+ RETURN_BOOL (php_dom_node_is_equal_node (nodep , otherp ));
1585
+ }
1586
+ /* }}} end DOMNode::isEqualNode */
1587
+
1428
1588
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Node3-lookupNamespacePrefix
1429
1589
Since: DOM Level 3
1430
1590
*/
0 commit comments