Get next element with PHP SimpleXML - php

I received a very weird XML file to process.
Instead of grouping names with group tags, like this:
<data>
<group>
<name>John</name>
<name>Mary</name>
<name>Susan</name>
</group>
<group>
<name>Cesar</name>
<name>Joseph</name>
<name>Sylvia</name>
<name>Steve</name>
</group>
</data>
It inserts a separator tag after each element, like this:
<data>
<name>John</name>
<separator>,</separator>
<name>Mary</name>
<separator>,</separator>
<name>Susan</name>
<separator>;</separator>
<name>Cesar</name>
<separator>,</separator>
<name>Joseph</name>
<separator>,</separator>
<name>Sylvia</name>
<separator>,</separator>
<name>Steve</name>
<separator>.</separator>
</data>
";" and "." are the group delimiters. (I know, that's weird, but I can't change that and I have to process a lot of these files)
To get all names and all separators I can use the following code:
$data = <<<XML
<data>
<name>John</name>
<separator>,</separator>
<name>Mary</name>
<separator>,</separator>
<name>Susan</name>
<separator>;</separator>
<name>Cesar</name>
<separator>,</separator>
<name>Joseph</name>
<separator>,</separator>
<name>Sylvia</name>
<separator>,</separator>
<name>Steve</name>
<separator>.</separator>
</data>
XML;
$xml = simplexml_load_string($data);
foreach ($xml->name as $name){
echo "$name\n";
}
foreach ($xml->separator as $sep){
echo "$sep\n";
}
But this way, I can't get the name and the correspondent separator on a single loop.
Is there any way to know, on the first loop, the next element of each name?

I hope that i understand your question.
$xml = simplexml_load_string($data);
$i = 0;
$res = '';
foreach ($xml->name as $name){
$res .= "$name ".$xml->separator[$i];
$i++;
}
$groups = explode(';',$res);

Parse the XML like this and build an array:
$xml = simplexml_load_string($x);
foreach ($xml->children() as $name => $value) {
if ($name == 'name') $names[$i][] = (string)$value;
elseif ($name == 'separator' && $value == ';') $i++;
}
Output $names:
array(2) {
[0]=>array(3) {
[0]=>string(4) "John"
[1]=>string(4) "Mary"
[2]=>string(5) "Susan"
}
[1]=>array(4) {
[0]=>string(5) "Cesar"
[1]=>string(6) "Joseph"
[2]=>string(6) "Sylvia"
[3]=>string(5) "Steve"
}
}
You can then just pick the names from the array.
see it working: https://eval.in/95853

Related

Merge SimpleXML elements by SKU attribute

