Html.php: The "future"[1] is here. Add features for space-separated value attributes...
authorKrinkle <krinkle@users.mediawiki.org>
Sat, 3 Sep 2011 03:55:23 +0000 (03:55 +0000)
committerKrinkle <krinkle@users.mediawiki.org>
Sat, 3 Sep 2011 03:55:23 +0000 (03:55 +0000)
* Has been suggested since August 2009 in r54767 (doc-comment from rawElement/element function)
* Implements normalization for these attributes (removal of duplicates and redundant space)
* Adds support for arrays (instead of just string) for these attributes.
* String are still supported, and are converted to arrays to get the same normalization.
* Wrote unit tests (which pass locally: $ php phpunit.php includes/HtmlTest.php)
* Not trigger for the media-attribute. Reason: Although some people think it's space-separated, it's actually comma-separated. Treating them as space separated might even destroy the value. [2] [3]. Neither the html4 or html5 spec documents media-attribute as space-separated, and as of HTML5/CSS3 the media attribute may contain "media queries".

[1] "In the future, other HTML-specific features might be added, like allowing arrays for the values of attributes like class= and media=" in r54767 by Simetrical.
[2] http://www.w3.org/TR/1999/REC-html401-19991224/types.html#h-6.13
[3] http://dev.w3.org/csswg/css3-mediaqueries/#background

Implementation note: I choose to have a single list of attributes that trigger this feature. Some of these attributes only support multiple values and/or are documented as space-separated as of html5 (such as accesskey), but since those attributes in general have existed in html4 as well (just different w3c spec), they are not stripped if wgHtml5 is not true. So if this feature would (eg. for accesskey) would only be done if wgHtml5=true, then people could get output like <a accesskey=Array /> depending on a configuration variable, which will get messy and make developers' life hard.

includes/Html.php
tests/phpunit/includes/HtmlTest.php

index 70dc186..fcdecaa 100644 (file)
@@ -111,9 +111,7 @@ class Html {
         * HTML-specific logic.  For instance, there is no $allowShortTag
         * parameter: the closing tag is magically omitted if $element has an empty
         * content model.  If $wgWellFormedXml is false, then a few bytes will be
-        * shaved off the HTML output as well.  In the future, other HTML-specific
-        * features might be added, like allowing arrays for the values of
-        * attributes like class= and media=.
+        * shaved off the HTML output as well.
         *
         * @param $element string The element's name, e.g., 'a'
         * @param $attribs array  Associative array of attributes, e.g., array(
@@ -415,6 +413,37 @@ class Html {
                                continue;
                        }
 
+                       // http://www.w3.org/TR/html401/index/attributes.html ("space-separated")
+                       // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated")
+                       $spaceSeparatedListAttributes = array(
+                               'class', // html4, html5
+                               'accesskey', // as of html5, multiple space-separated values allowed
+                               // html4-spec doesn't document rel= as space-separated
+                               // but has been used like that and is now documented as such 
+                               // in the html5-spec.
+                               'rel',
+                       );
+
+                       # Specific features for attributes that allow a list of space-separated values
+                       if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
+                               // Apply some normalization and remove duplicates
+
+                               // Convert into correct array. Array can contain space-seperated
+                               // values. Implode/explode to get those into the main array as well.
+                               if ( is_array( $value ) ) {
+                                       // If input wasn't an array, we can skip this step
+                                       $value = implode( ' ', $value );
+                               }
+                               $value = explode( ' ', $value );
+
+                               // Normalize spacing by fixing up cases where people used
+                               // more than 1 space and/or a trailing/leading space
+                               $value = array_diff( $value, array( '', ' ') );
+
+                               // Remove duplicates and create the string
+                               $value = implode( ' ', array_unique( $value ) );
+                       }
+
                        # See the "Attributes" section in the HTML syntax part of HTML5,
                        # 9.1.2.3 as of 2009-08-10.  Most attributes can have quotation
                        # marks omitted, but not all.  (Although a literal " is not
index 2a1ff95..339e2da 100644 (file)
@@ -91,4 +91,56 @@ class HtmlTest extends MediaWikiTestCase {
                        'Value is a numeric zero'
                );
        }
+
+       /**
+        * Html::expandAttributes has special features for HTML
+        * attributes that use space separated lists and also
+        * allows arrays to be used as values.
+        */
+       public function testExpandAttributesListValueAttributes() {
+               ### STRING VALUES
+               $this->AssertEquals(
+                       ' class="redundant spaces here"',
+                       Html::expandAttributes( array( 'class' => ' redundant  spaces  here  ' ) ),
+                       'Normalization should strip redundant spaces'
+               );
+               $this->AssertEquals(
+                       ' class="foo bar"',
+                       Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
+                       'Normalization should remove duplicates in string-lists'
+               );
+               ### "EMPTY" ARRAY VALUES
+               $this->AssertEquals(
+                       ' class=""',
+                       Html::expandAttributes( array( 'class' => array() ) ),
+                       'Value with an empty array'
+               );
+               $this->AssertEquals(
+                       ' class=""',
+                       Html::expandAttributes( array( 'class' => array( null, '', ' ', '  ' ) ) ),
+                       'Array with null, empty string and spaces'
+               );
+               ### NON-EMPTY ARRAY VALUES
+               $this->AssertEquals(
+                       ' class="foo bar"',
+                       Html::expandAttributes( array( 'class' => array(
+                               'foo',
+                               'bar',
+                               'foo',
+                               'bar',
+                               'bar',
+                       ) ) ),
+                       'Normalization should remove duplicates in the array'
+               );
+               $this->AssertEquals(
+                       ' class="foo bar"',
+                       Html::expandAttributes( array( 'class' => array(
+                               'foo bar',
+                               'bar foo',
+                               'foo',
+                               'bar bar',
+                       ) ) ),
+                       'Normalization should remove duplicates in string-lists in the array'
+               );
+       }
 }