Skip to content

Implement DOMElement::toggleAttribute() #11696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
164 changes: 140 additions & 24 deletions ext/dom/element.c
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,15 @@ PHP_METHOD(DOMElement, getAttributeNames)
}
/* }}} end DOMElement::getAttributeNames() */

static xmlNodePtr dom_create_attribute(xmlNodePtr nodep, const char *name, const char* value)
{
if (xmlStrEqual((xmlChar *)name, (xmlChar *)"xmlns")) {
return (xmlNodePtr) xmlNewNs(nodep, (xmlChar *)value, NULL);
} else {
return (xmlNodePtr) xmlSetProp(nodep, (xmlChar *) name, (xmlChar *)value);
}
}

/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68F082
Since:
*/
Expand Down Expand Up @@ -405,23 +414,40 @@ PHP_METHOD(DOMElement, setAttribute)

}

if (xmlStrEqual((xmlChar *)name, (xmlChar *)"xmlns")) {
if (xmlNewNs(nodep, (xmlChar *)value, NULL)) {
RETURN_TRUE;
}
} else {
attr = (xmlNodePtr)xmlSetProp(nodep, (xmlChar *) name, (xmlChar *)value);
}
attr = dom_create_attribute(nodep, name, value);
if (!attr) {
zend_argument_value_error(1, "must be a valid XML attribute");
RETURN_THROWS();
}
if (attr->type == XML_NAMESPACE_DECL) {
RETURN_TRUE;
}

DOM_RET_OBJ(attr, &ret, intern);

}
/* }}} end dom_element_set_attribute */

static bool dom_remove_attribute(xmlNodePtr attrp)
{
ZEND_ASSERT(attrp != NULL);
switch (attrp->type) {
case XML_ATTRIBUTE_NODE:
if (php_dom_object_get_data(attrp) == NULL) {
node_list_unlink(attrp->children);
xmlUnlinkNode(attrp);
xmlFreeProp((xmlAttrPtr)attrp);
} else {
xmlUnlinkNode(attrp);
}
break;
case XML_NAMESPACE_DECL:
return false;
EMPTY_SWITCH_DEFAULT_CASE();
}
return true;
}

/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6D6AC0F9
Since:
*/
Expand Down Expand Up @@ -450,23 +476,7 @@ PHP_METHOD(DOMElement, removeAttribute)
RETURN_FALSE;
}

switch (attrp->type) {
case XML_ATTRIBUTE_NODE:
if (php_dom_object_get_data(attrp) == NULL) {
node_list_unlink(attrp->children);
xmlUnlinkNode(attrp);
xmlFreeProp((xmlAttrPtr)attrp);
} else {
xmlUnlinkNode(attrp);
}
break;
case XML_NAMESPACE_DECL:
RETURN_FALSE;
default:
break;
}

RETURN_TRUE;
RETURN_BOOL(dom_remove_attribute(attrp));
}
/* }}} end dom_element_remove_attribute */

Expand Down Expand Up @@ -1460,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
2 changes: 2 additions & 0 deletions ext/dom/php_dom.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,8 @@ public function setIdAttributeNS(string $namespace, string $qualifiedName, bool
/** @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 */
Expand Down
9 changes: 8 additions & 1 deletion ext/dom/php_dom_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading