PHP SimpleXML: Feed modification - php

I want to modify an RSS feed. I want to remove X items from feed and then return the new feed as XML.
<?php
class RSS {
private $simpleXML;
public function __construct($address) {
$xml = file_get_contents($address);
$this->simpleXML = simplexml_load_string($xml);
}
private function getRssInformation() {
// Here I want to get the Rss Head information ...
}
// Here I get X items ...
private function getItems($itemsNumber, $UTF8) {
$xml = null;
$items = $this->simpleXML->xpath('/rss/channel/item');
if(count($items) > $itemsNumber) {
$items = array_slice($items, 0, $itemsNumber, true);
}
foreach($items as $item) {
$xml .= $item->asXML() . "\n";
}
return $xml;
}
public function getFeed($itemsNumber = 5, $UTF8 = true) {
// Here i will join rss information with X items ...
echo $this->getItems($itemsNumber, $UTF8);
}
}
?>
Is it possible with XPath?
Thank you.

Another way to do it (but could be cleaner) :
private function getItems($itemsNumber, $UTF8) {
$xml = '<?xml version="1.0" ?>
<rss version="2.0">
<channel>';
$i = 0;
if (count($this->simpleXML->rss->channel->item)>0){
foreach ($this->simpleXML->rss->channel->item as $item) {
$xml .= str_replace('<?xml version="1.0" ?>', '', $item->asXML());
$i++;
if($i==$itemsNumber) break;
}
}
$xml .=' </channel></rss> ';
return $xml;
}
But I think asXML(); add the XML declaration :
<?xml version="1.0" ?>
I edited my code. It's dirty but it should work. Any better idea?

Related

Cannot parse XML using simplexml_load_string

I have tried various methods as seen in here
and in here and many more.
I even tried the function in here.
The XML looks something like this:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse</a:Action></s:Header><s:Body><SaveFooStatusResponse xmlns="http://htempuri.org/"><SaveFooStatusResult xmlns:b="http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><b:AWBNumber>999999999</b:AWBNumber><b:IsError>true</b:IsError><b:Status><b:FooEntryStatus><b:StatusCode>Foo_ENTRY_FAILURE</b:StatusCode><b:StatusInformation>InvalidEmployeeCode</b:StatusInformation></b:FooEntryStatus></b:Status></SaveFooStatusResult></SaveFooStatusResponse></s:Body></s:Envelope>
And here's one example of my code (I have a dozen variations):
$ReturnData = $row["ReturnData"]; // string frm a database
if (strpos($ReturnData, "s:Envelope") !== false){
$ReturnXML = new SimpleXMLElement($ReturnData);
$xml = simplexml_load_string($ReturnXML);
$StatusCode = $xml["b:StatusCode"];
echo "<br>StatusCode: " . $StatusCode;
$IsError = $xml["b:IsError"];
echo "<br>IsError: " . $IsError;
}
Another option I tried:
$test = json_decode(json_encode($xml, 1); //this didn't work either
I either get an empty array or I get errors like:
"Fatal error: Uncaught exception 'Exception' with message 'String
could not be parsed as XML"
I have tried so many things, I may lost track of where my code is right now. Please help - I am really stuck...
I also tried:
$ReturnXML = new SimpleXMLElement($ReturnData);
foreach( $ReturnXML->children('b', true)->entry as $entries ) {
echo (string) 'Summary: ' . simplexml_load_string($entries->StatusCode->children()->asXML(), null, LIBXML_NOCDATA) . "<br />\n";
}
Method 1.
You can try the below code snippet to parse it an array
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $values, $indexes);// $xml containing the XML
xml_parser_free($p);
echo "Index array\n";
print_r($indexes);
echo "\nVals array\n";
print_r($values);
Method 2.
function XMLtoArray($xml) {
$previous_value = libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->loadXml($xml);
libxml_use_internal_errors($previous_value);
if (libxml_get_errors()) {
return [];
}
return DOMtoArray($dom);
}
function DOMtoArray($root) {
$result = array();
if ($root->hasAttributes()) {
$attrs = $root->attributes;
foreach ($attrs as $attr) {
$result['#attributes'][$attr->name] = $attr->value;
}
}
if ($root->hasChildNodes()) {
$children = $root->childNodes;
if ($children->length == 1) {
$child = $children->item(0);
if (in_array($child->nodeType,[XML_TEXT_NODE,XML_CDATA_SECTION_NODE]))
{
$result['_value'] = $child->nodeValue;
return count($result) == 1
? $result['_value']
: $result;
}
}
$groups = array();
foreach ($children as $child) {
if (!isset($result[$child->nodeName])) {
$result[$child->nodeName] = DOMtoArray($child);
} else {
if (!isset($groups[$child->nodeName])) {
$result[$child->nodeName] = array($result[$child->nodeName]);
$groups[$child->nodeName] = 1;
}
$result[$child->nodeName][] = DOMtoArray($child);
}
}
}
return $result;
}
You can get an array using print_r(XMLtoArray($xml));
I don't know how you would do this using SimpleXMLElement but judging by the fact you have tried so many things I trust that the actual method employed is not important so you should therefore find the following, which uses DOMDocument and DOMXPath, of interest.
/* The SOAP response */
$strxml='<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse</a:Action>
</s:Header>
<s:Body>
<SaveFooStatusResponse xmlns="http://htempuri.org/">
<SaveFooStatusResult xmlns:b="http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<b:AWBNumber>999999999</b:AWBNumber>
<b:IsError>true</b:IsError>
<b:Status>
<b:FooEntryStatus>
<b:StatusCode>Foo_ENTRY_FAILURE</b:StatusCode>
<b:StatusInformation>InvalidEmployeeCode</b:StatusInformation>
</b:FooEntryStatus>
</b:Status>
</SaveFooStatusResult>
</SaveFooStatusResponse>
</s:Body>
</s:Envelope>';
/* create the DOMDocument and manually control errors */
libxml_use_internal_errors( true );
$dom=new DOMDocument;
$dom->validateOnParse=true;
$dom->recover=true;
$dom->strictErrorChecking=true;
$dom->loadXML( $strxml );
libxml_clear_errors();
/* Create the XPath object */
$xp=new DOMXPath( $dom );
/* Register the various namespaces found in the XML response */
$xp->registerNamespace('b','http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo');
$xp->registerNamespace('i','http://www.w3.org/2001/XMLSchema-instance');
$xp->registerNamespace('s','http://www.w3.org/2003/05/soap-envelope');
$xp->registerNamespace('a','http://www.w3.org/2005/08/addressing');
/* make XPath queries for whatever pieces of information you need */
$Action=$xp->query( '//a:Action' )->item(0)->nodeValue;
$StatusCode=$xp->query( '//b:StatusCode' )->item(0)->nodeValue;
$StatusInformation=$xp->query( '//b:StatusInformation' )->item(0)->nodeValue;
printf(
"<pre>
%s
%s
%s
</pre>",
$Action,
$StatusCode,
$StatusInformation
);
The output from the above:
http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse
Foo_ENTRY_FAILURE
InvalidEmployeeCode

