MediaWiki  master
ApiFormatXml.php
Go to the documentation of this file.
1 <?php
27 class ApiFormatXml extends ApiFormatBase {
28 
29  private $mRootElemName = 'api';
30  public static $namespace = 'http://www.mediawiki.org/xml/api/';
31  private $mIncludeNamespace = false;
32  private $mXslt = null;
33 
34  public function getMimeType() {
35  return 'text/xml';
36  }
37 
38  public function setRootElement( $rootElemName ) {
39  $this->mRootElemName = $rootElemName;
40  }
41 
42  public function execute() {
43  $params = $this->extractRequestParams();
44  $this->mIncludeNamespace = $params['includexmlnamespace'];
45  $this->mXslt = $params['xslt'];
46 
47  $this->printText( '<?xml version="1.0"?>' );
48  if ( !is_null( $this->mXslt ) ) {
49  $this->addXslt();
50  }
51 
52  $result = $this->getResult();
53  if ( $this->mIncludeNamespace && $result->getResultData( 'xmlns' ) === null ) {
54  // If the result data already contains an 'xmlns' namespace added
55  // for custom XML output types, it will override the one for the
56  // generic API results.
57  // This allows API output of other XML types like Atom, RSS, RSD.
58  $result->addValue( null, 'xmlns', self::$namespace, ApiResult::NO_SIZE_CHECK );
59  }
60  $data = $result->getResultData( null, [
61  'Custom' => function ( &$data, &$metadata ) {
62  if ( isset( $metadata[ApiResult::META_TYPE] ) ) {
63  // We want to use non-BC for BCassoc to force outputting of _idx.
64  switch ( $metadata[ApiResult::META_TYPE] ) {
65  case 'BCassoc':
66  $metadata[ApiResult::META_TYPE] = 'assoc';
67  break;
68  }
69  }
70  },
71  'BC' => [ 'nobool', 'no*', 'nosub' ],
72  'Types' => [ 'ArmorKVP' => '_name' ],
73  ] );
74 
75  $this->printText(
76  static::recXmlPrint( $this->mRootElemName,
77  $data,
78  $this->getIsHtml() ? -2 : null
79  )
80  );
81  }
82 
92  public static function recXmlPrint( $name, $value, $indent, $attributes = [] ) {
93  $retval = '';
94  if ( $indent !== null ) {
95  if ( $name !== null ) {
96  $indent += 2;
97  }
98  $indstr = "\n" . str_repeat( ' ', $indent );
99  } else {
100  $indstr = '';
101  }
102 
103  if ( is_object( $value ) ) {
104  $value = (array)$value;
105  }
106  if ( is_array( $value ) ) {
107  $contentKey = $value[ApiResult::META_CONTENT] ?? '*';
108  $subelementKeys = $value[ApiResult::META_SUBELEMENTS] ?? [];
109  if ( isset( $value[ApiResult::META_BC_SUBELEMENTS] ) ) {
110  $subelementKeys = array_merge(
111  $subelementKeys, $value[ApiResult::META_BC_SUBELEMENTS]
112  );
113  }
114  $preserveKeys = $value[ApiResult::META_PRESERVE_KEYS] ?? [];
115  $indexedTagName = isset( $value[ApiResult::META_INDEXED_TAG_NAME] )
116  ? self::mangleName( $value[ApiResult::META_INDEXED_TAG_NAME], $preserveKeys )
117  : '_v';
118  $bcBools = $value[ApiResult::META_BC_BOOLS] ?? [];
119  $indexSubelements = isset( $value[ApiResult::META_TYPE] )
120  ? $value[ApiResult::META_TYPE] !== 'array'
121  : false;
122 
123  $content = null;
124  $subelements = [];
125  $indexedSubelements = [];
126  foreach ( $value as $k => $v ) {
127  if ( ApiResult::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
128  continue;
129  }
130 
131  $oldv = $v;
132  if ( is_bool( $v ) && !in_array( $k, $bcBools, true ) ) {
133  $v = $v ? 'true' : 'false';
134  }
135 
136  if ( $name !== null && $k === $contentKey ) {
137  $content = $v;
138  } elseif ( is_int( $k ) ) {
139  $indexedSubelements[$k] = $v;
140  } elseif ( is_array( $v ) || is_object( $v ) ) {
141  $subelements[self::mangleName( $k, $preserveKeys )] = $v;
142  } elseif ( in_array( $k, $subelementKeys, true ) || $name === null ) {
143  $subelements[self::mangleName( $k, $preserveKeys )] = [
144  'content' => $v,
145  ApiResult::META_CONTENT => 'content',
146  ApiResult::META_TYPE => 'assoc',
147  ];
148  } elseif ( is_bool( $oldv ) ) {
149  if ( $oldv ) {
150  $attributes[self::mangleName( $k, $preserveKeys )] = '';
151  }
152  } elseif ( $v !== null ) {
153  $attributes[self::mangleName( $k, $preserveKeys )] = $v;
154  }
155  }
156 
157  if ( $content !== null ) {
158  if ( $subelements || $indexedSubelements ) {
159  $subelements[self::mangleName( $contentKey, $preserveKeys )] = [
160  'content' => $content,
161  ApiResult::META_CONTENT => 'content',
162  ApiResult::META_TYPE => 'assoc',
163  ];
164  $content = null;
165  } elseif ( is_scalar( $content ) ) {
166  // Add xml:space="preserve" to the element so XML parsers
167  // will leave whitespace in the content alone
168  $attributes += [ 'xml:space' => 'preserve' ];
169  }
170  }
171 
172  if ( $content !== null ) {
173  if ( is_scalar( $content ) ) {
174  $retval .= $indstr . Xml::element( $name, $attributes, $content );
175  } else {
176  if ( $name !== null ) {
177  $retval .= $indstr . Xml::element( $name, $attributes, null );
178  }
179  $retval .= static::recXmlPrint( null, $content, $indent );
180  if ( $name !== null ) {
181  $retval .= $indstr . Xml::closeElement( $name );
182  }
183  }
184  } elseif ( !$indexedSubelements && !$subelements ) {
185  if ( $name !== null ) {
186  $retval .= $indstr . Xml::element( $name, $attributes );
187  }
188  } else {
189  if ( $name !== null ) {
190  $retval .= $indstr . Xml::element( $name, $attributes, null );
191  }
192  foreach ( $subelements as $k => $v ) {
193  $retval .= static::recXmlPrint( $k, $v, $indent );
194  }
195  foreach ( $indexedSubelements as $k => $v ) {
196  $retval .= static::recXmlPrint( $indexedTagName, $v, $indent,
197  $indexSubelements ? [ '_idx' => $k ] : []
198  );
199  }
200  if ( $name !== null ) {
201  $retval .= $indstr . Xml::closeElement( $name );
202  }
203  }
204  } else {
205  // to make sure null value doesn't produce unclosed element,
206  // which is what Xml::element( $name, null, null ) returns
207  if ( $value === null ) {
208  $retval .= $indstr . Xml::element( $name, $attributes );
209  } else {
210  $retval .= $indstr . Xml::element( $name, $attributes, $value );
211  }
212  }
213 
214  return $retval;
215  }
216 
223  private static function mangleName( $name, $preserveKeys = [] ) {
224  static $nsc = null, $nc = null;
225 
226  if ( in_array( $name, $preserveKeys, true ) ) {
227  return $name;
228  }
229 
230  if ( $name === '' ) {
231  return '_';
232  }
233 
234  if ( $nsc === null ) {
235  // Note we omit ':' from $nsc and $nc because it's reserved for XML
236  // namespacing, and we omit '_' from $nsc (but not $nc) because we
237  // reserve it.
238  $nsc = 'A-Za-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}' .
239  '\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}' .
240  '\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
241  $nc = $nsc . '_\-.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}';
242  }
243 
244  if ( preg_match( "/^[$nsc][$nc]*$/uS", $name ) ) {
245  return $name;
246  }
247 
248  return '_' . preg_replace_callback(
249  "/[^$nc]/uS",
250  function ( $m ) {
251  return sprintf( '.%X.', UtfNormal\Utils::utf8ToCodepoint( $m[0] ) );
252  },
253  str_replace( '.', '.2E.', $name )
254  );
255  }
256 
257  protected function addXslt() {
258  $nt = Title::newFromText( $this->mXslt );
259  if ( is_null( $nt ) || !$nt->exists() ) {
260  $this->addWarning( 'apiwarn-invalidxmlstylesheet' );
261 
262  return;
263  }
264  if ( $nt->getNamespace() != NS_MEDIAWIKI ) {
265  $this->addWarning( 'apiwarn-invalidxmlstylesheetns' );
266 
267  return;
268  }
269  if ( substr( $nt->getText(), -4 ) !== '.xsl' ) {
270  $this->addWarning( 'apiwarn-invalidxmlstylesheetext' );
271 
272  return;
273  }
274  $this->printText( '<?xml-stylesheet href="' .
275  htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
276  }
277 
278  public function getAllowedParams() {
279  return parent::getAllowedParams() + [
280  'xslt' => [
281  ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-xslt',
282  ],
283  'includexmlnamespace' => [
284  ApiBase::PARAM_DFLT => false,
285  ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-includexmlnamespace',
286  ],
287  ];
288  }
289 }
API XML output formatter.
getResult()
Get the result object.
Definition: ApiBase.php:640
static $namespace
setRootElement( $rootElemName)
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
const META_TYPE
Key for the &#39;type&#39; metadata item.
Definition: ApiResult.php:110
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Definition: ApiResult.php:793
printText( $text)
Append text to the output buffer.
This is the abstract base class for API formatters.
const META_PRESERVE_KEYS
Key for the &#39;preserve keys&#39; metadata item.
Definition: ApiResult.php:84
getIsHtml()
Returns true when the HTML pretty-printer should be used.
static mangleName( $name, $preserveKeys=[])
Mangle XML-invalid names to be valid in XML.
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don&#39;t use this unless yo...
Definition: ApiResult.php:58
const NS_MEDIAWIKI
Definition: Defines.php:68
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:131
static recXmlPrint( $name, $value, $indent, $attributes=[])
This method takes an array and converts it to XML.
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
const META_INDEXED_TAG_NAME
Key for the &#39;indexed tag name&#39; metadata item.
Definition: ApiResult.php:72
const META_SUBELEMENTS
Key for the &#39;subelements&#39; metadata item.
Definition: ApiResult.php:78
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1924
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
const META_BC_BOOLS
Key for the &#39;BC bools&#39; metadata item.
Definition: ApiResult.php:136
const META_CONTENT
Key for the &#39;content&#39; metadata item.
Definition: ApiResult.php:90
$content
Definition: router.php:78
const META_BC_SUBELEMENTS
Key for the &#39;BC subelements&#39; metadata item.
Definition: ApiResult.php:143
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319