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 ( $this->mXslt !== null ) {
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' => static 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  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
175  $retval .= $indstr . Xml::element( $name, $attributes, $content );
176  } else {
177  if ( $name !== null ) {
178  $retval .= $indstr . Xml::element( $name, $attributes, null );
179  }
180  $retval .= static::recXmlPrint( null, $content, $indent );
181  if ( $name !== null ) {
182  $retval .= $indstr . Xml::closeElement( $name );
183  }
184  }
185  } elseif ( !$indexedSubelements && !$subelements ) {
186  if ( $name !== null ) {
187  $retval .= $indstr . Xml::element( $name, $attributes );
188  }
189  } else {
190  if ( $name !== null ) {
191  $retval .= $indstr . Xml::element( $name, $attributes, null );
192  }
193  foreach ( $subelements as $k => $v ) {
194  $retval .= static::recXmlPrint( $k, $v, $indent );
195  }
196  foreach ( $indexedSubelements as $k => $v ) {
197  $retval .= static::recXmlPrint( $indexedTagName, $v, $indent,
198  $indexSubelements ? [ '_idx' => $k ] : []
199  );
200  }
201  if ( $name !== null ) {
202  $retval .= $indstr . Xml::closeElement( $name );
203  }
204  }
205  } else {
206  // to make sure null value doesn't produce unclosed element,
207  // which is what Xml::element( $name, null, null ) returns
208  if ( $value === null ) {
209  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
210  $retval .= $indstr . Xml::element( $name, $attributes );
211  } else {
212  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
213  $retval .= $indstr . Xml::element( $name, $attributes, $value );
214  }
215  }
216 
217  return $retval;
218  }
219 
226  private static function mangleName( $name, $preserveKeys = [] ) {
227  static $nsc = null, $nc = null;
228 
229  if ( in_array( $name, $preserveKeys, true ) ) {
230  return $name;
231  }
232 
233  if ( $name === '' ) {
234  return '_';
235  }
236 
237  if ( $nsc === null ) {
238  // Note we omit ':' from $nsc and $nc because it's reserved for XML
239  // namespacing, and we omit '_' from $nsc (but not $nc) because we
240  // reserve it.
241  $nsc = 'A-Za-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}' .
242  '\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}' .
243  '\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
244  $nc = $nsc . '_\-.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}';
245  }
246 
247  if ( preg_match( "/^[$nsc][$nc]*$/uS", $name ) ) {
248  return $name;
249  }
250 
251  return '_' . preg_replace_callback(
252  "/[^$nc]/uS",
253  static function ( $m ) {
254  return sprintf( '.%X.', UtfNormal\Utils::utf8ToCodepoint( $m[0] ) );
255  },
256  str_replace( '.', '.2E.', $name )
257  );
258  }
259 
260  protected function addXslt() {
261  $nt = Title::newFromText( $this->mXslt );
262  if ( $nt === null || !$nt->exists() ) {
263  $this->addWarning( 'apiwarn-invalidxmlstylesheet' );
264 
265  return;
266  }
267  if ( $nt->getNamespace() !== NS_MEDIAWIKI ) {
268  $this->addWarning( 'apiwarn-invalidxmlstylesheetns' );
269 
270  return;
271  }
272  if ( substr( $nt->getText(), -4 ) !== '.xsl' ) {
273  $this->addWarning( 'apiwarn-invalidxmlstylesheetext' );
274 
275  return;
276  }
277  $this->printText( '<?xml-stylesheet href="' .
278  htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
279  }
280 
281  public function getAllowedParams() {
282  return parent::getAllowedParams() + [
283  'xslt' => [
284  ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-xslt',
285  ],
286  'includexmlnamespace' => [
287  ApiBase::PARAM_DFLT => false,
288  ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-includexmlnamespace',
289  ],
290  ];
291  }
292 }
const NS_MEDIAWIKI
Definition: Defines.php:72
const PARAM_DFLT
Definition: ApiBase.php:74
getResult()
Get the result object.
Definition: ApiBase.php:629
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:765
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:163
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1364
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.
static mangleName( $name, $preserveKeys=[])
Mangle XML-invalid names to be valid in XML.
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:369
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