1

I have an xml document below:

<?xml version="1.0" encoding="UTF-8" ?>
<books>
    <book>
        <name>Title One</name>
        <year>2014</year>
        <authors>
            <author>
                <name>Author One</name>
            </author>
        </authors>
    </book>
    <book serie="yes">
        <name>Title Two</name>
        <year>2015</year>
        <authors>
            <author>
                <name>Author two</name>
            </author>
            <author>
                <name>Author three</name>
            </author>
        </authors>
    </book>
    <book serie="no">
        <name>Title Three</name>
        <year>2015</year>
        <authors>
            <author>
                <name>Author four</name>
            </author>
        </authors>
    </book>
</books>

I want to convert it into an array bellow.

array(
    array('Tittle one', 2014, 'Author One'),
    array('Tittle two', 2015, 'Author two, Author three'),
    array('Tittle three', 2015, 'Author four'),
);

My code below is failing to produce the array structure that I would want:

function arrayRepresentation(){

    $xmldoc = new DOMDocument();

    $xmldoc->load("data/data.xml");

    $parentArray =  array();

    foreach ($xmldoc->getElementsByTagName('book') as $item) {

        $parentArray[] = array_generate($item);

    }

    var_dump($parentArray);
}


function array_generate($item){

    $movieArray = array();
    $childMovieArray = array();

    for ($i = 0; $i < $item->childNodes->length; ++$i) {
        $child = $item->childNodes->item($i);

        if ($child->nodeType == XML_ELEMENT_NODE) {
            if(hasChild($child)){

                $childMovieArray = array_generate($child);

            }

        }
        $movieArray[] = trim($child->nodeValue);
    }
    
    if(!empty($childMovieArray)){
        $movieArray = array_merge($movieArray,$childMovieArray);
    }
    

    return $movieArray;

}

function hasChild($p)
{
    if ($p->hasChildNodes()) {
        foreach ($p->childNodes as $c) {
            if ($c->nodeType == XML_ELEMENT_NODE)
                return true;
        }
    }
}


arrayRepresentation();

Basically I am looping through the the nodes to get xml element values. I am then checking if I Node has more child nodes and if it has I loop through it again to get the values. I am failling to deduce a way that: (i) will not give me some empty array elements (ii) Check for any child nodes and put all the xml sibling elements in a single string

1 Answer 1

4

PHPs DOM supports Xpath expressions for fetching specific nodes and values. That drastically reduces the amount of loops and conditions you will need.

Here is a demo:

// bootstrap the XML document
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);

$data = [];
// iterate the node element nodes
foreach ($xpath->evaluate('/books/book') as $book) {
    $authors = array_map(
       fn ($node) => $node->textContent,
       // fetch author name nodes as an array
       iterator_to_array($xpath->evaluate('authors/author/name', $book))
    );
    $data[] = [
        // cast first name element child to string
        $xpath->evaluate('string(name)', $book),
        // cast first year element child to string
        $xpath->evaluate('string(year)', $book),
        implode(', ', $authors)
    ];
}

var_dump($data);
Sign up to request clarification or add additional context in comments.

2 Comments

While this encodes a bunch of assumptions about the structure that the code in the question does not, I think that is very much a good thing - trying to make the code perfectly generic is almost certain to cause more problems than it solves.
Yes, because the "generic conversion" often depends on specific amount and position of the elements. Otherwise it will produce inconsistent data. For example if you add an ISBN element before the name or change order of the name and the year element. It actually makes a lot of assumptions, they are just not that visible in in the source.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.