‪TYPO3CMS  ‪main
Typo3XmlSerializer.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
21 
121 {
134  array $input,
135  ‪Typo3XmlParserOptions $options = null,
136  array $additionalOptions = []
137  ): string {
138  try {
139  return $this->‪encode($input, $options, $additionalOptions);
140  } catch (\Throwable $e) {
141  return $e->getMessage();
142  }
143  }
144 
151  public function ‪encode(
152  array $input,
153  ‪Typo3XmlParserOptions $options = null,
154  array $additionalOptions = []
155  ): string {
156  $options = $options ?? new ‪Typo3XmlParserOptions();
157  return $this->‪parseArray(
158  $input,
159  $options,
160  $additionalOptions
161  );
162  }
163 
164  protected function ‪parseArray(
165  array $input,
166  ‪Typo3XmlParserOptions $options,
167  array $additionalOptions,
168  int $level = 0,
169  array $stackData = []
170  ): string {
171  $xml = '';
172 
173  $rootNodeName = $options->‪getRootNodeName();
174  if (empty($rootNodeName)) {
175  $indentation = str_repeat($options->‪getIndentationStep(), $level);
176  } else {
177  $indentation = str_repeat($options->‪getIndentationStep(), $level + 1);
178  }
179 
180  foreach ($input as $key => $value) {
181  // Construct the node name + attributes
182  $nodeName = $key = (string)$key;
183  $nodeAttributes = '';
184  if (isset(
185  $stackData['grandParentTagName'],
186  $stackData['parentTagName'],
187  $additionalOptions['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']]
188  )) {
189  // ... based on grand-parent + parent node name
190  $nodeName = (string)$additionalOptions['grandParentTagMap'][$stackData['grandParentTagName'] . '/' . $stackData['parentTagName']];
191  $nodeAttributes = ' index="' . htmlspecialchars($key) . '"';
192  } elseif (isset(
193  $stackData['parentTagName'],
194  $additionalOptions['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM']
196  ) {
197  // ... based on parent node name + if current node name is numeric
198  $nodeName = (string)$additionalOptions['parentTagMap'][$stackData['parentTagName'] . ':_IS_NUM'];
199  $nodeAttributes = ' index="' . htmlspecialchars($key) . '"';
200  } elseif (isset(
201  $stackData['parentTagName'],
202  $additionalOptions['parentTagMap'][$stackData['parentTagName'] . ':' . $nodeName]
203  )) {
204  // ... based on parent node name + current node name
205  $nodeName = (string)$additionalOptions['parentTagMap'][$stackData['parentTagName'] . ':' . $nodeName];
206  $nodeAttributes = ' index="' . htmlspecialchars($key) . '"';
207  } elseif (isset(
208  $stackData['parentTagName'],
209  $additionalOptions['parentTagMap'][$stackData['parentTagName']]
210  )) {
211  // ... based on parent node name
212  $nodeName = (string)$additionalOptions['parentTagMap'][$stackData['parentTagName']];
213  $nodeAttributes = ' index="' . htmlspecialchars($key) . '"';
214  } elseif (‪MathUtility::canBeInterpretedAsInteger($nodeName)) {
215  // ... if current node name is numeric
216  if ($additionalOptions['useNindex'] ?? false) {
217  $nodeName = 'n' . $nodeName;
218  } else {
219  $nodeName = ($additionalOptions['useIndexTagForNum'] ?? false) ?: 'numIndex';
220  $nodeAttributes = ' index="' . $key . '"';
221  }
222  } elseif (!empty($additionalOptions['useIndexTagForAssoc'])) {
223  // ... if current node name is string
224  $nodeName = $additionalOptions['useIndexTagForAssoc'];
225  $nodeAttributes = ' index="' . htmlspecialchars($key) . '"';
226  }
227  $nodeName = $this->‪cleanUpNodeName($nodeName);
228 
229  // Construct the node value
230  if (is_array($value)) {
231  // ... if has sub elements
232  if (isset($additionalOptions['alt_options'])
233  && ($additionalOptions['alt_options'][($stackData['path'] ?? '') . '/' . $nodeName] ?? false)
234  ) {
235  $subOptions = $additionalOptions['alt_options'][($stackData['path'] ?? '') . '/' . $nodeName];
236  $clearStackPath = (bool)($subOptions['clearStackPath'] ?? false);
237  } else {
238  $subOptions = $additionalOptions;
239  $clearStackPath = false;
240  }
241  if (empty($value)) {
242  $nodeValue = '';
243  } else {
244  $nodeValue = $options->‪getNewlineChar();
245  $nodeValue .= $this->‪parseArray(
246  $value,
247  $options,
248  $subOptions,
249  $level + 1,
250  [
251  'parentTagName' => $nodeName,
252  'grandParentTagName' => $stackData['parentTagName'] ?? '',
253  'path' => $clearStackPath ? '' : ($stackData['path'] ?? '') . '/' . $nodeName,
254  ]
255  );
256  $nodeValue .= $indentation;
257  }
258  // Dropping the "type=array" attribute makes the XML prettier, but means that empty
259  // arrays are not restored with XmlDecoder::decode().
260  if (($additionalOptions['disableTypeAttrib'] ?? false) !== 2) {
261  $nodeAttributes .= ' type="array"';
262  }
263  } else {
264  // ... if is simple value
265  if ($this->‪isBinaryValue($value)) {
266  $nodeValue = $options->‪getNewlineChar() . chunk_split(base64_encode($value));
267  $nodeAttributes .= ' base64="1"';
268  } else {
269  $type = gettype($value);
270  if ($type === 'string') {
271  $nodeValue = htmlspecialchars($value);
272  } else {
273  $nodeValue = $value;
274  if (($additionalOptions['disableTypeAttrib'] ?? false) === false) {
275  $nodeAttributes .= ' type="' . $type . '"';
276  }
277  }
278  }
279  }
280 
281  // Construct the node
282  if ($nodeName !== '') {
283  $xml .= $indentation;
284  $xml .= '<' . $options->‪getNamespacePrefix() . $nodeName . $nodeAttributes . '>';
285  $xml .= $nodeValue;
286  $xml .= '</' . $options->‪getNamespacePrefix() . $nodeName . '>';
287  $xml .= $options->‪getNewlineChar();
288  }
289  }
290 
291  // Wrap with the root node if it is on the outermost level.
292  if ($level === 0 && !empty($rootNodeName)) {
293  $xml = '<' . $rootNodeName . '>' . $options->‪getNewlineChar() . $xml . '</' . $rootNodeName . '>';
294  }
295 
296  return $xml;
297  }
298 
306  protected function ‪cleanUpNodeName(string $nodeName): string
307  {
308  return substr((string)preg_replace('/[^[:alnum:]_-]/', '', $nodeName), 0, 100);
309  }
310 
318  protected function ‪isBinaryValue(mixed $value): bool
319  {
320  if (!is_string($value)) {
321  return false;
322  }
323 
324  $binaryChars = "\0" . chr(1) . chr(2) . chr(3) . chr(4) . chr(5)
325  . chr(6) . chr(7) . chr(8) . chr(11) . chr(12)
326  . chr(14) . chr(15) . chr(16) . chr(17) . chr(18)
327  . chr(19) . chr(20) . chr(21) . chr(22) . chr(23)
328  . chr(24) . chr(25) . chr(26) . chr(27) . chr(28)
329  . chr(29) . chr(30) . chr(31);
330 
331  $length = strlen($value);
332 
333  return $length && strcspn($value, $binaryChars) !== $length;
334  }
335 }
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer\parseArray
‪parseArray(array $input, Typo3XmlParserOptions $options, array $additionalOptions, int $level=0, array $stackData=[])
Definition: Typo3XmlSerializer.php:164
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer\encode
‪string encode(array $input, Typo3XmlParserOptions $options=null, array $additionalOptions=[])
Definition: Typo3XmlSerializer.php:151
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer\encodeWithReturningExceptionAsString
‪string encodeWithReturningExceptionAsString(array $input, Typo3XmlParserOptions $options=null, array $additionalOptions=[])
Definition: Typo3XmlSerializer.php:133
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer
Definition: Typo3XmlSerializer.php:121
‪TYPO3\CMS\Core\Serializer\Typo3XmlParserOptions\getNewlineChar
‪getNewlineChar()
Definition: Typo3XmlParserOptions.php:53
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer\isBinaryValue
‪bool isBinaryValue(mixed $value)
Definition: Typo3XmlSerializer.php:318
‪TYPO3\CMS\Core\Serializer\Typo3XmlParserOptions\getRootNodeName
‪getRootNodeName()
Definition: Typo3XmlParserOptions.php:48
‪TYPO3\CMS\Core\Serializer\Typo3XmlParserOptions\getIndentationStep
‪getIndentationStep()
Definition: Typo3XmlParserOptions.php:58
‪TYPO3\CMS\Core\Serializer\Typo3XmlParserOptions
Definition: Typo3XmlParserOptions.php:24
‪TYPO3\CMS\Core\Serializer
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Serializer\Typo3XmlParserOptions\getNamespacePrefix
‪getNamespacePrefix()
Definition: Typo3XmlParserOptions.php:67
‪TYPO3\CMS\Core\Serializer\Typo3XmlSerializer\cleanUpNodeName
‪string cleanUpNodeName(string $nodeName)
Definition: Typo3XmlSerializer.php:306