I got a PHP array with a lot of XML users-file URL :
$tab_users[0]=john.xml
$tab_users[1]=chris.xml
$tab_users[n...]=phil.xml
For each user a <zoom> tag is filled or not, depending if user filled it up or not:
john.xml = <zoom>Some content here</zoom>
chris.xml = <zoom/>
phil.xml = <zoom/>
I'm trying to explore the users datas and display the first filled <zoom> tag, but randomized: each time you reload the page the <div id="zoom"> content is different.
$rand=rand(0,$n); // $n is the number of users
$datas_zoom=zoom($n,$rand);
My PHP function
function zoom($n,$rand) {
global $tab_users;
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
... some other taff here
// no "zoom" value found
if ($txt_zoom =='') {
echo 'RAND='.$rand.' XML='.$tab_users[$rand].'<br />';
$datas_zoom=zoom($r,$n,$rand); } // random zoom fct again and again till...
}
else {
echo 'ZOOM='.$txt_zoom.'<br />';
return $txt_zoom; // we got it!
}
}
echo '<br />Return='.$datas_zoom;
The prob is: when by chance the first XML explored contains a "zoom" information the function returns it, but if not nothing returns... An exemple of results when the first one is by chance the good one:
// for RAND=0, XML=john.xml
ZOOM=Anything here
Return=Some content here // we're lucky
Unlucky:
RAND=1 XML=chris.xml
RAND=2 XML=phil.xml
// the for RAND=0 and XML=john.xml
ZOOM=Anything here
// content founded but Return is empty
Return=
What's wrong?
I suggest importing the values into a database table, generating a single local file or something like that. So that you don't have to open and parse all the XML files for each request.
Reading multiple files is a lot slower then reading a single file. And using a database even the random logic can be moved to SQL.
You're are currently using SimpleXML, but fetching a single value from an XML document is actually easier with DOM. SimpleXMLElement::xpath() only supports Xpath expression that return a node list, but DOMXpath::evaluate() can return the scalar value directly:
$document = new DOMDocument();
$document->load($xmlFile);
$xpath = new DOMXpath($document);
$zoomValue = $xpath->evaluate('string(//zoom[1])');
//zoom[1] will fetch the first zoom element node in a node list. Casting the list into a string will return the text content of the first node or an empty string if the list was empty (no node found).
For the sake of this example assume that you generated an XML like this
<zooms>
<zoom user="u1">z1</zoom>
<zoom user="u2">z2</zoom>
</zooms>
In this case you can use Xpath to fetch all zoom nodes and get a random node from the list.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$zooms = $xpath->evaluate('//zoom');
$zoom = $zooms->item(mt_rand(0, $zooms->length - 1));
var_dump(
[
'user' => $zoom->getAttribute('user'),
'zoom' => $zoom->textContent
]
);
Your main issue is that you are not returning any value when there is no zoom found.
$datas_zoom=zoom($r,$n,$rand); // no return keyword here!
When you're using recursion, you usually want to "chain" return values on and on, till you find the one you need. $datas_zoom is not a global variable and it will not "leak out" outside of your function. Please read the php's variable scope documentation for more info.
Then again, you're calling zoom function with three arguments ($r,$n,$rand) while the function can only handle two ($n and $rand). Also the $r is undiefined, $n is not used at all and you are most likely trying to use the same $rand value again and again, which obviously cannot work.
Also note that there are too many closing braces in your code.
I think the best approach for your problem will be to shuffle the array and then to use it like FIFO without recursion (which should be slightly faster):
function zoom($tab_users) {
// shuffle an array once
shuffle($tab_users);
// init variable
$txt_zoom = null;
// repeat until zoom is found or there
// are no more elements in array
do {
$rand = array_pop($tab_users);
$datas_user = new SimpleXMLElement($rand, null, true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
} while(!$txt_zoom && !empty($tab_users));
return $txt_zoom;
}
$datas_zoom = zoom($tab_users); // your zoom is here!
Please read more about php scopes, php functions and recursion.
There's no reason for recursion. A simple loop would do.
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
$max = $tag->length;
while(true) {
$test_index = rand(0, $max);
if ($tag[$test_index]->zoom != "") {
break;
}
}
Of course, you might want to add a bit more logic to handle the case where NO zooms have text set, in which case the above would be an infinite loop.
Related
The PHP I have right now is only half working and it is a little clunky. I'm looking to display the 3 most recent press releases from an XML feed that match a specific value type. What I have right now is only looking at the first three items, and just echoing the ones that match the value. I'm also pretty sure DOM object is not the best approach here, but had issues getting xparse to work properly. Some help with the logic would be greatly appreciated.
//create new document object
$dom_object = new DOMDocument();
//load xml file
$dom_object->load("http://cws.huginonline.com/A/138060/releases_999_all.xml");
$cnt = 0;
foreach ($dom_object->getElementsByTagName('press_release') as $node) {
if($cnt == 3 ) {
break;
}
$valueID = $node->getAttribute('id');
$valueType = $node->getAttribute('type');
$headline = $dom_object->getElementsByTagName("headline");
$headlineContent = $headline->item(0)->nodeValue;
$releaseDate = $dom_object->getElementsByTagName("published");
$valueDate = $releaseDate->item(0)->getAttribute('date');
$cnt++;
if ($valueType == 5) {
echo "<div class=\"newsListItem\"> <p> $valueDate </p> <h4>$headlineContent</h4><p></p></div>";
}
}
DOM has the ability to execute Xpath expression on the XML tree to fetch nodes and scalar values. Use DOMXpath::evaluate() - not just the DOM methods.
But the "3 most recent" is not a filter. It is a sort with a limit. You will have to read each item and keep the 3 "newest" or read all of them into a list, sort it and get the first 3 from the list.
You can do this in PHP or using XSLT.
this did the trick...
if ($valueType == 5)
{
if ($printCount < 3) {
echo "...";
$printCount++;
}
}
I am parsing through an XML document and getting the values of nested tags using asXML(). This works fine, but I would like to move this data into a MySQL database whose columns match the tags of the file. So essentially how do I get the tags that asXML() is pulling text from?
This way I can eventually do something like: INSERT INTO db.table (TheXMLTag) VALUES ('XMLTagText');
This is my code as of now:
$xml = simplexml_load_file($target_file) or die ("Error: Cannot create object");
foreach ($xml->Message->SettlementReport->SettlementData as $main ){
$value = $main->asXML();
echo '<pre>'; echo $value; echo '</pre>';
}
foreach ($xml->Message->SettlementReport->Order as $main ){
$value = $main->asXML();
echo '<pre>'; echo $value; echo '</pre>';
}
This is what my file looks like to give you an idea (So essentially how do I get the tags within [SettlementData], [0], [Fulfillment], [Item], etc. ?):
I would like to move this data into a MySQL database whose columns match the tags of the file.
Your problem is two folded.
The first part of the problem is to do the introspection on the database structure. That is, obtain all table names and obtain the column names of these. Most modern databases offer this functionality, so does MySQL. In MySQL those are the INFORMATION_SCHEMA Tables. You can query them as if those were normal database tables. I generally recommend PDO for that in PHP, mysqli is naturally doing the job perfectly as well.
The second part is parsing the XML data and mapping it's data onto the database tables (you use SimpleXMLElement for that in your question so I related to it specifically). For that you first of all need to find out how you would like to map the data from the XML onto the database. An XML file does not have a 2D structure like a relational database table, but it has a tree structure.
For example (if I read your question right) you identify Message->SettlementReport->SettlementData as the first "table". For that specific example it is easy as the <SettlementData> only has child-elements that could represent a column name (the element name) and value (the text-content). For that it is easy:
header('Content-Type: text/plain; charset=utf-8');
$table = $xml->Message->SettlementReport->SettlementData;
foreach ($table as $name => $value ) {
echo $name, ': ', $value, "\n";
}
As you can see, specifying the key assignment in the foreach clause will give you the element name with SimpleXMLElement. Alternatively, the SimpleXMLElement::getName() method does the same (just an example which does the same just with slightly different code):
header('Content-Type: text/plain; charset=utf-8');
$table = $xml->Message->SettlementReport->SettlementData;
foreach ($table as $value) {
$name = $value->getName();
echo $name, ': ', $value, "\n";
}
In this case you benefit from the fact that the Iterator provided in the foreach of the SimpleXMLElement you access via $xml->...->SettlementData traverses all child-elements.
A more generic concept would be Xpath here. So bear with me presenting you a third example which - again - does a similar output:
header('Content-Type: text/plain; charset=utf-8');
$rows = $xml->xpath('/*/Message/SettlementReport/SettlementData');
foreach ($rows as $row) {
foreach ($row as $column) {
$name = $column->getName();
$value = (string) $column;
echo $name, ': ', $value, "\n";
}
}
However, as mentioned earlier, mapping a tree-structure (N-Depth) onto a 2D-structure (a database table) might now always be that straight forward.
If you're looking what could be an outcome (there will most often be data-loss or data-duplication) a more complex PHP example is given in a previous Q&A:
How excel reads XML file?
PHP XML to dynamic table
Please note: As the matter of fact such mappings on it's own can be complex, the questions and answers inherit from that complexity. This first of all means those might not be easy to read but also - perhaps more prominently - might just not apply to your question. Those are merely to broaden your view and provide and some examples for certain scenarios.
I hope this is helpful, please provide any feedback in form of comments below. Your problem might or might not be less problematic, so this hopefully helps you to decide how/where to go on.
I tried with SimpleXML but it skips text data. However, using the Document Object Model extension works.
This returns an array where each element is an array with 2 keys: tag and text, returned in the order in which the tree is walked.
<?php
// recursive, pass by reference (spare memory ? meh...)
// can skip non tag elements (removes lots of empty elements)
function tagData(&$node, $skipNonTag=false) {
// get function name, allows to rename function without too much work
$self = __FUNCTION__;
// init
$out = array();
$innerXML = '';
// get document
$doc = $node->nodeName == '#document'
? $node
: $node->ownerDocument;
// current tag
// we use a reference to innerXML to fill it later to keep the tree order
// without ref, this would go after the loop, children would appear first
// not really important but we never know
if(!(mb_substr($node->nodeName,0,1) == '#' && $skipNonTag)) {
$out[] = array(
'tag' => $node->nodeName,
'text' => &$innerXML,
);
}
// build current innerXML and process children
// check for children
if($node->hasChildNodes()) {
// process children
foreach($node->childNodes as $child) {
// build current innerXML
$innerXML .= $doc->saveXML($child);
// repeat process with children
$out = array_merge($out, $self($child, $skipNonTag));
}
}
// return current + children
return $out;
}
$xml = new DOMDocument();
$xml->load($target_file) or die ("Error: Cannot load xml");
$tags = tagData($xml, true);
//print_r($tags);
?>
I know there are plenty of these, and that my PHP may be riddled with errors (I'm new to PHP), but I cant figure this one out.... it's citing the "function must be a string" error on line 11:
<?php
$dom=new DOMDocument();
$dom->formatOutput=true;
$dom->load('test.xml');
$searchItems=$dom->getElementsByTagName('title');
$voteItems=$dom->getElementsByTagName('vote');
for ($i=0; $i < $searchItems->length; $i++){
$value = $voteItems($i)->textContent;
$value++;
$name=$searchItems($i);
$xpath = new DOMXPath($dom);
$resulted = $xpath->query('div[#class="active"]');
$active= $div->$resulted->query('div[#class="content"]');
$names=$active->getAttribute('song');
preg_match($names, "i");
if(preg_match($names, $name )){
$voteItems($i)->length->textContent=$value;
$vote = voteItems($i)->length->textContent;
$results=array($name, $vote);
$txt=($name." scored: ".$vote." votes");
echo $txt;
}
}
?>
What this is doing, just in case there's a WAYYY better way to do this is, it checks and XML sheet I have that looks like this:
<playslist>
<song>
<source>imgs/Beck.jpg</source>
<title>Modern Guilt</title>
<artist>Beck</artist>
<vote>0</vote>
<plays>y</plays>
</song>
</playlist>
It checks for the title, and vote for each song, and stores those.
It then for each gets the value of the vote tag, adds one, then searches the HTML for the <div> with the class="active", and then searches that div with active and finds the inner div that has the class="content", then returns that div's "song" attribute.
I then have a preg match to check whether the name of the current "i" in the loop matches the string from the "song" attribute.
If it's a match, that "i"'s value will +1 to its value. It will then store that name value, and the new vote value as an array for me to check.
After that I'd like to save the XML sheet's new change. (otherwise I would've just kept this in JavaScript)
Any tips, hints, and help would be greatly appreciated! As a newbie to PHP I love to learn more!
$value = $voteItems($i)->textContent;
This is incorrect. You're trying to take $voteItems and use it as the name of a function.
PHP allows it:
function foo() {}
$name = "foo";
$name(); // calls foo()
However, that can only work if $name is a string. Here, $voteItems is a DOMNodeList, hence the error message.
From looking at the documentation, it seems like you meant to write:
$value = $voteItems->item($i)->textContent;
// ^^^^^^^^^^
Using PHP 5.3.10, I created a link-list class and am trying to save a list of football players.
After calling the add function, it seems that the object never retains any information. var_dump($playerList) returns NULL for both my head and tail pointers. Or, if I replace it with var_dump($playerList->count), it prints nothing no matter where I place the var_dump count statement.
I have been through the manual and cannot find the error in my syntax. My gut is telling me mysql_fetch_array is doing something funky. As stated below, my testing shows that values are in fact being passed around when I call playerList->add(). Anyhow, here is my simple code:
/* Populates lists with available players. */
function populateList($sql)
{
$playerList = new PlayerList();
while ($row = mysql_fetch_array($sql, MYSQL_NUM))
{
$playerList->add(new Player($row[0], $row[1], $row[2], $row[3], $row[4]));
}
var_dump($playerList);
}
And my linked list class:
include 'PlayerNode.php';
class PlayerList
{
public $head;
public $tail;
public $count;
function PlayerList()
{
$head = null;
$tail = null;
$count = 0;
}
function add($player)
{
$count ++;
$node = new PlayerNode($player);
//First time in
if ($head == null)
{
$head = $node;
$tail = $node;
$head->nextPtr = null;
}
// All other times
else
{
$tail->nextPtr = $node;
$tail = $node;
$node->nextPtr = null;
}
$count++;
}
}
I can place var_dump($node) and echo statements in the linked list class and observe that PlayerNode is working correctly.
But, another strange observation... if($head==null) ALWAYS evaluates to true too. Could this be related?
Insertion in the head of the Singly Linked Lists :
We can easily insert the elements in the head of the list. So how we do it? Create a new node, set the next of the new node point to the current head node, and set the head variable (in the class) point to the new node. This method works even if the Linked List is empty. Note that we set the next of the new node point to the head node, before we sent the head variable to point to the new node.
Insertion in the tail of the Singly Linked Lists:
We can also easily insert elements in the tail of the Linked List, provided we keep a reference for the tail node of the Linked Lists. Create an new node set the next of the new node to null, set the next of the tail node point to the new node, set the tail variable to point to the new element. Note we set the next of the previous tail node before we change the tail variable to point to the new node.
In all the other times add the new node to the head or tail.
// All other times if head
else{
$temp = $head;
$head = $node;
$node->nextPtr = $temp;
count ++;
}
I'm building a script that takes the contents of several (~13) news feeds and parses the XML data and inserts the records into a database. Since I don't have any control over the structure of the feeds, I need to tailor an object operator for each one to drill down into the structure in order to get the information I need.
The script works just fine if the target node is one step below the root, but if my string contains a second step, it fails ( 'foo' works, but 'foo->bar' fails). I've tried escaping characters and eval(), but I feel like I'm missing something glaringly obvious. Any help would be greatly appreciated.
// Roadmaps for xml navigation
$roadmap[1] = "deal"; // works
$roadmap[2] = "channel->item"; // fails
$roadmap[3] = "deals->deal";
$roadmap[4] = "resource";
$roadmap[5] = "object";
$roadmap[6] = "product";
$roadmap[8] = "channel->deal";
$roadmap[13] = "channel->item";
$roadmap[20] = "product";
$xmlSource = $xmlURL[$fID];
$xml=simplexml_load_file($xmlSource) or die(mysql_error());
if (!(empty($xml))) {
foreach($xml->$roadmap[$fID] as $div) {
include('./_'.$incName.'/feedVars.php');
include('./_includes/masterCategory.php.inc');
$test = sqlVendors($vendorName);
} // end foreach
echo $vUpdated." records updated.<br>";
echo $vInserted." records Inserted.<br><br>";
} else {
echo $xmlSource." returned an empty set!";
} // END IF empty $xml result
While Fosco's solution will work, it is indeed very dirty.
How about using xpath instead of object properties?
$xml->xpath('deals/deal');
PHP isn't going to magically turn your string which includes -> into a second level search.
Quick and dirty hack...
eval("\$node = \"\$xml->" . $roadmap[$fID] . "\";");
foreach($node as $div) {