简体   繁体   中英

Merge two variable SimpleXML Objects in PHP


I've been trying to merge two XML files I use to build my menubar in my web application for hours, but I can't get it to work.
I have my main XML file which looks like this:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<root>
    <version>1.0.0</version>
    <menu>
        <Category1>
            <item>
                <id>Cake</id>
                <nr>1</nr>
                <hint>I like these</hint>
                <userlevel>5</userlevel>                
            </item>
            <item>
                <id>Cake 2</id>
                <nr>2</nr>
                <hint>I like these too, but only for me</hint>
                <userlevel>10</userlevel>               
            </item>
        <Category1>

        <Category2WithApples>
        <item>
            <id>Apple Cake</id>
            <nr>1</nr>
            <hint>Sweet</hint>
            <userlevel>5</userlevel>                
        </item>
        <item>
            <id>Rainbow Cake</id>
            <nr>2</nr>
            <hint>Mine!!</hint>
            <userlevel>10</userlevel>               
        </item>
        <Category2WithApples>
    </menu>
</root>

Now, I want each user to be able to load in his custom XML which is in the same folder as the main.xml which looks like this:

<CategoryMyOwn>
    <item>
        <id>Item in my Category</id>
        <nr>0</nr>
        <hint>Some text</hint>
        <userlevel>0</userlevel>                
    </item>
</CategoryMyOwn>
<Category1>
    <item>
        <id>Item in existing category</id>
        <nr>0</nr>
        <hint>Some text</hint>
        <userlevel>0</userlevel>                
    </item>
</Category1>    

I've tried solutions from

but they all do not work at all for me or just append the second file to the end of my main.xml. So, my question is, how do I properly merge the user.xml into my main.xml so it looks like this:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<root>
    <version>1.0.0</version>
    <menu>
        <Category1>
            <item>
                <id>Cake</id>
                <nr>1</nr>
                <hint>I like these</hint>
                <userlevel>5</userlevel>                
            </item>
            <item>
                <id>Cake 2</id>
                <nr>2</nr>
                <hint>I like these too, but only for me</hint>
                <userlevel>10</userlevel>               
            </item>
            <item>
                <id>Item in existing category</id>
                <nr>0</nr>
                <hint>Some text</hint>
                <userlevel>0</userlevel>                
            </item>
        <Category1>

        <Category2WithApples>
        <item>
            <id>Apple Cake</id>
            <nr>1</nr>
            <hint>Sweet</hint>
            <userlevel>5</userlevel>                
        </item>
        <item>
            <id>Rainbow Cake</id>
            <nr>2</nr>
            <hint>Mine!!</hint>
            <userlevel>10</userlevel>               
        </item>
        <Category2WithApples>

        <CategoryMyOwn>
            <item>
                <id>Item in my Category</id>
                <nr>0</nr>
                <hint>Some text</hint>
                <userlevel>0</userlevel>                
            </item>
        </CategoryMyOwn>

    </menu>
</root>

Your second XML is not a document, XML documents need to have a document element node. In other words here at the top level only a single element node is allowed. All other element nodes have to be descendants of that node.

You can treat this as an XML fragment however. A fragment is the inner XML of an element node.

In both cases it easier to use DOM for that.

Append a fragment to a parent element node

Let's keep it simple for the first step and append the fragment to the menu node.

$document = new DOMDocument();
$document->loadXml($targetXml);
$xpath = new DOMXpath($document);

$fragment = $document->createDocumentFragment();
$fragment->appendXml($fragmentXml);

foreach ($xpath->evaluate('/root/menu[1]') as $menu) {
  $menu->appendChild($fragment);
}

echo $document->saveXml();

The Xpath expression can /root/menu[1] selects the first menu element node inside the root . This can be only one node or none.

A document fragment in DOM is a node object and can be appended like any other node (element, text, ...).

Merging nodes

Merging the category nodes is a little more difficult. But Xpath will help.

$document = new DOMDocument();
$document->loadXml($targetXml);
$xpath = new DOMXpath($document);

$fragment = $document->createDocumentFragment();
$fragment->appendXml($fragmentXml);

$menu = $xpath->evaluate("/root/menu[1]")->item(0);

foreach ($xpath->evaluate('*', $fragment) as $category) {
  $targets = $xpath->evaluate("{$category->nodeName}[1]", $menu);
  if ($targets->length > 0) {
    $targetCategory = $targets->item(0);
    foreach ($category->childNodes as $item) {
      $targetCategory->appendChild($item);
    }
  } else {
    $menu->appendChild($category);
  }
}
echo $document->saveXml();

Fetching the menu node

$menu = $xpath->evaluate("/root/menu[1]")->item(0);

This is about the same like in the first simple example. It fetch the menu nodes in root and returns the first found node. You should check if the list contained a node. But for this example just take it for guaranteed.

Iterating the fragment

foreach ($xpath->evaluate('*', $fragment) as $category) {
  ...
}

* is a simple Xpath expression that returns any element child node. The fragment can contain other nodes (whitespace, text, comment, ...). The second argument for DOMXpath::evaluate() is the context for the Xpath expression.

Fetching the target category

Next you need to fetch the category node with the same name from the target document. This will return a list with one node or an empty list.

$targets = $xpath->evaluate("{$category->nodeName}[1]", $menu);
if ($targets->length > 0) {
  ...
} else {
  ...
}

Append to the found target category

If the category exists append all child nodes from the category in the fragment to the target.

$targetCategory = $targets->item(0);
foreach ($category->childNodes as $item) {
  $targetCategory->appendChild($item);
}

Append a category

$menu->appendChild($category);

If the category doesn't exists, just append it to the menu .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM