Reaching the last XML element - php

so I got this XML.
I got a lot of such similar blocks in XML, and I can loop through it. But how would I know how many blocks are there ?
Or how would I stop after the last block ?
Any suggestion is appreciated.
<StockBalanceOut xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/BON_StockBalanceOut" class="entity">
<_DocumentHash>f5a598f180ccdecffeb7774d58ca8743</_DocumentHash>
<AvailPhysicalAvailableQty>0</AvailPhysicalAvailableQty>
<AvailPhysicalReservedQty>0</AvailPhysicalReservedQty>
<AvailPhysicalReturnQty>0</AvailPhysicalReturnQty>
<AvailPhysicalReworkQty>0</AvailPhysicalReworkQty>
<AvailPhysicalScrapQty>0</AvailPhysicalScrapQty>
<Date>2014-09-26</Date>
<ItemId>15742-20907</ItemId>
<ItemShippingClass>Empty</ItemShippingClass>
<OnOrderQty>0</OnOrderQty>
<PhysicalInventAvailableQty>0</PhysicalInventAvailableQty>
<PhysicalInventReservedQty>0</PhysicalInventReservedQty>
<PhysicalInventReturnQty>0</PhysicalInventReturnQty>
<PhysicalInventReworkQty>0</PhysicalInventReworkQty>
<PhysicalInventScrapQty>0</PhysicalInventScrapQty>
<RecId>5637416600</RecId>
<RecVersion>1</RecVersion>
<Time>15:25:52</Time>
</StockBalanceOut>
<StockBalanceOut xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/BON_StockBalanceOut" class="entity">
<_DocumentHash>6c6a3aa160f3ab9388f8e1b5b2fd7dc1</_DocumentHash>
<AvailPhysicalAvailableQty>99</AvailPhysicalAvailableQty>
<AvailPhysicalReservedQty>0</AvailPhysicalReservedQty>
<AvailPhysicalReturnQty>0</AvailPhysicalReturnQty>
<AvailPhysicalReworkQty>0</AvailPhysicalReworkQty>
<AvailPhysicalScrapQty>0</AvailPhysicalScrapQty>
<Date>2014-09-26</Date>
<ItemId>21234-29752</ItemId>
<ItemShippingClass>Empty</ItemShippingClass>
<OnOrderQty>0</OnOrderQty>
<PhysicalInventAvailableQty>99</PhysicalInventAvailableQty>
<PhysicalInventReservedQty>0</PhysicalInventReservedQty>
<PhysicalInventReturnQty>0</PhysicalInventReturnQty>
<PhysicalInventReworkQty>0</PhysicalInventReworkQty>
<PhysicalInventScrapQty>0</PhysicalInventScrapQty>
<RecId>5637416601</RecId>
<RecVersion>1</RecVersion>
<Time>15:25:52</Time>
</StockBalanceOut>

