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