MediaWiki master
ApiFormatXml.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
13
19
21 private $mRootElemName = 'api';
23 public static $namespace = 'http://www.mediawiki.org/xml/api/';
25 private $mIncludeNamespace = false;
26
28 public function getMimeType() {
29 return 'text/xml';
30 }
31
32 public function setRootElement( string $rootElemName ) {
33 $this->mRootElemName = $rootElemName;
34 }
35
36 public function execute() {
37 $params = $this->extractRequestParams();
38 $this->mIncludeNamespace = $params['includexmlnamespace'];
39
40 $this->printText( '<?xml version="1.0"?>' );
41
42 $result = $this->getResult();
43 if ( $this->mIncludeNamespace && $result->getResultData( 'xmlns' ) === null ) {
44 // If the result data already contains an 'xmlns' namespace added
45 // for custom XML output types, it will override the one for the
46 // generic API results.
47 // This allows API output of other XML types like Atom, RSS, RSD.
48 $result->addValue( null, 'xmlns', self::$namespace, ApiResult::NO_SIZE_CHECK );
49 }
50 $data = $result->getResultData( null, [
51 'Custom' => static function ( &$data, &$metadata ) {
52 if ( isset( $metadata[ApiResult::META_TYPE] ) ) {
53 // We want to use non-BC for BCassoc to force outputting of _idx.
54 switch ( $metadata[ApiResult::META_TYPE] ) {
55 case 'BCassoc':
56 $metadata[ApiResult::META_TYPE] = 'assoc';
57 break;
58 }
59 }
60 },
61 'BC' => [ 'nobool', 'no*', 'nosub' ],
62 'Types' => [ 'ArmorKVP' => '_name' ],
63 ] );
64
65 $this->printText(
66 static::recXmlPrint( $this->mRootElemName,
67 $data,
68 $this->getIsHtml() ? -2 : null
69 )
70 );
71 }
72
82 public static function recXmlPrint( $name, $value, $indent, $attributes = [] ) {
83 $retval = '';
84 if ( $indent !== null ) {
85 if ( $name !== null ) {
86 $indent += 2;
87 }
88 $indstr = "\n" . str_repeat( ' ', $indent );
89 } else {
90 $indstr = '';
91 }
92
93 if ( is_object( $value ) ) {
94 $value = (array)$value;
95 }
96 if ( is_array( $value ) ) {
97 $contentKey = $value[ApiResult::META_CONTENT] ?? '*';
98 $subelementKeys = $value[ApiResult::META_SUBELEMENTS] ?? [];
99 if ( isset( $value[ApiResult::META_BC_SUBELEMENTS] ) ) {
100 $subelementKeys = array_merge(
101 $subelementKeys, $value[ApiResult::META_BC_SUBELEMENTS]
102 );
103 }
104 $preserveKeys = $value[ApiResult::META_PRESERVE_KEYS] ?? [];
105 $indexedTagName = isset( $value[ApiResult::META_INDEXED_TAG_NAME] )
106 ? self::mangleName( $value[ApiResult::META_INDEXED_TAG_NAME], $preserveKeys )
107 : '_v';
108 $bcBools = $value[ApiResult::META_BC_BOOLS] ?? [];
109 $indexSubelements = isset( $value[ApiResult::META_TYPE] )
110 && $value[ApiResult::META_TYPE] !== 'array';
111
112 $content = null;
113 $subelements = [];
114 $indexedSubelements = [];
115 foreach ( $value as $k => $v ) {
116 if ( ApiResult::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
117 continue;
118 }
119
120 $oldv = $v;
121 if ( is_bool( $v ) && !in_array( $k, $bcBools, true ) ) {
122 $v = $v ? 'true' : 'false';
123 }
124
125 if ( $name !== null && $k === $contentKey ) {
126 $content = $v;
127 } elseif ( is_int( $k ) ) {
128 $indexedSubelements[$k] = $v;
129 } elseif ( is_array( $v ) || is_object( $v ) ) {
130 $subelements[self::mangleName( $k, $preserveKeys )] = $v;
131 } elseif ( in_array( $k, $subelementKeys, true ) || $name === null ) {
132 $subelements[self::mangleName( $k, $preserveKeys )] = [
133 'content' => $v,
134 ApiResult::META_CONTENT => 'content',
135 ApiResult::META_TYPE => 'assoc',
136 ];
137 } elseif ( is_bool( $oldv ) ) {
138 if ( $oldv ) {
139 $attributes[self::mangleName( $k, $preserveKeys )] = '';
140 }
141 } elseif ( $v !== null ) {
142 $attributes[self::mangleName( $k, $preserveKeys )] = $v;
143 }
144 }
145
146 if ( $content !== null ) {
147 if ( $subelements || $indexedSubelements ) {
148 $subelements[self::mangleName( $contentKey, $preserveKeys )] = [
149 'content' => $content,
150 ApiResult::META_CONTENT => 'content',
151 ApiResult::META_TYPE => 'assoc',
152 ];
153 $content = null;
154 } elseif ( is_scalar( $content ) ) {
155 // Add xml:space="preserve" to the element so XML parsers
156 // will leave whitespace in the content alone
157 $attributes += [ 'xml:space' => 'preserve' ];
158 }
159 }
160
161 if ( $content !== null ) {
162 if ( is_scalar( $content ) ) {
163 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
164 $retval .= $indstr . Xml::element( $name, $attributes, $content );
165 } else {
166 if ( $name !== null ) {
167 $retval .= $indstr . Xml::element( $name, $attributes, null );
168 }
169 $retval .= static::recXmlPrint( null, $content, $indent );
170 if ( $name !== null ) {
171 $retval .= $indstr . Xml::closeElement( $name );
172 }
173 }
174 } elseif ( !$indexedSubelements && !$subelements ) {
175 if ( $name !== null ) {
176 $retval .= $indstr . Xml::element( $name, $attributes );
177 }
178 } else {
179 if ( $name !== null ) {
180 $retval .= $indstr . Xml::element( $name, $attributes, null );
181 }
182 foreach ( $subelements as $k => $v ) {
183 $retval .= static::recXmlPrint( $k, $v, $indent );
184 }
185 foreach ( $indexedSubelements as $k => $v ) {
186 $retval .= static::recXmlPrint( $indexedTagName, $v, $indent,
187 $indexSubelements ? [ '_idx' => $k ] : []
188 );
189 }
190 if ( $name !== null ) {
191 $retval .= $indstr . Xml::closeElement( $name );
192 }
193 }
194 } else {
195 // to make sure null value doesn't produce unclosed element,
196 // which is what Xml::element( $name, null, null ) returns
197 if ( $value === null ) {
198 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
199 $retval .= $indstr . Xml::element( $name, $attributes );
200 } else {
201 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable name is check for null in other code
202 $retval .= $indstr . Xml::element( $name, $attributes, $value );
203 }
204 }
205
206 return $retval;
207 }
208
215 private static function mangleName( $name, $preserveKeys = [] ) {
216 static $nsc = null, $nc = null;
217
218 if ( in_array( $name, $preserveKeys, true ) ) {
219 return $name;
220 }
221
222 if ( $name === '' ) {
223 return '_';
224 }
225
226 if ( $nsc === null ) {
227 // Note we omit ':' from $nsc and $nc because it's reserved for XML
228 // namespacing, and we omit '_' from $nsc (but not $nc) because we
229 // reserve it.
230 $nsc = 'A-Za-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}' .
231 '\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}' .
232 '\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
233 $nc = $nsc . '_\-.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}';
234 }
235
236 if ( preg_match( "/^[$nsc][$nc]*$/uS", $name ) ) {
237 return $name;
238 }
239
240 return '_' . preg_replace_callback(
241 "/[^$nc]/uS",
242 static function ( $m ) {
243 return sprintf( '.%X.', \UtfNormal\Utils::utf8ToCodepoint( $m[0] ) );
244 },
245 str_replace( '.', '.2E.', $name )
246 );
247 }
248
250 public function getAllowedParams() {
251 return parent::getAllowedParams() + [
252 'includexmlnamespace' => [
253 ParamValidator::PARAM_DEFAULT => false,
254 ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-includexmlnamespace',
255 ],
256 ];
257 }
258}
259
261class_alias( ApiFormatXml::class, 'ApiFormatXml' );
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
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(string $rootElemName)
getMimeType()
Overriding class returns the MIME type that should be sent to the client.When getIsHtml() returns tru...
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:56
const META_PRESERVE_KEYS
Key for the 'preserve keys' metadata item.
Definition ApiResult.php:89
static isMetadataKey( $key)
Test whether a key should be considered metadata.
const META_SUBELEMENTS
Key for the 'subelements' metadata item.
Definition ApiResult.php:83
const META_INDEXED_TAG_NAME
Key for the 'indexed tag name' metadata item.
Definition ApiResult.php:77
const META_CONTENT
Key for the 'content' metadata item.
Definition ApiResult.php:95
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
const META_TYPE
Key for the 'type' metadata item.
Module of static functions for generating XML.
Definition Xml.php:19
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:36
static closeElement( $element)
Shortcut to close an XML element.
Definition Xml.php:111
Service for formatting and validating API parameters.
Language name search API.