diff --git a/src/Xml2Array.php b/src/Xml2Array.php
index 0cae476..5663867 100644
--- a/src/Xml2Array.php
+++ b/src/Xml2Array.php
@@ -191,7 +191,11 @@ protected function loadXml($inputXml)
$this->xml = new DOMDocument($this->config['version'], $this->config['encoding']);
if (is_string($inputXml)) {
- $this->xml->loadXML($inputXml);
+ $parse = @$this->xml->loadXML($inputXml);
+
+ if ($parse === false) {
+ throw new DOMException('Error parsing XML string, input is not a well-formed XML string.');
+ }
} elseif ($inputXml instanceof SimpleXMLElement) {
$this->xml->loadXML($inputXml->asXML());
} elseif ($inputXml instanceof DOMDocument) {
@@ -215,21 +219,16 @@ protected function parseNode(DOMNode $node)
switch ($node->nodeType) {
case XML_CDATA_SECTION_NODE:
- $output[$this->config['cdataKey']] = trim($node->textContent);
+ $output[$this->config['cdataKey']] = $this->normalizeTextContent($node->textContent);
break;
case XML_TEXT_NODE:
- $output = trim(preg_replace([
- '/\n+\s+/',
- '/\r+\s+/',
- '/\n+\t+/',
- '/\r+\t+/'
- ], ' ', $node->textContent));
+ $output = $this->normalizeTextContent($node->textContent);
break;
case XML_ELEMENT_NODE:
$output = $this->parseChildNodes($node, $output);
- $output = $this->normalizeValues($output);
+ $output = $this->normalizeNodeValues($output);
$output = $this->collectAttributes($node, $output);
break;
}
@@ -247,32 +246,36 @@ protected function parseNode(DOMNode $node)
*/
protected function parseChildNodes(DOMNode $node, $output)
{
- if ($node->childNodes->length == 1) {
- if (!empty($output)) {
- $output[$this->config['valueKey']] = $this->parseNode($node->firstChild);
+ foreach ($node->childNodes as $child) {
+ if ($child->nodeType === XML_CDATA_SECTION_NODE) {
+ if (!is_array($output)) {
+ if (!empty($output)) {
+ $output = [$this->config['valueKey'] => $output];
+ } else {
+ $output = [];
+ }
+ }
+
+ $output[$this->config['cdataKey']] = $this->normalizeTextContent($child->textContent);
} else {
- $output = $this->parseNode($node->firstChild);
- }
- } else {
- foreach ($node->childNodes as $child) {
- if ($child->nodeType === XML_CDATA_SECTION_NODE) {
- $output[$this->config['cdataKey']] = trim($child->textContent);
- } else {
- $value = $this->parseNode($child);
+ $value = $this->parseNode($child);
- if ($child->nodeType == XML_TEXT_NODE) {
- if ($value != '') {
+ if ($child->nodeType == XML_TEXT_NODE) {
+ if ($value != '') {
+ if (!empty($output)) {
$output[$this->config['valueKey']] = $value;
+ } else {
+ $output = $value;
}
- } else {
- $nodeName = $child->nodeName;
-
- if (!isset($output[$nodeName])) {
- $output[$nodeName] = [];
- }
+ }
+ } else {
+ $nodeName = $child->nodeName;
- $output[$nodeName][] = $value;
+ if (!isset($output[$nodeName])) {
+ $output[$nodeName] = [];
}
+
+ $output[$nodeName][] = $value;
}
}
}
@@ -281,32 +284,49 @@ protected function parseChildNodes(DOMNode $node, $output)
}
/**
- * Normalize values
+ * Clean text content of text node
+ *
+ * @param string $textContent
+ *
+ * @return string
+ */
+ protected function normalizeTextContent($textContent)
+ {
+ return trim(preg_replace([
+ '/\n+\s+/',
+ '/\r+\s+/',
+ '/\n+\t+/',
+ '/\r+\t+/'
+ ], ' ', $textContent));
+ }
+
+ /**
+ * Normalize values of node
*
- * @param mixed $output
+ * @param mixed $values
*
* @return mixed
*/
- protected function normalizeValues($output)
+ protected function normalizeNodeValues($values)
{
- if (is_array($output)) {
+ if (is_array($values)) {
// if only one node of its kind, assign it directly instead if array($value);
- foreach ($output as $key => $value) {
+ foreach ($values as $key => $value) {
if (is_array($value) && count($value) === 1) {
$keyName = array_keys($value)[0];
if (is_numeric($keyName)) {
- $output[$key] = $value[$keyName];
+ $values[$key] = $value[$keyName];
}
}
}
- if (empty($output)) {
- $output = '';
+ if (empty($values)) {
+ $values = '';
}
}
- return $output;
+ return $values;
}
/**
diff --git a/tests/Array2XmlTest.php b/tests/Array2XmlTest.php
index 5a72542..eb487b0 100644
--- a/tests/Array2XmlTest.php
+++ b/tests/Array2XmlTest.php
@@ -59,29 +59,75 @@ class Array2XmlTest extends TestCase
];
/**
- * Expected xml string
+ * Throw DOMException when there are more than one root node
*
- * @var string
+ * @test
+ *
+ * @return void
*/
- protected $expected_xml_string = 'Example tagAnother tag with attributesSub tag 1Sub tag 2HelloContent';
+ public function throw_dom_exception_when_there_are_more_than_one_root_node()
+ {
+ $this->expectException(DOMException::class);
+ $this->expectExceptionMessage('XML documents are allowed only one root element. Wrap your elements in a key or set the `rootElement` parameter in the configuration.');
+
+ $process = Array2Xml::convert([
+ 'root' => 'content',
+ 'another_root' => 'Another content'
+ ]);
+ }
/**
- * Convert array to xml string
+ * Throw DOMException when node name is invalid
*
* @test
*
* @return void
*/
- public function convert_array_to_xml_string()
+ public function throw_dom_exception_when_node_name_is_invalid()
{
- $dom = new DOMDocument;
- $dom->loadXML($this->expected_xml_string);
+ $this->expectException(DOMException::class);
+ $this->expectExceptionMessage('Invalid character in the tag name being generated: 0');
+
+ $process = Array2Xml::convert(['content']);
+ }
+
+ /**
+ * Throw DOMException when attribute name is invalid
+ *
+ * @test
+ *
+ * @return void
+ */
+ public function throw_dom_exception_when_attaribute_name_is_invalid()
+ {
+ $this->expectException(DOMException::class);
+ $this->expectExceptionMessage('Invalid character in the attribute name being generated: invalid attribute');
+
+ $process = Array2Xml::convert([
+ 'root' => [
+ 'sub' => [
+ '@attributes' => [
+ 'invalid attribute' => 'Attribute value'
+ ]
+ ]
+ ]
+ ]);
+ }
- $this->assertSame($dom->saveXML(), Array2Xml::convert($this->input_array)->toXml());
+ /**
+ * Convert array to XML string
+ *
+ * @test
+ *
+ * @return void
+ */
+ public function convert_array_to_xml_string()
+ {
+ $this->assertXmlStringEqualsXmlFile(__DIR__ . '/resources/example.xml', Array2Xml::convert($this->input_array)->toXml());
}
/**
- * Convert array to dom
+ * Convert array to DOM
*
* @test
*
@@ -90,8 +136,8 @@ public function convert_array_to_xml_string()
public function convert_array_to_dom()
{
$dom = new DOMDocument;
- $dom->loadXML($this->expected_xml_string);
+ $dom->loadXML(file_get_contents(__DIR__ . '/resources/example.xml'));
- $this->assertSame($dom->saveXML(), Array2Xml::convert($this->input_array)->toDom()->saveXML());
+ $this->assertEquals($dom, Array2Xml::convert($this->input_array)->toDom());
}
}
diff --git a/tests/Xml2ArrayTest.php b/tests/Xml2ArrayTest.php
index d49d46b..ff62907 100644
--- a/tests/Xml2ArrayTest.php
+++ b/tests/Xml2ArrayTest.php
@@ -59,7 +59,7 @@ class Xml2ArrayTest extends TestCase
];
/**
- * Convert to array from an xml string containing only one empty node
+ * Convert to array from an XML string containing only one empty node
*
* @test
*
@@ -69,13 +69,13 @@ public function convert_to_array_from_an_xml_string_containing_only_one_empty_no
{
$string = '';
- $this->assertSame([
+ $this->assertEquals([
'root' => ''
], Xml2Array::convert($string)->toArray());
}
/**
- * Convert to array from an xml string containing one node with empty value
+ * Convert to array from an XML string containing one node with empty value
*
* @test
*
@@ -85,13 +85,13 @@ public function convert_to_array_from_an_xml_string_containing_one_node_with_emp
{
$string = '';
- $this->assertSame([
+ $this->assertEquals([
'root' => ''
], Xml2Array::convert($string)->toArray());
}
/**
- * Convert to array from xml string containing one node that whose value is on one line
+ * Convert to array from XML string containing one node that whose value is on one line
*
* @test
*
@@ -101,13 +101,13 @@ public function convert_to_array_from_xml_string_containing_one_node_that_whose_
{
$string = 'Welcome to Xml2Array Converter';
- $this->assertSame([
+ $this->assertEquals([
'root' => 'Welcome to Xml2Array Converter'
], Xml2Array::convert($string)->toArray());
}
/**
- * Convert to array from xml string containing one node that whose value is on multilines
+ * Convert to array from XML string containing one node that whose value is on multilines
*
* @test
*
@@ -122,13 +122,13 @@ public function convert_to_array_from_xml_string_containing_one_node_that_whose_
Xml2Array Converter
';
- $this->assertSame([
+ $this->assertEquals([
'root' => "Welcome to Xml2Array Converter"
], Xml2Array::convert($string)->toArray());
}
/**
- * Convert to array from xml string containing one node that whose value is cdata section
+ * Convert to array from XML string containing one node that whose value is Cdata Section
*
* @test
*
@@ -138,7 +138,7 @@ public function convert_to_array_from_xml_string_containing_one_node_that_whose_
{
$string = '';
- $this->assertSame([
+ $this->assertEquals([
'root' => [
'@cdata' => 'This is CDATA section'
]
@@ -146,7 +146,7 @@ public function convert_to_array_from_xml_string_containing_one_node_that_whose_
}
/**
- * Convert to array from xml string that root node has sub node
+ * Convert to array from XML string that root node has sub node
*
* @test
*
@@ -159,7 +159,7 @@ public function convert_to_array_from_xml_string_that_root_node_has_sub_node()
Node value
';
- $this->assertSame([
+ $this->assertEquals([
'root' => [
'subnode_1' => 'Node value',
'subnode_2' => 'Node value'
@@ -168,7 +168,7 @@ public function convert_to_array_from_xml_string_that_root_node_has_sub_node()
}
/**
- * Convert to array from xml string that node only has attributes
+ * Convert to array from XML string that node only has attributes
*
* @test
*
@@ -181,7 +181,7 @@ public function convert_to_array_from_xml_string_that_node_only_has_attributes()
';
- $this->assertSame([
+ $this->assertEquals([
'root' => [
'subnode_1' => [
'@attributes' => [
@@ -198,7 +198,7 @@ public function convert_to_array_from_xml_string_that_node_only_has_attributes()
}
/**
- * Convert to array from xml string that node has value and attributes
+ * Convert to array from XML string that node has value and attributes
*
* @test
*
@@ -211,7 +211,7 @@ public function convert_to_array_from_xml_string_that_node_has_value_and_attribu
Node value
';
- $this->assertSame([
+ $this->assertEquals([
'root' => [
'subnode_1' => [
'@value' => 'Node value',
@@ -230,7 +230,7 @@ public function convert_to_array_from_xml_string_that_node_has_value_and_attribu
}
/**
- * Convert to array from xml string containing has namespaces
+ * Convert to array from XML string containing has namespaces
*
* @test
*
@@ -245,7 +245,7 @@ public function convert_to_array_from_xml_string_containing_has_namespaces()
';
- $this->assertSame([
+ $this->assertEquals([
'root_node' => [
'example:node_with_namespace' => [
'example:sub' => [
@@ -263,7 +263,7 @@ public function convert_to_array_from_xml_string_containing_has_namespaces()
}
/**
- * Convert to array from xml string with special config
+ * Convert to array from XML string with special config
*
* @test
*
@@ -276,7 +276,7 @@ public function convert_to_array_from_xml_string_with_special_config()
Content
';
- $this->assertSame([
+ $this->assertEquals([
'root_node' => [
'sub_node' => [
'#text' => 'Content',
@@ -292,7 +292,7 @@ public function convert_to_array_from_xml_string_with_special_config()
}
/**
- * Convert from xml string to array with all cases
+ * Convert from XML string to array with all cases
*
* @test
*/
@@ -304,11 +304,11 @@ public function convert_from_xml_string_to_array_with_all_cases()
'namespacesOnRoot' => false
])->toArray();
- $this->assertSame($this->fulltest_expected_result, $result);
+ $this->assertEquals($this->fulltest_expected_result, $result);
}
/**
- * Convert from xml object to array with all cases
+ * Convert from XML object to array with all cases
*
* @test
*/
@@ -320,11 +320,11 @@ public function convert_from_xml_object_to_array()
'namespacesOnRoot' => false
])->toArray();
- $this->assertSame($this->fulltest_expected_result, $result);
+ $this->assertEquals($this->fulltest_expected_result, $result);
}
/**
- * Convert from dom object to array with all cases
+ * Convert from DOM object to array with all cases
*
* @test
*/
@@ -337,11 +337,26 @@ public function convert_from_dom_object_to_array()
'namespacesOnRoot' => false
])->toArray();
- $this->assertSame($this->fulltest_expected_result, $result);
+ $this->assertEquals($this->fulltest_expected_result, $result);
}
/**
- * Throw dom exception when invalid input
+ * Throw DOMException when input XML string is not well-formed XML
+ *
+ * @test
+ *
+ * @return void
+ */
+ public function throw_dom_exception_when_input_xml_string_not_well_formed()
+ {
+ $this->expectException(DOMException::class);
+ $this->expectExceptionMessage('Error parsing XML string, input is not a well-formed XML string.');
+
+ Xml2Array::convert('123');
+ }
+
+ /**
+ * Throw DOMException when invalid input
*
* @test
*
@@ -350,23 +365,21 @@ public function convert_from_dom_object_to_array()
public function throw_dom_exception_when_invalid_input()
{
$this->expectException(DOMException::class);
+ $this->expectExceptionMessage('The input XML must be one of types DOMDocument, SimpleXMLElement or well-formed XML string.');
Xml2Array::convert([]);
}
/**
- * Convert from xml string to json
+ * Convert from XML string to Json
*
* @test
*/
public function convert_from_xml_string_to_json()
{
$string = file_get_contents(__DIR__ . '/resources/example.xml');
-
$result = Xml2Array::convert($string)->toJson();
- $expected = '{"root_node":{"tag":"Example tag","attribute_tag":{"@value":"Another tag with attributes","@attributes":{"description":"This is a tag with attribute"}},"cdata_section":{"@cdata":"This is CDATA section"},"tag_with_subtag":{"sub_tag":["Sub tag 1","Sub tag 2"]},"mixed_section":{"@value":"Hello","@cdata":"This is another CDATA section","section":[{"@value":"Section number 1","@attributes":{"id":"sec_1"}},{"@value":"Section number 2","@attributes":{"id":"sec_2"}},{"@value":"Section number 3","@attributes":{"id":"sec_3"}}]},"example:with_namespace":{"example:sub":"Content"},"@attributes":{"xmlns:example":"http:\/\/example.com"}}}';
-
- $this->assertSame($expected, $result);
+ $this->assertJsonStringEqualsJsonFile(__DIR__ . '/resources/example.json', $result);
}
}
diff --git a/tests/resources/example.json b/tests/resources/example.json
new file mode 100644
index 0000000..41c844d
--- /dev/null
+++ b/tests/resources/example.json
@@ -0,0 +1,57 @@
+{
+ "root_node":
+ {
+ "tag": "Example tag",
+ "attribute_tag":
+ {
+ "@value": "Another tag with attributes",
+ "@attributes":
+ {
+ "description": "This is a tag with attribute"
+ }
+ },
+ "cdata_section":
+ {
+ "@cdata": "This is CDATA section"
+ },
+ "tag_with_subtag":
+ {
+ "sub_tag": ["Sub tag 1", "Sub tag 2"]
+ },
+ "mixed_section":
+ {
+ "@value": "Hello",
+ "@cdata": "This is another CDATA section",
+ "section": [
+ {
+ "@value": "Section number 1",
+ "@attributes":
+ {
+ "id": "sec_1"
+ }
+ },
+ {
+ "@value": "Section number 2",
+ "@attributes":
+ {
+ "id": "sec_2"
+ }
+ },
+ {
+ "@value": "Section number 3",
+ "@attributes":
+ {
+ "id": "sec_3"
+ }
+ }]
+ },
+ "example:with_namespace":
+ {
+ "example:sub": "Content"
+ },
+ "@attributes":
+ {
+ "xmlns:example": "http:\/\/example.com"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/resources/example.xml b/tests/resources/example.xml
index a03cfe5..d0a9623 100644
--- a/tests/resources/example.xml
+++ b/tests/resources/example.xml
@@ -1,25 +1,2 @@
-
- Example tag
-
- Another tag with attributes
-
-
-
-
- Sub tag 1
- Sub tag 2
-
-
-
- Hello
-
-
-
-
-
-
-
- Content
-
-
\ No newline at end of file
+Example tagAnother tag with attributesSub tag 1Sub tag 2HelloContent