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 ++;
}
Related
I am writing scraper links from all over the site, including subpages and encountered a small problem. I came up with the idea to use a recursive function because the page I want to scan has several levels. Its structure looks more or less like this:
Level 1 reference
- Second level reference
-- Third level reference
-- Third level reference
- Second level reference
-- Third level reference
-- Third level reference
-- Third level reference
--- Level four reference
It is never entirely clear whether there are more or less hidden under the tested link, hence I came up with the idea of a recursive function.
It takes a link to the main page, takes the first one and if the number of links in it is greater than one, it refers to the same function.
Unfortunately, something goes wrong and I get an empty whiteboard, how can I fix it?
function scanWebsite($url) {
$html = file_get_contents($url);
$dom = new DOMDocument();
#$dom->loadHTML($html);
$xpath = new DOMXpath($dom);
$nodes = $xpath->query("/html/body//a");
$output = [];
foreach($nodes as $node) {
$url = $node->getAttribute("href");
if(count($nodes) > 1) {
scanWebsite("http://samplewebsite.com" .$url);
} else {
if(preg_match("/\/title\/.*\//", $url)) {
array_push($output, $url);
}
continue;
}
}
return $output;
}
echo '<pre>';
print_r(scanWebsite("http://samplewebsite.com"));
echo '</pre>';
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.
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);
?>
If I have the following data in my XML file;
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CstmrDrctDbtInitn>
<PmtInf>
<PmtInfId>5n7gfUaPGK</PmtInfId>
<PmtMtd>DD</PmtMtd>
<NbOfTxs>1</NbOfTxs>
<CtrlSum>200.2</CtrlSum>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
<LclInstrm>
<Cd>CORE</Cd>
</LclInstrm>
<SeqTp>RCUR</SeqTp>
</PmtTpInf>
<DrctDbtTxInf>
<PmtId>
<EndToEndId>nmu5AOhE7G</EndToEndId>
</PmtId>
</DrctDbtTxInf>
</PmtInf>
<PmtInf>
<PmtInfId>5jAcoNoId3</PmtInfId>
<PmtMtd>DD</PmtMtd>
<NbOfTxs>3</NbOfTxs>
<CtrlSum>100.5</CtrlSum>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
<LclInstrm>
<Cd>CORE</Cd>
</LclInstrm>
<SeqTp>FRST</SeqTp>
</PmtTpInf>
<DrctDbtTxInf>
<PmtId>
<EndToEndId>nmu5AbdfG</EndToEndId>
</PmtId>
</DrctDbtTxInf>
<DrctDbtTxInf>
<PmtId>
<EndToEndId>nmu5A3r5jgG</EndToEndId>
</PmtId>
</DrctDbtTxInf>
</PmtInf>
</CstmrDrctDbtInitn>
</Document>
How would I access <NbOfTxs> in the second <PmtInf> block (where the value is 3) instead of <NbOfTxs> in the first <PmtInf> block (where the value is 1)?
If I just used the following line of code;
$FRSTTransaction = $xml->getElementsByTagName('NbOfTxs')->nodeValue;
It doesn't know which <NbOfTxs> I am attempting to access.
The only difference between each payment block is the <SeqTp>. There will be 4 Payment Blocks in total.
I am trying to count the number of <DrctDbtTxInf>blocks in each Payment Block and then put this value into <NbOfTxs>.
<PmtId>
<EndToEndId>nmu5AOhE7G</EndToEndId>
</PmtId>
</DrctDbtTxInf>
The code I tried is as follows;
$filename = date('Y-W').'.xml'; //2014-26.xml
$xml = new DOMDocument;
$xml->load($filename);
$NumberTransactions = 0;
$RCURTransaction = $xml->getElementsByTagName('DrctDbtTxInf');
$NodeValue = $xml->getElementsByTagName('NbOfTxs')->nodeValue;
foreach ($RCURTransaction as $Transaction) {
$NodeValue = $NodeValue + 1;
}
$Document = simplexml_load_file($filename);
$Document->CstmrDrctDbtInitn->PmtInf->NbOfTxs = $NodeValue;
$Document->asXML($filename);
I receive no errors, it just doesnt seem to access the node value.
Read the manual. DOMDocument::getElementsByTagName does not return a node, it returns an instance of the DOMNodeList class. This class implements the Traversable interface (which means you can foreach it), and has one method of its own item (cf, again, the manual).
You are attempting to access the nodeValue property of what you think is a DOMNode instance, but is in fact a DOMNodeList instance. As you can see in the manual, there is no nodeValue property available. Instead, get a specific node from this list, and then get the node value:
$nodes = $xml->getElementsByTagName('NbOfTxs');
foreach ($nodes as $node)
echo $node->nodeValue, PHP_EOL;//first, second, third node
Or, if you want, for example to see the value of just the third occurrence of this node:
if ($nodes->length > 2)//zero-indexed!
echo $nodes->item(2)->nodeValue, PHP_EOL;
else
echo 'Error, only ', $nodes->length, ' occurrences of that node found', PHP_EOL;
Bottom line, as often, really is RTM. The documentation for DOMDocument::getElementsByTagName clearly shows what the return type of the given method is. if it's an instance of a particular class, that return type is clickable on the PHP website, and links you through to the manual page of that class. Navigating an API couldn't be simpler than that, IMHO:
//from the docs
public DOMNodeList DOMDocument::getElementsByTagName ( string $name )
// class::methodName arguments + expected type
|-> return type, links to docs for this class
Update
Addressing the things you mention in your updated question:
How to count specific children for a node
I'm assuming each PmtInf is a payment block, but all the SeqTp tags seem to me, to be children of PmtTpInf tags. Since we're working with a DOMNodeList, which consists of DOMNode instances. Looking at the manual is the first thing to do. As you can see, each DOMNode has numerous, handy properties and methods: $childNodes, $nodeName and $parentNode are the ones we will be using here.
$payments = $xml->getElementsByTagName('PmtTpInf');//don't get PmtInf, we can access that through `PmtTpInf` nodes' parentNode property
$idx = -1;
$counts = array();
$parents = array();
foreach ($payments as $payment)
{
if (!$parents || $parents[$idx] !== $payment->parentNode)
{//current $payment is not a child of last processed payment block
$parents[++$idx] = $payment->parentNode;//add reference to new payment block
$count[$idx] = 0;//set count to 0
}
foreach ($payment->childNodes as $child)
{
if ($child->nodeName === 'SeqTp')
++$counts[$idx];//add 1 to count
}
}
Ok, now we have 2 arrays, $parents, which contains each payment block, and $counts, which contains the total count of all SeqTp blocks in that payment block. Let's set about adding/updating that data:
foreach ($parents as $idx => $block)
{//iterate over the payment blocks
$nbNode = null;//no node found yet
for ($i=0;$i<$block->childNodes->length;++$i)
{
if ($block->childNodes->item($i)->nodeName === 'NbOfTxs')
{
$nbNode = $block->childNodes->item($i);
break;//found the node, stop here
}
}
if ($nbNode === null)
{//NbOfTxs tag does not exist yet
$nbNode = $xml->createElement('NbOfTxs', 0);//create new node
$block->appendChild($nbNode);//add as child of the payment-block node
}
$nbNode->nodeValue = $counts[$idx];//set value using the counts array we constructed above
}
Lastly, to save this updated XML dom:
$xml->save($filename);
That's all, no need for simplexml_load_file at all, because that parses the XML DOM, which DOMDocument already did for you.
For the past few days, I have been trying to find a way to count all of the non-cyclic paths between two nodes. I've been working with a breadth-first search and a depth-first search. I'm fairly sure either can accomplish this task. However, I've been struggling with how to adapt the DFS code below to find all possible paths between two nodes. I've tried a few different things (remembering nodes in array, recursion), but I haven't implemented them correctly and haven't been able to output the possible paths.
Ultimately, I would like return an array of arrays that contain all possible paths between two selected nodes. Is there any simple modification I could make to accomplish this? The code below is what I'm currently working with.
function init(&$visited, &$graph){
foreach ($graph as $key => $vertex) {
$visited[$key] = 0;
}
}
/* DFS args
$graph = Node x Node sociomatrix
$start = starting node
$end = target node (currently not used)
$visited = list of visited nodes
$list = hold keys' corresponding node values, for printing path;
*/
function depth_first(&$graph, $start, $end, $visited, $list){
// create an empty stack
$s = array();
// put the starting node on the stack
array_push($s, $start);
// note that we visited the start node
$visited[$start] = 1;
// do the following while there are items on the stack
while (count($s)) {
// remove the last item from the stack
$t = array_pop($s);
// move through all connections
foreach ($graph[$t] as $key => $vertex) {
// if node hasn't been visited and there's a connection
if (!$visited[$key] && $vertex == 1) {
// note that we visited this node
$visited[$key] = 1;
// push key onto stack
array_push($s, $key);
// print the node we're at
echo $list[$key];
}
}
}
}
// example usage
$visited = array();
$visited = init($visited, $sym_sociomatrix);
breadth_first($sym_sociomatrix, 1, 3, $visited, $list);
Assuming you have a framework / library to create a graph data structure and to traverse it, you could do a backtracking depth-first search with an early return if you get a cycle. Cycle detection is easy if you store the path from the starting node. In C-style pseudo-code (sorry don't know PHP, or if it is capable of recursion):
void DFS(Vertex current, Vertex goal, List<Vertex> path) {
// avoid cycles
if (contains(path, current)
return;
// got it!
if (current == goal)) {
print(path);
return;
}
// keep looking
children = successors(current); // optionally sorted from low to high cost
for(child: children)
DFS(child, add_path(path, child));
}
and you can then call it as DFS(start, goal, List<Vertex>(empty))