XML File - Get specific child nodes in unlimited node depths - php

I'm working on a website that uses hierarchical data. After struggling to do it with MySQL databases (really complicated...), I decided to dive into XML because it sounds like XML works perfectly for my needs.
Now I'm experimenting with an XML File and SimpleXML. But first of all, here is what my XML File looks like:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<content>
<parent>
<child id="1">
<title>child 1</title>
<child id="1">
<title>child 1.1</title>
<child id="1">
<title>child 1.1.1</title>
</child>
</child>
<child id="2">
<title>child 1.2</title>
<child id="1">
<title>child 1.2.1</title>
<child id="1">
<title>child 1.2.1.1</title>
</child>
</child>
<child id="2">
<title>child 1.2.2</title>
</child>
</child>
<child id="3">
<title>child 1.3</title>
</child>
</child>
</parent>
</content>
As you can see, it has a variing "depth" of child nodes. I also don't know the depth of childs, as they are created by the web app. This depth or "number of layers" can get quite high.
Now I want to read this XML File in my website. For example, I want to visualize it as a tree, with all the child nodes represented as circles connected to their parent circle.
I've managed to have a foreach getting all the first-layer "child" elements an then another foreach in it getting all second-layer "child" elements. The problem is, that this limits the number of layers I can visualize because I cannot have a dozen nested foreach'es.
Now I already have a headache thinking of a way of "unlimited nested foreach structures" to get all the layers of "child" nodes. But I'm not able to find a way of doing it.
Do you have an idea how to do it? Please help me! Thanks in advance.
PS: Sorry for my english, I'm an german teenager student :)
EDIT: Here is the code in my test.php:
<?php
if (file_exists('mydata.xml'))
{
$xml = simplexml_load_file('mydata.xml');
?>
<ul>
<?php
foreach($xml->parent->child as $item) // Go through first layer
{
echo "<li>".$item->title;
echo "<ul>"; // Open second layer <ul>
foreach($item->child as $item) // Go through second layer
{
echo "<li>".$item->title."</li>";
}
echo "</ul>"; // Close second layer <ul>
echo "</li>"; // Close child <li>
}
}
else
{
exit('Konnte Datei nicht laden.');
}
?>
</ul>
This is the result, just what I was expecting:
- child 1
- child 1.1
- child 1.2
- child 1.3
So this works fine, but as mentioned in the comments, I need this not only for layer 1 to 2, but for layer 1 to n. Would really appreciate if someone has an idea :)

What you have in the XML file is a tree structure of elements.
One common way to display such structures in PHP is to make use of the RecursiveTreeIterator which displays ASCII trees:
\-child 1
|-child 1.1
| \-child 1.1.1
|-Chapter 1.2
| |-child 1.2.1
| | \-child 1.2.1.1
| \-child 1.2.2
\-child 1.3
It's usage is relatively straight forward but it requires that you write a RecursiveIterator your own for the data-structure you have. Here is the example code that makes use of such an recursive iterator, namely RecursiveChildIterator specifically created for your use-case:
<?php
/**
* recursive display of XML contents
*/
require 'RecursiveChildIterator.php';
$content = simplexml_load_file('content.xml');
$iterator = new RecursiveChildIterator($content->parent->child);
$tree = new RecursiveTreeIterator($iterator);
foreach ($tree as $line) {
echo $line, "\n";
}
As this example shows the RecursiveChildIterator is required on top with its own file RecursiveChildIterator.php that contains the following code which is the class definition.
In the constructor most work that is done is to validate the $children parameter to be either false-y or foreach-able and if foreach-able that each iteration gives a SimpleXMLElement:
/**
* Class RecursiveChildIterator
*/
class RecursiveChildIterator extends IteratorIterator implements RecursiveIterator
{
/**
* #var SimpleXMLElement
*/
private $children;
public function __construct($children)
{
if ($children) {
foreach ($children as $child) {
if (!$child instanceof SimpleXMLElement) {
throw new UnexpectedValueException(
sprintf('SimpleXMLElement expected, %s given ', var_export($child, true))
);
}
}
}
The constructor then continues to create an appropriate Traversable out of the parameter so that the parent class IteratorIterator can use it as the dependency:
if ($children instanceof Traversable) {
$iterator = $children;
} elseif (!$children) {
$iterator = new EmptyIterator();
} elseif (is_array($children) || is_object($children)) {
$iterator = new ArrayObject($children);
} else {
throw new UnexpectedValueException(
sprintf("Array or Object expected, %s given", gettype($children))
);
}
$this->children = $children;
parent::__construct($iterator);
}
Then it's defined what the value of the current element is which is for the text-tree the title value:
public function current()
{
return parent::current()->title;
}
And then the needed implementation as a RecursiveIterator to handle the recursive iteration with the two children methods of the interface:
public function hasChildren()
{
$current = parent::current();
return (bool)$current->child->count();
}
public function getChildren()
{
$current = parent::current();
return new self($current->child);
}
}
Implementing the logic to traverse children in a class implementing the interface RecursiveIterator your own does allow you to pass it along to everything accepting a RecursiveIterator like it is the case with RecursiveTreeIterator.

