+
+ return "<$element" . self::expandAttributes(
+ self::dropDefaults( $element, $attribs ) ) . '>';
+ }
+
+ /**
+ * Returns "</$element>", except if $wgWellFormedXml is off, in which case
+ * it returns the empty string when that's guaranteed to be safe.
+ *
+ * @since 1.17
+ * @param $element string Name of the element, e.g., 'a'
+ * @return string A closing tag, if required
+ */
+ public static function closeElement( $element ) {
+ global $wgWellFormedXml;
+
+ $element = strtolower( $element );
+
+ # Reference:
+ # http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
+ if ( !$wgWellFormedXml && in_array( $element, array(
+ 'html',
+ 'head',
+ 'body',
+ 'li',
+ 'dt',
+ 'dd',
+ 'tr',
+ 'td',
+ 'th',
+ ) ) ) {
+ return '';
+ }
+ return "</$element>";
+ }
+
+ /**
+ * Given an element name and an associative array of element attributes,
+ * return an array that is functionally identical to the input array, but
+ * possibly smaller. In particular, attributes might be stripped if they
+ * are given their default values.
+ *
+ * This method is not guaranteed to remove all redundant attributes, only
+ * some common ones and some others selected arbitrarily at random. It
+ * only guarantees that the output array should be functionally identical
+ * to the input array (currently per the HTML 5 draft as of 2009-09-06).
+ *
+ * @param $element string Name of the element, e.g., 'a'
+ * @param $attribs array Associative array of attributes, e.g., array(
+ * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
+ * further documentation.
+ * @return array An array of attributes functionally identical to $attribs
+ */
+ private static function dropDefaults( $element, $attribs ) {
+ # Don't bother doing anything if we aren't outputting HTML5; it's too
+ # much of a pain to maintain two sets of defaults.
+ global $wgHtml5;
+ if ( !$wgHtml5 ) {
+ return $attribs;
+ }
+
+ static $attribDefaults = array(
+ 'area' => array( 'shape' => 'rect' ),
+ 'button' => array(
+ 'formaction' => 'GET',
+ 'formenctype' => 'application/x-www-form-urlencoded',
+ 'type' => 'submit',
+ ),
+ 'canvas' => array(
+ 'height' => '150',
+ 'width' => '300',
+ ),
+ 'command' => array( 'type' => 'command' ),
+ 'form' => array(
+ 'action' => 'GET',
+ 'autocomplete' => 'on',
+ 'enctype' => 'application/x-www-form-urlencoded',
+ ),
+ 'input' => array(
+ 'formaction' => 'GET',
+ 'type' => 'text',
+ 'value' => '',
+ ),
+ 'keygen' => array( 'keytype' => 'rsa' ),
+ 'link' => array( 'media' => 'all' ),
+ 'menu' => array( 'type' => 'list' ),
+ # Note: the use of text/javascript here instead of other JavaScript
+ # MIME types follows the HTML5 spec.
+ 'script' => array( 'type' => 'text/javascript' ),
+ 'style' => array(
+ 'media' => 'all',
+ 'type' => 'text/css',
+ ),
+ 'textarea' => array( 'wrap' => 'soft' ),
+ );
+
+ $element = strtolower( $element );
+
+ foreach ( $attribs as $attrib => $value ) {
+ $lcattrib = strtolower( $attrib );
+ $value = strval( $value );
+
+ # Simple checks using $attribDefaults
+ if ( isset( $attribDefaults[$element][$lcattrib] ) &&
+ $attribDefaults[$element][$lcattrib] == $value ) {
+ unset( $attribs[$attrib] );
+ }
+
+ if ( $lcattrib == 'class' && $value == '' ) {
+ unset( $attribs[$attrib] );
+ }
+ }
+
+ # More subtle checks
+ if ( $element === 'link' && isset( $attribs['type'] )
+ && strval( $attribs['type'] ) == 'text/css' ) {
+ unset( $attribs['type'] );
+ }
+ if ( $element === 'select' && isset( $attribs['size'] ) ) {
+ if ( in_array( 'multiple', $attribs )
+ || ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false )
+ ) {
+ # A multi-select
+ if ( strval( $attribs['size'] ) == '4' ) {
+ unset( $attribs['size'] );
+ }
+ } else {
+ # Single select
+ if ( strval( $attribs['size'] ) == '1' ) {
+ unset( $attribs['size'] );
+ }
+ }
+ }
+
+ return $attribs;