MediaWiki REL1_39
ApiFormatXml.php
Go to the documentation of this file.
1<?php
24
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: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:1372
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)
Service for formatting and validating API parameters.
$content
Definition router.php:76