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