MediaWiki master
ApiFormatXml.php
Go to the documentation of this file.
1<?php
25
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() {
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
array $params
The job parameters.
getResult()
Get the result object.
Definition ApiBase.php:671
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:811
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1451
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:78
Service for formatting and validating API parameters.