XSLT: adding key based on another key in XML - php

It is possible with XSLT to check for value and create another key under it?
<url>http://xy.com/380094.jpg</url>
Where 380094 is the <product_code>.
and remove childs, where the available key is No.
<available>No</available> -
Original XML:
<?xml version="1.0"?>
<xml>
<produkt>
<product_code>380094</product_code>
<nazov_produktu>loremipsum</nazov_produktu>
<strucny_popis_produktu></strucny_popis_produktu>
<popis_produktu>loremipsum</popis_produktu>
<znacka>LOREMIPSUM</znacka>
<available>Yes</available>
<mj>ks </mj>
<cena>999</cena>
</produkt>
<produkt>
<product_code>000161</product_code>
<nazov_produktu>loremipsum2</nazov_produktu>
<strucny_popis_produktu></strucny_popis_produktu>
<popis_produktu></popis_produktu>
<znacka>LOREM</znacka>
<available>No</available>
<mj>sad</mj>
<cena>19,90</cena>
</produkt>
</xml>
Example output:
<?xml version="1.0"?>
<xml>
<produkt>
<product_code>380094</product_code>
<url>http://xy.com/380094.jpg</url>
<nazov_produktu>loremipsum</nazov_produktu>
<strucny_popis_produktu></strucny_popis_produktu>
<popis_produktu>loremipsum</popis_produktu>
<znacka>LOREMIPSUM</znacka>
<available>Yes</available>
<mj>ks </mj>
<cena>999</cena>
</produkt>
</xml>

You could try doing a select like
select=produkt[available != 'No']
So if you are in a loop for example
<xsl:for-each select=produkt[available != 'No']>
//do stuff
</xsl:for-each>

Related

How to parse XML-file to array and retrieve all children of an child with specific attribute in PHP?

my XML-file is as follows: agents.xml
<?xml version="1.0" encoding="UTF-8"?>
<agents>
<agent id="1">
<aname>pi1</aname>
<alive>0</alive>
<scenarios>1,2,3</scenarios>
</agent>
<agent id="2">
<aname>pi2</aname>
<alive>1</alive>
<scenarios>4,5,6</scenarios>
</agent>
</agents>
I want to retrieve all child elements of an agent, selected by attribute value "id". I tried the following, passing a the variable "id" to the script:
$agents_xml = simplexml_load_file("/<path_to>/agents.xml");
$json = json_encode($agents_xml);
$array = json_decode($json,TRUE);
//decrement id for correct index
$id=$id-1;
//I want to return in JSON format
$testarr=json_encode($array['agent'][$id]);
echo $testarr;
When "id" has value 1, i got this:
{"#attributes":{"id":"1"},"aname":"pi1","alive":"0","scenarios":"1,2,3"}
But i know, if the XML is disordered like:
<?xml version="1.0" encoding="UTF-8"?>
<agents>
<agent id="2">
<aname>pi2</aname>
<alive>1</alive>
<scenarios>4,5,6</scenarios>
</agent>
<agent id="1">
<aname>pi1</aname>
<alive>0</alive>
<scenarios>1,2,3</scenarios>
</agent>
</agents>
This is not very reliable.
Thanks for any idea!
Try
$str = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<agents>
<agent id="1">
<aname>pi1</aname>
<alive>0</alive>
<scenarios>1,2,3</scenarios>
</agent>
<agent id="2">
<aname>pi2</aname>
<alive>1</alive>
<scenarios>4,5,6</scenarios>
</agent>
</agents>
XML;
$xml = new SimpleXMLElement($str);
foreach($xml->agent as $agent)
{
var_dump($agent);
echo "<hr>";
}

Display data within XML tags in PHP

The XML that's received is below. How can I get the values ('AI', '3', '20.78'...) and display them in PHP?
The values are always returned in that order but the length can vary.
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item Type="AI" Chan="3" Value="20.78" Manual="OFF" Min="0.00" Max="100.00" Units="degC" Name="Workshop Temp" />
</Data>
Any ideas would be much appreciated!
You might find the documentation of SimpleXMLElement::attributes useful,
SimpleXMLElement::attributes — Identifies an element's attributes
Return Values
Returns a SimpleXMLElement object that can be iterated over to loop through the attributes on the tag.
Returns NULL if called on a SimpleXMLElement object that already represents an attribute and not a tag.
here is how you should use it:
$str = <<< XML
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item Type="AI" Chan="3" Value="20.78" Manual="OFF" Min="0.00" Max="100.00" Units="degC" Name="Workshop Temp" />
</Data>
XML;
$xml = simplexml_load_string($str);
foreach($xml->Item[0]->attributes() as $key => $att) {
echo $att."\n";
}

PHP SimpleXML add child to each parent repeatedly

I have this kind of XML:
<?xml version="1.0" encoding="utf-8"?>
<data>
<stats>
</stats>
<params>
</params>
<results>
<record id='SJDGH'>
<item>abc</item>
<item>def</item>
<item>ghi</item>
</record>
<record id='OIIO'>
<item>abc</item>
<item>def</item>
<item>ghi</item>
</record>
</results>
</data>
I'm generating a new <item> for every <record> in <results> in a loop:
// $data is SimpleXml objec from XML above
foreach ($data->results->record as $record)
{
$newitem = 'New item!'.time().$record->attributes()->id;
}
Somehow in this loop i need to change the SimpleXML object ($data) to contain new items in every <record>.
is it possible?
I needed a little guessing, but this might what you're looking for:
$records = $data->results->record;
foreach($records as $record)
{
$value = sprintf('New Item! %s / id:%s', time(), $record['id']);
$record->item[] = $value;
}
$data->asXML('php://output');
See it in action.
I think you might want to use addChild.
Check it out here: http://php.net/manual/en/simplexmlelement.addchild.php

