PHP XPath query error after changing DOM - php

I always receive the message PHP Notice: Trying to get property of non-object in ... when trying to XPath-query a newly created node.
My XML file looks like this:
<products xmlns='http://example.com/products'>
<product id='1'>
<name>Product name</name>
</product>
</products>
My PHP file essentially applies an XPath query to get the existing <product> and a second query for its <name>. This works fine.
Then I insert a new <product> with the child <name> to the DOM root element and try the second query on the newly created elements. Getting the attribute works fine, but the second query, which should get the first child <name>'s value, fails with the PHP Notice Trying to get property of non-object in ....
$xmlFile = __DIR__ . '/products.xml';
$xml = new DOMDocument();
$xml->load($xmlFile);
$xml->formatOutput = true;
$xpath = new DOMXPath($xml);
$xpath->registerNamespace('p', $xml->lookupNamespaceUri($xml->namespaceURI));
/*
* query the first product's ID and name
*/
$product1 = Product::$xpath->query("//p:product[#id=1]")->item(0);
$product1Id = $product1->attributes->getNamedItem('id')->nodeValue;
// => "1"
$product1Name = $xpath->query("p:name", $product1)->item(0)->nodeValue;
// => "Product name"
/*
* create the second product
*/
$product2Node = $xml->createElement('product');
$product2Node->setAttribute('id', '2');
$product2NameNode = $xml->createElement('name', 'Test');
$product2Node->appendChild($product2NameNode);
$product2 = $xml->documentElement->appendChild($product2Node);
/*
* query the second product's ID and name
*/
$product2Id = $product2->attributes->getNamedItem('id')->nodeValue;
// => "2"
$product2Name = $xpath->query("p:name", $product2)->item(0)->nodeValue;
// => PHP Notice: Trying to get property of non-object in ...
$xml->save($xmlFile);
After running the PHP file, the XML looks correct:
<products xmlns='http://example.com/products'>
<product id='1'>
<name>Product name</name>
</product>
<product id='2'>
<name>Test</name>
</product>
</products>
I am really stuck on this, I tried to save the XML before querying, reloading the XML after saving, recreating the XPath object, etc.