I have 3 xml files with same structure. What I need to do is merge them together via php.
Take a look at my example file below
1.xml
<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="" pricesale="" hn="0"></product>
</data>
2.xml
<data>
<product productsku="ABC2" price="550000" pricesale="" dn="2"></product>
<product productsku="ABC3" price="" pricesale="" dn="0"></product>
</data>
3.xml
<data>
<product productsku="ABC3" price="550000" pricesale="" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>
I would like to get the following result:
<data><product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="550000" pricesale="" dn="2" hn="0"></product>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>
The code that I am trying
<?php
$xml1 = file_get_contents('1.xml');
$xml2 = file_get_contents('2.xml');
$targetDom = new DOMDocument();
$targetDom->loadXml($xml1);
$targetXpath = new DOMXpath($targetDom);
$addDom = new DOMDocument();
$addDom->loadXml($xml2);
$addXpath = new DOMXpath($addDom);
// merge attributes of product elements depending on productsku
foreach ($targetXpath->evaluate('//product[#productsku]') as $product) {
$productsku = $product->getAttribute('productsku');
foreach ($addXpath->evaluate('//product[#productsku='.$productsku.']/#*') as $attribute) {
if (!$product->hasAttribute($attribute->name)) {
$product->setAttribute($attribute->name, $attribute->value);
}
}
}
// copy products elements that are not in target dom
foreach ($addXpath->evaluate('//product[#productsku]') as $product) {
$productsku = $product->getAttribute('productsku');
if ($targetXpath->evaluate('count(//product[#productsku='.$productsku.'])') == 0) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($product)
);
}
}
echo $targetDom->saveXml();
I tried the above, it works fine if the SKU is numeric. But my SKU or ID is not a number. And I have 3 xml files
I tried to find the solution on stackoverflow.
But all is not as I expected.
I'm really not good at this. Please help me through a php snippet.
I found the xml_adopt function here: PHP - SimpleXML - AddChild with another SimpleXMLElement
Then it's a matter of loading each file, keeping track of products and overwriting them if the product has only been seen with no price, then adopting them into a single SimpleXMLElement.
<?php
/*
Question Author: Phan Vũ
Question Answerer: Jacob Mulquin
Question: Merge SimpleXML elements by SKU attribute
URL: https://stackoverflow.com/questions/74625741/merge-simplexml-elements-by-sku-attribute
Tags: php, xml
*/
// https://stackoverflow.com/a/11727581/1427345
function xml_adopt($root, $new) {
$node = $root->addChild($new->getName(), (string) $new);
foreach($new->attributes() as $attr => $value) {
$node->addAttribute($attr, $value);
}
foreach($new->children() as $ch) {
xml_adopt($node, $ch);
}
}
$xml = new SimpleXMLElement('<data></data>');
$files = ['1.xml', '2.xml', '3.xml'];
$to_adopt = [];
foreach ($files as $file) {
$load = simplexml_load_file($file);
foreach ($load->product as $product) {
$sku = (string) $product->attributes()['productsku'];
if (!isset($to_adopt[$sku])) {
$to_adopt[$sku] = $product;
} else {
$price = (string) $to_adopt[$sku]->attributes()['price'];
if (empty($price)) {
$to_adopt[$sku]['price'] = $price;
}
$existing_attributes = ((array) $to_adopt[$sku]->attributes())['#attributes'];
foreach ($product->attributes() as $name => $attribute) {
if (!in_array($name, $existing_attributes)) {
$to_adopt[$sku][$name] = $attribute;
}
}
}
}
}
foreach ($to_adopt as $adopt) {
xml_adopt($xml, $adopt);
}
file_put_contents('output.xml', $xml->asXML());
Yields:
<?xml version="1.0"?>
<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"/>
<product productsku="ABC2" price="550000" pricesale="" hn="0" dn="2"/>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"/>
<product productsku="ABC4" price="" pricesale="" gn="0"/>
</data>

Can I use SQL syntax on SimpleXML?

Can I use a WHERE selector (like SQL syntax) on SimpleXML?
Example XML
<?xml version="1.0" encoding="utf-8" ?>
<documentElement>
<row>
<id>1</id>
<name>David</name>
<surname>Johnson</surname>
</row>
<row>
<id>2</id>
<name>Jack</name>
<surname>Nixon</surname>
</row>
</documentElement>
My Example Where Selector
$where = "Jack";
$xml = "example.xml";
$sxml = simplexml_load_string($xml);
foreach ($sxml->row as $data=>$row)
{
if ($where == $data->name) // some code here
else // other some code here
}
Please let me know.
Thank you.
Yes, there is a way: XPath
$where = "Jack";
$xml = "example.xml";
$sxml = simplexml_load_string($xml);
var_dump($sxml->xpath('/documentElement/row/name[.="'.$where.'"]/..'));
No, but you can do this:
$where = "Jack";
$xml = "example.xml";
$sxml = simplexml_load_string($xml);
foreach ($sxml->row as $row)
{
if ($row->name == $where) {
// ...
} else {
// other some code here
}
}

PHP SOAP XML DOM