How to generate xml in codeigniter

I am trying to generate xml from database query.
Here is generated xml link: http://mydeal.ge/api/xml
But when I try to parse this xml, I get an error: http://wallsparade.com/test.php.
My code is:
public function xml()
{
$res = $this->db->query('custom query');
if($res->num_rows() > 0)
{$output = '<?xml version="1.0"?>'. "\n";
$output .= "<deals>";
foreach($res->result() as $item)
{
$output .= "<sale id = '".$item->id."'>";
$output .= "<link>".$item->link."</link>";
$output .= "<title>".urlencode($item->title)."</title>";
$output .= "<image>".$item->image."</image>";
$output .= "<text>".urlencode($item->text)."</text>";
$output .= "<time>".$item->time."</time>";
$output .= "<price>".$item->price."</price>";
$output .= "<parcent>".$item->parcent."</parcent>";
$output .= "</sale>";
}
$output .= '</deals>';
}
echo $output;
}
What is problem?
Looks like you are developing an API. Why don't you use the RESTServer plugin for CodeIgniter?
It's all handled for you and you don't have to worry about the format. You can choose to output in JSON or XML.
Plugin developed by Phil: https://github.com/philsturgeon/codeigniter-restserver
I believe the most practical, logical and error free way to generate an XML is to create a DOMDocument as suggested by Eineki in this answer, allowing the xmltree to be searched through an xpath query.
With this said, some years ago Dan Simmons created a single MY_xml_helper.php that you can just copy to your application/helpers folder directly. Here's the entire code without comments:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('xml_dom'))
{
function xml_dom()
{
return new DOMDocument('1.0', 'UTF-8');
}
}
if ( ! function_exists('xml_add_child'))
{
function xml_add_child($parent, $name, $value = NULL, $cdata = FALSE)
{
if($parent->ownerDocument != "")
{
$dom = $parent->ownerDocument;
}
else
{
$dom = $parent;
}
$child = $dom->createElement($name);
$parent->appendChild($child);
if($value != NULL)
{
if ($cdata)
{
$child->appendChild($dom->createCdataSection($value));
}
else
{
$child->appendChild($dom->createTextNode($value));
}
}
return $child;
}
}
if ( ! function_exists('xml_add_attribute'))
{
function xml_add_attribute($node, $name, $value = NULL)
{
$dom = $node->ownerDocument;
$attribute = $dom->createAttribute($name);
$node->appendChild($attribute);
if($value != NULL)
{
$attribute_value = $dom->createTextNode($value);
$attribute->appendChild($attribute_value);
}
return $node;
}
}
if ( ! function_exists('xml_print'))
{
function xml_print($dom, $return = FALSE)
{
$dom->formatOutput = TRUE;
$xml = $dom->saveXML();
if ($return)
{
return $xml;
}
else
{
echo $xml;
}
}
}
Notice that you set the encoding like this: new DOMDocument('1.0', 'UTF-8');. Here's an example:
$this->load->helper('xml');
$dom = xml_dom();
$book = xml_add_child($dom, 'book');
xml_add_child($book, 'title', 'Hyperion');
$author = xml_add_child($book, 'author', 'Dan Simmons');
xml_add_attribute($author, 'birthdate', '1948-04-04');
xml_add_child($author, 'name', 'Dan Simmons');
xml_add_child($author, 'info', 'The man that wrote MY_xml_helper');
xml_print($dom);
Would simply output:
<?xml version="1.0" encoding="UTF-8"?>
<book>
<title>Hyperion</title>
<author birthdate="1948-04-04">
<name>Dan Simmons</name>
<info>The man that wrote MY_xml_helper</info>
</author>
</book>
The xml_print either echos or returns the $xml->saveXML().
Notice: you can still use the one and only function from the default XML helper from CodeIgniter: xml_convert("<title>'Tom' & \"Jerry\"") which just outputs: <title>&apos;Tom&apos; & "Jerry".
Your XML document have to start with:
<?xml version="1.0"?>
no white symbols no "spaces" no "enters". Your document starts with line-break as the error message says:
Warning: simplexml_load_file() [function.simplexml-load-file]: http://mydeal.ge/api/xml:2: parser error : XML declaration allowed only at the start of the document in /home1/stinky/public_html/test.php on line 2
and delete spaces from this line:
$output .= "<sale id = '".$item->id."'>";
$output .= "<sale id='".$item->id."'>";
You shouldn't write XML through string concatenation when you have built-in library such as SimpleXMLElement available.
Try to change your code to this. I couldn't test it myself but it should work as expected:
<?php
public function xml()
{
$res = $this->db->query('custom query');
if ($res->num_rows() > 0) {
$sxe = new SimpleXMLElement();
$deals = $sxe->addChild('deals');
foreach($res->result() as $item) {
$sale = $deals->addChild('sale');
$sale->addAttribute('id', $item->id);
$sale->addChild('link', $item->link);
$sale->addChild('title', urlencode($item->title));
$sale->addChild('image', $item->image);
$sale->addChild('text', urlencode($item->text));
$sale->addChild('time', $item->time);
$sale->addChild('price', $item->price);
$sale->addChild('parcent', $item->parcent);
}
}
echo $sxe->saveXML();
}

