I'm trying to create a dynamic table based on an XML. The outputted XML is the following:
<tickets xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<status>1</status>
<message>
<ticketpage>
<ticket>
<row1>0-10-27-30-45-0-0-0-80</row1>
<row2>0-15-0-38-0-51-62-0-85</row2>
<row3>5-0-0-37-0-57-60-77-0</row3>
</ticket>
<ticket>
<row1>0-0-20-33-0-56-68-0-90</row1>
<row2>8-0-0-0-49-0-64-71-84</row2>
<row3>1-18-22-32-0-59-0-0-0</row3>
</ticket>
<ticket>
<row1>0-0-23-0-47-58-67-0-86</row1>
<row2>4-16-0-0-43-53-0-0-88</row2>
<row3>3-0-28-35-0-0-65-72-0</row3>
</ticket>
<ticket>
<row1>0-19-26-0-48-52-0-74-0</row1>
<row2>0-0-21-0-40-0-63-75-82</row2>
<row3>9-11-0-34-41-0-0-76-0</row3>
</ticket>
<ticket>
<row1>0-12-29-36-44-0-0-78-0</row1>
<row2>6-14-0-39-0-0-69-0-89</row2>
<row3>2-0-0-0-0-54-66-70-81</row3>
</ticket>
<ticket>
<row1>0-17-25-0-46-0-0-73-87</row1>
<row2>0-0-24-0-42-50-61-79-0</row2>
<row3>7-13-0-31-0-55-0-0-83</row3>
</ticket>
</ticketpage>
</message>
</tickets>
Each "ticket" is a new table, each "row" is a row and each number in the row is a column (except for the 0, that will be an empty cell).
I know I should be using PHP's SimpleXML, but I have no idea how.
How do I go about doing this?
I've given an extensive example in the question PHP simplexml xpath search for value in an ELEMENT containing tab delimited text? that shows how you can manipulate a SimpleXMLElement on-the-fly containing encoded data turning it into more accessible data.
This could be done similarly in your case, however this time I'd like to to show how this can be done with custom iterators.
But first let's consider how a table could be represented: A table consists of rows and columns. So we could iterate over rows and each row could be an iterator of columns.
And with your question, there is also another iterator over the tables. So in pseudo-code this can look like:
foreach($tables as $table)
{
foreach ($table as $rows) {
foreach ($rows as $column) {
}
}
}
Doing the output somewhere around that. So for example, if we have got an Iterator that is able to iterate over rows and columns that way to convert is into a table (for example as plain text for demonstration purposes so far), the pseudo-code can be reduced to this:
foreach($tables as $table)
{
echo new TextTable($table);
}
As this example shows, the problem has been already reduced to display a single table - because all tables follow the same structure. I do not show TextTable
works right now, it's not of further interest. If you are looking for an iterator that outputs a HTML table following the same iteration, see the linked question above, it has got a HtmlTableIterator
which does most of the work. But right now, let's look how to create the iterator over rows and columns.
For that I introduce you to an existing iterator class named DecoratingIterator
. It allows to decorate each current element in an iteration. This is pretty flexible because we can use it to compose the final table-iterator in a pretty flexible way in small steps.
Let's solve all the problems you face, step by step. First of all a function is needed to turn the number 0 into an empty string. Let's create a function for that (and I also add some more formatting by prefixing with spaces if necessary to get a length of two):
$formatNumber = function($number)
{
if ($number === '0') {
$number = '';
}
return sprintf("%' 2s", $number);
};
The next problem to solve is to turn the SimpleXMLElement
of numbers into something foreach-able that are those formatted numbers. Let's just write it as well and use the previous function to help get it done:
$numbersToArray = function($stringNumbers) use ($formatNumber) {
return array_map($formatNumber, explode('-', $stringNumbers));
};
Wow, that was short. The next problem to solve is to turn all rows of a single table into these array of numbers. Well, again, we do it with the help of the last function:
$tableToRows = function(SimpleXMLElement $table) use ($numbersToArray) {
return new DecoratingIterator($table->children(), $numbersToArray);
};
And finally one problem is left: The whole XML needs to be turned into an iterator of all the tables. Again with the previous function this is more easy again:
$tables = new DecoratingIterator(
$xml->message->ticketpage->ticket,
$tableToRows
);
Wow. Probably a bit long, let's review:
$formatNumber = function($number)
{
if ($number === '0') {
$number = '';
}
return sprintf("%' 2s", $number);
};
$numbersToArray = function(SimpleXMLElement $stringNumbers) use ($formatNumber) {
return array_map($formatNumber, explode('-', $stringNumbers));
};
$tableToRows = function(SimpleXMLElement $table) use ($numbersToArray) {
return new DecoratingIterator($table->children(), $numbersToArray);
};
$tables = new DecoratingIterator(
$xml->message->ticketpage->ticket,
$tableToRows
);
All these lines of code do is to provide the $tables
iterator we can use to display the tables. So to make this more easy to use, we wrap this into a class of it's own and signal PHP that this class can aggregate an iterator which works via the IteratorAggregate
interface:
class TableAggregator implements IteratorAggregate
{
private $xml;
public function __construct(SimpleXMLElement $xml)
{
$this->xml = $xml;
}
public function getIterator()
{
# ... code to create the tables iterator
return $tables;
}
}
Okay, that probably was a lot so far. Better show the usage-example now to make visible why aggregation makes sense here:
$xml = simplexml_load_file('example.xml');
$tables = new TableAggregator($xml);
foreach ($tables as $table) {
echo new TextTable($table), "\n";
}
As this example shows, its because this is easy to use. Also if we have a different formatting needs, we could create a different aggregator - that simple it is. Let's see the exemplary output:
+--+--+--+--+--+--+--+--+--+
| |10|27|30|45| | | |80|
+--+--+--+--+--+--+--+--+--+
| |15| |38| |51|62| |85|
+--+--+--+--+--+--+--+--+--+
| 5| | |37| |57|60|77| |
+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+
| | |20|33| |56|68| |90|
+--+--+--+--+--+--+--+--+--+
| 8| | | |49| |64|71|84|
+--+--+--+--+--+--+--+--+--+
| 1|18|22|32| |59| | | |
+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+
| | |23| |47|58|67| |86|
+--+--+--+--+--+--+--+--+--+
| 4|16| | |43|53| | |88|
+--+--+--+--+--+--+--+--+--+
| 3| |28|35| | |65|72| |
+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+
| |19|26| |48|52| |74| |
+--+--+--+--+--+--+--+--+--+
| | |21| |40| |63|75|82|
+--+--+--+--+--+--+--+--+--+
| 9|11| |34|41| | |76| |
+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+
| |12|29|36|44| | |78| |
+--+--+--+--+--+--+--+--+--+
| 6|14| |39| | |69| |89|
+--+--+--+--+--+--+--+--+--+
| 2| | | | |54|66|70|81|
+--+--+--+--+--+--+--+--+--+
+--+--+--+--+--+--+--+--+--+
| |17|25| |46| | |73|87|
+--+--+--+--+--+--+--+--+--+
| | |24| |42|50|61|79| |
+--+--+--+--+--+--+--+--+--+
| 7|13| |31| |55| | |83|
+--+--+--+--+--+--+--+--+--+
The whole example at a glance: https://gist.github.com/hakre/5734770
You can try this:
<?php
$str = '<tickets xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<status>1</status>
<message>
<ticketpage>
<ticket>
<row>0-10-27-30-45-0-0-0-80</row>
<row>0-15-0-38-0-51-62-0-85</row>
<row>5-0-0-37-0-57-60-77-0</row>
</ticket>
</ticketpage>
</message>
</tickets>';
$dom = new SimpleXMLElement($str);
foreach ($dom->message->ticketpage->ticket as $ticket) {
echo '<table border=1>';
foreach($ticket->row as $row){
$cellList = explode('-',$row);
echo '<tr>';
foreach ($cellList as $cell){
echo '<td>', $cell, '</td>';
}
echo '</tr>';
}
echo '</table>';
}