simpleXML - How to precisely control a added child node placement?

Hello all,

Given the following XML for example:


<?xml version="1.0" encoding="UTF-8"?>
    <node0>
      <node1>
      </node1>
      <node3>
      </node3>
    </node0>

Let’s say we wanted to add a <node2> exactly between </node1> and <node3> . How can we do that by using simpleXML ?

If I add a child into node0, it places himself always after the </node3>.
I’ve changed the position on the php xml generation code, but it’s ignored.

Can I please have your help?

Thanks a lot in advance,
Márcio

Sure. The way something reads the DOM, there, we could have a difference. On this case, this servers, needs to have elements on the first place.
I can question the server itself, about, why he forces to order our elements were, apparently, we shouldn’t have… but that’s another history.

If I were able to use 01010101 I will do anything. :stuck_out_tongue:
True. But not what I was looking for.

I’ve opted to have always a structured empty xml file that will be common to ALL commands, except some new stuff that we need to add.

I would do what you suggested, or something similar, if I had to precisely change a lot on the XML file. It isn’t the case here. And still I had to think about it, because I would like to keep the XML on one place, and the logic that works with it on another… on an effort to organize things.

I will investigate about the ‘//s’.

Thanks a lot,
Márcio

After a quick look at the docs, I don’t think it is possible, you could however extend it and add a method to do so.

The method would have to create a DOM document from the SimpleXML object, add the child then re-import into SimpleXML.

Or, it may well be possible.

Cue, Salathe. :stuck_out_tongue:

[createDocument]This function creates a new instance of class DOMElement. This node will not show up in the document unless it is inserted with (e.g.) DOMNode->appendChild().

:slight_smile:

Ok… I still have doubts. :slight_smile:
But we can achieve the intend result by doing:


//grab the target.
$nodePeriod = $xmlObj->command->create->children(self::OBJ_URI_DOMAIN)->create->period;

//transform the target into dom object for manipulation
$nodePeriodDom = dom_import_simplexml($nodePeriod);

//do not create a node using simpleXML, and then importing to DOM. Using DOM instead:
$nsNodeDom = $nodePeriodDom->ownerDocument->createElement('domain:ns');
           
//the same node placed on a specific position:
$nsNodeDom2 = $nodePeriodDom->parentNode->insertBefore($nsNodeDom, $nodePeriodDom->nextSibling);
           
//grab that node, and bring it back to a simpleXML so that, simply :) we can add children
 $simpleXmlNsNode = simplexml_import_dom($nsNodeDom2);
//add childrens:
$hostAttr = $simpleXmlNsNode->addChild('domain:hostAttr', null, self::OBJ_URI_DOMAIN);
$hostName = $hostAttr->addChild('domain:hostName', $nsNome);

$hostAddr = $hostAttr->addChild('domain:hostAddr', $nsEndereco);
           
$hostAddr->addAttribute('ip', 'v4');

so, here we are creating but, if we do only this, he doesn’t get placed anywhere?

$nsNodeDom = $nodePeriodDom->ownerDocument->createElement('domain:ns');

I have declared, again, the namespace for this domain prefix, otherwise, it get’s ignored. why?

$hostAttr = $simpleXmlNsNode->addChild('domain:hostAttr', null, self::OBJ_URI_DOMAIN);

those don’t need the Namespace address argument, they will automagicly get it. Why?

$hostName = $hostAttr->addChild('domain:hostName', $nsNome);

note that I have added the value directly using addChild attribute.
Because if I do something like this:


$xmlObj->command->create->children(self::OBJ_URI_DOMAIN)->create->ns->hostAttr->hostName = $nsNome;

I get ANOTHER node added at the end. Why?