Get last node in XML using DOMDocument PhP

I've made a class that reads value and node name of value and combines it into array to use it as a simple and quick access to config in XML. But this solution works only if ill give bottom node, XML looks like this.
<?xml version="1.0" encoding="ISO-8859-2"?>
<settings>
<const>
<inscript>true</inscript>
<title>Template</title>
</const>
<meta>
</meta>
<db>
<user>user</user>
<pass>pass</pass>
<host>host</host>
<name>name</name>
</db>
<path>
<style>
<css>/Template/view/www/style/</css>
<img>/Template/view/www/style/img</img>
</style>
</path>
</settings>
Now I want to get for example whole db node and return it as an array where node name wold be the key and node value, value. but im stuck at this. Heres what i made so far.
class config {
private static $xml = "lib/config/settings.xml";
private static $xmlRoot = "settings";
public static function loadConfig($value) {
$domDocument = new DOMDocument();
$domDocument->load(self::$xml);
$settings = $domDocument->getElementsByTagName(self::$xmlRoot);
try {
foreach ($settings as $setting) {
$configValue = $setting->getElementsByTagName($value)->item(0)->nodeValue;
$configNode = $setting->getElementsByTagName($value)->item(0)->nodeName;
$test = $setting->getElementsByTagName("path")->item(0)->childNodes->item(2)->nodeName;
var_dump($test);
}
$configValue = explode(' ', trim(preg_replace( '/\s+/', ' ', $configValue)));
$configNode = explode(' ', trim(preg_replace( '/\s+/', ' ', $configNode)));
$configArray = array_combine($configNode, $configValue);
return $configArray;
}
catch(Exception $e) {
echo '<h1>Błąd - '.$e->getMessage().'</h1>';
}
}
}
You could use XPath like this:
$xp = new DOMXPath($domDocument);
$config = array();
foreach ($xp->query('./db/*') as $node) {
$config[$node->nodeName] = $node->textContent;
}
return $config;

