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