(:

I realise now that this error is telling us that, before we try to append the prefix, we need, somehow, to register it.
The parent of the child we want to add, already has a namespace declaration.

So I’m wondering, instead of register the namespace (and I only found a method that after the registration, requires xpath usage, that we don’t have), maybe we could use the ns of the parent node?

Can I have a push… ? :smiley:

Salathe… :smiley:

I do have some questions before I try to adapt your example to my needs.

If my interpretation is correct:


/**
 *
 * @param SimpleXMLElement $sxe - is the source XML document;
 * @param SimpleXMLElement $insert - is the simpleXML node object that we want to insert.
 * @param SimpleXMLElement $target - is the simpleXML node object that will serve as target.
 * @return <dom object> - will finish with the xml edition. 
 * 
 */
function simplexml_insert_after(SimpleXMLElement $sxe, SimpleXMLElement $insert, SimpleXMLElement $target)
{
    /*
     * import de simpleXML node object that will serve as target; "<node1> object" on this case.
     * converts to a dom reference node.
     */
    $target_dom = dom_import_simplexml($target);

    /*
     * From our root object element of our target - <node0>
     * copy that "simpleXML node object" (and all the children): the simpleXML node object that we want to insert: <node2>;
     * and associate it with the "current document" - The document where $target belongs to.
     * 
     * Convert it to a dom node object.
     * 
     */
    $insert_dom = $target_dom->ownerDocument->importNode(dom_import_simplexml($insert), true);

    /*
     * If we have a sibling after <node1>
     */
    if ($target_dom->nextSibling) 
    {
        //insert a child on the target(node1) parent node(node0) 
        //and insert it, before "target(node1)'s next node" (node3) 
        return $target_dom->parentNode->insertBefore($insert_dom, $target_dom->nextSibling);
    } else {
        //if we have no sibling, do not insertBefore, just create the child node.
        return $target_dom->parentNode->appendChild($insert_dom);
    }
}


If all those assumptions are correct:
Why do we have $sxe as an argument of our function ? If seems it isn’t used anywhere inside. ?:shifty:

Else, if they are not correct:
can I please ask your help on interpreting those?

Thanks a lot,
Márcio

Omg, I’m doing if/else statements on text. :s

:eek: (Forgot my glasses already).

Try 1?
:s

//grab the root node and all others into a simpleXML object.
    $xmlObj = simplexml_load_file('RepositorioXml/testCaseDomXml.xml');

    //instanciante dom
    $xmlDom = new DomDocument();

    //load (again? :s)
    $xmlDom->load('RepositorioXml/testCaseDomXml.xml');

    //grab the reference code using simpleXML - why? Well...
    //the serious one has namespaces:
    //$xmlObj->command->create->children(self::OBJ_URI_DOMAIN)->create->registrant; and I found simplier this way.
    $referenceNode1 = $xmlObj->node1;

    //convert it into a dom node:
    $referenceDomNode1 = dom_import_simplexml($referenceNode1);
    
    //creates the node that we want to add:
    //in the real code this will have a prefix 'domain:ns' ;
    $node2 = $xmlDom->createElement('node2');

    //Before our reference node, insert $node2.
    //THIS DOESN'T WORK. "not found error" exception. :s
    $insertedNode2 = $xmlDom->insertBefore($node2, $referenceDomNode1);

    //Convert back to simpleXML to add attributes and other childs to that node..
    $simpleXmlNode2 = simplexml_import_dom($insertedNode2);

I will give another try. Any comments are appreciated. This doesn’t work. :s

Converting:


error_reporting(E_ALL);

$xmlObj = simplexml_load_file('RepositorioXml/testCaseDomXml.xml');

$targetNode = $xmlObj->node1;

$targetNodeDom = dom_import_simplexml($targetNode);

$insertNode = new SimpleXMLElement('<node2/>');

$insertNodeDom = $targetNodeDom->ownerDocument->importNode(dom_import_simplexml($insertNode), true);

$targetNodeDom->parentNode->insertBefore($insertNodeDom, $targetNodeDom->nextSibling);

$simpleXmlNode2 = simplexml_import_dom($insertNodeDom);

$xmlString = $xmlObj->asXML();

var_dump($xmlString);

This seems to work. However, this allows an insertion of a self close node.
How can we do it, in a way that it supports start and end tags of a given node?
<node2></node2>
On simpleXML we will use for example: addChild(‘domain:ns’);

?

I’ve tried to comment out the instantiation line and use simplexml_load_string

//$insertNode = new SimpleXMLElement('<node2/>');
$insertNode = simplexml_load_string('<node2></node2>');

But I still get <node2/> printed. :s

Man, for DOM all the siblings are same, their orientation doesn’t make any difference.

Therefore, use regular expression if you really want to do what you said. Use “s” modifier (‘//s’) to first get all the doc into string and be treated as a single string. The “s” modifier makes newline be treated as normal character.
After this keep track of “</node>” and exactly after second “</node>” insert your sting.

Hope you understand.

DOMDocument::insertBefore maybe help inspire you. :slight_smile:

Here’s an idea for you to learn from (not a copy/paste solution!) from a similar question asked elsewhere which creates a basic [FONT=monospace]simplexml_insert_after()[/FONT] function.

I was just reading this!

As Anthony said, SimpleXML is not designed for what you want to do. Instead you will have to make use of the DOM extension to do the heavy work of appending a node tree at a specific position: this isn’t such a headache because fortunately SimpleXML and DOM can both work on the same XML structure at the same time.

Here’s an idea for you to learn from (not a copy/paste solution!) from a similar question asked elsewhere which creates a basic [font=monospace]simplexml_insert_after()[/font] function.

Switching between DOM to/from SimpleXML takes a little getting used to, but it is nice to be able to use both interfaces at the same time.

:s OMG!

I will just hope Salathe could see this post. :slight_smile:

This should be a simple task. Just add a node after another. No recursive, no abstract stuff, this is only for this case. I don’t need a class or a function for this…

I have to deal with namespaces… and… :eek::eek::eek:
When you or other hight level SP php guru says that, it’s probably true.

Either I run, or I deal with it… hmm…

Let’s start small then.

I will try to post back something.
:cool:

Regards,
Márcio

No luck.

Warning: SimpleXMLElement::__construct() [simplexmlelement.–construct]: namespace error : Namespace prefix domain on ns is not defined in on line 296

And on that line I have:

$nsNode = new SimpleXMLElement('<domain:ns>');

The context:


//grab the target.
$nodeRegistrant = $xmlObj->command->create->children(self::OBJ_URI_DOMAIN)->create->registrant;

//transform the target into dom object for manipulation
$nodeRegistrantDom = dom_import_simplexml($nodeRegistrant);

//creates the simpleXML object node to be inserted.
$nsNode = new SimpleXMLElement('<domain:ns>');

//grabs the node and all his children (none in this case), by importing the node we want to add,
//into the root object element that contains the <domain:registrant> node.
$nsNodeDom = $nodeRegistrantDom->ownerDocument->importNode(dom_import_simplexml($nsNode), true);
           
$nodeRegistrantDom->parentNode->insertBefore($nsNodeDom, $nodeRegistrantDom->nextSibling);

$simpleXmlNsNode = simplexml_import_dom($nsNodeDom);
           
//cria os nós necessários:
$hostAttr = $simpleXmlNsNode->addChild('domain:hostAttr');
$hostName = $hostAttr->addChild('domain:hostName');
$hostAddr = $hostAttr->addChild('domain:hostAddr');
$hostAddr->addAttribute('ip', 'v4');

Thanks a lot,
Márcio

If we add children to that node, the magic happens, and somehow, the xml structure changes from <node/> TO <node> </node>;

Nice. :slight_smile: (but I’m still ignorant about the reason :shifty:(:).

I will now try to pass all from this test case with the other case with namespaces and all…