@@ -1218,4 +1218,111 @@ PHP_METHOD(DOMElement, replaceWith)
1218
1218
}
1219
1219
/* }}} end DOMElement::prepend */
1220
1220
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
+
1221
1328
#endif
0 commit comments