Two essentially identical examples are below. In each we define a function renderNode() that gets called recursively to render the nested lists. There's not a lot of code so there's not a lot to say.
One is based on SimpleXML, because that's what you're currently experimenting with.
The other is based on the DOM extension, because I personally find it to be a better API to work with (for all the reasons listed here and then some.)
For what you're doing here it's not terribly relevant which you use, but options are always nice.
DOM Example:
$dom = new DOMDocument();
$dom->load('mydata.xml');
$xpath = new DOMXPath($dom);
echo "<ul>";
foreach ($xpath->query('/content/parent/child') as $node) {
renderNode($node, $xpath);
}
echo "</ul>";
function renderNode(DOMElement $node, DOMXPath $xpath) {
echo "<li>", $xpath->evaluate('string(title)', $node);
$children = $xpath->query('child', $node);
if ($children->length) {
echo "<ul>";
foreach ($children as $child) {
renderNode($child, $xpath);
}
echo "</ul>";
}
echo "</li>";
};
SimpleXML Example:
$xml = simplexml_load_file('mydata.xml');
echo "<ul>";
foreach ($xml->parent->child as $node) {
renderNode($node);
}
echo "</ul>";
function renderNode($node) {
echo "<li>", $node->title;
if ($node->child) {
echo "<ul>";
foreach ($node->child as $child) {
renderNode($child);
}
echo "</ul>";
}
echo "</li>";
}
Output (beautified, identical for both examples):
<ul>
<li>child 1
<ul>
<li>child 1.1
<ul><li>child 1.1.1</li></ul>
</li>
<li>child 1.2
<ul>
<li>child 1.2.1
<ul><li>child 1.2.1.1</li></ul>
</li>
<li>child 1.2.2</li>
</ul>
</li>
<li>child 1.3</li>
</ul>
</li>
</ul>
And just for kicks, here's a bonus option using XSLT. The beautified output is the same as above.
XSLT Example:
PHP:
$xmldoc = new DOMDocument();
$xmldoc->load('mydata.xml');
$xsldoc = new DOMDocument();
$xsldoc->load('example.xsl');
$xsl = new XSLTProcessor();
$xsl->importStyleSheet($xsldoc);
echo $xsl->transformToXML($xmldoc);
example.xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="no"/>
<xsl:template match="/content/parent">
<ul>
<xsl:apply-templates select="child"/>
</ul>
</xsl:template>
<xsl:template match="child">
<li>
<xsl:value-of select="title"/>
<xsl:if test="child">
<ul>
<xsl:apply-templates select="child"/>
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>

Related

Use XMLReader to find node and retrieve XML from current node and following children

