I'm trying to generate a RSS feed using PHP SimpleXMLElement, the problem is that i need to prefix elements and can't find a way to do this using the SimpleXMLElement class.
I've tried using $item->addChild('prefix:element', 'value') but in the result xml it strips the prefix, any idea why this happens ?.
I wonder if there is a way to solve this using the SimpleXMLElement or any other cleaner way than just echoing the XML.
For clarification, this is my PHP code:
$xml = new SimpleXMLElement('<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0"/>');
$channel = $xml->addChild('channel');
$channel->addChild('title', 'Text');
$channel->addChild('link', 'http://example.com');
$channel->addChild('description', 'An example item from the feed.');
foreach($this->products as $product) {
$item = $channel->addChild('item');
foreach($product as $key => $value)
$item->addChild($key, $value);
}
return $xml->asXML();
And this is the example XML i'm trying to generate:
<?xml version="1.0"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
<channel>
<title>Test Store</title>
<link>http://www.example.com</link>
<description>An example item from the feed</description>
<item>
<g:id>DB_1</g:id>
<g:title>Dog Bowl In Blue</g:title>
<g:description>Solid plastic Dog Bowl in marine blue color</g:description>
...
</item>
...
Thanks in advance
You need to pass the namespace uri of the prefix to add child element with prefix :
$item->addChild($key, $value, 'http://base.google.com/ns/1.0');
eval.in demo :
$xml = new SimpleXMLElement('<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0"/>');
$channel = $xml->addChild('channel');
$channel->addChild('title', 'Text');
$channel->addChild('link', 'http://example.com');
$channel->addChild('description', 'An example item from the feed.');
$item = $channel->addChild('item');
$item->addChild('g:foo', 'bar', 'http://base.google.com/ns/1.0');
print $xml->asXML();
Related
I have an XML like below
<entries>
<entry>
<title lang="en">Sample</title>
<entrydate>0</entrydate>
<contents>0</contents>
<entrynum>0</entrynum>
</entry>
<entry>
<title lang="fr">Sample</title>
<entrydate>1</entrydate>
<contents>1</contents>
<entrynum>1</entrynum>
</entry>
</entries>
Is there a way in PHP to delete the parent node (entry) based on the title lang attribute? I need to keep only the en ones, so in this case I would need to get the XML without the second entry node.
I tried looking around but couldn't find any solution...
You need to use DOMDocument class to parse string to XML document. Then use DOMXpath class to find target element in document and use DOMNode::removeChild() to remove selected element from document.
$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXpath($doc);
// select target entry tag
$entry = $xpath->query("//entry[title[#lang='fr']]")->item(0);
// remove selected element
$entry->parentNode->removeChild($entry);
$xml = $doc->savexml();
You can check result in demo
You could also read your file and generated new one with your modification
<?php
$entries = array('title' => "What's For Dinner",
'link' => 'http://menu.example.com/',
'description' => 'Choose what to eat tonight.');
print "<entries>\n";
foreach ($entries as $element => $content) {
print " <$element>";
print htmlentities($content);
print "</$element>\n";
}
print "</entries>";
?>
Use the method described in this answer, i.e.
<?php
$xml = simplexml_load_file('1.xml');
$del_items = [];
foreach ($xml->entry as $e) {
$attr = $e->title->attributes();
if ($attr && $attr['lang'] != 'en') {
$del_items []= $e;
}
}
foreach ($del_items as $e) {
$dom = dom_import_simplexml($e);
$dom->parentNode->removeChild($dom);
}
echo $xml->asXML();
Output
<?xml version="1.0" encoding="UTF-8"?>
<entries>
<entry>
<title lang="en">Sample</title>
<entrydate>0</entrydate>
<contents>0</contents>
<entrynum>0</entrynum>
</entry>
</entries>
The items cannot be removed within the first loop, because otherwise we may break the iteration chain. Instead, we collect the entry objects into $del_items array, then remove them from XML in separate loop.
I have the below RSS to parse, something like:
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:x-wr="http://www.w3.org/2002/12/cal/prod/Apple_Comp_628d9d8459c556fa#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:x-example="http://www.example.com/rss/x-example" xmlns:x-microsoft="http://schemas.microsoft.com/x-microsoft" xmlns:xCal="urn:ietf:params:xml:ns:xcal" version="2.0">
<channel>
<item>
<title>About Apples</title>
<author>David K. Lowie</title>
<description>Some description about apples</description>
<xCal:description>This is the full description about apples</xCal:description>
</item>
<item>
<title>About Oranges</title>
<author>Marry L. Jones</title>
<description>Some description about oranges</description>
<xCal:description>This is the full description about oranges</xCal:description>
</item>
</channel>
</rss>
In PHP, i parse it something like:
$rss = new DOMDocument();
$rss->load( "http://www.example.com/books.rss" );
foreach( $rss->getElementsByTagName("item") as $node ) {
echo $node->getElementsByTagName("title")->item(0)->nodeValue,
echo $node->getElementsByTagName("author")->item(0)->nodeValue,
echo $node->getElementsByTagName("description")->item(0)->nodeValue,
echo $node->getElementsByTagName("xCal:description")->item(0)->nodeValue,
}
I can read everything except the xCal:description node there. (The node names are exactly like that: description and the xCal:description.)
How to parse (read) the nodes like xCal:description
Is it because of the similar node names, like: description and the xCal:description ?
(I can't change the RSS source since it's not under my control.)
Please kindly help.
Use getElementsByTagNameNS():
$node->getElementsByTagNameNS("urn:ietf:params:xml:ns:xcal", "description")->item(0)->nodeValue
While using the namespace aware variants of the DOM methods is a correct answer, you might want to take a look at Xpath. It is a much more comfortable way to fetch data from a DOM.
For the Xpath expression, you can register own prefixes for the namespaces as needed.
$rss = new DOMDocument();
$rss->load("http://www.example.com/books.rss");
$xpath = new DOMXpath($rss);
$xpath->registerNamespace('xc', 'urn:ietf:params:xml:ns:xcal');
foreach($xpath->evaluate("//item") as $item) {
echo $xpath->evaluate('string(title)', $item), "\n";
echo $xpath->evaluate('string(author)', $item), "\n";
echo $xpath->evaluate('string(description)', $item), "\n";
echo $xpath->evaluate('string(xc:description)', $item), "\n";
}
Output:
About Apples
David K. Lowie
Some description about apples
This is the full description about apples
About Oranges
Marry L. Jones
Some description about oranges
This is the full description about oranges
I've used SO for many years and always found an answer but this time I have got myself well and truly lost.
I have an xml file I would like to split the compatbility into well formed xml
`<product>
<item>
<partno>abc123</partno>
<Compatbility>model1: 110C, 115C, 117C. model2: 1835C, 1840C. model3: 210C, 215C, 3240C.</Compatbility>
</item>
</product>`
In Compatbility the word model changes with each item entry although the : after model is always there as is the . after each model group.
Should I use SimpleXml DomXml or an xpath to get the following result
`<product>
<item>
<partno>abc123</partno>
<Compatbility>
<model>model1: 110C, 115C, 117C.</model>
<model>model2: 1835C, 1840C.</model>
<model>model3: 210C, 215C, 3240C.</model>
</Compatbility>
</item>
</product>`
Thanks
For simplexml, you can run a regular expression matching on the text-value of an element.
You can then remove all inner text and add the parsed result as new child elements.
This can be done with all you said: DOMDocument, SimpleXMLElement - both with or without xpath.
Here is a commented example in SimpleXML (online demo):
<?php
/**
* #link http://stackoverflow.com/q/24304095/367456
* #link https://eval.in/164934
*/
$buffer = <<<XML
<product>
<item>
<partno>abc123</partno>
<Compatbility>model1: 110C, 115C, 117C. model2: 1835C, 1840C. model3: 210C, 215C, 3240C.</Compatbility>
</item>
</product>
XML;
# load the xml string
$xml = simplexml_load_string($buffer);
# obtain the element in question
$compatbility = $xml->item->Compatbility;
# parse it's inner text-value for the models by a regex
$pattern = '~(model\\d?: [^.]+\\.) ?~u';
$result = preg_match_all($pattern, $compatbility, $matches);
# remove the text (so called simplexml self-reference)
$compatbility->{0} = '';
# add the parsed models as new model elements
foreach ($matches[1] as $model) {
$compatbility->model[] = $model;
}
# output the xml
$xml->asXML('php://output');
The output it gives is:
<?xml version="1.0"?>
<product>
<item>
<partno>abc123</partno>
<Compatbility><model>model1: 110C, 115C, 117C.</model><model>model2: 1835C, 1840C.</model><model>model3: 210C, 215C, 3240C.</model></Compatbility>
</item>
</product>
First ofcourse, you need to convert that first into something that you can manipulate (arrays). Then the usual parsing (using explode). In the end, you will need to create a new xml again. Consider this example:
$xml_string = '<product><item><partno>abc123</partno><Compatbility>model1: 110C, 115C, 117C. model2: 1835C, 1840C. model3: 210C, 215C, 3240C.</Compatbility></item></product>';
$original_xml = simplexml_load_string($xml_string);
$data = json_decode(json_encode($original_xml), true);
$compatbility = $data['item']['Compatbility']; // get all compatibility values
// explode values
$compatbility = array_filter(array_map('trim', explode('.', $compatbility)));
$new_xml = new SimpleXMLElement('<product/>'); // initialize new xml
// add necessary values
$new_xml->addChild('item')->addChild('partno', $data['item']['partno']);
$new_xml->item->addChild('Compatbility');
// loop the values and add them as children
foreach($compatbility as $value) {
$value = trim(preg_replace('/(\w+):/', '', $value));
$new_xml->item->Compatbility->addChild('model', $value);
}
echo $new_xml->asXML(); // output as xml
Here is the XML that I am working on :
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:noo="http://www.myscheme.com/schema">
<channel>
<item>
<title>A Simple Title</title>
<noo:subcategory>the sub category</noo:subcategory>
<noo:relatedInfos>
<noo:teams>
<noo:team id="3">New York</noo:team>
<noo:team id="4">Las Vegas</noo:team>
</noo:teams>
</noo:relatedInfos>
</item>
</channel>
</rss>
I am doing this php code to get the two "team" but it does not work ($xml has the previous content) :
$xml_datas = simplexml_load_string($xml);
foreach($xml_datas->channel->item as $item){
$noo = $item->children('noo');
echo $noo->team;
}
Do you have any idea why it is not working ?
Thanks
See if this helps:
<?php // RAY_temp_userco.php
error_reporting(E_ALL);
$xml = <<<ENDXML
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:noo="http://www.myscheme.com/schema">
<channel>
<item>
<title>A Simple Title</title>
<noo:subcategory>the sub category</noo:subcategory>
<noo:relatedInfos>
<noo:teams>
<noo:team id="3">New York</noo:team>
<noo:team id="4">Las Vegas</noo:team>
</noo:teams>
</noo:relatedInfos>
</item>
</channel>
</rss>
ENDXML;
$obj = simplexml_load_string($xml);
$ns = $obj->getNamespaces(TRUE);
foreach($obj->channel->item as $item){
$noo = $item->children($ns['noo']);
var_dump($noo);
}
"noo" is just a local alias for that namespace, and the ->children() method (and most XML handling functions) want to know its actual global identifier, which is the URI in the xmlns attribute.
You need to either specify the full identifier of the namespace (i.e. ->children('http://www.myscheme.com/schema')) or set the optional second parameter to tell SimpleXML to look up the prefix (->children('noo', true). The second may be more readable, but it will break if a future document has the same schema, but gives the namespace a different local alias.
Additionally, the team nodes aren't directly under the item node, so you need to traverse further to get them:
// Give the namespace a readable name that won't change
define('NS_NOO', 'http://www.myscheme.com/schema');
$xml_datas = simplexml_load_string($xml);
foreach($xml_datas->channel->item as $item){
$teams = $item->children(NS_NOO)->relatedInfo->teams;
echo $teams->team[0];
}
I need to get <name> and <URL> tag's value where subtype="mytype".How can do it in PHP?
I want document name and test.pdf path in my result.
<?xml version="1.0" encoding="UTF-8"?>
<test>
<required>
<item type="binary">
<name>The name</name>
<url visibility="restricted">c:/temp/test/widget.exe</url>
</item>
<item type="document" subtype="mytype">
<name>document name</name>
<url visiblity="visible">c:/temp/test.pdf</url>
</item>
</required>
</test>
Use SimpleXML and XPath, eg
$xml = simplexml_load_file('path/to/file.xml');
$items = $xml->xpath('//item[#subtype="mytype"]');
foreach ($items as $item) {
$name = (string) $item->name;
$url = (string) $item->url;
}
PHP 5.1.2+ has an extension called SimpleXML enabled by default. It's very useful for parsing well-formed XML like your example above.
First, create a SimpleXMLElement instance, passing the XML to its constructor. SimpleXML will parse the XML for you. (This is where I feel the elegance of SimpleXML lies - SimpleXMLElement is the entire library's sole class.)
$xml = new SimpleXMLElement($yourXml);
Now, you can easily traverse the XML as if it were any PHP object. Attributes are accessible as array values. Since you're looking for tags with specific attribute values, we can write a simple loop to go through the XML:
<?php
$yourXml = <<<END
<?xml version="1.0" encoding="UTF-8"?>
<test>
<required>
<item type="binary">
<name>The name</name>
<url visibility="restricted">c:/temp/test/widget.exe</url>
</item>
<item type="document" subtype="mytype">
<name>document name</name>
<url visiblity="visible">c:/temp/test.pdf</url>
</item>
</required>
</test>
END;
// Create the SimpleXMLElement
$xml = new SimpleXMLElement($yourXml);
// Store an array of results, matching names to URLs.
$results = array();
// Loop through all of the tests
foreach ($xml->required[0]->item as $item) {
if ( ! isset($item['subtype']) || $item['subtype'] != 'mytype') {
// Skip this one.
continue;
}
// Cast, because all of the stuff in the SimpleXMLElement is a SimpleXMLElement.
$results[(string)$item->name] = (string)$item->url;
}
print_r($results);
Tested to be correct in codepad.
Hope this helps!
You can use the XML Parser or SimpleXML.