]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/Xml.php
MediaWiki 1.17.1
[autoinstallsdev/mediawiki.git] / includes / Xml.php
1 <?php
2
3 /**
4  * Module of static functions for generating XML
5  */
6
7 class Xml {
8         /**
9          * Format an XML element with given attributes and, optionally, text content.
10          * Element and attribute names are assumed to be ready for literal inclusion.
11          * Strings are assumed to not contain XML-illegal characters; special
12          * characters (<, >, &) are escaped but illegals are not touched.
13          *
14          * @param $element String: element name
15          * @param $attribs Array: Name=>value pairs. Values will be escaped.
16          * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
17          * @param $allowShortTag Bool: whether '' in $contents will result in a contentless closed tag
18          * @return string
19          */
20         public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
21                 $out = '<' . $element;
22                 if( !is_null( $attribs ) ) {
23                         $out .=  self::expandAttributes( $attribs );
24                 }
25                 if( is_null( $contents ) ) {
26                         $out .= '>';
27                 } else {
28                         if( $allowShortTag && $contents === '' ) {
29                                 $out .= ' />';
30                         } else {
31                                 $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
32                         }
33                 }
34                 return $out;
35         }
36
37         /**
38          * Given an array of ('attributename' => 'value'), it generates the code
39          * to set the XML attributes : attributename="value".
40          * The values are passed to Sanitizer::encodeAttribute.
41          * Return null if no attributes given.
42          * @param $attribs Array of attributes for an XML element
43          */
44         public static function expandAttributes( $attribs ) {
45                 $out = '';
46                 if( is_null( $attribs ) ) {
47                         return null;
48                 } elseif( is_array( $attribs ) ) {
49                         foreach( $attribs as $name => $val ) {
50                                 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
51                         }
52                         return $out;
53                 } else {
54                         throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
55                 }
56         }
57
58         /**
59          * Format an XML element as with self::element(), but run text through the
60          * $wgContLang->normalize() validator first to ensure that no invalid UTF-8
61          * is passed.
62          *
63          * @param $element String:
64          * @param $attribs Array: Name=>value pairs. Values will be escaped.
65          * @param $contents String: NULL to make an open tag only; '' for a contentless closed tag (default)
66          * @return string
67          */
68         public static function elementClean( $element, $attribs = array(), $contents = '') {
69                 global $wgContLang;
70                 if( $attribs ) {
71                         $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
72                 }
73                 if( $contents ) {
74                         wfProfileIn( __METHOD__ . '-norm' );
75                         $contents = $wgContLang->normalize( $contents );
76                         wfProfileOut( __METHOD__ . '-norm' );
77                 }
78                 return self::element( $element, $attribs, $contents );
79         }
80
81         /**
82          * This opens an XML element
83          *
84          * @param $element String name of the element
85          * @param $attribs array of attributes, see Xml::expandAttributes()
86          * @return string
87          */
88         public static function openElement( $element, $attribs = null ) {
89                 return '<' . $element . self::expandAttributes( $attribs ) . '>';
90         }
91
92         /**
93          * Shortcut to close an XML element
94          * @param $element String element name
95          * @return string
96          */
97         public static function closeElement( $element ) { return "</$element>"; }
98
99         /**
100          * Same as Xml::element(), but does not escape contents. Handy when the
101          * content you have is already valid xml.
102          *
103          * @param $element String element name
104          * @param $attribs array of attributes
105          * @param $contents String content of the element
106          * @return string
107          */
108         public static function tags( $element, $attribs = null, $contents ) {
109                 return self::openElement( $element, $attribs ) . $contents . "</$element>";
110         }
111
112         /**
113          * Build a drop-down box for selecting a namespace
114          *
115          * @param $selected Mixed: Namespace which should be pre-selected
116          * @param $all Mixed: Value of an item denoting all namespaces, or null to omit
117          * @param $element_name String: value of the "name" attribute of the select tag
118          * @param $label String: optional label to add to the field
119          * @return string
120          */
121         public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
122                 global $wgContLang;
123                 $namespaces = $wgContLang->getFormattedNamespaces();
124                 $options = array();
125
126                 // Godawful hack... we'll be frequently passed selected namespaces
127                 // as strings since PHP is such a shithole.
128                 // But we also don't want blanks and nulls and "all"s matching 0,
129                 // so let's convert *just* string ints to clean ints.
130                 if( preg_match( '/^\d+$/', $selected ) ) {
131                         $selected = intval( $selected );
132                 }
133
134                 if( !is_null( $all ) )
135                         $namespaces = array( $all => wfMsg( 'namespacesall' ) ) + $namespaces;
136                 foreach( $namespaces as $index => $name ) {
137                         if( $index < NS_MAIN ) {
138                                 continue;
139                         }
140                         if( $index === 0 ) {
141                                 $name = wfMsg( 'blanknamespace' );
142                         }
143                         $options[] = self::option( $name, $index, $index === $selected );
144                 }
145
146                 $ret = Xml::openElement( 'select', array( 'id' => 'namespace', 'name' => $element_name,
147                         'class' => 'namespaceselector' ) )
148                         . "\n"
149                         . implode( "\n", $options )
150                         . "\n"
151                         . Xml::closeElement( 'select' );
152                 if ( !is_null( $label ) ) {
153                         $ret = Xml::label( $label, $element_name ) . '&#160;' . $ret;
154                 }
155                 return $ret;
156         }
157
158         /**
159          * Create a date selector
160          *
161          * @param $selected Mixed: the month which should be selected, default ''
162          * @param $allmonths String: value of a special item denoting all month. Null to not include (default)
163          * @param $id String: Element identifier
164          * @return String: Html string containing the month selector
165          */
166         public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
167                 global $wgLang;
168                 $options = array();
169                 if( is_null( $selected ) )
170                         $selected = '';
171                 if( !is_null( $allmonths ) )
172                         $options[] = self::option( wfMsg( 'monthsall' ), $allmonths, $selected === $allmonths );
173                 for( $i = 1; $i < 13; $i++ )
174                         $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
175                 return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
176                         . implode( "\n", $options )
177                         . self::closeElement( 'select' );
178         }
179
180         /**
181          * @param $year Integer
182          * @param $month Integer
183          * @return string Formatted HTML
184          */
185         public static function dateMenu( $year, $month ) {
186                 # Offset overrides year/month selection
187                 if( $month && $month !== -1 ) {
188                         $encMonth = intval( $month );
189                 } else {
190                         $encMonth = '';
191                 }
192                 if( $year ) {
193                         $encYear = intval( $year );
194                 } else if( $encMonth ) {
195                         $thisMonth = intval( gmdate( 'n' ) );
196                         $thisYear = intval( gmdate( 'Y' ) );
197                         if( intval($encMonth) > $thisMonth ) {
198                                 $thisYear--;
199                         }
200                         $encYear = $thisYear;
201                 } else {
202                         $encYear = '';
203                 }
204                 return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
205                         Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
206                         Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
207                         Xml::monthSelector( $encMonth, -1 );
208         }
209
210         /**
211          *
212          * @param $selected The language code of the selected language
213          * @param $customisedOnly If true only languages which have some content are listed
214          * @return array of label and select
215          */
216         public static function languageSelector( $selected, $customisedOnly = true ) {
217                 global $wgLanguageCode;
218                 /**
219                  * Make sure the site language is in the list; a custom language code
220                  * might not have a defined name...
221                  */
222                 $languages = Language::getLanguageNames( $customisedOnly );
223                 if( !array_key_exists( $wgLanguageCode, $languages ) ) {
224                         $languages[$wgLanguageCode] = $wgLanguageCode;
225                 }
226                 ksort( $languages );
227
228                 /**
229                  * If a bogus value is set, default to the content language.
230                  * Otherwise, no default is selected and the user ends up
231                  * with an Afrikaans interface since it's first in the list.
232                  */
233                 $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
234                 $options = "\n";
235                 foreach( $languages as $code => $name ) {
236                         $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
237                 }
238
239                 return array(
240                         Xml::label( wfMsg('yourlanguage'), 'wpUserLanguage' ),
241                         Xml::tags( 'select',
242                                 array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ),
243                                 $options
244                         )
245                 );
246
247         }
248
249         /**
250          * Shortcut to make a span element
251          * @param $text String content of the element, will be escaped
252          * @param $class String class name of the span element
253          * @param $attribs other attributes
254          * @return string
255          */
256         public static function span( $text, $class, $attribs=array() ) {
257                 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
258         }
259
260         /**
261          * Shortcut to make a specific element with a class attribute
262          * @param $text content of the element, will be escaped
263          * @param $class class name of the span element
264          * @param $tag element name
265          * @param $attribs other attributes
266          * @return string
267          */
268         public static function wrapClass( $text, $class, $tag='span', $attribs=array() ) {
269                 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
270         }
271
272         /**
273          * Convenience function to build an HTML text input field
274          * @param $name String value of the name attribute
275          * @param $size value of the size attribute
276          * @param $value value of the value attribute
277          * @param $attribs other attributes
278          * @return string HTML
279          */
280         public static function input( $name, $size=false, $value=false, $attribs=array() ) {
281                 $attributes = array( 'name' => $name );
282
283                 if( $size ) {
284                         $attributes['size'] = $size;
285                 }
286
287                 if( $value !== false ) { // maybe 0
288                         $attributes['value'] = $value;
289                 }
290
291                 return self::element( 'input', $attributes + $attribs );
292         }
293
294         /**
295          * Convenience function to build an HTML password input field
296          * @param $name value of the name attribute
297          * @param $size value of the size attribute
298          * @param $value value of the value attribute
299          * @param $attribs other attributes
300          * @return string HTML
301          */
302         public static function password( $name, $size=false, $value=false, $attribs=array() ) {
303                 return self::input( $name, $size, $value, array_merge($attribs, array('type' => 'password')));
304         }
305
306         /**
307          * Internal function for use in checkboxes and radio buttons and such.
308          * @return array
309          */
310         public static function attrib( $name, $present = true ) {
311                 return $present ? array( $name => $name ) : array();
312         }
313
314         /**
315          * Convenience function to build an HTML checkbox
316          * @param $name String value of the name attribute
317          * @param $checked Bool Whether the checkbox is checked or not
318          * @param $attribs Array other attributes
319          * @return string HTML
320          */
321         public static function check( $name, $checked=false, $attribs=array() ) {
322                 return self::element( 'input', array_merge(
323                         array(
324                                 'name' => $name,
325                                 'type' => 'checkbox',
326                                 'value' => 1 ),
327                         self::attrib( 'checked', $checked ),
328                         $attribs ) );
329         }
330
331         /**
332          * Convenience function to build an HTML radio button
333          * @param $name value of the name attribute
334          * @param $value value of the value attribute
335          * @param $checked Whether the checkbox is checked or not
336          * @param $attribs other attributes
337          * @return string HTML
338          */
339         public static function radio( $name, $value, $checked=false, $attribs=array() ) {
340                 return self::element( 'input', array(
341                         'name' => $name,
342                         'type' => 'radio',
343                         'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
344         }
345
346         /**
347          * Convenience function to build an HTML form label
348          * @param $label String text of the label
349          * @param $id
350          * @param $attribs Array an attribute array.  This will usuall be 
351          *     the same array as is passed to the corresponding input element,
352          *     so this function will cherry-pick appropriate attributes to 
353          *     apply to the label as well; currently only class is applied.
354          * @return string HTML
355          */
356         public static function label( $label, $id, $attribs=array() ) {
357                 $a = array( 'for' => $id );
358                 if( isset( $attribs['class'] ) ){
359                                 $a['class'] = $attribs['class'];
360                 }
361                 return self::element( 'label', $a, $label );
362         }
363
364         /**
365          * Convenience function to build an HTML text input field with a label
366          * @param $label String text of the label
367          * @param $name String value of the name attribute
368          * @param $id String id of the input
369          * @param $size value of the size attribute
370          * @param $value value of the value attribute
371          * @param $attribs other attributes
372          * @return string HTML
373          */
374         public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
375                 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
376                 return $label . '&#160;' . $input;
377         }
378
379         /**
380          * Same as Xml::inputLabel() but return input and label in an array
381          */
382         public static function inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
383                 return array(
384                         Xml::label( $label, $id, $attribs ),
385                         self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
386                 );
387         }
388
389         /**
390          * Convenience function to build an HTML checkbox with a label
391          * @return string HTML
392          */
393         public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
394                 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
395                         '&#160;' .
396                         self::label( $label, $id, $attribs );
397         }
398
399         /**
400          * Convenience function to build an HTML radio button with a label
401          * @return string HTML
402          */
403         public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
404                 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
405                         '&#160;' .
406                         self::label( $label, $id, $attribs );
407         }
408
409         /**
410          * Convenience function to build an HTML submit button
411          * @param $value String: label text for the button
412          * @param $attribs Array: optional custom attributes
413          * @return string HTML
414          */
415         public static function submitButton( $value, $attribs=array() ) {
416                 return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
417         }
418
419         /**
420          * @deprecated Synonymous to Html::hidden()
421          */
422         public static function hidden( $name, $value, $attribs = array() ) {
423                 return Html::hidden( $name, $value, $attribs );
424         }
425
426         /**
427          * Convenience function to build an HTML drop-down list item.
428          * @param $text String: text for this item
429          * @param $value String: form submission value; if empty, use text
430          * @param $selected boolean: if true, will be the default selected item
431          * @param $attribs array: optional additional HTML attributes
432          * @return string HTML
433          */
434         public static function option( $text, $value=null, $selected=false,
435                         $attribs=array() ) {
436                 if( !is_null( $value ) ) {
437                         $attribs['value'] = $value;
438                 }
439                 if( $selected ) {
440                         $attribs['selected'] = 'selected';
441                 }
442                 return self::element( 'option', $attribs, $text );
443         }
444
445         /**
446          * Build a drop-down box from a textual list.
447          *
448          * @param $name Mixed: Name and id for the drop-down
449          * @param $class Mixed: CSS classes for the drop-down
450          * @param $other Mixed: Text for the "Other reasons" option
451          * @param $list Mixed: Correctly formatted text to be used to generate the options
452          * @param $selected Mixed: Option which should be pre-selected
453          * @param $tabindex Mixed: Value of the tabindex attribute
454          * @return string
455          */
456         public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = Null ) {
457                 $optgroup = false;
458
459                 $options = self::option( $other, 'other', $selected === 'other' );
460
461                 foreach ( explode( "\n", $list ) as $option) {
462                                 $value = trim( $option );
463                                 if ( $value == '' ) {
464                                         continue;
465                                 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
466                                         // A new group is starting ...
467                                         $value = trim( substr( $value, 1 ) );
468                                         if( $optgroup ) $options .= self::closeElement('optgroup');
469                                         $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
470                                         $optgroup = true;
471                                 } elseif ( substr( $value, 0, 2) == '**' ) {
472                                         // groupmember
473                                         $value = trim( substr( $value, 2 ) );
474                                         $options .= self::option( $value, $value, $selected === $value );
475                                 } else {
476                                         // groupless reason list
477                                         if( $optgroup ) $options .= self::closeElement('optgroup');
478                                         $options .= self::option( $value, $value, $selected === $value );
479                                         $optgroup = false;
480                                 }
481                         }
482                         if( $optgroup ) $options .= self::closeElement('optgroup');
483
484                 $attribs = array();
485                 if( $name ) {
486                         $attribs['id'] = $name;
487                         $attribs['name'] = $name;
488                 }
489                 if( $class ) {
490                         $attribs['class'] = $class;
491                 }
492                 if( $tabindex ) {
493                         $attribs['tabindex'] = $tabindex;
494                 }
495                 return Xml::openElement( 'select', $attribs )
496                         . "\n"
497                         . $options
498                         . "\n"
499                         . Xml::closeElement( 'select' );
500         }
501
502         /**
503          * Shortcut for creating fieldsets.
504          *
505          * @param $legend Legend of the fieldset. If evaluates to false, legend is not added.
506          * @param $content Pre-escaped content for the fieldset. If false, only open fieldset is returned.
507          * @param $attribs Any attributes to fieldset-element.
508          */
509         public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
510                 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
511                 if ( $legend ) {
512                         $s .= Xml::element( 'legend', null, $legend ) . "\n";
513                 }
514                 if ( $content !== false ) {
515                         $s .= $content . "\n";
516                         $s .= Xml::closeElement( 'fieldset' ) . "\n";
517                 }
518
519                 return $s;
520         }
521
522         /**
523          * Shortcut for creating textareas.
524          *
525          * @param $name The 'name' for the textarea
526          * @param $content Content for the textarea
527          * @param $cols The number of columns for the textarea
528          * @param $rows The number of rows for the textarea
529          * @param $attribs Any other attributes for the textarea
530          */
531         public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
532                 return self::element( 'textarea',
533                                         array(  'name' => $name,
534                                                 'id' => $name,
535                                                 'cols' => $cols,
536                                                 'rows' => $rows
537                                         ) + $attribs,
538                                         $content, false );
539         }
540
541         /**
542          * Returns an escaped string suitable for inclusion in a string literal
543          * for JavaScript source code.
544          * Illegal control characters are assumed not to be present.
545          *
546          * @param $string String to escape
547          * @return String
548          */
549         public static function escapeJsString( $string ) {
550                 // See ECMA 262 section 7.8.4 for string literal format
551                 $pairs = array(
552                         "\\" => "\\\\",
553                         "\"" => "\\\"",
554                         '\'' => '\\\'',
555                         "\n" => "\\n",
556                         "\r" => "\\r",
557
558                         # To avoid closing the element or CDATA section
559                         "<" => "\\x3c",
560                         ">" => "\\x3e",
561
562                         # To avoid any complaints about bad entity refs
563                         "&" => "\\x26",
564
565                         # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
566                         # Encode certain Unicode formatting chars so affected
567                         # versions of Gecko don't misinterpret our strings;
568                         # this is a common problem with Farsi text.
569                         "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
570                         "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
571                 );
572                 return strtr( $string, $pairs );
573         }
574
575         /**
576          * Encode a variable of unknown type to JavaScript.
577          * Arrays are converted to JS arrays, objects are converted to JS associative
578          * arrays (objects). So cast your PHP associative arrays to objects before
579          * passing them to here.
580          */
581         public static function encodeJsVar( $value ) {
582                 if ( is_bool( $value ) ) {
583                         $s = $value ? 'true' : 'false';
584                 } elseif ( is_null( $value ) ) {
585                         $s = 'null';
586                 } elseif ( is_int( $value ) || is_float( $value ) ) {
587                         $s = strval($value);
588                 } elseif ( is_array( $value ) && // Make sure it's not associative.
589                                         array_keys($value) === range( 0, count($value) - 1 ) ||
590                                         count($value) == 0
591                                 ) {
592                         $s = '[';
593                         foreach ( $value as $elt ) {
594                                 if ( $s != '[' ) {
595                                         $s .= ', ';
596                                 }
597                                 $s .= self::encodeJsVar( $elt );
598                         }
599                         $s .= ']';
600                 } elseif ( $value instanceof XmlJsCode ) {
601                         $s = $value->value;
602                 } elseif ( is_object( $value ) || is_array( $value ) ) {
603                         // Objects and associative arrays
604                         $s = '{';
605                         foreach ( (array)$value as $name => $elt ) {
606                                 if ( $s != '{' ) {
607                                         $s .= ', ';
608                                 }
609                                 $s .= '"' . self::escapeJsString( $name ) . '": ' .
610                                         self::encodeJsVar( $elt );
611                         }
612                         $s .= '}';
613                 } else {
614                         $s = '"' . self::escapeJsString( $value ) . '"';
615                 }
616                 return $s;
617         }
618
619         /**
620          * Create a call to a JavaScript function. The supplied arguments will be 
621          * encoded using Xml::encodeJsVar(). 
622          *
623          * @param $name The name of the function to call, or a JavaScript expression
624          *    which evaluates to a function object which is called.
625          * @param $args Array of arguments to pass to the function.
626          * @since 1.17
627          */
628         public static function encodeJsCall( $name, $args ) {
629                 $s = "$name(";
630                 $first = true;
631                 foreach ( $args as $arg ) {
632                         if ( $first ) {
633                                 $first = false;
634                         } else {
635                                 $s .= ', ';
636                         }
637                         $s .= Xml::encodeJsVar( $arg );
638                 }
639                 $s .= ");\n";
640                 return $s;
641         }
642
643
644         /**
645          * Check if a string is well-formed XML.
646          * Must include the surrounding tag.
647          *
648          * @param $text String: string to test.
649          * @return bool
650          *
651          * @todo Error position reporting return
652          */
653         public static function isWellFormed( $text ) {
654                 $parser = xml_parser_create( "UTF-8" );
655
656                 # case folding violates XML standard, turn it off
657                 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
658
659                 if( !xml_parse( $parser, $text, true ) ) {
660                         //$err = xml_error_string( xml_get_error_code( $parser ) );
661                         //$position = xml_get_current_byte_index( $parser );
662                         //$fragment = $this->extractFragment( $html, $position );
663                         //$this->mXmlError = "$err at byte $position:\n$fragment";
664                         xml_parser_free( $parser );
665                         return false;
666                 }
667                 xml_parser_free( $parser );
668                 return true;
669         }
670
671         /**
672          * Check if a string is a well-formed XML fragment.
673          * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
674          * and can use HTML named entities.
675          *
676          * @param $text String:
677          * @return bool
678          */
679         public static function isWellFormedXmlFragment( $text ) {
680                 $html =
681                         Sanitizer::hackDocType() .
682                         '<html>' .
683                         $text .
684                         '</html>';
685                 return Xml::isWellFormed( $html );
686         }
687
688         /**
689          * Replace " > and < with their respective HTML entities ( &quot;,
690          * &gt;, &lt;)
691          *
692          * @param $in String: text that might contain HTML tags.
693          * @return string Escaped string
694          */
695         public static function escapeTagsOnly( $in ) {
696                 return str_replace(
697                         array( '"', '>', '<' ),
698                         array( '&quot;', '&gt;', '&lt;' ),
699                         $in );
700         }
701
702         /**
703         * Generate a form (without the opening form element).
704         * Output optionally includes a submit button.
705         * @param $fields Array Associative array, key is message corresponding to a description for the field (colon is in the message), value is appropriate input.
706         * @param $submitLabel String A message containing a label for the submit button.
707         * @return string HTML form.
708         */
709         public static function buildForm( $fields, $submitLabel = null ) {
710                 $form = '';
711                 $form .= "<table><tbody>";
712
713                 foreach( $fields as $labelmsg => $input ) {
714                         $id = "mw-$labelmsg";
715                         $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
716                         $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMsgExt( $labelmsg, array('parseinline') ) );
717                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
718                         $form .= Xml::closeElement( 'tr' );
719                 }
720
721                 if( $submitLabel ) {
722                         $form .= Xml::openElement( 'tr' );
723                         $form .= Xml::tags( 'td', array(), '' );
724                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' );
725                         $form .= Xml::closeElement( 'tr' );
726                 }
727
728                 $form .= "</tbody></table>";
729
730
731                 return $form;
732         }
733
734         /**
735          * Build a table of data
736          * @param $rows An array of arrays of strings, each to be a row in a table
737          * @param $attribs An array of attributes to apply to the table tag [optional]
738          * @param $headers An array of strings to use as table headers [optional]
739          * @return string
740          */
741         public static function buildTable( $rows, $attribs = array(), $headers = null ) {
742                 $s = Xml::openElement( 'table', $attribs );
743                 if ( is_array( $headers ) ) {
744                         foreach( $headers as $id => $header ) {
745                                 $attribs = array();
746                                 if ( is_string( $id ) ) $attribs['id'] = $id;
747                                 $s .= Xml::element( 'th', $attribs, $header );
748                         }
749                 }
750                 foreach( $rows as $id => $row ) {
751                         $attribs = array();
752                         if ( is_string( $id ) ) $attribs['id'] = $id;
753                         $s .= Xml::buildTableRow( $attribs, $row );
754                 }
755                 $s .= Xml::closeElement( 'table' );
756                 return $s;
757         }
758
759         /**
760          * Build a row for a table
761          * @param $attribs An array of attributes to apply to the tr tag
762          * @param $cells An array of strings to put in <td>
763          * @return string
764          */
765         public static function buildTableRow( $attribs, $cells ) {
766                 $s = Xml::openElement( 'tr', $attribs );
767                 foreach( $cells as $id => $cell ) {
768                         $attribs = array();
769                         if ( is_string( $id ) ) $attribs['id'] = $id;
770                         $s .= Xml::element( 'td', $attribs, $cell );
771                 }
772                 $s .= Xml::closeElement( 'tr' );
773                 return $s;
774         }
775 }
776
777 class XmlSelect {
778         protected $options = array();
779         protected $default = false;
780         protected $attributes = array();
781
782         public function __construct( $name = false, $id = false, $default = false ) {
783                 if ( $name ) {
784                         $this->setAttribute( 'name', $name );
785                 }
786                 if ( $id ) {
787                         $this->setAttribute( 'id', $id );
788                 }
789                 if ( $default !== false ) {
790                         $this->default = $default;
791                 }
792         }
793
794         public function setDefault( $default ) {
795                 $this->default = $default;
796         }
797
798         public function setAttribute( $name, $value ) {
799                 $this->attributes[$name] = $value;
800         }
801
802         public function getAttribute( $name ) {
803                 if ( isset($this->attributes[$name]) ) {
804                         return $this->attributes[$name];
805                 } else {
806                         return null;
807                 }
808         }
809
810         public function addOption( $name, $value = false ) {
811                 // Stab stab stab
812                 $value = ($value !== false) ? $value : $name;
813                 $this->options[] = Xml::option( $name, $value, $value === $this->default );
814         }
815
816         // This accepts an array of form
817         // label => value
818         // label => ( label => value, label => value )
819         public function addOptions( $options ) {
820                 $this->options[] = trim(self::formatOptions( $options, $this->default ));
821         }
822
823         // This accepts an array of form
824         // label => value
825         // label => ( label => value, label => value )
826         static function formatOptions( $options, $default = false ) {
827                 $data = '';
828                 foreach( $options as $label => $value ) {
829                         if ( is_array( $value ) ) {
830                                 $contents = self::formatOptions( $value, $default );
831                                 $data .= Xml::tags( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
832                         } else {
833                                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
834                         }
835                 }
836
837                 return $data;
838         }
839
840         public function getHTML() {
841                 return Xml::tags( 'select', $this->attributes, implode( "\n", $this->options ) );
842         }
843
844 }
845
846 /**
847  * A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to 
848  * interpret a given string as being a JavaScript expression, instead of string 
849  * data.
850  *
851  * Example:
852  *
853  *    Xml::encodeJsVar( new XmlJsCode( 'a + b' ) );
854  *
855  * Returns "a + b".
856  * @since 1.17
857  */
858 class XmlJsCode {
859         public $value;
860
861         function __construct( $value ) {
862                 $this->value = $value;
863         }
864 }