I'm trying to retrieve one specific node based on the <id> element from a huge XML file. I have used DOMDocument, but its not ideal since it loads the whole document first. There is around 1400 <item> nodes in the document. This is a simplified version of the document:
<main>
<body>
...
<sub>
...
<items>
...
<item>
<name>Abc</name>
...
<id>123</id>
<calls>
<call>
<name>Monkey</name>
<text>Monkeys r cool</text>
...
</call>
<call>
<name>Pig</name>
<text>Pigs too!</text>
...
</call>
</calls>
<cones>
<cone>
<name>Lorem</name>
<text>Lorem ipsum</text>
...
</cone>
<cone>
<name>More</name>
<text>Placeholder</text>
...
</cone>
</cones>
<a>true</a>
</item>
<item>
<name>Def</name>
...
<id>456</id>
<calls>
<call>
<name>aa</name>
<text>aa</text>
...
</call>
<call>
<name>bb</name>
<text>bb</text>
...
</call>
</calls>
<cones>
<cone>
<name>cc</name>
<text>cc</text>
...
</cone>
<cone>
<name>dd</name>
<text>dd</text>
...
</cone>
</cones>
<a>true</a>
</item>
</items>
</sub>
</body>
</main>
So basically I'm trying to retrieve the current node and its children's data from matching the <id> element. I have tried find tutorials on XMLReader, but can't seem to find that much. This is what I've tried so far:
$xml = new XMLReader();
$xml->open('doc.xml');
while($xml->read()) {
if($xml->nodeType == XMLREADER::ELEMENT && $xml->localName == 'id') {
$xml->read();
echo $xml->value;
}
}
This finds every <id> element, but i want to find one specific and read the data from the current node, and its children. Maybe using the example to find the node and readInnerXml() to get the data
I'm not an expert so any help / push to the right direction is much appreciated :D
If all the item elements are siblings you can use XMLReader::read() to find the first element and XMLReader::next() to iterate them.
Then use XMLReader::expand() to load the item and its descendants into DOM, use Xpath to read data from it.
$searchForID = '123';
$reader = new XMLReader();
$reader->open('data:text/xml;base64,'.base64_encode(getXMLString()));
$document = new DOMDocument();
$xpath = new DOMXpath($document);
// look for the first "item" element node
while (
$reader->read() && $reader->localName !== 'item'
) {
continue;
}
// iterate "item" sibling elements
while ($reader->localName === 'item') {
// expand into DOM
$item = $reader->expand($document);
// if the node has a child "id" with the searched contents
if ($xpath->evaluate("count(self::*[id = '$searchForID']) > 0", $item)) {
var_dump(
[
// fetch node text content as string
'name' => $xpath->evaluate('string(name)', $item),
// fetch list of "call" elements and map them
'calls' => array_map(
function(DOMElement $call) use ($xpath) {
return [
'name' => $xpath->evaluate('string(name)', $call),
'text' => $xpath->evaluate('string(text)', $call)
];
},
iterator_to_array(
$xpath->evaluate('calls/call', $item)
)
)
]
);
}
$reader->next('item');
}
$reader->close();
XML with namespaces
If the XML uses a namespace (like the one you linked in the comments) you will have to takes it into consideration.
For the XMLReader that means validating not just localName (the node name without any namespace prefix/alias) but the namespaceURI as well.
For DOM methods that would mean using the namespace aware methods (with the suffix NS) and registering your own alias/prefix for the Xpath expressions.
$searchForID = '2755';
$reader = new XMLReader();
$reader->open('data:text/xml;base64,'.base64_encode(getXMLString()));
// the namespace uri
$xmlns_siri = 'http://www.siri.org.uk/siri';
$document = new DOMDocument();
$xpath = new DOMXpath($document);
// register an alias for the siri namespace
$xpath->registerNamespace('siri', $xmlns_siri);
// look for the first "item" element node
while (
$reader->read() &&
(
$reader->localName !== 'EstimatedVehicleJourney' ||
$reader->namespaceURI !== $xmlns_siri
)
) {
continue;
}
// iterate "item" sibling elements
while ($reader->localName === 'EstimatedVehicleJourney') {
// validate the namespace of the node
if ($reader->namespaceURI === $xmlns_siri) {
// expand into DOM
$item = $reader->expand($document);
// if the node has a child "VehicleRef" with the searched contents
// note the use of the registered namespace alias
if ($xpath->evaluate("count(self::*[siri:VehicleRef = '$searchForID']) > 0", $item)) {
var_dump(
[
// fetch node text content as string
'name' => $xpath->evaluate('string(siri:OriginName)', $item),
// fetch list of "call" elements and map them
'calls' => array_map(
function(DOMElement $call) use ($xpath) {
return [
'name' => $xpath->evaluate('string(siri:StopPointName)', $call),
'reference' => $xpath->evaluate('string(siri:StopPointRef)', $call)
];
},
iterator_to_array(
$xpath->evaluate('siri:RecordedCalls/siri:RecordedCall', $item)
)
)
]
);
}
}
$reader->next('EstimatedVehicleJourney');
}
$reader->close();

Parse XML Document recursive

