mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Implement DOMElement::toggleAttribute()
ref: https://dom.spec.whatwg.org/#dom-element-toggleattribute Closes GH-11696.
This commit is contained in:
parent
5b5a3d79da
commit
db5e8ae6cf
6 changed files with 276 additions and 1 deletions
1
NEWS
1
NEWS
|
@ -31,6 +31,7 @@ PHP NEWS
|
|||
. Added DOMNode::isEqualNode(). (nielsdos)
|
||||
. Added DOMElement::insertAdjacentElement() and
|
||||
DOMElement::insertAdjacentText(). (nielsdos)
|
||||
. Added DOMElement::toggleAttribute(). (nielsdos)
|
||||
|
||||
- FPM:
|
||||
. Added warning to log when fpm socket was not registered on the expected
|
||||
|
|
|
@ -281,6 +281,7 @@ PHP 8.3 UPGRADE NOTES
|
|||
. Added DOMNode::isEqualNode().
|
||||
. Added DOMElement::insertAdjacentElement() and
|
||||
DOMElement::insertAdjacentText().
|
||||
. Added DOMElement::toggleAttribute().
|
||||
|
||||
- JSON:
|
||||
. Added json_validate(), which returns whether the json is valid for
|
||||
|
|
|
@ -1470,5 +1470,111 @@ PHP_METHOD(DOMElement, insertAdjacentText)
|
|||
}
|
||||
}
|
||||
/* }}} end DOMElement::insertAdjacentText */
|
||||
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-toggleattribute
|
||||
Since:
|
||||
*/
|
||||
PHP_METHOD(DOMElement, toggleAttribute)
|
||||
{
|
||||
char *qname, *qname_tmp = NULL;
|
||||
size_t qname_length;
|
||||
bool force, force_is_null = true;
|
||||
xmlNodePtr thisp;
|
||||
zval *id;
|
||||
dom_object *intern;
|
||||
bool retval;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b!", &qname, &qname_length, &force, &force_is_null) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
|
||||
|
||||
/* Step 1 */
|
||||
if (xmlValidateName((xmlChar *) qname, 0) != 0) {
|
||||
php_dom_throw_error(INVALID_CHARACTER_ERR, 1);
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
/* Step 2 */
|
||||
if (thisp->doc->type == XML_HTML_DOCUMENT_NODE && (thisp->ns == NULL || xmlStrEqual(thisp->ns->href, (const xmlChar *) "http://www.w3.org/1999/xhtml"))) {
|
||||
qname_tmp = zend_str_tolower_dup_ex(qname, qname_length);
|
||||
if (qname_tmp != NULL) {
|
||||
qname = qname_tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 3 */
|
||||
xmlNodePtr attribute = dom_get_dom1_attribute(thisp, (xmlChar *) qname);
|
||||
|
||||
/* Step 4 */
|
||||
if (attribute == NULL) {
|
||||
/* Step 4.1 */
|
||||
if (force_is_null || force) {
|
||||
/* The behaviour for namespaces isn't defined by spec, but this is based on observing browers behaviour.
|
||||
* It follows the same rules when you'd manually add an attribute using the other APIs. */
|
||||
int len;
|
||||
const xmlChar *split = xmlSplitQName3((const xmlChar *) qname, &len);
|
||||
if (split == NULL || strncmp(qname, "xmlns:", len + 1) != 0) {
|
||||
/* unqualified name, or qualified name with no xml namespace declaration */
|
||||
dom_create_attribute(thisp, qname, "");
|
||||
} else {
|
||||
/* qualified name with xml namespace declaration */
|
||||
xmlNewNs(thisp, (const xmlChar *) "", (const xmlChar *) (qname + len + 1));
|
||||
}
|
||||
retval = true;
|
||||
goto out;
|
||||
}
|
||||
/* Step 4.2 */
|
||||
retval = false;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Step 5 */
|
||||
if (force_is_null || !force) {
|
||||
if (attribute->type == XML_NAMESPACE_DECL) {
|
||||
/* The behaviour isn't defined by spec, but by observing browsers I found
|
||||
* that you can remove the nodes, but they'll get reconciled.
|
||||
* So if any reference was left to the namespace, the only effect is that
|
||||
* the definition is potentially moved closer to the element using it.
|
||||
* If no reference was left, it is actually removed. */
|
||||
xmlNsPtr ns = (xmlNsPtr) attribute;
|
||||
if (thisp->nsDef == ns) {
|
||||
thisp->nsDef = ns->next;
|
||||
} else if (thisp->nsDef != NULL) {
|
||||
xmlNsPtr prev = thisp->nsDef;
|
||||
xmlNsPtr cur = prev->next;
|
||||
while (cur) {
|
||||
if (cur == ns) {
|
||||
prev->next = cur->next;
|
||||
break;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
ns->next = NULL;
|
||||
dom_set_old_ns(thisp->doc, ns);
|
||||
dom_reconcile_ns(thisp->doc, thisp);
|
||||
} else {
|
||||
/* TODO: in the future when namespace bugs are fixed,
|
||||
* the above if-branch should be merged into this called function
|
||||
* such that the removal will work properly with all APIs. */
|
||||
dom_remove_attribute(attribute);
|
||||
}
|
||||
retval = false;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Step 6 */
|
||||
retval = true;
|
||||
|
||||
out:
|
||||
if (qname_tmp) {
|
||||
efree(qname_tmp);
|
||||
}
|
||||
RETURN_BOOL(retval);
|
||||
}
|
||||
/* }}} end DOMElement::prepend */
|
||||
|
||||
#endif
|
||||
|
|
|
@ -642,6 +642,8 @@ class DOMElement extends DOMNode implements DOMParentNode, DOMChildNode
|
|||
/** @tentative-return-type */
|
||||
public function setIdAttributeNode(DOMAttr $attr, bool $isId): void {}
|
||||
|
||||
public function toggleAttribute(string $qualifiedName, ?bool $force = null): bool {}
|
||||
|
||||
public function remove(): void {}
|
||||
|
||||
/** @param DOMNode|string $nodes */
|
||||
|
|
9
ext/dom/php_dom_arginfo.h
generated
9
ext/dom/php_dom_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 850ab297bd3e6162e0497769cace87a41e8e8a00 */
|
||||
* Stub hash: 3a37adaf011606d10ae1fa12ce135a23b3e07cf4 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
||||
|
@ -282,6 +282,11 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_setId
|
|||
ZEND_ARG_TYPE_INFO(0, isId, _IS_BOOL, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMElement_toggleAttribute, 0, 1, _IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, force, _IS_BOOL, 1, "null")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_DOMElement_remove arginfo_class_DOMChildNode_remove
|
||||
|
||||
#define arginfo_class_DOMElement_before arginfo_class_DOMParentNode_append
|
||||
|
@ -591,6 +596,7 @@ ZEND_METHOD(DOMElement, setAttributeNodeNS);
|
|||
ZEND_METHOD(DOMElement, setIdAttribute);
|
||||
ZEND_METHOD(DOMElement, setIdAttributeNS);
|
||||
ZEND_METHOD(DOMElement, setIdAttributeNode);
|
||||
ZEND_METHOD(DOMElement, toggleAttribute);
|
||||
ZEND_METHOD(DOMElement, remove);
|
||||
ZEND_METHOD(DOMElement, before);
|
||||
ZEND_METHOD(DOMElement, after);
|
||||
|
@ -817,6 +823,7 @@ static const zend_function_entry class_DOMElement_methods[] = {
|
|||
ZEND_ME(DOMElement, setIdAttribute, arginfo_class_DOMElement_setIdAttribute, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, setIdAttributeNS, arginfo_class_DOMElement_setIdAttributeNS, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, setIdAttributeNode, arginfo_class_DOMElement_setIdAttributeNode, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, toggleAttribute, arginfo_class_DOMElement_toggleAttribute, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, remove, arginfo_class_DOMElement_remove, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, before, arginfo_class_DOMElement_before, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(DOMElement, after, arginfo_class_DOMElement_after, ZEND_ACC_PUBLIC)
|
||||
|
|
158
ext/dom/tests/DOMElement_toggleAttribute.phpt
Normal file
158
ext/dom/tests/DOMElement_toggleAttribute.phpt
Normal file
|
@ -0,0 +1,158 @@
|
|||
--TEST--
|
||||
DOMElement::toggleAttribute()
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$html = new DOMDocument();
|
||||
$html->loadHTML('<!DOCTYPE HTML><html id="test"></html>');
|
||||
$xml = new DOMDocument();
|
||||
$xml->loadXML('<?xml version="1.0"?><html id="test"></html>');
|
||||
|
||||
try {
|
||||
var_dump($html->documentElement->toggleAttribute("\0"));
|
||||
} catch (DOMException $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
echo "--- Selected attribute tests (HTML) ---\n";
|
||||
|
||||
var_dump($html->documentElement->toggleAttribute("SELECTED", false));
|
||||
echo $html->saveHTML();
|
||||
var_dump($html->documentElement->toggleAttribute("SELECTED"));
|
||||
echo $html->saveHTML();
|
||||
var_dump($html->documentElement->toggleAttribute("selected", true));
|
||||
echo $html->saveHTML();
|
||||
var_dump($html->documentElement->toggleAttribute("selected"));
|
||||
echo $html->saveHTML();
|
||||
|
||||
echo "--- Selected attribute tests (XML) ---\n";
|
||||
|
||||
var_dump($xml->documentElement->toggleAttribute("SELECTED", false));
|
||||
echo $xml->saveXML();
|
||||
var_dump($xml->documentElement->toggleAttribute("SELECTED"));
|
||||
echo $xml->saveXML();
|
||||
var_dump($xml->documentElement->toggleAttribute("selected", true));
|
||||
echo $xml->saveXML();
|
||||
var_dump($xml->documentElement->toggleAttribute("selected"));
|
||||
echo $xml->saveXML();
|
||||
|
||||
echo "--- id attribute tests ---\n";
|
||||
|
||||
var_dump($html->getElementById("test") === NULL);
|
||||
var_dump($html->documentElement->toggleAttribute("id"));
|
||||
var_dump($html->getElementById("test") === NULL);
|
||||
|
||||
echo "--- Namespace tests ---\n";
|
||||
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadXML("<?xml version='1.0'?><container xmlns='some:ns' xmlns:foo='some:ns2' xmlns:anotherone='some:ns3'><foo:bar/><baz/></container>");
|
||||
|
||||
echo "Toggling namespaces:\n";
|
||||
var_dump($dom->documentElement->toggleAttribute('xmlns'));
|
||||
echo $dom->saveXML();
|
||||
var_dump($dom->documentElement->toggleAttribute('xmlns:anotherone'));
|
||||
echo $dom->saveXML();
|
||||
var_dump($dom->documentElement->toggleAttribute('xmlns:anotherone'));
|
||||
echo $dom->saveXML();
|
||||
var_dump($dom->documentElement->toggleAttribute('xmlns:foo'));
|
||||
echo $dom->saveXML();
|
||||
var_dump($dom->documentElement->toggleAttribute('xmlns:nope', false));
|
||||
echo $dom->saveXML();
|
||||
|
||||
echo "Toggling namespaced attributes:\n";
|
||||
var_dump($dom->documentElement->toggleAttribute('test:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('foo:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('doesnotexist:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('doesnotexist:test2', false));
|
||||
echo $dom->saveXML();
|
||||
|
||||
echo "namespace of test:test = ";
|
||||
var_dump($dom->documentElement->getAttributeNode('test:test')->namespaceURI);
|
||||
echo "namespace of foo:test = ";
|
||||
var_dump($dom->documentElement->firstElementChild->getAttributeNode('foo:test')->namespaceURI);
|
||||
echo "namespace of doesnotexist:test = ";
|
||||
var_dump($dom->documentElement->firstElementChild->getAttributeNode('doesnotexist:test')->namespaceURI);
|
||||
|
||||
echo "Toggling namespaced attributes:\n";
|
||||
var_dump($dom->documentElement->toggleAttribute('test:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('foo:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('doesnotexist:test'));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('doesnotexist:test2', true));
|
||||
var_dump($dom->documentElement->firstElementChild->toggleAttribute('doesnotexist:test3', false));
|
||||
echo $dom->saveXML();
|
||||
|
||||
echo "Checking toggled namespace:\n";
|
||||
var_dump($dom->documentElement->getAttribute('xmlns:anotheron'));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Invalid Character Error
|
||||
--- Selected attribute tests (HTML) ---
|
||||
bool(false)
|
||||
<!DOCTYPE HTML>
|
||||
<html id="test"></html>
|
||||
bool(true)
|
||||
<!DOCTYPE HTML>
|
||||
<html id="test" selected></html>
|
||||
bool(true)
|
||||
<!DOCTYPE HTML>
|
||||
<html id="test" selected></html>
|
||||
bool(false)
|
||||
<!DOCTYPE HTML>
|
||||
<html id="test"></html>
|
||||
--- Selected attribute tests (XML) ---
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<html id="test"/>
|
||||
bool(true)
|
||||
<?xml version="1.0"?>
|
||||
<html id="test" SELECTED=""/>
|
||||
bool(true)
|
||||
<?xml version="1.0"?>
|
||||
<html id="test" SELECTED="" selected=""/>
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<html id="test" SELECTED=""/>
|
||||
--- id attribute tests ---
|
||||
bool(false)
|
||||
bool(false)
|
||||
bool(true)
|
||||
--- Namespace tests ---
|
||||
Toggling namespaces:
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns:foo="some:ns2" xmlns:anotherone="some:ns3" xmlns="some:ns"><foo:bar/><baz/></container>
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns:foo="some:ns2" xmlns="some:ns"><foo:bar/><baz/></container>
|
||||
bool(true)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns:foo="some:ns2" xmlns="some:ns" xmlns:anotherone=""><foo:bar/><baz/></container>
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns="some:ns" xmlns:anotherone=""><foo:bar xmlns:foo="some:ns2"/><baz/></container>
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns="some:ns" xmlns:anotherone=""><foo:bar xmlns:foo="some:ns2"/><baz/></container>
|
||||
Toggling namespaced attributes:
|
||||
bool(true)
|
||||
bool(true)
|
||||
bool(true)
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns="some:ns" xmlns:anotherone="" test:test=""><foo:bar xmlns:foo="some:ns2" foo:test="" doesnotexist:test=""/><baz/></container>
|
||||
namespace of test:test = NULL
|
||||
namespace of foo:test = string(8) "some:ns2"
|
||||
namespace of doesnotexist:test = NULL
|
||||
Toggling namespaced attributes:
|
||||
bool(false)
|
||||
bool(false)
|
||||
bool(false)
|
||||
bool(true)
|
||||
bool(false)
|
||||
<?xml version="1.0"?>
|
||||
<container xmlns="some:ns" xmlns:anotherone=""><foo:bar xmlns:foo="some:ns2" doesnotexist:test2=""/><baz/></container>
|
||||
Checking toggled namespace:
|
||||
string(0) ""
|
Loading…
Add table
Add a link
Reference in a new issue