I believe you need to use the createElementNS function (http://php.net/manual/en/domdocument.createelementns.php; you may also want to check on setAttributeNS -- http://www.php.net/manual/en/domelement.setattributens.php) instead of createElement in order to explicitly indicate that these elements belong to the http://example.com/products namespace.
$product2Node = $xml->createElementNS('http://example.com/products', 'product');
$product2Node->setAttribute('id', '2');
$product2NameNode = $xml->createElementNS('http://example.com/products', 'name', 'Test');
$product2Node->appendChild($product2NameNode);
(It's a little surprising that reloading the XML after saving did not resolve this, but without seeing the code that attempted to do the reload it's difficult to know what might have gone wrong.)

Related

update child node xml php from another child

please help.
I have XML file which is not filled correctly (some values are missed)
Bad example of XML
<?xml version="1.0"?>
<csv_data>
<row>
<brand>Maxxis</brand>
<code>1126668/02</code>
<articul_brand></articul_brand>
<tiporazmer></tiporazmer>
<name>(26/9.00 R12) 6PR TL</name>
<model>M-966 Mudzilla</model>
<sklad_msk></sklad_msk>
<sklad_nahabino></sklad_nahabino>
<sklad_chelny>8</sklad_chelny>
<price_big_opt>6,786р.</price_big_opt>
<price_small_opt>7,100р.</price_small_opt>
<price_retail>8,170р.</price_retail>
</row>
</csv_data>
Good example of XML
<?xml version="1.0"?>
<csv_data>
<row>
<brand>Metzeler</brand>
<code>ЦО04257/21</code>
<articul_brand>2491500</articul_brand>
<tiporazmer>110/80R18</tiporazmer>
<name>(110/80 R18) 58W TL (M) Front</name>
<model>Roadtec Z8</model>
<sklad_msk></sklad_msk>
<sklad_nahabino>2</sklad_nahabino>
<sklad_chelny></sklad_chelny>
<price_big_opt>7,720р.</price_big_opt>
<price_small_opt>8,106р.</price_small_opt>
<price_retail>9,270р.</price_retail>
</row>
</csv_data>
The point is that values in <tiporazmer></tiporazmer> are missed in bad example but they can be found in <name>(26/9.00 R12) 6PR TL</name>
The question is how to update fields <tiporazmer></tiporazmer> using name filed? Copy that without brackets? How?
What I did, but i've got an error Fatal error: Call to a member function getElementsByTagName() on a non-object in
Code:
<?php
$xml = new DOMDocument('1.0', 'utf-8');
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$xml->load('test2.xml');
$new_tiporazmer="26/9.00 R12";
//Get item Element
$element = $xml->getElementsByTagName('rows')->item(0);
//Load child elements
$tiporazmer = $element->getElementsByTagName('tiporazmer')->item(0);
//Replace old elements with new
$element->replaceChild($tiporazmer, $new_tiporazmer);
?>
The error is caused because you're calling getElementsByTagName('rows'), but the name of your XML node is row, so there shouldn't be an s in rows
Then in order to Replace old elements with new, you will need to use nodeValue instead of replaceChild unless you already have a constructed DOMDocument object ready.
But a better way to achieve what you want is to simply find the element by name directly, replace its text, and then save as a new file. I saved to the "/tmp" folder because permissions are usually writeable there on unix systems, but you can write anywhere your php interpreter is allowed.
<?php
$xml = new DOMDocument('1.0', 'utf-8');
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$xml->load('test2.xml');
$new_tiporazmer="26/9.00 R12";
//Get item Element and Replace old elements with new
$xml->getElementsByTagName('tiporazmer')->item(0)->nodeValue = $new_tiporazmer;
//Save as new file
$xml->save("/tmp/good.xml");
This results in good.xml:
<?xml version="1.0"?>
<csv_data>
<row>
<brand>Maxxis</brand>
<code>1126668/02</code>
<articul_brand/>
<tiporazmer>26/9.00 R12</tiporazmer>
<name>(26/9.00 R12) 6PR TL</name>
<model>M-966 Mudzilla</model>
<sklad_msk/>
<sklad_nahabino/>
<sklad_chelny>8</sklad_chelny>
<price_big_opt>6,786р.</price_big_opt>
<price_small_opt>7,100р.</price_small_opt>
<price_retail>8,170р.</price_retail>
</row>
</csv_data>

Count the Number of a Specific Tag in an XML file - PHP

If I have the following structure in an XML File;
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CstmrDrctDbtInitn>
<GrpHdr>
<MsgID>rBYEqfjzEU</MsgID>
<CreDtTm>2014-07-01T12:36:15</CreDtTm>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>400.4</CtrlSum>
<InitgPty>
<Id>
<PrvtId>
<Othr>
<Id>IA1234567</Id>
</Othr>
</PrvtId>
</Id>
</InitgPty>
</GrpHdr>
</CstmrDrctDbtInitn>
</Document>
The above code represents one transaction between the <CstmrDrctDbtInitn> and <\CstmrDrctDbtInitn> tags. This file will be appended to include more transactions which will all start and end with a <CstmrDrctDbtInitn> and <\CstmrDrctDbtInitn> tags. I need to count the number of transactions in the file, so i basically need to count the number of <CstmrDrctDbtInitn> tags in the file. Any suggestions? Sorry if I am explaining this badly, confused!
I altered the following PHP code as suggested but no luck :(
$filename = date('Y-W').'.xml'; //2014-26.xml
//Check if a file exists
if (file_exists($filename))
{
$dom = simplexml_load_string($xml);
global $NumberTransactions;
$NumberTransactions = count($dom->CstmrDrctDbtInitn);
// call xml appendFile function
appendFile($filename);
}
else
{
// call xml createFile function
createFile($filename);
}
Assuming you mean to count the number of child nodes under GrpHdr - you could use SimpleXML:
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CstmrDrctDbtInitn>
<GrpHdr>
<MsgID>rBYEqfjzEU</MsgID>
<CreDtTm>2014-07-01T12:36:15</CreDtTm>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>400.4</CtrlSum>
<InitgPty>
<Id>
<PrvtId>
<Othr>
<Id>IA1234567</Id>
</Othr>
</PrvtId>
</Id>
</InitgPty>
</GrpHdr>
</CstmrDrctDbtInitn>
</Document>';
$dom = simplexml_load_string($xml);
// var_dump($dom->CstmrDrctDbtInitn->GrpHdr);
echo count($dom->CstmrDrctDbtInitn->GrpHdr->children());
If you meant a variation you should be able to get a good clue of the structure by uncommenting that var_dump line and working through the object structure.
If you meant something else - give us a clue by telling us in detail what you wanted the count value to be based on your example data.
===================== UPDATED FOLLOWING CLARIFICATION BELOW=============
To count the number of CstmrDrctDbtInitn groups you can use the original example, but instead the count line would be:
echo count($dom->CstmrDrctDbtInitn);
DOMXpath::evaluate() can use Xpath to count the nodes.
// create and load
$dom= new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
// register an alias/prefix for the namespace
$xpath->registerNamespace('iso', 'urn:iso:std:iso:20022:tech:xsd:pain.008.001.02');
// get the count
var_dump($xpath->evaluate('count(//iso:CstmrDrctDbtInitn)'));
Demo: https://eval.in/169122

Hide XML declaration in files generated using PHP

I was tesing with a simple example of how to display XML in browser using PHP and found this example which works good
<?php
$xml = new DOMDocument("1.0");
$root = $xml->createElement("data");
$xml->appendChild($root);
$id = $xml->createElement("id");
$idText = $xml->createTextNode('1');
$id->appendChild($idText);
$title = $xml->createElement("title");
$titleText = $xml->createTextNode('Valid');
$title->appendChild($titleText);
$book = $xml->createElement("book");
$book->appendChild($id);
$book->appendChild($title);
$root->appendChild($book);
$xml->formatOutput = true;
echo "<xmp>". $xml->saveXML() ."</xmp>";
$xml->save("mybooks.xml") or die("Error");
?>
It produces the following output:
<?xml version="1.0"?>
<data>
<book>
<id>1</id>
<title>Valid</title>
</book>
</data>
Now I have got two questions regarding how the output should look like.
The first line in the xml file '', should not be displayed, that is it should be hidden
How can I display the TextNode in the next line. In total I am exepecting an output in this fashion
<data>
<book>
<id>1</id>
<title>
Valid
</title>
</book>
</data>
Is that possible to get the desired output, if so how can I accomplish that.
Thanks
To skip the XML declaration you can use the result of saveXML on the root node:
$xml_content = $xml->saveXML($root);
file_put_contents("mybooks.xml", $xml_content) or die("cannot save XML");
Please note that saveXML(node) has a different output from saveXML().
First question:
here is my post where all usable threads with answers are listed: How do you exclude the XML prolog from output?
Second question:
I don't know of any PHP function that outputs text nodes like that.
You could:
read xml using DomDocument and save each node as string
iterate trough nodes
detect text nodes and add new lines to xml string manually
At the end you would have the same XML with text node values in new line:
<node>
some text data
</node>

php only appending 3 out of 5 child nodes

This code is only appending 3 of the 5 name nodes. Why is that?
Here is the original XML:
It has 5 name nodes.
<?xml version='1.0'?>
<products>
<product>
<itemId>531670</itemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<categoryPath>
<category><name>Buy</name></category>
<category><name>Car, Marine & GPS</name></category>
<category><name>Car Installation Parts</name></category>
<category><name>Deck Installation Parts</name></category>
<category><name>Antennas & Adapters</name></category>
</categoryPath>
</product>
</products>
Then is run this PHP code. which is suppossed to appened ALL name nodes into the product node.
<?php
// load up your XML
$xml = new DOMDocument;
$xml->load('book.xml');
// Find all elements you want to replace. Since your data is really simple,
// you can do this without much ado. Otherwise you could read up on XPath.
// See http://www.php.net/manual/en/class.domxpath.php
//$elements = $xml->getElementsByTagName('category');
// WARNING: $elements is a "live" list -- it's going to reflect the structure
// of the document even as we are modifying it! For this reason, it's
// important to write the loop in a way that makes it work correctly in the
// presence of such "live updates".
foreach ($xml->getElementsByTagName('product') as $product ) {
foreach($product->getElementsByTagName('name') as $name ) {
$product->appendChild($name );
}
$product->removeChild($xml->getElementsByTagName('categoryPath')->item(0));
}
// final result:
$result = $xml->saveXML();
echo $result;
?>
The end result is this and it only appends 3 of the name nodes:
<?xml version="1.0"?>
<products>
<product>
<itemId>531670</itemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name>
<name>Antennas & Adapters</name>
<name>Car Installation Parts</name>
</product>
</products>
Why is it only appending 3 of the name nodes?
You can temporarily add the name elements to an array before appending them, owing to the fact that you're modifying the DOM in real time. The node list generated by getElementsByTagName() may change as you are moving nodes around (and indeed that appears to be what's happening).
<?php
// load up your XML
$xml = new DOMDocument;
$xml->load('book.xml');
// Array to store them
$append = array();
foreach ($xml->getElementsByTagName('product') as $product ) {
foreach($product->getElementsByTagName('name') as $name ) {
// Stick $name onto the array
$append[] = $name;
}
// Now append all of them to product
foreach ($append as $a) {
$product->appendChild($a);
}
$product->removeChild($xml->getElementsByTagName('categoryPath')->item(0));
}
// final result:
$result = $xml->saveXML();
echo $result;
?>
Output, with all values appended:
<?xml version="1.0"?>
<products>
<product>
<ItemId>531670</ItemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name><name>Car, Marine & GPS</name><name>Car Installation Parts</name><name>Deck Installation Parts</name><name>Antennas & Adapters</name></product>
</products>
You're modifying the DOM tree as you're pulling results from it. Any modifications to the tree that cover the results of a previous query operation (your getElementsByTagName) invalidate those results, so you're getting undefined results. This is especially true of operations that add/remove nodes.
You're moving nodes as you're iterating through them so 2 are being skipped. I'm not a php guy so I can't give you the code to do this, but what you need to do is build a collection of the name nodes and iterate through that collection in reverse.
A less complicated way to do it is to manipulate the nodes with insertBefore
foreach($xml->getElementsByTagName('name') as $node){
$gp = $node->parentNode->parentNode;
$ggp = $gp->parentNode;
// move the node above gp without removing gp or parent
$ggp->insertBefore($node,$gp);
}
// remove the empty categoryPath node
$ggp->removeChild($gp);

XML search for a node from an array and add new data to a new node

From this tutorial - section on Adding nodes over at PHPFreaks, I did post over there but have had no replies strange as the tutorial was written by them.
http://www.phpfreaks...ndling-xml-data
When I use my xml file it creates the node but does not insert the new data. There are no page errors. I'm sure I have just missed something very simple and after hours of trying I will now bow down and ask for help.
This is the script I'm using
<?php
// isbn => pages
$page_numbers = array(
'1234' => '654', // insert data into catparent node
'5678' => '789', // insert data into catparent node
);
$dom = new DOMDocument();
$dom->load('edtest.xml');
$xpath = new DOMXPath($dom);
$items = $xpath->query('item');
foreach($items as $item)
{
$item->appendChild($dom->createElement('catparent', $page_numbers[$item->getAttribute('catcode')]));
}
$dom->save('edtest_new.xml');
?>
My XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<catcode>1234</catcode>
<catdesc>Systems - System Bundles</catdesc>
<price_cost>999.95</price_cost>
<price_sell>999.95</price_sell>
</item>
</items>
Output XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<catcode>1234</catcode>
<catdesc>Systems - System Bundles</catdesc>
<price_cost>999.95</price_cost>
<price_sell>999.95</price_sell>
<catparent></catparent> // it creates but does not insert required data
</item>
</items>
It runs the script creates the required node, but it does not insert the required data. The object of the script is to find <catcode> = 1234 and add a new <catparent> </catparent> with required data from the array.
If there is a better way to achieve the result or just the correction needed.
Thanks
You're using $item->getAttribute('catcode'). However, catcode is not an attribute of <item>, it's a child element.
Try
$catcode = $item->getElementsByTagName('catcode');
$parent_code = $page_numbers[ $catcode[0] ];
$item->appendChild( $dom->createElement('catparent', $parent_code) );

Categories