I have XML documents containing information of articles, that have a kind of hierarchy:
<?xml version="1.0" encoding="UTF-8"?>
<page>
<elements>
<element>
<type>article</type>
<id>1</id>
<parentContainerID>page</parentContainerID>
<parentContainerType>page</parentContainerType>
</element>
<element>
<type>article</type>
<id>2</id>
<parentContainerID>1</parentContainerID>
<parentContainerType>article</parentContainerType>
</element>
<element>
<type>photo</type>
<id>3</id>
<parentContainerID>2</parentContainerID>
<parentContainerType>article</parentContainerType>
</element>
<... more elements ..>
</elements>
</page>
The element has the node parentContainerID and the node parentContainerType. If parentContainerType == page, this is the master element. The parentContainerID shows what's the element's master. So it should look like: 1 <- 2 <- 3
Now I need to build a new page (html) of this stuff that looks like this:
content of ID 1, content of ID 2, content of ID 3 (the IDs are not ongoing).
I guess this could be done with a recursive function. But I have no idea how to manage this?
Here is no nesting/recursion in the XML. The <element/> nodes are siblings. To build the parent child relations I would suggest looping over the XML and building two arrays. One for the relations and one referencing the elements.
$xml = file_get_contents('php://stdin');
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$relations = [];
$elements = [];
foreach ($xpath->evaluate('//element') as $element) {
$id = (int)$xpath->evaluate('string(id)', $element);
$parentId = (int)$xpath->evaluate('string(parentContainerID)', $element);
$relations[$parentId][] = $id;
$elements[$id] = $element;
}
var_dump($relations);
Output:
array(3) {
[0]=>
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
The relations array now contains the child ids for any parent, elements without a parent are in index 0. This allows you use a recursive function access the elements as a tree.
function traverse(
int $parentId, callable $callback, array $elements, array $relations, $level = -1
) {
if ($elements[$parentId]) {
$callback($elements[$parentId], $parentId, $level);
}
if (isset($relations[$parentId]) && is_array($relations[$parentId])) {
foreach ($relations[$parentId] as $childId) {
traverse($childId, $callback, $elements, $relations, ++$level);
}
}
}
This executes the callback for each node. The proper implementation for this would be a RecursiveIterator but the function should do for the example.
traverse(
0,
function(DOMNode $element, int $id, int $level) use ($xpath) {
echo str_repeat(' ', $level);
echo $id, ": ", $xpath->evaluate('string(type)', $element), "\n";
},
$elements,
$relations
);
Output:
1: article
2: article
3: photo
Notice that the $xpath object is provided as context to the callback. Because the $elements array contains the original nodes, you can use Xpath expression to fetch detailed data from the DOM related to the current element node.

Pass XML node as parameter on object instantiation and then calling subnodes from it

I want to know if passing an XML node and then calling upon a method to access it is legal syntax in PHP. I tried converting to string, but that didn't work.
What am I doing wrong?
What would be the best/simplest alternative?
XML
<user>
<widgets>
<widget>Widget 1</widget>
<stuff>
<morestuff>Things</morestuff>
</stuff>
<stuff>
<morestuff>Things</morestuff>
</stuff>
<widget>Widget 2</widget>
</widgets>
</user>
PHP
<?php
$xmlfile = 'widgets/widgets_files/widgets.xml';
$widgets = array();
$user = new SimpleXMLElement($xmlfile, NULL, true);
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom = dom_import_simplexml($user)->ownerDocument;
foreach ($user->widgets->widget as $widget) {
$new_widget = new Widget($widget); //Where the node gets passed
array_push($widgets, $new_widget);
}
//For example
$new_widget[0]->set_subnodes();
$new_widget[0]->get_subnodes();
class Widget {
private $widget;
private $stuffArray = array();
public function __construct($widget) {
$this->widget = $widget;
}
public function set_subnodes() {
foreach ($this->widget->stuff->morestuff as $morestuff => $value) {
$this->stuffArray[$morestuff] = $value;
}
}
public function get_subnodes() {
foreach ($this->stuffArray as $stuff) {
echo$stuff;
}
}
}
It is indeed possible to pass XML objects as parameters to objects and to call methods on them, but there are a number of errors in your code which are stopping it from working. In particular, the XML that you are using isn't the structure that you think it is--the stuff and morestuff nodes are not children of widget, so none of the actions that you're trying to perform with them will work. Here's a corrected version of the XML and some PHP code that does what I think you're trying to do above:
$widgets = array();
# you can load your code from a file, obviously--for the purposes of the example,
# I'm loading mine using a function.
$sxe = simplexml_load_string( get_my_xml() );
foreach ($sxe->widgets->widget as $widget) {
$new_widget = new Widget($widget); // Where the node gets passed
array_push($widgets, $new_widget);
}
// For example
foreach ($widgets as $w) {
$w->set_subnodes();
$w->get_subnodes();
}
function get_my_xml() {
return <<<XML
<user>
<widgets>
<widget>Widget 1
<stuff>
<morestuff>Things</morestuff>
</stuff>
<stuff>
<morestuff>Other Things</morestuff>
</stuff>
</widget>
<widget>Widget 2
<stuff>
<morestuff>Widget Two's Things</morestuff>
</stuff>
<stuff>
<morestuff>Widget Two's Other Things</morestuff>
</stuff>
</widget>
</widgets>
</user>
XML;
}
The Widget object:
class Widget {
private $widget;
private $stuffArray = array();
public function __construct($widget) {
$this->widget = $widget;
}
public function set_subnodes() {
# put all the "morestuff" nodes into the stuffArray
foreach ($this->widget->xpath("stuff/morestuff") as $ms) {
print "pushing $ms on to array" . PHP_EOL;
array_push($this->stuffArray, $ms);
}
}
public function get_subnodes() {
foreach ($this->stuffArray as $stuff) {
print "Running get_subnodes: got $stuff" . PHP_EOL;
}
}
}
Output:
pushing Things on to array
pushing Other Things on to array
Running get_subnodes: got Things
Running get_subnodes: got Other Things
pushing Widget Two's Things on to array
pushing Widget Two's Other Things on to array
Running get_subnodes: got Widget Two's Things
Running get_subnodes: got Widget Two's Other Things

How to build a XML from an Array in php?

This seams obvious, but what I found the most was how to manipulate existing XML and now I wish to build from ground zero. The source is a database converted into an Array. The root is a single "menu" and all child elements are called "item". The structure is defined by the value of "parent" property and "code" property.
item[0] ("code"=>"first" "somevar"=>"somevalue")
item[1] ("code"=>"second", "parent"=>"first" "somevar"=>"othervalue")
Means item[1] is a child of item[0].
<menu>
<item code="first" somevar="somevalue">
<item code="second" somevar="othervalue" />
</item>
</menu>
There will be only two levels of items this time, maybe later I'll expand the capabilities to "n" levels...
I tried with SimpleXML, but it seams is too simple. So I tried with DOMDocument, but I'm stuck creating new elements...
$domMenu = new DOMDocument();
$domMenu->createElement("menu");
... creating the $domItem as a DOMElement with attributes ...
$domMenu->menu->appendChild($domItem);
This generates an error, it seams "menu" is not seen as an DOMElement. Should I use getElements methods or there is a better way of build this XML?
You did not append the menu element to the DOM. And DOM does not map element names to object properties like SimpleXML. The root element is accessible using the DOMDocument::$documentElement property.
$domMenu = new DOMDocument();
$domMenu->appendChild(
$menuNode = $domMenu->createElement("menu")
);
... creating the $domItem as a DOMElement with attributes ...
$menuNode->appendChild($domItem);
In you case I would suggest using xpath to find the parent node for the itemNode and if not found let the function call itself (recursion) to append the parent element first. If here is not parent item, append the node to the document element.
$data = [
["code"=>"second", "parent"=>"first", "somevar"=>"othervalue"],
["code"=>"first", "somevar"=>"somevalue"]
];
function appendItem($xpath, $items, $item) {
// create the new item node
$itemNode = $xpath->document->createElement('item');
$itemNode->setAttribute('code', $item['code']);
$itemNode->setAttribute('somevar', $item['somevar']);
$parentCode = isset($item['parent']) ? $item['parent'] : NULL;
// does it have a parent and exists this parent in the $items array
if (isset($parentCode) && isset($items[$parentCode])) {
// fetch the existing parent
$nodes = $xpath->evaluate('//item[#code = "'.$parentCode.'"]');
if ($nodes->length > 0) {
$parentNode = $nodes->item(0);
} else {
// parent node not found create it
$parentNode = appendItem($xpath, $items, $items[$parentCode]);
}
} else {
$parentNode = $xpath->document->documentElement;
}
$parentNode->appendChild($itemNode);
return $itemNode;
}
$dom = new DOMDocument();
$xpath = new DOMXpath($dom);
$dom->appendChild(
$dom->createElement("menu")
);
// build an indexed list using the "code" values
$items = [];
foreach ($data as $item) {
$items[$item['code']] = $item;
}
foreach ($items as $item) {
// check if the item has already been added
if ($xpath->evaluate('count(//item[#code = "'.$item['code'].'"])') == 0) {
// add it
appendItem($xpath, $items, $item);
}
}
$dom->formatOutput = TRUE;
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<menu>
<item code="first" somevar="somevalue">
<item code="second" somevar="othervalue"/>
</item>
</menu>
$xml = new SimpleXMLElement('<menu/>');
array_walk_recursive($array, array ($xml, 'addChild'));
print $xml->asXML();

PHP SimpleXML recursive function to list children and attributes

I need some help on the SimpleXML calls for a recursive function that lists the elements name and attributes. Making a XML config file system but each script will have it's own config file as well as a new naming convention. So what I need is an easy way to map out all the elements that have attributes, so like in example 1 I need a simple way to call all the processes but I don't know how to do this without hard coding the elements name is the function call. Is there a way to recursively call a function to match a child element name? I did see the xpath functionality but I don't see how to use this for attributes.
Also does the XML in the examples look correct? can I structure my XML like this?
Example 1:
<application>
<processes>
<process id="123" name="run batch A" />
<process id="122" name="run batch B" />
<process id="129" name="run batch C" />
</processes>
<connections>
<databases>
<database usr="test" pss="test" hst="test" dbn="test" />
</databases>
<shells>
<ssh usr="test" pss="test" hst="test-2" />
<ssh usr="test" pss="test" hst="test-1" />
</shells>
</connections>
</application>
Example 2:
<config>
<queues>
<queue id="1" name="test" />
<queue id="2" name="production" />
<queue id="3" name="error" />
</queues>
</config>
Pseudo code:
// Would return matching process id
getProcess($process_id) {
return the process attributes as array that are in the XML
}
// Would return matching DBN (database name)
getDatabase($database_name) {
return the database attributes as array that are in the XML
}
// Would return matching SSH Host
getSSHHost($ssh_host) {
return the ssh attributes as array that are in the XML
}
// Would return matching SSH User
getSSHUser($ssh_user) {
return the ssh attributes as array that are in the XML
}
// Would return matching Queue
getQueue($queue_id) {
return the queue attributes as array that are in the XML
}
EDIT:
Can I pass two parms? on the first method you have suggested #Gordon
I just got it, thnx, see below
public function findProcessById($id, $name)
{
$attr = false;
$el = $this->xml->xpath("//process[#id='$id'][#name='$name']"); // How do I also filter by the name?
if($el && count($el) === 1) {
$attr = (array) $el[0]->attributes();
$attr = $attr['#attributes'];
}
return $attr;
}
The XML looks good to me. The only thing I wouldn't do is making name an attribute in process, because it contains spaces and should be a textnode then (in my opinion). But since SimpleXml does not complain about it, I guess it boils down to personal preference.
I'd likely approach this with a DataFinder class, encapsulating XPath queries, e.g.
class XmlFinder
{
protected $xml;
public function __construct($xml)
{
$this->xml = new SimpleXMLElement($xml);
}
public function findProcessById($id)
{
$attr = false;
$el = $this->xml->xpath("//process[#id='$id']");
if($el && count($el) === 1) {
$attr = (array) $el[0]->attributes();
$attr = $attr['#attributes'];
}
return $attr;
}
// ... other methods ...
}
and then use it with
$finder = new XmlFinder($xml);
print_r( $finder->findProcessById(122) );
Output:
Array
(
[id] => 122
[name] => run batch B
)
XPath tutorial:
http://www.w3schools.com/XPath/default.asp
An alternative would be to use SimpleXmlIterator to iterate over the elements. Iterators can be decorated with other Iterators, so you can do:
class XmlFilterIterator extends FilterIterator
{
protected $filterElement;
public function setFilterElement($name)
{
$this->filterElement = $name;
}
public function accept()
{
return ($this->current()->getName() === $this->filterElement);
}
}
$sxi = new XmlFilterIterator(
new RecursiveIteratorIterator(
new SimpleXmlIterator($xml)));
$sxi->setFilterElement('process');
foreach($sxi as $el) {
var_dump( $el ); // will only give process elements
}
You would have to add some more methods to have the filter work for attributes, but this is a rather trivial task.
Introduction to SplIterators:
http://www.phpro.org/tutorials/Introduction-to-SPL.html

Categories