diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9fada8f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,41 @@ +name: Integrity check + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - name: Install PHP + uses: shivammathur/setup-php@master + with: + php-version: 7.4 + + - name: Install composer deps + run: | + composer create-project nette/code-checker temp/code-checker ^3 --no-progress + composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + + # Install app deps + composer install --no-interaction --prefer-dist + + # Check code checker and coding standards + - name: Check coding standards + run: | + php temp/code-checker/code-checker --short-arrays --strict-types --fix --no-progress + php temp/coding-standard/ecs check src --config temp/coding-standard/coding-standard-php71.yml + + - name: Check PHPStan rules + run: composer phpstan + + - name: Run tests + run: tests/test.sh diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2c31e5c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <> \ No newline at end of file diff --git a/README.md b/README.md index 8c5d61d..4e76274 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ +XML to PHP array convertor +========================== + +Smart tool to convert your XML to PHP array. + +Install and simply use +---------------------- + +Use Composer: + +```shell +composer require gaarf/xml-to-php-array +``` + +And then package will be automatically installed to your project and you can simply call: + +```php +$resultArray = Convertor::covertToArray($xml); +``` + +Documentation +------------- + One common need when working in PHP is a way to convert an XML document into a serializable array. If you ever tried to serialize() and then unserialize() a SimpleXML or DOMDocument object, you know what I’m @@ -5,74 +28,82 @@ talking about. Assume the following XML snippet: - - - Brian - Chris - Meg - - +```xml + + + Brian + Chris + Meg + + +``` There’s a quick and dirty way to do convert such a document to an array, using type casting and the JSON functions to ensure there are no exotic values that would cause problems when unserializing: - +```php +$a = json_decode(json_encode((array) Convertor::covertToArray($s)), true); +``` Here is the result for our sample XML, eg if we `print_r($a)`: - Array - ( - [show] => Array - ( - [@attributes] => Array - ( - [name] => Family Guy - ) - [dog] => Brian - [kid] => Array - ( - [0] => Chris - [1] => Meg - ) - ) - ) +``` +Array +( + [show] => Array + ( + [@attributes] => Array + ( + [name] => Family Guy + ) + [dog] => Brian + [kid] => Array + ( + [0] => Chris + [1] => Meg + ) + ) +) +``` Pretty nifty, eh? But maybe we want to embed some HTML tags or something crazy along those lines. then we need a CDATA node… - - - Brian - Chris - Meg - Stewie]]> - - +```xml + + + Brian + Chris + Meg + Stewie]]> + + +``` The snippet of XML above would yield the following: - Array - ( - [show] => Array - ( - [@attributes] => Array - ( - [name] => Family Guy - ) - [dog] => Brian - [kid] => Array - ( - [0] => Chris - [1] => Meg - [2] => Array - ( - ) - ) - ) - ) +``` +Array +( + [show] => Array + ( + [@attributes] => Array + ( + [name] => Family Guy + ) + [dog] => Brian + [kid] => Array + ( + [0] => Chris + [1] => Meg + [2] => Array + ( + ) + ) + ) +) +``` That’s not very useful. We got in trouble because the CDATA node, a SimpleXMLElement, is being cast to an array instead of a string. To @@ -82,27 +113,38 @@ hereby released under a do-whatever-but-dont-sue-me license. The result, for our *Stewie* snippet: - Array - ( - [show] => Array - ( - [@attributes] => Array - ( - [name] => Family Guy - ) - [dog] => Brian - [kid] => Array - ( - [0] => Chris - [1] => Meg - [2] => Stewie - ) - ) - ) +``` +Array +( + [show] => Array + ( + [@attributes] => Array + ( + [name] => Family Guy + ) + [dog] => Brian + [kid] => Array + ( + [0] => Chris + [1] => Meg + [2] => Stewie + ) + ) +) +``` Victory is mine! :D --- ### Contributions + [clh-code#1] If a node has attributes, but contains only text, then the output will be an array with both ```@content``` and ```@attributes``` keys + +[reggi#4] store root element tag name in ```@root``` + +[janbarasek#13] Add support for PHP 7.1 + better code style. + +[janbarasek#15] Rewrite repository as Composer package. + +[roland-d#18] Treat empty node as string diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d6d3a20 --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "gaarf/xml-to-php-array", + "description": "XML to PHP array convertor", + "homepage": "https://github.com/gaarf/XML-string-to-PHP-array", + "authors": [ + { + "name": "Adrien Cahen", + "homepage": "http://gaarf.info" + }, + { + "name": "Jan Barášek", + "homepage": "https://baraja.cz" + } + ], + "require": { + "php": ">=7.1.0", + "ext-dom": "*" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "tracy/tracy": "^2.7", + "phpstan/phpstan-nette": "^0.12.6" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpstan": [ + "vendor/bin/phpstan analyse src -c phpstan.neon --level 6 --no-progress" + ] + }, + "minimum-stability": "stable" +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..25bf1ec --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +includes: + - vendor/phpstan/phpstan-nette/extension.neon + - vendor/phpstan/phpstan-nette/rules.neon diff --git a/src/Convertor.php b/src/Convertor.php new file mode 100644 index 0000000..300a360 --- /dev/null +++ b/src/Convertor.php @@ -0,0 +1,31 @@ +loadXML($xml); + $root = $doc->documentElement; + $output = (array) Helper::domNodeToArray($root); + $output['@root'] = $root->tagName; + + return $output ?? []; + } +} \ No newline at end of file diff --git a/src/Helper.php b/src/Helper.php new file mode 100644 index 0000000..a4acbe4 --- /dev/null +++ b/src/Helper.php @@ -0,0 +1,69 @@ +nodeType) { + case 4: // XML_CDATA_SECTION_NODE + case 3: // XML_TEXT_NODE + $output = trim($node->textContent); + break; + case 1: // XML_ELEMENT_NODE + for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { + $child = $node->childNodes->item($i); + $v = self::domNodeToArray($child); + if (isset($child->tagName)) { + $t = $child->tagName; + if (!isset($output[$t])) { + $output[$t] = []; + } + if (is_array($v) && empty($v)) { + $v = ''; + } + $output[$t][] = $v; + } elseif ($v || $v === '0') { + $output = (string) $v; + } + } + if ($node->attributes->length && !is_array($output)) { // has attributes but isn't an array + $output = ['@content' => $output]; // change output into an array. + } + if (is_array($output)) { + if ($node->attributes->length) { + $a = []; + foreach ($node->attributes as $attrName => $attrNode) { + $a[$attrName] = (string) $attrNode->value; + } + $output['@attributes'] = $a; + } + foreach ($output as $t => $v) { + if ($t !== '@attributes' && is_array($v) && count($v) === 1) { + $output[$t] = $v[0]; + } + } + } + break; + } + + return $output; + } +} diff --git a/test.sh b/tests/test.sh similarity index 74% rename from test.sh rename to tests/test.sh index 02dc458..663964b 100755 --- a/test.sh +++ b/tests/test.sh @@ -1,7 +1,8 @@ #!/usr/bin/env php + Brian Chris @@ -34,6 +35,7 @@ $xmlstr = << array( array( @@ -62,7 +64,7 @@ $expected = array( ), array( - "empty" => array(), + "empty" => "", "foo" => array( "@attributes" => array( "empty" => "" @@ -76,13 +78,22 @@ $expected = array( ) ) - ) -); + ), -$result = xmlstr_to_array($xmlstr); + "@attributes" => array( "type" => "cartoon" ), + "@root" => 'tv', +); -prettyPrint("Input", $xmlstr); -prettyPrint("Expected", $expected); -prettyPrint("Output", $result); -prettyPrint("Result", $result == $expected ? "SUCCESS :-)" : "FAILURE :-("); +$result = \Gaarf\XmlToPhp\Convertor::covertToArray($xmlstr); + +if ($result == $expected) { + prettyPrint('Result', 'SUCCESS :-)'); +} else { + prettyPrint('Result', 'FAILURE :-('); + prettyPrint('Input', $xmlstr); + prettyPrint('Expected', $expected); + prettyPrint('Output', $result); + prettyPrint('Result', 'FAILURE :-('); + exit(1); +} diff --git a/xmlstr_to_array.php b/xmlstr_to_array.php deleted file mode 100644 index c847de2..0000000 --- a/xmlstr_to_array.php +++ /dev/null @@ -1,63 +0,0 @@ -loadXML($xmlstr); - return domnode_to_array($doc->documentElement); -} - -function domnode_to_array($node) { - $output = array(); - switch ($node->nodeType) { - - case XML_CDATA_SECTION_NODE: - case XML_TEXT_NODE: - $output = trim($node->textContent); - break; - - case XML_ELEMENT_NODE: - for ($i=0, $m=$node->childNodes->length; $i<$m; $i++) { - $child = $node->childNodes->item($i); - $v = domnode_to_array($child); - if(isset($child->tagName)) { - $t = $child->tagName; - if(!isset($output[$t])) { - $output[$t] = array(); - } - $output[$t][] = $v; - } - elseif($v || $v === '0') { - $output = (string) $v; - } - } - if($node->attributes->length && !is_array($output)) { //Has attributes but isn't an array - $output = array('@content'=>$output); //Change output into an array. - } - if(is_array($output)) { - if($node->attributes->length) { - $a = array(); - foreach($node->attributes as $attrName => $attrNode) { - $a[$attrName] = (string) $attrNode->value; - } - $output['@attributes'] = $a; - } - foreach ($output as $t => $v) { - if(is_array($v) && count($v)==1 && $t!='@attributes') { - $output[$t] = $v[0]; - } - } - } - break; - } - return $output; -} -?> \ No newline at end of file