Skip to content

Commit 540ea2b

Browse files
committed
Implement DOMElement::toggleAttribute()
ref: https://dom.spec.whatwg.org/#dom-element-toggleattribute
1 parent c502c58 commit 540ea2b

File tree

5 files changed

+119
-1
lines changed

5 files changed

+119
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ PHP NEWS
1616

1717
- DOM:
1818
. Added DOMNode::contains() and DOMNameSpaceNode::contains(). (nielsdos)
19+
. Added DOMElement::toggleAttribute(). (nielsdos)
1920

2021
- Intl:
2122
. Fix memory leak in MessageFormatter::format() on failure. (Girgias)

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ PHP 8.3 UPGRADE NOTES
240240

241241
- DOM:
242242
. Added DOMNode::contains() and DOMNameSpaceNode::contains().
243+
. Added DOMElement::toggleAttribute().
243244

244245
- JSON:
245246
. Added json_validate(), which returns whether the json is valid for

ext/dom/element.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,4 +1218,111 @@ PHP_METHOD(DOMElement, replaceWith)
12181218
}
12191219
/* }}} end DOMElement::prepend */
12201220

1221+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-element-toggleattribute
1222+
Since:
1223+
*/
1224+
PHP_METHOD(DOMElement, toggleAttribute)
1225+
{
1226+
char *qname, *qname_tmp = NULL;
1227+
size_t qname_length;
1228+
bool force, force_is_null = true;
1229+
xmlNodePtr thisp;
1230+
zval *id;
1231+
dom_object *intern;
1232+
bool retval;
1233+
1234+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b!", &qname, &qname_length, &force, &force_is_null) == FAILURE) {
1235+
RETURN_THROWS();
1236+
}
1237+
1238+
DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, intern);
1239+
1240+
/* Step 1 */
1241+
if (xmlValidateName((xmlChar *) qname, 0) != 0) {
1242+
php_dom_throw_error(INVALID_CHARACTER_ERR, 1);
1243+
RETURN_THROWS();
1244+
}
1245+
1246+
/* Step 2 */
1247+
if (thisp->doc->type == XML_HTML_DOCUMENT_NODE && (thisp->ns == NULL || xmlStrEqual(thisp->ns->href, (const xmlChar *) "http://www.w3.org/1999/xhtml"))) {
1248+
qname_tmp = zend_str_tolower_dup_ex(qname, qname_length);
1249+
if (qname_tmp != NULL) {
1250+
qname = qname_tmp;
1251+
}
1252+
}
1253+
1254+
/* Step 3 */
1255+
xmlNodePtr attribute = dom_get_dom1_attribute(thisp, (xmlChar *) qname);
1256+
1257+
/* Step 4 */
1258+
if (attribute == NULL) {
1259+
/* Step 4.1 */
1260+
if (force_is_null || force) {
1261+
/* The behaviour for namespaces isn't defined by spec, but this is based on observing browers behaviour.
1262+
* It follows the same rules when you'd manually add an attribute using the other APIs. */
1263+
int len;
1264+
const xmlChar *split = xmlSplitQName3((const xmlChar *) qname, &len);
1265+
if (split == NULL || strncmp(qname, "xmlns:", len + 1) != 0) {
1266+
/* unqualified name, or qualified name with no xml namespace declaration */
1267+
dom_create_attribute(thisp, qname, "");
1268+
} else {
1269+
/* qualified name with xml namespace declaration */
1270+
xmlNewNs(thisp, (const xmlChar *) "", (const xmlChar *) (qname + len + 1));
1271+
}
1272+
retval = true;
1273+
goto out;
1274+
}
1275+
/* Step 4.2 */
1276+
retval = false;
1277+
goto out;
1278+
}
1279+
1280+
/* Step 5 */
1281+
if (force_is_null || !force) {
1282+
if (attribute->type == XML_NAMESPACE_DECL) {
1283+
/* The behaviour isn't defined by spec, but by observing browsers I found
1284+
* that you can remove the nodes, but they'll get reconciled.
1285+
* So if any reference was left to the namespace, the only effect is that
1286+
* the definition is potentially moved closer to the element using it.
1287+
* If no reference was left, it is actually removed. */
1288+
xmlNsPtr ns = (xmlNsPtr) attribute;
1289+
if (thisp->nsDef == ns) {
1290+
thisp->nsDef = ns->next;
1291+
} else if (thisp->nsDef != NULL) {
1292+
xmlNsPtr prev = thisp->nsDef;
1293+
xmlNsPtr cur = prev->next;
1294+
while (cur) {
1295+
if (cur == ns) {
1296+
prev->next = cur->next;
1297+
break;
1298+
}
1299+
prev = cur;
1300+
cur = cur->next;
1301+
}
1302+
}
1303+
1304+
ns->next = NULL;
1305+
dom_set_old_ns(thisp->doc, ns);
1306+
dom_reconcile_ns(thisp->doc, thisp);
1307+
} else {
1308+
/* TODO: in the future when namespace bugs are fixed,
1309+
* the above if-branch should be merged into this called function
1310+
* such that the removal will work properly with all APIs. */
1311+
dom_remove_attribute(attribute);
1312+
}
1313+
retval = false;
1314+
goto out;
1315+
}
1316+
1317+
/* Step 6 */
1318+
retval = true;
1319+
1320+
out:
1321+
if (qname_tmp) {
1322+
efree(qname_tmp);
1323+
}
1324+
RETURN_BOOL(retval);
1325+
}
1326+
/* }}} end DOMElement::prepend */
1327+
12211328
#endif

ext/dom/php_dom.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,8 @@ public function setIdAttributeNS(string $namespace, string $qualifiedName, bool
614614
/** @tentative-return-type */
615615
public function setIdAttributeNode(DOMAttr $attr, bool $isId): void {}
616616

617+
public function toggleAttribute(string $qualifiedName, ?bool $force = null): bool {}
618+
617619
public function remove(): void {}
618620

619621
/** @param DOMNode|string $nodes */

ext/dom/php_dom_arginfo.h

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)