I have a soap xml that contains a bunch of variables that I need to access. Here is the XML.
`<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<searchPersonsResponse xmlns="">
<searchPersonsReturn>
<attributes>
<attributes>
<name>ercreatedate</name>
<values>
<values>201104070130Z</values>
</values>
</attributes>
<attributes>
<name>status</name>
<values>
<values>Stuff1</values>
<values>Stuff2</values>
<values>Stuff3</values>
<values>Stuff4</values>
<values>Stuff5</values>
<values>Stuff6</values>
<values>Stuff7</values>
</values>
</attributes>
</attributes>
<itimDN>blah</itimDN>
<name>Smith, Bob</name>
<profileName>PER</profileName>
<select>false</select>
</searchPersonsReturn>
</searchPersonsResponse>
</soapenv:Body>
</soapenv:Envelope>
I'm trying to access the inner attribute node and pull out the Name and values into a multidimentional array like this ....
$array["status"][0]="stuff1";
$array["status"][1]="stuff2";
$array["status"][2]="stuff3";
$array["status"][3]="stuff4";
so far I have been able to access the nodes but not really get them the way I want. here is the code I have been playing around with .....
$dom_document = new DOMDocument();
$dom_document->loadXML($thexml);
$tag_els_names = $dom_document->getElementsByTagname('name');
$tag_els_values = $dom_document->getElementsByTagname('values');
$data = array();
$data2 = array();
foreach($tag_els_names as $node){
$data[] = array($node->nodeName => $node->nodeValue);
//grabs all the <name> node values
}
$i=0;$j=0;
foreach($tag_els_values as $node){
$j=0;
foreach($node->childNodes as $child) {
$data2[$i][$j] = $child->nodeValue;
//grabs all the value node values
$j++;
}
$i++;
$j=0;
}
Does anyone know an easy way to do this? I think that I have been looking at this for way to long.
How about something like:
$dom_document = new DOMDocument();
$dom_document->loadXML($thexml);
$xpath = new DOMXpath($dom_document);
$attr = $xpath->evaluate("//attribute/attributes");
$names = array();
$values = array();
$i = 0;
foreach($attr as $attr_node) {
$values[i] = array();
foreach($xpath->evaluate("name", $attr_node) as $name){
$names[] = $name->nodeValue;
}
$foreach($xpath->evaluate("value", $attr_node) as $value){
$values[i][] = $value->nodeValue;
}
i++;
}
This would, however, miss the <name> element that's outside of the <attributes> group. Did you mean to be including that?
I figured this out and thought it could help someone else
$doc = new DOMDocument();
$values=array();
if ($doc->loadXML($temp)) {
$attributes = $doc->getElementsByTagName('attributes');
foreach($attributes as $attribute) {
if($attribute->childNodes->length) {
$previous_nodeValue="";
foreach($attribute->childNodes as $i) {
if($i->nodeValue=="status"){
$previous_nodeValue=$i->nodeValue;
}
if($i->nodeName=="values" && $previous_nodeValue== "status"){
foreach($i->childNodes as $j){
$values[]=$j->nodeValue;
}
}
}
$previous_nodeValue="";
}
}
}

Import XML with attributes into mysql

I have a large (~30Mb) XML file like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<LIC Version="2.0" Title="Products">
<Item>
<Field Name="Filename">/root/_DOWNLOAD/Bird.txt</Field>
<Field Name="Read_By">Lisa Hannigan</Field>
<Field Name="Posit">Passenger</Field>
</Item>
<Item>
<Field Name="Filename">D:\03\Strypers.pdf</Field>
<Field Name="Read_By">Stryper</Field>
<Field Name="Intensity">2</Field>
<Field Name="IMG">78</Field>
<Field Name="Rotate">0</Field>
</Item>
<Item>
<Field Name="Filename">D:\Afriu.txt</Field>
<Field Name="Read_By">Africano</Field>
<Field Name="Posit">Canto Africano vol.1</Field>
<Field Name="File_Resource">mp3</Field>
</Item>
<Item>
<Field Name="Filename">D:\_VARIOUS\Knots.pdf</Field>
<Field Name="Date">40624</Field>
</Item>
...
</LIC>
I want to import this xml into mysql database, with a php script. I've used SIMPLEXML and xpath:
$url = 'FILE.xml';
$xml = simplexml_load_file($url);
$result = $xml->xpath("//Field[#Name]");
foreach { ... }
What do i need? What is the correct "foreach" to create an array to use for mysql sql?
Notes that every row (identify by "Item") is not same (not have the same "Field Name").
Is it correct to use simplexml for larger file?
Thank you for help!
update
This is an example to use "foreach", i tried:
$result = $xml->xpath("//Field[#Name]");
foreach($result as $key => $value) {
echo $value['Name']."=".$value.",";
}
Now I want to find out how to create the string to insert in mysql
First create a table that matches all possible fields as columns. Then you can load it by a LOAD XML LOCAL INFILE query.
LOAD XML LOCAL INFILE 'file.xml'
INTO TABLE person
ROWS IDENTIFIED BY '<Item>';
I try to answer my question.
<?php
$url = 'FILEXML';
$xml = simplexml_load_file($url);
$i = 1;
foreach($xml->xpath("/LIC/Item") as $docs)
{
foreach($docs->Field as $field)
{
$resultstr[] = $field["Name"];
}
$sql_head = headquote($resultstr);
$sql_ins = "INSERT INTO table_name (";
$sql_dec = ") VALUE (";
unset($resultstr);
$fresult = (array)$docs;
$fvalue = array_pop($fresult);
$sql_val = numking($fvalue);
$sql_end = ");";
$query_to_use_for_mysql = ($sql_ins.$sql_head.$sql_dec.$sql_val.$sql_end);
unset($fresult);
unset($fvalue);
}
?>
And add this two functions:
<?php
function headquote($hdarray) {
$hdata = array();
foreach ( $hdarray as $hdval ) {
# Use backticks instead quotes!
$hdata[] = "`$hdval`";
}
$hdarray = implode($hdata, ',');
return $hdarray;
}
function numking($input) {
$data = array();
foreach ( $input as $value ) {
$data[] = is_numeric($value) ? $value : "'".mysql_escape_string($value)."'";
}
$input = implode($data, ',');
return $input;
}
?>
Thanks to all for help!
$url = 'FILE.xml';
$xml = simplexml_load_file($url);
for($i=0;$i<count($xml->Item);$i++)
{
print_r($xml->Item[$i]);
}

how to print xml data with xpath and php if its matches a string pattern?

I am using php and xpath to display an xml file which is having a xml code like this:
<?xml version="1.0" encoding="utf-8"?>
<cities>
<city>
<city_id>8393</city_id>
<country>ITALY</country>
<name>Petrosino</name>
<establishment_count>1</establishment_count>
</city>
<city>
<city_id>7920</city_id>
<country>AUSTRIA</country>
<name>Traiskirchen</name>
<establishment_count>1</establishment_count>
</city>
</cities>
and the php code like this:
<?php
$source = file_get_contents('cities.xml');
$xml = new SimpleXMLElement($source);
foreach ($xml as $node)
{
$row = simplexml_load_string($node->asXML());
$result = $row->xpath("//city/name");
if ($result[0])
{
$name = $row->name;
echo "<div>".$name.", ".$row->country."</div>";
}
}
?>
the code is doing fine and printing the result like this:
Petrosino, ITALY
Traiskirchen, AUSTRIA
here i dont know how to print the data if its matching the string pattern. Just like if i pass the string "lon" so its display only those city name which are having "lon" string pattern like "london"
please help me with this
Use contains():
$string = '<?xml version="1.0" encoding="utf-8"?>
<cities>
<city>
<city_id>8393</city_id>
<country>ITALY</country>
<name>Petrosino</name>
<establishment_count>1</establishment_count>
</city>
<city>
<city_id>7920</city_id>
<country>AUSTRIA</country>
<name>Traiskirchen</name>
<establishment_count>1</establishment_count>
</city>
</cities>';
$xml = new SimpleXMLElement($string);
$result = $xml->xpath("//city/name[contains(., 'Pet')]");
print_r($result);
/*Array
(
[0] => SimpleXMLElement Object
(
[0] => Petrosino
)
)*/
or for your problem:
$string = 'Pet';
foreach ($xml as $node){
$row = simplexml_load_string($node->asXML());
$result = $row->xpath("name[contains(., '".$string."')]");
if ($result[0]){
$name = $row->name;
echo "<div>".$name.", ".$row->country."</div>";
}
}
Codepad Example
for case insensitive it's somewhat ugly:
$string = 'pet';
$result = $xml->xpath("//city[contains(translate(name,'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), '".strtoupper($string)."')]");
foreach($result as $one){
echo "<div>".$one->name.", ".$one->country."</div>";
}
You want to use preg_match. E.g.
$pattern = '/^lon/';
if (preg_match($pattern, $name)){
// do your print out
}
More info here: http://php.net/manual/en/function.preg-match.php

Categories