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