@@ -418,9 +418,68 @@ PHP_METHOD(DOMElement, setAttribute)
418
418
}
419
419
/* }}} end dom_element_set_attribute */
420
420
421
- static bool dom_remove_attribute (xmlNodePtr attrp )
421
+ typedef struct {
422
+ xmlNodePtr current_node ;
423
+ xmlNsPtr defined_ns ;
424
+ } dom_deep_ns_redef_item ;
425
+
426
+ /* Reconciliation for a *single* namespace, but reconciles *closest* to the subtree needing it. */
427
+ static void dom_deep_ns_redef (xmlNodePtr node , xmlNsPtr ns_to_redefine )
422
428
{
429
+ size_t worklist_capacity = 128 ;
430
+ dom_deep_ns_redef_item * worklist = emalloc (sizeof (dom_deep_ns_redef_item ) * worklist_capacity );
431
+ worklist [0 ].current_node = node ;
432
+ worklist [0 ].defined_ns = NULL ;
433
+ size_t worklist_size = 1 ;
434
+
435
+ while (worklist_size > 0 ) {
436
+ worklist_size -- ;
437
+ dom_deep_ns_redef_item * current_worklist_item = & worklist [worklist_size ];
438
+ ZEND_ASSERT (current_worklist_item -> current_node -> type == XML_ELEMENT_NODE );
439
+ xmlNsPtr defined_ns = current_worklist_item -> defined_ns ;
440
+
441
+ if (current_worklist_item -> current_node -> ns == ns_to_redefine ) {
442
+ if (defined_ns == NULL ) {
443
+ defined_ns = xmlNewNs (current_worklist_item -> current_node , ns_to_redefine -> href , ns_to_redefine -> prefix );
444
+ }
445
+ current_worklist_item -> current_node -> ns = defined_ns ;
446
+ }
447
+
448
+ for (xmlAttrPtr attr = current_worklist_item -> current_node -> properties ; attr ; attr = attr -> next ) {
449
+ if (attr -> ns == ns_to_redefine ) {
450
+ if (defined_ns == NULL ) {
451
+ defined_ns = xmlNewNs (current_worklist_item -> current_node , ns_to_redefine -> href , ns_to_redefine -> prefix );
452
+ }
453
+ attr -> ns = defined_ns ;
454
+ }
455
+ }
456
+
457
+ for (xmlNodePtr child = current_worklist_item -> current_node -> children ; child ; child = child -> next ) {
458
+ if (child -> type != XML_ELEMENT_NODE ) {
459
+ continue ;
460
+ }
461
+ if (worklist_size == worklist_capacity ) {
462
+ if (UNEXPECTED (worklist_capacity >= SIZE_MAX / 3 * 2 / sizeof (dom_deep_ns_redef_item ))) {
463
+ /* Shouldn't be possible to hit, but checked for safety anyway */
464
+ return ;
465
+ }
466
+ worklist_capacity = worklist_capacity * 3 / 2 ;
467
+ worklist = erealloc (worklist , sizeof (dom_deep_ns_redef_item ) * worklist_capacity );
468
+ }
469
+ worklist [worklist_size ].current_node = child ;
470
+ worklist [worklist_size ].defined_ns = defined_ns ;
471
+ worklist_size ++ ;
472
+ }
473
+ }
474
+
475
+ efree (worklist );
476
+ }
477
+
478
+ static bool dom_remove_attribute (xmlNodePtr thisp , xmlNodePtr attrp )
479
+ {
480
+ ZEND_ASSERT (thisp != NULL );
423
481
ZEND_ASSERT (attrp != NULL );
482
+
424
483
switch (attrp -> type ) {
425
484
case XML_ATTRIBUTE_NODE :
426
485
if (php_dom_object_get_data (attrp ) == NULL ) {
@@ -431,8 +490,42 @@ static bool dom_remove_attribute(xmlNodePtr attrp)
431
490
xmlUnlinkNode (attrp );
432
491
}
433
492
break ;
434
- case XML_NAMESPACE_DECL :
435
- return false;
493
+ case XML_NAMESPACE_DECL : {
494
+ /* They will always be removed, but can be re-added.
495
+ *
496
+ * If any reference was left to the namespace, the only effect is that
497
+ * the definition is potentially moved closer to the element using it.
498
+ * If no reference was left, it is actually removed. */
499
+ xmlNsPtr ns = (xmlNsPtr ) attrp ;
500
+ if (thisp -> nsDef == ns ) {
501
+ thisp -> nsDef = ns -> next ;
502
+ } else if (thisp -> nsDef != NULL ) {
503
+ xmlNsPtr prev = thisp -> nsDef ;
504
+ xmlNsPtr cur = prev -> next ;
505
+ while (cur ) {
506
+ if (cur == ns ) {
507
+ prev -> next = cur -> next ;
508
+ break ;
509
+ }
510
+ prev = cur ;
511
+ cur = cur -> next ;
512
+ }
513
+ } else {
514
+ /* defensive: attrp not defined in thisp ??? */
515
+ #if ZEND_DEBUG
516
+ ZEND_UNREACHABLE ();
517
+ #endif
518
+ break ; /* defensive */
519
+ }
520
+
521
+ ns -> next = NULL ;
522
+ php_libxml_set_old_ns (thisp -> doc , ns ); /* note: can't deallocate as it might be referenced by a "fake namespace node" */
523
+ /* xmlReconciliateNs() redefines at the top of the tree instead of closest to the child, own reconciliation here.
524
+ * Similarly, the DOM version has other issues too (see dom_libxml_reconcile_ensure_namespaces_are_declared). */
525
+ dom_deep_ns_redef (thisp , ns );
526
+
527
+ break ;
528
+ }
436
529
EMPTY_SWITCH_DEFAULT_CASE ();
437
530
}
438
531
return true;
@@ -461,7 +554,7 @@ PHP_METHOD(DOMElement, removeAttribute)
461
554
RETURN_FALSE ;
462
555
}
463
556
464
- RETURN_BOOL (dom_remove_attribute (attrp ));
557
+ RETURN_BOOL (dom_remove_attribute (nodep , attrp ));
465
558
}
466
559
/* }}} end dom_element_remove_attribute */
467
560
@@ -1425,37 +1518,7 @@ PHP_METHOD(DOMElement, toggleAttribute)
1425
1518
1426
1519
/* Step 5 */
1427
1520
if (force_is_null || !force ) {
1428
- if (attribute -> type == XML_NAMESPACE_DECL ) {
1429
- /* The behaviour isn't defined by spec, but by observing browsers I found
1430
- * that you can remove the nodes, but they'll get reconciled.
1431
- * So if any reference was left to the namespace, the only effect is that
1432
- * the definition is potentially moved closer to the element using it.
1433
- * If no reference was left, it is actually removed. */
1434
- xmlNsPtr ns = (xmlNsPtr ) attribute ;
1435
- if (thisp -> nsDef == ns ) {
1436
- thisp -> nsDef = ns -> next ;
1437
- } else if (thisp -> nsDef != NULL ) {
1438
- xmlNsPtr prev = thisp -> nsDef ;
1439
- xmlNsPtr cur = prev -> next ;
1440
- while (cur ) {
1441
- if (cur == ns ) {
1442
- prev -> next = cur -> next ;
1443
- break ;
1444
- }
1445
- prev = cur ;
1446
- cur = cur -> next ;
1447
- }
1448
- }
1449
-
1450
- ns -> next = NULL ;
1451
- php_libxml_set_old_ns (thisp -> doc , ns );
1452
- dom_reconcile_ns (thisp -> doc , thisp );
1453
- } else {
1454
- /* TODO: in the future when namespace bugs are fixed,
1455
- * the above if-branch should be merged into this called function
1456
- * such that the removal will work properly with all APIs. */
1457
- dom_remove_attribute (attribute );
1458
- }
1521
+ dom_remove_attribute (thisp , attribute );
1459
1522
retval = false;
1460
1523
goto out ;
1461
1524
}
0 commit comments