├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src └── ArrayToXml.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 8 | __DIR__ . '/src', 9 | __DIR__ . '/tests', 10 | ]) 11 | ->name('*.php') 12 | ->notName('*.blade.php') 13 | ->ignoreDotFiles(true) 14 | ->ignoreVCS(true); 15 | $config = new Config(); 16 | 17 | return $config->setFinder($finder) 18 | ->setRules([ 19 | '@PSR2' => true, 20 | 'array_syntax' => ['syntax' => 'short'], 21 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 22 | 'no_unused_imports' => true, 23 | 'not_operator_with_successor_space' => true, 24 | 'trailing_comma_in_multiline' => ['elements' => ['arrays']], 25 | 'phpdoc_scalar' => true, 26 | 'unary_operator_spaces' => true, 27 | 'binary_operator_spaces' => true, 28 | 'blank_line_before_statement' => [ 29 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 30 | ], 31 | 'phpdoc_single_line_var_spacing' => true, 32 | 'phpdoc_var_without_name' => true, 33 | 'class_attributes_separation' => [ 34 | 'elements' => [ 35 | 'method' => 'one', 36 | ], 37 | ], 38 | 'method_argument_space' => [ 39 | 'on_multiline' => 'ensure_fully_multiline', 40 | 'keep_multiple_spaces_after_comma' => true, 41 | ], 42 | 'single_trait_insert_per_statement' => true, 43 | 'nullable_type_declaration_for_default_null_value' => true, 44 | ]) 45 | ->setRiskyAllowed(true); 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `array-to-xml` will be documented in this file 4 | 5 | ## 3.4.0 - 2024-12-16 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.1.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/232 10 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/234 11 | * Allow passing of saveXML options by @VincentBean in https://github.com/spatie/array-to-xml/pull/235 12 | 13 | ### New Contributors 14 | 15 | * @VincentBean made their first contribution in https://github.com/spatie/array-to-xml/pull/235 16 | 17 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.3.0...3.4.0 18 | 19 | ## 3.3.0 - 2024-05-01 20 | 21 | ### What's Changed 22 | 23 | * Update README.md by @trippo in https://github.com/spatie/array-to-xml/pull/229 24 | * Fix explicit nullable parameter for PHP 8.4 compatibility by @GromNaN in https://github.com/spatie/array-to-xml/pull/233 25 | 26 | ### New Contributors 27 | 28 | * @trippo made their first contribution in https://github.com/spatie/array-to-xml/pull/229 29 | * @GromNaN made their first contribution in https://github.com/spatie/array-to-xml/pull/233 30 | 31 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.2.3...3.3.0 32 | 33 | ## 3.2.3 - 2024-XX-XX 34 | 35 | ### What's Changed 36 | 37 | - Convert boolean values to proper xml representation by @radeno in https://github.com/spatie/array-to-xml/pull/228 38 | 39 | ## 3.2.2 - 2023-11-14 40 | 41 | ### What's Changed 42 | 43 | - Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/spatie/array-to-xml/pull/224 44 | - Conver null based on option by @radeno in https://github.com/spatie/array-to-xml/pull/226 45 | 46 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.2.1...3.2.2 47 | 48 | ## 3.2.1 - 2023-11-08 49 | 50 | ### What's Changed 51 | 52 | - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/spatie/array-to-xml/pull/222 53 | - Use proper NULL value when we wanna use with XSD by @radeno in https://github.com/spatie/array-to-xml/pull/225 54 | 55 | ### New Contributors 56 | 57 | - @radeno made their first contribution in https://github.com/spatie/array-to-xml/pull/225 58 | 59 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.2.0...3.2.1 60 | 61 | ## 3.2.0 - 2023-07-19 62 | 63 | ### What's Changed 64 | 65 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/spatie/array-to-xml/pull/217 66 | - Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/218 67 | - Added Closure value support by @SuperDJ in https://github.com/spatie/array-to-xml/pull/219 68 | 69 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.6...3.2.0 70 | 71 | ## 3.1.6 - 2023-05-11 72 | 73 | ### What's Changed 74 | 75 | - V3 - Code smell ('incorrect' method call) by @ExeQue in https://github.com/spatie/array-to-xml/pull/208 76 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/array-to-xml/pull/210 77 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/array-to-xml/pull/214 78 | - Add addXmlDeclaration parameter by @silnex in https://github.com/spatie/array-to-xml/pull/216 79 | 80 | ### New Contributors 81 | 82 | - @silnex made their first contribution in https://github.com/spatie/array-to-xml/pull/216 83 | 84 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.5...3.1.6 85 | 86 | ## 3.1.5 - 2022-12-24 87 | 88 | ### What's Changed 89 | 90 | - Add Dependabot Automation by @patinthehat in https://github.com/spatie/array-to-xml/pull/196 91 | - Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/array-to-xml/pull/197 92 | - Fix PHP version by @parallels999 in https://github.com/spatie/array-to-xml/pull/198 93 | - fix deprecated `passing null as string type` by @trin4ik in https://github.com/spatie/array-to-xml/pull/204 94 | 95 | ### New Contributors 96 | 97 | - @dependabot made their first contribution in https://github.com/spatie/array-to-xml/pull/197 98 | - @parallels999 made their first contribution in https://github.com/spatie/array-to-xml/pull/198 99 | - @trin4ik made their first contribution in https://github.com/spatie/array-to-xml/pull/204 100 | 101 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.4...3.1.5 102 | 103 | ## 3.1.4 - 2022-11-24 104 | 105 | ### What's Changed 106 | 107 | - PHP 8.2 support by @SuperDJ in https://github.com/spatie/array-to-xml/pull/194 108 | - Added more types by @SuperDJ in https://github.com/spatie/array-to-xml/pull/195 109 | 110 | ### New Contributors 111 | 112 | - @SuperDJ made their first contribution in https://github.com/spatie/array-to-xml/pull/194 113 | 114 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.3...3.1.4 115 | 116 | ## 3.1.3 - 2022-05-08 117 | 118 | ## What's Changed 119 | 120 | - Rewrite phpunit tests to pest by @otsch in https://github.com/spatie/array-to-xml/pull/183 121 | - PHP 8.1 fix deprecated null parameters by @gigerIT in https://github.com/spatie/array-to-xml/pull/187 122 | 123 | ## New Contributors 124 | 125 | - @otsch made their first contribution in https://github.com/spatie/array-to-xml/pull/183 126 | - @gigerIT made their first contribution in https://github.com/spatie/array-to-xml/pull/187 127 | 128 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.2...3.1.3 129 | 130 | ## 3.1.2 - 2022-03-03 131 | 132 | ## What's Changed 133 | 134 | - Fix basic collection with namespace by @vaclavvanik in https://github.com/spatie/array-to-xml/pull/182 135 | 136 | ## New Contributors 137 | 138 | - @vaclavvanik made their first contribution in https://github.com/spatie/array-to-xml/pull/182 139 | 140 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.1...3.1.2 141 | 142 | ## 3.1.1 - 2022-02-11 143 | 144 | ## What's Changed 145 | 146 | - Fix a typo in the result by @olsza in https://github.com/spatie/array-to-xml/pull/172 147 | 148 | ## New Contributors 149 | 150 | - @olsza made their first contribution in https://github.com/spatie/array-to-xml/pull/172 151 | 152 | **Full Changelog**: https://github.com/spatie/array-to-xml/compare/3.1.0...3.1.1 153 | 154 | ## 3.1.0 - 2021-09-12 155 | 156 | - add support for processing instructions 157 | 158 | ## 3.0.1 - 2021-09-05 159 | 160 | - allow null inside array to be converted to xml (#170) 161 | 162 | ## 3.0.0 - 2021-04-23 163 | 164 | - require PHP 8+ 165 | - drop support for PHP 7.x 166 | - convert syntax to PHP 8 167 | 168 | ## 2.16.0 - 2020-11-18 169 | 170 | - add escapable colons in custom keys (#151) 171 | 172 | ## 2.15.1 - 2020-11-12 173 | 174 | - add support for PHP 8 175 | 176 | ## 2.15.0 - 2020-10-29 177 | 178 | - add $xmlStandalone as a new parameter (#148) 179 | 180 | ## 2.14.0 - 2020-09-14 181 | 182 | - add support for dropping XML declaration (#145) 183 | 184 | ## 2.13.0 - 2020-08-24 185 | 186 | - add support for custom keys (#140) 187 | 188 | ## 2.12.1 - 2020-06-17 189 | 190 | - add XML prettification (#136) 191 | 192 | ## 2.11.2 - 2019-08-21 193 | 194 | - fix XML structure when using numeric keys 195 | 196 | ## 2.11.1 - 2019-07-25 197 | 198 | - do not interpret "0" as a non-empty value 199 | 200 | ## 2.11.0 - 2019-09-26 201 | 202 | - drop support for PHP 7.1 203 | 204 | ## 2.10.0 - 2019-09-26 205 | 206 | - add `setDomProperties` 207 | 208 | ## 2.9.0 - 2019-05-06 209 | 210 | - add support for numeric keys 211 | 212 | ## 2.8.1 - 2019-03-15 213 | 214 | - fix tests 215 | - drop support for PHP 7.0 216 | 217 | ## 2.8.0 - 2018-11-29 218 | 219 | - added support for mixed content 220 | 221 | ## 2.7.3 - 2018-10-30 222 | 223 | - fix for `DomExeception`s being thrown 224 | 225 | ## 2.7.2 - 2018-09-17 226 | 227 | - remove control characters 228 | 229 | ## 2.7.1 - 2018-02-02 230 | 231 | - fix setting attributes 232 | 233 | ## 2.7.0 - 2017-09-07 234 | 235 | - allow wrapping data in a CDATA section 236 | 237 | ## 2.6.1- 2017-08-29 238 | 239 | - add fix for multiple empty/self-closing child elements 240 | 241 | ## 2.6.0 - 2017-08-25 242 | 243 | - add support for naming a root element and adding properties to it 244 | 245 | ## 2.5.2 - 2017-08-03 246 | 247 | - avoid pulling in the snapshot package on install 248 | 249 | ## 2.5.1 - 2017-05-30 250 | 251 | - PHP 7 is now required 252 | 253 | ## 2.5.0 - 2017-05-22 254 | 255 | - allow encoding and version to be set 256 | 257 | ## 2.4.0 - 2017-02-18 258 | 259 | - attributes and value can be set in SimpleXMLElement style 260 | 261 | ## 2.3.0 - 2017-02-18 262 | 263 | - attributes and value can be set in SimpleXMLElement style 264 | 265 | ## 2.2.1 - 2016-12-08 266 | 267 | - fixed an error when there is a special character to the value set in _value 268 | 269 | ## 2.2.0 - 2016-06-04 270 | 271 | - added `toDom` method 272 | 273 | ## 2.1.1 - 2016-02-23 274 | 275 | - Fixed typo in the name of the `addSequentialNode`-function 276 | 277 | ## 2.1.0 - 2015-10-08 278 | 279 | - Add ability to use attributes 280 | 281 | ## 2.0.0 - 2015-10-08 282 | 283 | - Add support to collection arrays and dynamically XML convertion when keys are numeric 284 | 285 | ## 1.0.3 - 2015-10-03 286 | 287 | - handle values with special characters 288 | 289 | ## 1.0.1 - 2015-03-18 290 | 291 | - use DOMDocument for better validation 292 | - added an option to opt out of the automatic space replacement 293 | 294 | ## 1.0.0 - 2015-03-17 295 | 296 | - initial release 297 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Convert an array to xml 2 | 3 | [![Latest Version](https://img.shields.io/github/release/spatie/array-to-xml.svg?style=flat-square)](https://github.com/spatie/array-to-xml/releases) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 5 | ![Tests](https://github.com/spatie/array-to-xml/workflows/Tests/badge.svg) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/array-to-xml.svg?style=flat-square)](https://packagist.org/packages/spatie/array-to-xml) 7 | 8 | This package provides a very simple class to convert an array to an xml string. 9 | 10 | ## Support us 11 | 12 | [](https://spatie.be/github-ad-click/array-to-xml) 13 | 14 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 15 | 16 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 17 | 18 | ## Install 19 | 20 | You can install this package via composer. 21 | 22 | ``` bash 23 | composer require spatie/array-to-xml 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```php 29 | use Spatie\ArrayToXml\ArrayToXml; 30 | ... 31 | $array = [ 32 | 'Good guy' => [ 33 | 'name' => 'Luke Skywalker', 34 | 'weapon' => 'Lightsaber' 35 | ], 36 | 'Bad guy' => [ 37 | 'name' => 'Sauron', 38 | 'weapon' => 'Evil Eye' 39 | ] 40 | ]; 41 | 42 | $result = ArrayToXml::convert($array); 43 | ``` 44 | After running this piece of code `$result` will contain: 45 | 46 | ```xml 47 | 48 | 49 | 50 | Luke Skywalker 51 | Lightsaber 52 | 53 | 54 | Sauron 55 | Evil Eye 56 | 57 | 58 | ``` 59 | 60 | 61 | ### Setting the name of the root element 62 | 63 | Optionally you can set the name of the rootElement by passing it as the second argument. If you don't specify 64 | this argument (or set it to an empty string) "root" will be used. 65 | ``` 66 | $result = ArrayToXml::convert($array, 'customrootname'); 67 | ``` 68 | 69 | ### Handling key names 70 | 71 | By default all spaces in the key names of your array will be converted to underscores. If you want to opt out of 72 | this behaviour you can set the third argument to false. We'll leave all keynames alone. 73 | ``` 74 | $result = ArrayToXml::convert($array, 'customrootname', false); 75 | ``` 76 | 77 | ### Adding attributes 78 | 79 | You can use a key named `_attributes` to add attributes to a node, and `_value` to specify the value. 80 | 81 | ```php 82 | $array = [ 83 | 'Good guy' => [ 84 | '_attributes' => ['attr1' => 'value'], 85 | 'name' => 'Luke Skywalker', 86 | 'weapon' => 'Lightsaber' 87 | ], 88 | 'Bad guy' => [ 89 | 'name' => 'Sauron', 90 | 'weapon' => 'Evil Eye' 91 | ], 92 | 'The survivor' => [ 93 | '_attributes' => ['house'=>'Hogwarts'], 94 | '_value' => 'Harry Potter' 95 | ] 96 | ]; 97 | 98 | $result = ArrayToXml::convert($array); 99 | ``` 100 | 101 | This code will result in: 102 | 103 | ```xml 104 | 105 | 106 | 107 | Luke Skywalker 108 | Lightsaber 109 | 110 | 111 | Sauron 112 | Evil Eye 113 | 114 | 115 | Harry Potter 116 | 117 | 118 | ``` 119 | 120 | *Note, that the value of the `_value` field must be a string. [(More)](https://github.com/spatie/array-to-xml/issues/75#issuecomment-413726065)* 121 | 122 | ### Using reserved characters 123 | 124 | It is also possible to wrap the value of a node into a CDATA section. This allows you to use reserved characters. 125 | 126 | ```php 127 | $array = [ 128 | 'Good guy' => [ 129 | 'name' => [ 130 | '_cdata' => '

