Xml.php 24 KB

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