How do I add an XMLElement to an XMLElement?

I want to create some XML dynamically and I wonder how I could add an XMLElement to an XMLElement?
Here's my code:
$table = new SimpleXMLElement("<table></table>");
$tableRow = new SimpleXMLElement("<tr></tr>");
$count = count($this->dataSource->columns);
for ($i = 0; $i < $count; $i++)
{
$tableRow->addChild("<th></th>","Hi!")
}
$table->addChild($tableRow); // Not good, but this is what I want to do.
Take a look at this: Add Children with SimpleXML
Example:
<?php
$xml = <<<XML
<books>
<book title="Fahrenheit 451" author="Ray Bradbury"/>
<book title="Stranger in a Strange Land" author="Robert Heinlein"/>
</books>
XML;
$sxe = new SimpleXMLElement($xml);
$new_book = $sxe->addChild('book');
$new_book->addAttribute('title', '1984');
$new_book->addAttribute('author', 'George Orwell');
echo $sxe->asXML();
?>
I haven't found the answer to my question.
I've coded a class which inherit from SimpleXmlElement and added a method "addChildElement(XElement $element)". The method take an XElement in parameter and add it (and its childs) recursively.
I don't know if this is the best way to do it but it seems to work pretty well for me.
Tell me if something is wrong with this function.
Note that you can't modify an XElement after you've added it to the xml, but I'm working on it.
class XElement extends SimpleXMLElement
{
/**
* Add an XElement to an XElement.
* #param XElement $element
* #return void
*/
public function addChildElement(XElement $element)
{
// TODO Handle namespaces
$addedElement = $this->addChild($element->getName(), (string)$element);
foreach ($element->children() as $node)
{
$addedElement->addChildElement($node);
}
}
}
How to use:
$tableRow = new XElement("<tr></tr>");
$asd = new XElement("<tr2></tr2>");
$asd2 = new XElement("<tr3></tr3>");
$asd->addChildElement($asd2);
$tableRow->addChildElement($asd);
This wont work, but I'm working on it:
$tableRow = new XElement("<tr></tr>");
$asd = new XElement("<tr2></tr2>");
$asd2 = new XElement("<tr3></tr3>");
$tableRow->addChildElement($asd);
$asd->addChildElement($asd2);
Expanding off of the accepted answer I created a static method for more of a XElement from C# feel. It also copied attributes when adding the child XElement node.
class XElement extends SimpleXMLElement
{
/**
* Add an XElement to an XElement.
*
* #param XElement $element A SimpleXmlElement
*
* #return void
*/
public function add(XElement $element)
{
// TODO Handle namespaces
$addedElement = $this->addChild($element->getName(), (string)$element);
$this->_copyAttributes($element, $addedElement);
foreach ($element->children() as $node) {
$addedElement->add($node);
}
}
private function _copyAttributes($from, $to)
{
foreach ($from->attributes() as $n => $v) {
$to->addAttribute($n, $v);
}
}
public static function Node(string $name, $content, $attributes = null)
{
$content_type = gettype($content);
$element = null;
if ($content_type == 'string') {
$element = new XElement("<$name />");
$element[0] = $content;
} else {
if (substr($name, 0, 1) === "<") {
$element = new XElement($name);
} else {
$element = new XElement("<$name />");
}
if ($content_type == 'object' && get_class($content) == 'XElement') {
$element->add($content);
} else if ($content_type == 'array') {
foreach ($content as $c) {
$element->add($c);
}
}
}
if (!empty($attributes)) {
foreach ($attributes as $n => $v) {
$element->addAttribute($n, $v);
}
}
return $element;
}
}
To use:
//auto close tags, add content text
$xml = XElement::Node('cXML', 'nothing');
//nested elements
$xml = XElement::Node('cXML',
XElement::Node('Header',
XElement::Node('Data', 'Your Text Here')
)
);
//attributes
$xml = XElement::Node('cXML',
XElement::Node('Header', '', ['type' => 'no_data'])
);
//multiple nodes (sibling nodes)
$header = XElement::Node('Header', '', ['type' => 'no_data']);
$request = XElement::Node('Request',
XElement::Node('Order',
//inline array
[
XElement::Node('Item',
XElement::Node('Qty', '1')
),
XElement::Node('Item',
XElement::Node('Qty', '2')
),
]
),
['type' => 'data']
);
$nodes = [ $header, $request ];
$xml = XElement::Node('cXML', $nodes);
Output:
<!-- auto close tags, add content text -->
<?xml version="1.0"?>
<cXML>nothing</cXML>
<!-- nested elements -->
<?xml version="1.0"?>
<cXML>
<Header>
<Data>Your Text Here</Data>
</Header>
</cXML>
<!-- attributes -->
<?xml version="1.0"?>
<cXML>
<Header type="no_data"/>
</cXML>
<!-- multiple nodes (sibling nodes) -->
<?xml version="1.0"?>
<cXML>
<Header type="no_data"/>
<Request type="data">
<Order>
<Item>
<Qty>1</Qty>
</Item>
<Item>
<Qty>2</Qty>
</Item>
</Order>
</Request>
</cXML>