Luke Skywalker

' 131 | ], 132 | 'weapon' => 'Lightsaber' 133 | ], 134 | 'Bad guy' => [ 135 | 'name' => '

Sauron

', 136 | 'weapon' => 'Evil Eye' 137 | ] 138 | ]; 139 | 140 | $result = ArrayToXml::convert($array); 141 | ``` 142 | 143 | This code will result in: 144 | 145 | ```xml 146 | 147 | 148 | 149 | Luke Skywalker]]> 150 | Lightsaber 151 | 152 | 153 | <h1>Sauron</h1> 154 | Evil Eye 155 | 156 | 157 | ``` 158 | 159 | If your input contains something that cannot be parsed a `DOMException` will be thrown. 160 | 161 | 162 | ### Customize the XML declaration 163 | 164 | You could specify specific values in for: 165 | - encoding as the fourth argument (string) 166 | - version as the fifth argument (string) 167 | - DOM properties as the sixth argument (array) 168 | - standalone as seventh argument (boolean) 169 | 170 | ```php 171 | $result = ArrayToXml::convert($array, [], true, 'UTF-8', '1.1', [], true); 172 | ``` 173 | 174 | This will result in: 175 | 176 | ```xml 177 | 178 | ``` 179 | 180 | 181 | ### Adding attributes to the root element 182 | 183 | To add attributes to the root element provide an array with an `_attributes` key as the second argument. 184 | The root element name can then be set using the `rootElementName` key. 185 | 186 | ```php 187 | $result = ArrayToXml::convert($array, [ 188 |    'rootElementName' => 'helloyouluckypeople', 189 | '_attributes' => [ 190 | 'xmlns' => 'https://github.com/spatie/array-to-xml', 191 | ], 192 | ], true, 'UTF-8'); 193 | ``` 194 | 195 | ### Using a multi-dimensional array 196 | 197 | Use a multi-dimensional array to create a collection of elements. 198 | ```php 199 | $array = [ 200 | 'Good guys' => [ 201 | 'Guy' => [ 202 | ['name' => 'Luke Skywalker', 'weapon' => 'Lightsaber'], 203 | ['name' => 'Captain America', 'weapon' => 'Shield'], 204 | ], 205 | ], 206 | 'Bad guys' => [ 207 | 'Guy' => [ 208 | ['name' => 'Sauron', 'weapon' => 'Evil Eye'], 209 | ['name' => 'Darth Vader', 'weapon' => 'Lightsaber'], 210 | ], 211 | ], 212 | ]; 213 | ``` 214 | 215 | This will result in: 216 | 217 | ```xml 218 | 219 | 220 | 221 | 222 | Luke Skywalker 223 | Lightsaber 224 | 225 | 226 | Captain America 227 | Shield 228 | 229 | 230 | 231 | 232 | Sauron 233 | Evil Eye 234 | 235 | 236 | Darth Vader 237 | Lightsaber 238 | 239 | 240 | 241 | ``` 242 | 243 | ### Using Closure values 244 | The package can use Closure values: 245 | 246 | ```php 247 | $users = [ 248 | [ 249 | 'name' => 'one', 250 | 'age' => 10, 251 | ], 252 | [ 253 | 'name' => 'two', 254 | 'age' => 12, 255 | ], 256 | ]; 257 | 258 | $array = [ 259 | 'users' => function () use ($users) { 260 | $new_users = []; 261 | foreach ($users as $user) { 262 | $new_users[] = array_merge( 263 | $user, 264 | [ 265 | 'double_age' => $user['age'] * 2, 266 | ] 267 | ); 268 | } 269 | 270 | return $new_users; 271 | }, 272 | ]; 273 | 274 | ArrayToXml::convert($array) 275 | ``` 276 | 277 | This will result in: 278 | 279 | ```xml 280 | 281 | 282 | 283 | one 284 | 10 285 | 20 286 | 287 | 288 | two 289 | 12 290 | 24 291 | 292 | 293 | ``` 294 | 295 | ### Handling numeric keys 296 | 297 | The package can also can handle numeric keys: 298 | 299 | ```php 300 | $array = [ 301 | 100 => [ 302 | 'name' => 'Vladimir', 303 | 'nickname' => 'greeflas', 304 | ], 305 | 200 => [ 306 | 'name' => 'Marina', 307 | 'nickname' => 'estacet', 308 | ], 309 | ]; 310 | 311 | $result = ArrayToXml::convert(['__numeric' => $array]); 312 | ``` 313 | 314 | This will result in: 315 | 316 | ```xml 317 | 318 | 319 | 320 | Vladimir 321 | greeflas 322 | 323 | 324 | Marina 325 | estacet 326 | 327 | 328 | ``` 329 | 330 | You can change key prefix with setter method called `setNumericTagNamePrefix()`. 331 | 332 | ### Using custom keys 333 | 334 | The package can also can handle custom keys: 335 | 336 | ```php 337 | $array = [ 338 | '__custom:custom-key:1' => [ 339 | 'name' => 'Vladimir', 340 | 'nickname' => 'greeflas', 341 | ], 342 | '__custom:custom-key:2' => [ 343 | 'name' => 'Marina', 344 | 'nickname' => 'estacet', 345 | 'tags' => [ 346 | '__custom:tag:1' => 'first-tag', 347 | '__custom:tag:2' => 'second-tag', 348 | ] 349 | ], 350 | ]; 351 | 352 | $result = ArrayToXml::convert($array); 353 | ``` 354 | 355 | This will result in: 356 | 357 | ```xml 358 | 359 | 360 | 361 | Vladimir 362 | greeflas 363 | 364 | 365 | Marina 366 | estacet 367 | 368 | first-tag 369 | second-tag 370 | 371 | 372 | 373 | ``` 374 | 375 | A custom key contains three, colon-separated parts: "__custom:[custom-tag]:[unique-string]". 376 | 377 | - "__custom" 378 | - The key always starts with "__custom". 379 | - [custom-tag] 380 | - The string to be rendered as the XML tag. 381 | - [unique-string] 382 | - A unique string that avoids overwriting of duplicate keys in PHP arrays. 383 | 384 | a colon character can be included within the custom-tag portion by escaping it with a backslash: 385 | 386 | ```php 387 | $array = [ 388 | '__custom:ns\\:custom-key:1' => [ 389 | 'name' => 'Vladimir', 390 | 'nickname' => 'greeflas', 391 | ], 392 | '__custom:ns\\:custom-key:2' => [ 393 | 'name' => 'Marina', 394 | 'nickname' => 'estacet', 395 | 'tags' => [ 396 | '__custom:ns\\:tag:1' => 'first-tag', 397 | '__custom:ns\\:tag:2' => 'second-tag', 398 | ] 399 | ], 400 | ]; 401 | ``` 402 | This will result in: 403 | 404 | ```xml 405 | 406 | 407 | 408 | Vladimir 409 | greeflas 410 | 411 | 412 | Marina 413 | estacet 414 | 415 | first-tag 416 | second-tag 417 | 418 | 419 | 420 | ``` 421 | 422 | ### Setting DOMDocument properties 423 | 424 | To set properties of the internal DOMDocument object just pass an array consisting of keys and values. For a full list of valid properties consult https://www.php.net/manual/en/class.domdocument.php. 425 | 426 | You can use the constructor to set DOMDocument properties. 427 | 428 | ```php 429 | $result = ArrayToXml::convert( 430 | $array, 431 | $rootElement, 432 | $replaceSpacesByUnderScoresInKeyNames, 433 | $xmlEncoding, 434 | $xmlVersion, 435 | ['formatOutput' => true] 436 | ); 437 | 438 | ``` 439 | 440 | Alternatively you can use `setDomProperties` 441 | 442 | ```php 443 | $arrayToXml = new ArrayToXml($array); 444 | $arrayToXml->setDomProperties(['formatOutput' => true]); 445 | $result = $arrayToXml->toXml(); 446 | ``` 447 | 448 | ### XML Prettification 449 | 450 | Call `$arrayToXml->prettify()` method on ArrayToXml to set XML in pretty form. 451 | 452 | Example: 453 | 454 | ```php 455 | $array = [ 456 | 'Good guy' => [ 457 | 'name' => 'Luke Skywalker', 458 | 'weapon' => 'Lightsaber' 459 | ], 460 | 'Bad guy' => [ 461 | 'name' => 'Sauron', 462 | 'weapon' => 'Evil Eye' 463 | ] 464 | ]; 465 | $arrayToXml = new ArrayToXml($array); 466 | ``` 467 | 468 | With prettification: 469 | 470 | ```php 471 | $arrayToXml->prettify()->toXml(); 472 | ``` 473 | 474 | will result in: 475 | 476 | ```xml 477 | 478 | 479 | 480 | Luke Skywalker 481 | Lightsaber 482 | 483 | 484 | Sauron 485 | Evil Eye 486 | 487 | 488 | ``` 489 | 490 | Without prettification: 491 | 492 | ```php 493 | $arrayToXml->toXml(); 494 | ``` 495 | 496 | will result in: 497 | 498 | ```xml 499 | 500 | Luke SkywalkerLightsaberSauronEvil Eye 501 | ``` 502 | 503 | ### Dropping XML declaration 504 | 505 | Call `$arrayToXml->dropXmlDeclaration()` method on ArrayToXml object to omit default XML declaration on top of the generated XML. 506 | 507 | Example: 508 | 509 | ```php 510 | $root = [ 511 | 'rootElementName' => 'soap:Envelope', 512 | '_attributes' => [ 513 | 'xmlns:soap' => 'http://www.w3.org/2003/05/soap-envelope/', 514 | ], 515 | ]; 516 | $array = [ 517 | 'soap:Header' => [], 518 | 'soap:Body' => [ 519 | 'soap:key' => 'soap:value', 520 | ], 521 | ]; 522 | $arrayToXml = new ArrayToXml($array, $root); 523 | 524 | $result = $arrayToXml->dropXmlDeclaration()->toXml(); 525 | ``` 526 | 527 | This will result in: 528 | 529 | ```xml 530 | soap:value 531 | ``` 532 | 533 | ### Adding processing instructions 534 | 535 | Call `$arrayToXml->addProcessingInstruction($target, $data)` method on ArrayToXml object to prepend a processing instruction before the root element. 536 | 537 | Example: 538 | 539 | ```php 540 | $arrayToXml = new ArrayToXml($array); 541 | $arrayToXml->addProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="base.xsl"'); 542 | $result = $arrayToXml->toXml(); 543 | ``` 544 | 545 | This will result in: 546 | 547 | ```xml 548 | 549 | 550 | Luke SkywalkerLightsaberSauronEvil Eye 551 | ``` 552 | 553 | ## Testing 554 | 555 | ```bash 556 | vendor/bin/phpunit 557 | ``` 558 | 559 | ## Changelog 560 | 561 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 562 | 563 | ## Contributing 564 | 565 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 566 | 567 | ## Security Vulnerabilities 568 | 569 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 570 | 571 | ## Postcardware 572 | 573 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 574 | 575 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 576 | 577 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 578 | 579 | ## Credits 580 | 581 | - [Freek Van der Herten](https://github.com/freekmurze) 582 | - [All Contributors](../../contributors) 583 | 584 | ## License 585 | 586 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 587 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/array-to-xml", 3 | "description": "Convert an array to xml", 4 | "keywords": [ 5 | "convert", 6 | "array", 7 | "xml" 8 | ], 9 | "homepage": "https://github.com/spatie/array-to-xml", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Freek Van der Herten", 14 | "email": "freek@spatie.be", 15 | "homepage": "https://freek.dev", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php" : "^8.0", 21 | "ext-dom": "*" 22 | }, 23 | "require-dev": { 24 | "mockery/mockery": "^1.2", 25 | "pestphp/pest": "^1.21", 26 | "spatie/pest-plugin-snapshots": "^1.1" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Spatie\\ArrayToXml\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Spatie\\ArrayToXml\\Test\\": "tests" 36 | } 37 | }, 38 | "scripts": { 39 | "test": "vendor/bin/pest" 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-main": "3.x-dev" 44 | } 45 | }, 46 | "config": { 47 | "allow-plugins": { 48 | "pestphp/pest-plugin": true 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ArrayToXml.php: -------------------------------------------------------------------------------- 1 | false, 'convertBoolToString' => false]; 23 | 24 | public function __construct( 25 | array $array, 26 | string | array $rootElement = '', 27 | bool $replaceSpacesByUnderScoresInKeyNames = true, 28 | string | null $xmlEncoding = null, 29 | string $xmlVersion = '1.0', 30 | array $domProperties = [], 31 | bool | null $xmlStandalone = null, 32 | bool $addXmlDeclaration = true, 33 | array | null $options = ['convertNullToXsiNil' => false, 'convertBoolToString' => false] 34 | ) { 35 | $this->document = new DOMDocument($xmlVersion, $xmlEncoding ?? ''); 36 | 37 | if (! is_null($xmlStandalone)) { 38 | $this->document->xmlStandalone = $xmlStandalone; 39 | } 40 | 41 | if (! empty($domProperties)) { 42 | $this->setDomProperties($domProperties); 43 | } 44 | 45 | $this->addXmlDeclaration = $addXmlDeclaration; 46 | 47 | $this->options = array_merge($this->options, $options); 48 | 49 | $this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames; 50 | 51 | if (! empty($array) && $this->isArrayAllKeySequential($array)) { 52 | throw new DOMException('Invalid Character Error'); 53 | } 54 | 55 | $this->rootNode = $this->createRootElement($rootElement); 56 | 57 | $this->document->appendChild($this->rootNode); 58 | 59 | $this->convertElement($this->rootNode, $array); 60 | } 61 | 62 | public function setNumericTagNamePrefix(string $prefix): void 63 | { 64 | $this->numericTagNamePrefix = $prefix; 65 | } 66 | 67 | public static function convert( 68 | array $array, 69 | $rootElement = '', 70 | bool $replaceSpacesByUnderScoresInKeyNames = true, 71 | ?string $xmlEncoding = null, 72 | string $xmlVersion = '1.0', 73 | array $domProperties = [], 74 | ?bool $xmlStandalone = null, 75 | bool $addXmlDeclaration = true, 76 | array $options = ['convertNullToXsiNil' => false] 77 | ): string { 78 | $converter = new static( 79 | $array, 80 | $rootElement, 81 | $replaceSpacesByUnderScoresInKeyNames, 82 | $xmlEncoding, 83 | $xmlVersion, 84 | $domProperties, 85 | $xmlStandalone, 86 | $addXmlDeclaration, 87 | $options 88 | ); 89 | 90 | return $converter->toXml(); 91 | } 92 | 93 | public function toXml($options = 0): string 94 | { 95 | return $this->addXmlDeclaration 96 | ? $this->document->saveXML(options: $options) 97 | : $this->document->saveXML($this->document->documentElement, $options); 98 | } 99 | 100 | public function toDom(): DOMDocument 101 | { 102 | return $this->document; 103 | } 104 | 105 | protected function ensureValidDomProperties(array $domProperties): void 106 | { 107 | foreach ($domProperties as $key => $value) { 108 | if (! property_exists($this->document, $key)) { 109 | throw new Exception("{$key} is not a valid property of DOMDocument"); 110 | } 111 | } 112 | } 113 | 114 | public function setDomProperties(array $domProperties): self 115 | { 116 | $this->ensureValidDomProperties($domProperties); 117 | 118 | foreach ($domProperties as $key => $value) { 119 | $this->document->{$key} = $value; 120 | } 121 | 122 | return $this; 123 | } 124 | 125 | public function prettify(): self 126 | { 127 | $this->document->preserveWhiteSpace = false; 128 | $this->document->formatOutput = true; 129 | 130 | return $this; 131 | } 132 | 133 | public function dropXmlDeclaration(): self 134 | { 135 | $this->addXmlDeclaration = false; 136 | 137 | return $this; 138 | } 139 | 140 | public function addProcessingInstruction(string $target, string $data): self 141 | { 142 | $elements = $this->document->getElementsByTagName('*'); 143 | 144 | $rootElement = $elements->count() > 0 ? $elements->item(0) : null; 145 | 146 | $processingInstruction = $this->document->createProcessingInstruction($target, $data); 147 | 148 | $this->document->insertBefore($processingInstruction, $rootElement); 149 | 150 | return $this; 151 | } 152 | 153 | protected function convertElement(DOMElement $element, mixed $value): void 154 | { 155 | if ($value instanceof Closure) { 156 | $value = $value(); 157 | } 158 | 159 | $sequential = $this->isArrayAllKeySequential($value); 160 | 161 | if (! is_array($value)) { 162 | $value = htmlspecialchars($value ?? ''); 163 | 164 | $value = $this->removeControlCharacters($value); 165 | 166 | $element->nodeValue = $value; 167 | 168 | return; 169 | } 170 | 171 | foreach ($value as $key => $data) { 172 | if (! $sequential) { 173 | if (($key === '_attributes') || ($key === '@attributes')) { 174 | $this->addAttributes($element, $data); 175 | } elseif ((($key === '_value') || ($key === '@value')) && is_string($data)) { 176 | $element->nodeValue = htmlspecialchars($data); 177 | } elseif ((($key === '_cdata') || ($key === '@cdata')) && is_string($data)) { 178 | $element->appendChild($this->document->createCDATASection($data)); 179 | } elseif ((($key === '_mixed') || ($key === '@mixed')) && is_string($data)) { 180 | $fragment = $this->document->createDocumentFragment(); 181 | $fragment->appendXML($data); 182 | $element->appendChild($fragment); 183 | } elseif ($key === '__numeric') { 184 | $this->addNumericNode($element, $data); 185 | } elseif (str_starts_with($key, '__custom:')) { 186 | $this->addNode($element, str_replace('\:', ':', preg_split('/(?addNode($element, $key, $data); 189 | } 190 | } elseif (is_array($data)) { 191 | $this->addCollectionNode($element, $data); 192 | } else { 193 | $this->addSequentialNode($element, $data); 194 | } 195 | } 196 | } 197 | 198 | protected function addNumericNode(DOMElement $element, mixed $value): void 199 | { 200 | foreach ($value as $key => $item) { 201 | $this->convertElement($element, [$this->numericTagNamePrefix.$key => $item]); 202 | } 203 | } 204 | 205 | protected function addNode(DOMElement $element, string $key, mixed $value): void 206 | { 207 | if ($this->replaceSpacesByUnderScoresInKeyNames) { 208 | $key = str_replace(' ', '_', $key); 209 | } 210 | 211 | $child = $this->document->createElement($key); 212 | 213 | $this->addNodeTypeAttribute($child, $value); 214 | 215 | $element->appendChild($child); 216 | 217 | $value = $this->convertNodeValue($child, $value); 218 | 219 | $this->convertElement($child, $value); 220 | } 221 | 222 | protected function convertNodeValue(DOMElement $element, mixed $value): mixed 223 | { 224 | if ($this->options['convertBoolToString'] && is_bool($value)) { 225 | $value = $value ? 'true' : 'false'; 226 | } 227 | 228 | return $value; 229 | } 230 | 231 | protected function addNodeTypeAttribute(DOMElement $element, mixed $value): void 232 | { 233 | if ($this->options['convertNullToXsiNil'] && is_null($value)) { 234 | if (! $this->rootNode->hasAttribute('xmlns:xsi')) { 235 | $this->rootNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 236 | } 237 | 238 | $element->setAttribute('xsi:nil', 'true'); 239 | } 240 | } 241 | 242 | protected function addCollectionNode(DOMElement $element, mixed $value): void 243 | { 244 | if ($element->childNodes->length === 0 && $element->attributes->length === 0) { 245 | $this->convertElement($element, $value); 246 | 247 | return; 248 | } 249 | 250 | $child = $this->document->createElement($element->tagName); 251 | $element->parentNode->appendChild($child); 252 | $this->convertElement($child, $value); 253 | } 254 | 255 | protected function addSequentialNode(DOMElement $element, mixed $value): void 256 | { 257 | if (empty($element->nodeValue) && ! is_numeric($element->nodeValue)) { 258 | $element->nodeValue = htmlspecialchars($value); 259 | 260 | return; 261 | } 262 | 263 | $child = $this->document->createElement($element->tagName); 264 | $child->nodeValue = htmlspecialchars($value); 265 | $element->parentNode->appendChild($child); 266 | } 267 | 268 | protected function isArrayAllKeySequential(array | string | null $value): bool 269 | { 270 | if (! is_array($value)) { 271 | return false; 272 | } 273 | 274 | if (count($value) <= 0) { 275 | return true; 276 | } 277 | 278 | if (\key($value) === '__numeric') { 279 | return false; 280 | } 281 | 282 | return array_unique(array_map('is_int', array_keys($value))) === [true]; 283 | } 284 | 285 | protected function addAttributes(DOMElement $element, array $data): void 286 | { 287 | foreach ($data as $attrKey => $attrVal) { 288 | $element->setAttribute($attrKey, $attrVal ?? ''); 289 | } 290 | } 291 | 292 | protected function createRootElement(string|array $rootElement): DOMElement 293 | { 294 | if (is_string($rootElement)) { 295 | $rootElementName = $rootElement ?: 'root'; 296 | 297 | return $this->document->createElement($rootElementName); 298 | } 299 | 300 | $rootElementName = $rootElement['rootElementName'] ?? 'root'; 301 | 302 | $element = $this->document->createElement($rootElementName); 303 | 304 | foreach ($rootElement as $key => $value) { 305 | if ($key !== '_attributes' && $key !== '@attributes') { 306 | continue; 307 | } 308 | 309 | $this->addAttributes($element, $value); 310 | } 311 | 312 | return $element; 313 | } 314 | 315 | protected function removeControlCharacters(string $value): string 316 | { 317 | return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value); 318 | } 319 | } 320 | --------------------------------------------------------------------------------