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