Reset XML Parser PHP

Hi I'm using a class to parse my XML.
I have to parse request XML and response XML, and I'd like to do it with the same parser. When I parse the response XML, I keep getting junk after document element error.
I narrowed my XML down to <?xml version="1.0" encoding="UTF-8" ?><check></check> and I'm still getting the error, so the only thing I could think of is the second <?xml ..?> header being parsed as 'junk'.
What I basically want to do is reset the XML parser so it can start off as a new document, but I don't want to create a new parser object. Is this possible?
Edit:
I'm using the following code in my XmlParser object.
<?php
class XmlComponent extends Object {
private $parser, $c, $current_tag;
public $contents;
function initialize(&$controller, $settings = array())
{
$this->controller =& $controller;
$this->parser = xml_parser_create();
xml_set_object($this->parser, $this);
xml_set_element_handler($this->parser, "tag_open", "tag_close");
xml_set_character_data_handler($this->parser, "cdata");
xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
if (isset($settings['xml'])) {
$contents = $settings['xml'];
}
if (isset($settings['file'])) {
$f = fopen($settings['file'], 'r');
$contents = fread($f, filesize($settings['file']));
fclose($f);
}
debug($this->parse($contents));
}
public function parse($xml)
{
$xml = trim(preg_replace("/>\s+</", '><', $xml));
$this->contents = array();
$this->c = &$this->contents;
xml_parse($this->parser, $xml);
return xml_error_string(xml_get_error_code($this->parser))."\r\n".$xml;
}
//*/
public function xml($root)
{
$xml = '';
foreach ($root as $tag=>$elem) {
if (substr($tag, 0,1) != '#') {
foreach ($elem as $val) {
$xml .= '<'.$tag;
if (is_array($val) && isset($val['#attributes'])) {
foreach($val['#attributes'] as $a_key => $a_val) {
$xml .= ' '.$a_key.'="'.$a_val.'"';
}
}
$xml .= '>';
if (is_array($val) && isset($val['#data'])) {
$xml .= $val['#data'];
}
$xml .= $this->xml($val);
$xml .= '</'.$tag.'>';
}
}
}
return $xml;
}
//*/
private function tag_open($parser, $tag, $attributes)
{
if (!empty($attributes)) { $this->c[$tag][] = array('#attributes' => $attributes, '#parent' => &$this->c); }
else { $this->c[$tag][] = array('#parent' => &$this->c); }
$this->c =& $this->c[$tag][count($this->c[$tag]) - 1];
}
private function tag_close($parser, $tag)
{
$parent = &$this->c['#parent'];
unset($this->c['#parent']);
$this->c =& $parent;
}
private function cdata($parser, $data)
{
if (!empty($data)) {$this->c['#data'] = $data;}
}
//*/
}
?>
What I basically want to do is reset the XML parser so it can start off as a new document, but I don't want to create a new parser object. Is this possible?
No, you have to create a new parser. You can, of course, provide in your class a method that "resets" the parser; i.e., deletes the current one and creates a new one, hence hiding it from the class consumer.

Categories