From your XML, I got this information.
There are multiple 'blocks' for <StockBalanceOut>, you can access each one by :-
$objectOfXMLFile->StockBalanceOut[0];
$objectOfXMLFile->StockBalanceOut[1];
To reach till the end you can run a while loop. If any index (suppose 10 doesn't exist) for StockBalanceOut doesn't exist, then it will return null.
$counter=0; //run from 0
while(!is_null($xmlOBJ->StockBalanceOut[$counter]))
{
//do anything here
$counter++;
}

I would look at trying something like this. To save you the link trip, here is some example code to get you started.
<?php
$xml = <<<EOF
<people>
<person name="Person 1">
<child/>
<child/>
<child/>
</person>
<person name="Person 2">
<child/>
<child/>
<child/>
<child/>
<child/>
</person>
</people>
EOF;
$elem = new SimpleXMLElement($xml);
foreach ($elem as $person) {
printf("%s has got %d children.\n", $person['name'], $person->count());
}
?>

Related

Merge people profiles based on email match

I have an existing directory (php with xml datasource) which contains people information such as this:
MainSource.xml
<people>
<person>
<id></id>
<last_name></last_name>
<first_name></first_name>
<email></email>
<phone></phone>
</person>
...
</people>
I need to add a new node to MainSource.xml from NewSource.xml, matching on email address, from the new datasource which contains people info like this:
NewSource.xml
<people>
<person>
<email></email>
<website_url></website_url>
</person>
...
</people>
I have tried a number of variations, but I think my hangup is properly comparing the two documents. Logically, it feels like I need to be iterating, as opposed to foreach? Or two foreach, one for each source? Here's a sample of what I'm thinking. Please offer any clarity or insight which can nudge me along in the right direction.
<?php
$doc1 = new DOMDocument();
$doc1->load('MainSource.xml');
$doc2 = new DOMDocument();
$doc2->load('NewSource.xml');
foreach ($doc1->person as $person) {
if ($person->email === $doc2->person->email) {
$node = $doc1->createElement("website_url", $valueFromDoc2);
$newnode = $doc1->appendChild($node);
}
}
$merged = $doc1->saveXML();
file_put_contents('MergedSource.xml', $merged)
?>
As mentioned by #waterloomatt, you need to use xpath to achieve that.
Assuming that MainSource.xml looks like this:
<people>
<person>
<id>1</id>
<last_name>smith</last_name>
<first_name>john</first_name>
<email>js#example.com</email>
<phone>555-123-1234</phone>
</person>
<person>
<id>2</id>
<last_name>doe</last_name>
<first_name>jane</first_name>
<email>jd#anotherexample.com</email>
<phone>666-234-2345</phone>
</person>
</people>
and NewSource.xml looks like this:
<people>
<person>
<email>js#example.com</email>
<website_url>js.example.com</website_url>
</person>
<person>
<email>jd#anotherexample.com</email>
<website_url>jd.anotherexample.com</website_url>
</person>
</people>
you can try this:
$doc1->loadXML('MainSource.xml');
$xpath1 = new DOMXPath($doc1);
# find each person's email address
$sources = $xpath1->query('//person//email');
$doc2->loadXML('NewSource.xml');
$xpath2 = new DOMXPath($doc2);
foreach ($sources as $source) {
#for each email address, get the parent and use that as the destination
#of the new web address element
$destination = $xpath1->query('..',$source);
#in the other doc, search for each person whose email address matches
#that of the first doc and get the relevant web address
$exp2 = "//person[email[text()='{$source->nodeValue}']]//website_url";
$target = $xpath2->query($exp2);
#import the result of the search as a node into the first doc
$node = $doc1->importNode($target[0], true);
#finally, append the imported node in the right location of the first doc
$destination[0]->appendChild($node);
};
echo $doc1->saveXml();
Output:
<people>
<person>
<id>1</id>
<last_name>smith</last_name>
<first_name>john</first_name>
<email>js#example.com</email>
<phone>555-123-1234</phone>
<website_url>js.example.com</website_url></person>
<person>
<id>2</id>
<last_name>doe</last_name>
<first_name>jane</first_name>
<email>jd#anotherexample.com</email>
<phone>666-234-2345</phone>
<website_url>jd.anotherexample.com</website_url></person>
</people>

Count how many children are in XML with PHP

i know a few about php, so sorry for the question:
i have this file xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<alert>
<status> </status>
<nothing> </nothing>
<info>
<area>
</area>
</info>
<info>
<area>
</area>
</info>
<info>
<area>
</area>
</info>
</alert>
i must do a for loop and inside a "foreach" for each
The problem is that i'm not sure what is a way to know how many times i had to repeat a for loop. Because in this file xml (that is an example) i don't know how many are
Is good if:
$url = "pathfile";
$xml = simplexml_load_file($url);
$numvulcani = count($xml->alert->info); // is good ?
for ($i = 0; $i <= $numvulcani; $i++) {
foreach ($xml->alert->info[$i] as $entry) {
$area = $entry->area;
}
}
is true ?
sorry for bad english
You need to use SimpleXMLElement::count function for this — It counts the children of an element.
<?php
$xml = <<<EOF
<people>
<person name="Person 1">
<child/>
<child/>
<child/>
</person>
<person name="Person 2">
<child/>
<child/>
<child/>
<child/>
<child/>
</person>
</people>
EOF;
$elem = new SimpleXMLElement($xml);
foreach ($elem as $person) {
printf("%s has got %d children.\n", $person['name'], $person->count());
}
?>
The output will be as follows :
Person 1 has got 3 children.
Person 2 has got 5 children.
Also take a look at this link : xml count using php
Try replacing foreach ($xml->alert->info[$i] as $entry) with:
foreach ($xml->alert->info[$i] as $j => $entry)
The current item index will be $j
You're perhaps overcomplicating this a bit as it's new to you.
First of all, you don't need to reference the alert root element like $xml->alert because the SimpleXMLElement named by the variable $xml represents that document element already.
And second, you don't need to count here, you can just foreach directly:
foreach ($xml->info as $info) {
echo ' * ', $info->asXML(), "\n";
}
This iterates over those three info elements that are children of the alert element.
I recommend the Basic SimpleXML usage guide in the PHP manual for a good start with SimpleXML.

PHP XML append to created file

I have the following XML documment:
<list>
<person>
<name>Simple name</name>
</person>
</list>
I try to read it, and basically create another "person" element. The output I want to achieve is:
<list>
<person>
<name>Simple name</name>
</person>
<person>
<name>Simple name again</name>
</person>
</list>
Here is how I am doing it:
$xml = new DOMDocument();
$xml->load('../test.xml');
$list = $xml->getElementsByTagName('list') ;
if ($list->length > 0) {
$person = $xml->createElement("person");
$name = $xml->createElement("name");
$name->nodeValue = 'Simple name again';
$person->appendChild($name);
$list->appendChild($person);
}
$xml->save("../test.xml");
What I am missing here?
Edit: I have translated the tags, so that example would be clearer.
Currently, you're pointing/appending to the node list instead of that found parent node:
$list->appendChild($person);
// ^ DOMNodeList
You should point to the element:
$list->item(0)->appendChild($person);
Sidenote: The text can already put inside the second argument of ->createElement():
$name = $xml->createElement("name", 'Simple name again');

PHP xpath get elements by attribute in foreach loop

im am trying to loop through all the LINE_ITEMS and it works fine but in the foreach-loop im trying to access the single LINE_ITEM by attribute by using xpath but i don't get any result. Does anyone know the problem ?
foreach($items = $xmlData->xpath('//zw:LINE_ITEM') as $item) {
$item->xpath('//namespace:PID[#type="erp_pid"]'); No result
}
$xmlData->xpath('//zw:LINE_ITEM')
works fine i get all LINE_ITEMS but
when i try to do some xpath on the item i don't get any result.
How can i access the PID value of for example "erp_pid" ?
<?xml version="1.0" encoding="UTF-8"?>
<NM_DOCS>
<NM_DOC>
<DOCUMENT qualifier="default" role="original" test="false" type="orders">
<VERSION>4.0</VERSION>
<HEADER>
<CONTROL_INFO>
<LAST_SAVE_DATE>2015-03-18T13:44:32+01:00</LAST_SAVE_DATE>
<PROCESS_TYPE>silent</PROCESS_TYPE>
<SOURCE>sales</SOURCE>
</CONTROL_INFO>
<SOURCING_INFO>
<REFERENCES>
<REFERENCE type="order_nexmart">
<ID>109546063</ID>
<DATES>
<DATE type="order">2015-03-18T13:44:30+01:00</DATE>
</DATES>
</REFERENCE>
</REFERENCES>
</SOURCING_INFO>
<DOCUMENT_INFO>
<DOCUMENT_ID>Test bestellnummer</DOCUMENT_ID>
<DATES>
<DATE type="delivery_ordered">2015-04-19T12:41:41+02:00</DATE>
</DATES>
<PARTIES>
<PARTY type="buyer">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<PORTAL_ID>BDE600028</PORTAL_ID>
<ADDITIONAL_IDS>
</ADDITIONAL_IDS>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
<ACCOUNTS>
<ID type="emart">sales.nexmart</ID>
</ACCOUNTS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="supplier">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<PORTAL_ID>zweygart_app_de</PORTAL_ID>
<ADDITIONAL_IDS>
</ADDITIONAL_IDS>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="delivery">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="invoice_recipient">
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
</PARTIES>
<REMARKS>
<REMARK type="order">Test bemerkung</REMARK>
</REMARKS>
</DOCUMENT_INFO>
</HEADER>
<LINE_ITEMS>
<LINE_ITEM>
<PRODUCT_ID>
<SUPPLIER_PID>119556</SUPPLIER_PID>
<GTIN>4030646269130</GTIN>
<ADDITIONAL_PIDS>
<PID type="supplier_pid_original">119556</PID>
<PID type="gtin_original">4030646269130</PID>
<PID type="erp_pid">119556</PID>
</ADDITIONAL_PIDS>
<DESCRIPTIONS>
<DESCR type="short">short desc</DESCR>
<DESCR type="short_original">some text</DESCR>
</DESCRIPTIONS>
</PRODUCT_ID>
</LINE_ITEM>
<LINE_ITEM>
<PRODUCT_ID>
<SUPPLIER_PID>123456789</SUPPLIER_PID>
<GTIN>123456789</GTIN>
<ADDITIONAL_PIDS>
<PID type="supplier_pid_original">123456</PID>
<PID type="gtin_original">123456</PID>
<PID type="erp_pid">123456</PID>
</ADDITIONAL_PIDS>
<DESCRIPTIONS>
<DESCR type="short">short desc</DESCR>
<DESCR type="short_original">Some description</DESCR>
</DESCRIPTIONS>
</PRODUCT_ID>
</LINE_ITEM>
</LINE_ITEMS>
</DOCUMENT>
</NM_DOC>
</NM_DOCS>
The problem is not XPath but SimpleXML. SimpleXMLElement::xpath() is limited. It converts the result into an array of SimpleXMLElement objects, but here are other nodes in a DOM. More important you will have to register the namespaces on each new SimpleXMLElement again.
$element = new SimpleXMLElement($xml);
$element->registerXPathNamespace('namespace', 'urn:foo');
foreach($element->xpath('//namespace:LINE_ITEMS/namespace:LINE_ITEM') as $item) {
$item->registerXPathNamespace('namespace', 'urn:foo');
var_dump((string)$item->xpath('.//namespace:PID[#type="erp_pid"]')[0]);
}
Output:
string(6) "119556"
string(6) "123456"
You might notice that I prefixed your detail expression with an .. A slash at the start of the expression always makes it relative to the document itself, not the current node. The . represents the current node.
If you use DOM directly, you create a separate DOMXPath object and register the namespaces on this object. Additionally you can use XPath expressions that return scalar values.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('namespace', 'urn:foo');
foreach($xpath->evaluate('//namespace:LINE_ITEMS/namespace:LINE_ITEM') as $node) {
var_dump($xpath->evaluate('string(.//namespace:PID[#type="erp_pid"])', $node));
}
This works for me:
foreach($xmlData->LINE_ITEM as $item) {
$erp = ( $item->xpath('//PID[#type="erp_pid"]'));
foreach($erp as $v) {
echo $v. " / ";
}
}
Just remove the namespace in your xpath, your xml doesn't use a namespace.
If you want to iterate through a part of the xml be sure to use the correct path.

Prepending raw XML using PHP's SimpleXML

Given a base $xml and a file containing a <something> tag with attributes, children and children of its children, I would like to append it as first child and all of its children as raw XML.
Original XML:
<root>
<people>
<person>
<name>John Doe</name>
<age>47</age>
</person>
<person>
<name>James Johnson</name>
<age>13</age>
</person>
</people>
</root>
XML in file:
<something someval="x" otherthing="y">
<child attr="val" ..> { some children and values ... }</child>
<child attr="val2" ..> { some children and values ... }</child>
...
</something>
Result XML:
<root>
<something someval="x" otherthing="y">
<child attr="val" ..> { some children and values ... }</child>
<child attr="val2" ..> { some children and values ... }</child>
...
</something>
<people>
<person>
<name>John Doe</name>
<age>47</age>
</person>
<person>
<name>James Johnson</name>
<age>13</age>
</person>
</people>
</root>
This tag would contain several children both direct and recursively, so it would not be practical to build the XML via the SimpleXML operations. Besides, keeping it in a file would result in lower maintenance costs.
Technically it would simply be prepending one child. The problem is that this child would have other children and so on.
On the PHP addChild page there's a comment that says:
$x = new SimpleXMLElement('<root name="toplevel"></root>');
$f1 = new SimpleXMLElement('<child pos="1">alpha</child>');
$x->{$f1->getName()} = $f1; // adds $f1 to $x
However, this does not seem to treat my XML as raw XML therefore causing < and > escaped tags to appear. Several warnings concerning namespaces seem to appear as well.
I suppose I could do a quick replace of such tags but I am not sure whether it could cause future problems and it certainly does not feel right.
Manually hacking the XML is not an option and neither is adding children one by one. Choosing a different library could be.
Any clues on how to get this working?
Thanks!
I'm really not sure if that will work. Try this or downvote this, but I hope it helps. Using DOMDocument (Reference)
<?php
$xml = new DOMDocument();
$xml->loadHTML($yourOriginalXML);
$newNode = DOMDocument::createElement($someXMLtoPrepend);
$nodeRoot = $xml->getElementsByTagName('root')->item(0);
$nodeOriginal = $xml->getElementsByTagName('people')->item(0);
$nodeRoot->insertBefore($newNode,$nodeOriginal);
$finalXmlAsString = $xml->saveXML();
?>
Sometimes UTF-8 can make problems, then try this:
<?php
$xml = new DOMDocument();
$xml->loadHTML(mb_convert_encoding($yourOriginalXML, 'HTML-ENTITIES', 'UTF-8'));
$newNode = DOMDocument::createElement(mb_convert_encoding($someXMLtoPrepend, 'HTML-ENTITIES', 'UTF-8'));
$nodeRoot = $xml->getElementsByTagName('root')->item(0);
$nodeOriginal = $xml->getElementsByTagName('people')->item(0);
$nodeRoot->insertBefore($newNode,$nodeOriginal);
$finalXmlAsString = $xml->saveXML();
?>

Categories