PHP & XML Read element by attribute value

$xml = '<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
</resData>
</response>
</entries>';
$xml = simplexml_load_string($xml);
$domain = $xml->response[2]->resData[0]->data[0];
$crdate = $xml->response[2]->resData[0]->data[1];
$exdate = $xml->response[2]->resData[0]->data[2];
With the above code i can get the values.
But how can i get the values by attribute value?
For example i want to get the values with something like this:
$domain = $xml->response[2]->resData[0]->data["domain"];
$crdate = $xml->response[2]->resData[0]->data["crdate"];
$exdate = $xml->response[2]->resData[0]->data["exdate"];
One more question.
If i have two elements with the same name?
For example i would like to parse the dns. How could i do it?
The xml code is like this:
<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
<data name="dns">ns1.google.com</data>
<data name="dns">ns2.google.com</data>
</resData>
</response>
</entries>
As you can see the ns1 and ns2 have the same name. name="dns".
How can i parse each one in a different variable?
Thank you!
With the element["attribute"] syntax, attribute is the name of an attribute on the element. It is not the value of some randomly chosen attribute belonging to an element.
The example below creates an array containing a mapping for the data elements of name attribute value to text value.
$data = array();
foreach ($xml->response[2]->resData->data as $d) {
$data[strtolower($d['name'])] = (string) $d;
}
// Now you can access the values via $data['domain'], $data['crdate'], etc.
Nick, your code expects XML structured like:
<resData>
<data domain="google.com" crdate="2004-12-16" exdate="2013-12-16" />
</resData>
Edit due to question change
In a marvelous dose of eating my own words, due to the change in the question an XPath approach would be more appropriate (don't you love OPs who do that?).
You can easily get an array of the name="dns" elements with a basic XPath expression.
$xml = simplexml_load_string($xml);
$dns = $xml->xpath('response[category="domain"]/resData/data[#name="dns"]');
You can use XPath to to do queries against your XML, e.g.
$entries = simplexml_load_string($xml);
$dataElements = $entries->xpath('/entries/response/resData/data[#name="dns"]');
foreach ($dataElements as $dataElement) {
echo $dataElement;
}
The above XPath finds all <data> elements with a name attribute of "dns" that are direct children of the given element hierarchy, e.g.
<entries>
…
<response>
…
<resData>
…
<data name="dns">
IMO this is easier and more appropriate than having the extra step of copying over the values into an array which would disconnect it from the actual DOM tree and which you would have to repeat for all the elements you want to map this way. XPath is built-in. You just query and get the result.
Because $dataElements is an array, you can also access the elements in it individually with
echo $dataElements[0]; // prints "ns1.google.com"
echo $dataElements[1]; // prints "ns2.google.com"
Note that the $dataElements array actually contains SimpleXmlElements connected to the main document. Any changes you do to them will also be reflected in the main document.

PHP: How to add the missing tags in each group of elements in XML?

This is the XML that we have right now:
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
As you could see the first group of elements only has three tags namely firstname, surname, and age while the second group has an additional tag name sex.
What we need is to make all the element groups in the XML contains all the tags that each group has, in this case the first group should also contain the sex tag but in a blank state as well like this:
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
<sex></sex>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
Also what if there's a third, fourth or on the 50th group has another new tag named nickname? In this case all the group should have the tag nickname as well but in a blank state.
How could I do this efficiently in PHP?
Using SimpleXML, the script makes two passes: one to find all the possible tags, the other to create empty elements:
$str = <<<STR
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age></age>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age></age>
<sex>Female</sex>
</person>
</persons>
STR;
$xml = simplexml_load_string($str);
// Create an array of all the possible tags
$tags = array();
foreach($xml->person as $person)
{
$current_tags = array_keys(get_object_vars($person));
$tags = array_unique(array_merge($tags, $current_tags));
}
// Add empty tags to elements who don't have them
foreach($xml->person as $person)
{
foreach($tags as $tag)
{
if(!property_exists($person, $tag))
{
$person->$tag = '';
}
}
}
// Output the new XML
echo $xml->asXML();
The easiest to make this maintainable over a long time (e.g. anticipating more new fields and stuff like that) would be to process the XML with an XSLT that contains all the required fields:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/persons">
<persons>
<xsl:apply-templates select="person"/>
</persons>
</xsl:template>
<xsl:template match="person">
<person>
<firstname><xsl:value-of select="firstname"/></firstname>
<surname><xsl:value-of select="surname"/></surname>
<age><xsl:value-of select="age"/></age>
<sex><xsl:value-of select="sex"/></sex>
</person>
</xsl:template>
</xsl:stylesheet>
Then, whenever you get a new copy from the generator service, do (manual)
$dom = new DOMDocument;
$dom->load('people.xml');
$xsl = new DOMDocument;
$xsl->load('people.xsl');
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
echo $proc->transformToXML($dom);
This will produce (demo)
<?xml version="1.0"?>
<persons>
<person>
<firstname>John</firstname>
<surname>Doe</surname>
<age/>
<sex/>
</person>
<person>
<firstname>Jane</firstname>
<surname>Doe</surname>
<age/>
<sex>Female</sex>
</person>
</persons>
I agree with #Gordon that the best solution here is XSLT. However, I suggest using a slightly different XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="person">
<person>
<xsl:apply-templates select="#* | *[not(self::sex)]"/>
<sex><xsl:value-of select="sex"/></sex>
</person>
</xsl:template>
</xsl:stylesheet>
I tried it out with W3Schools' online XSLT evaluator and it works as requested.

Categories