How can I move XML elements with PHP's SimpleXML? - php

How can I move an xml element elsewhere in a document? So I have this:
<outer>
<foo>
<child name="a"/>
<child name="b"/>
<child name="c"/>
</foo>
<bar />
</outer>
and want to end up with:
<outer>
<foo />
<bar>
<child name="a"/>
<child name="b"/>
<child name="c"/>
</bar>
</outer>
Using PHP's simpleXML.
Is there a function I'm missing (appendChild-like)?

You could make a recusrive function that clones the attributes and children. There is no other way to move the children with SimpleXML

class ExSimpleXMLElement extends SimpleXMLElement {
//ajoute un object à un autre
function sxml_append(ExSimpleXMLElement $to, ExSimpleXMLElement $from) {
$toDom = dom_import_simplexml($to);
$fromDom = dom_import_simplexml($from);
$toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
}
}
$customerXML = <<<XML
<customer>
<address_billing>
<address_book_id>10</address_book_id>
<customers_id>20</customers_id>
<telephone>0120524152</telephone>
<entry_country_id>73</entry_country_id>
</address_billing>
<countries>
<countries_id>73</countries_id>
<countries_name>France</countries_name>
<countries_iso_code_2>FR</countries_iso_code_2>
</countries>
</customer>
XML;
$customer = simplexml_load_string($customerXML, "ExSimpleXMLElement");
$customer->sxml_append($customer->address_billing, $customer->countries);
echo $customer->asXML();
<?xml version="1.0"?>
<customer>
<address_billing>
<address_book_id>10</address_book_id>
<customers_id>20</customers_id>
<telephone>0120524152</telephone>
<entry_country_id>73</entry_country_id>
<countries>
<countries_id>73</countries_id>
<countries_name>France</countries_name>
<countries_iso_code_2>FR</countries_iso_code_2>
</countries>
</address_billing>
</customer>

Related

XPATH check if an attribute contains one of multiple values

I need to check if an attribute of an XML node contains one of values
Here's my XML:
<manifest>
<item id="item_557c683790288" href="navigation.ncx" media-type="application/x-dtbncx+xml"/>
<item id="toc" href="navigation.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<item id="item_557c68379035d" href="title-page.html" media-type="application/xhtml+xml" properties="scripted"/>
<item id="item_557c683790414" href="imprint.html" media-type="application/xhtml+xml" properties="scripted svg"/>
<item id="item_557c6837904b6" href="author.html" media-type="application/xhtml+xml" properties="scripted"/>
<item id="item_557c683790572" href="file_557c6766c75a9.html" media-type="application/xhtml+xml" properties="scripted"/>
<item id="item_557c683790625" href="liberio.css" media-type="text/css"/>
<item id="item_557c6837906ef" href="assets/2dcc626f-387f-4658-d6f6-58570ae176e7.jpg" media-type="image/jpeg"/>
<item id="item_557c6837907c4" href="assets/liberio_color.svg" media-type="image/svg+xml"/>
<item id="item_557c683790879" href="assets/93d7f25284aeda831bde692e6b002b9f.png" media-type="image/png"/>
<item id="item_557c683790949" href="assets/properties.js" media-type="application/javascript"/>
</manifest>
Right now I'm using the following expression:
$images = $this->opfSxml->xpath("//*[local-name()='manifest']/*[local-name()='item'][contains(#media-type,'png') or contains(#media-type, 'jpg') or contains(#media-type, 'ico') ]");
My code is working but repeating OR and CONTAINS for each value I'm checking against does't feel right for me.
Is there a short way to write this?
Use:
//manifest/*[contains('png|jpeg|ico',substring-after(#media-type,'/'))]
This assumes that the media types are prefix-free strings -- that is, no such string is a prefix of another string.
In case the prefix-free assumption doesn't hold, use:
//manifest/*[contains('|png|jpeg|ico|',concat('|',substring-after(#media-type,'/'),'|'))]
In pure XPath -- no.
The shortest XPath I can think of is:
//manifest/item[contains(#media-type,'png') or contains(#media-type, 'jpg') or contains(#media-type, 'ico') ]
I'm not thinking that it's shorter,
$dom = new DOMDocument;
$dom->loadXML($str);
$xp = new DOMXPath($dom);
// Create function returning boolean
function is_image($str) {
str_replace(['png','jpeg','ico'], '', $str, $c);
// If substring found, it returns true
return $c !== 0 ;
}
$xp->registerPHPFunctions();
$xp->registerNamespace("php", "http://php.net/xpath");
// And now our short Xpath :)
$images = $xp->query("//item[php:function('is_image', string(#media-type))]");
foreach($images as $img)
print_r($img);
Here is the method I would use. No ORs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string[] media_types = { "png", "jpg", "ico" };
XElement manifest = XElement.Load(FILENAME);
var items = manifest.Descendants("item").Select(x => new
{
id = (string)x.Attribute("item"),
href = (string)x.Attribute("href"),
media = (string)x.Attribute("media-type")
}).ToList();
var media = items.Where(x => media_types.Contains(x.href.Substring(x.href.LastIndexOf(".") + 1))).ToList();
}
}
}

Parsing XML into a returnable multidimensional array in PHP

I have tried this a few different ways, returning the SimpleXML object directly, and now running through it, turning it into a multidimensional array, but nothing seems to work, as soon as I try to return it to another function, it just blanks. I must be missing something here, but for the life of me I just cannot see what it is.
public function getSettings() {
$xml = simplexml_load_file('SETTINGS.xml');
$settings = array();
foreach ($xml->settings->page as $page) {
$settings[$page->title] = array("Navbar" => $page->navbar, "Elements" => array());
foreach ($page->element as $element){
array_push($settings[$page->title]["Elements"],
["Name" => $element->name,
"File" => $element->location,
"Style" => $element->style]);
}
}
return $settings;
}
SETTINGS.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<page>
<title>Login</title>
<navbar></navbar>
<element>
<name>Login</name>
<location>Login.php</location>
<style>Style.css</style>
</element>
</page>
<page>
<title>Dashboard</title>
<navbar>Navbar.php</navbar>
<element>
<name>Recent Punishments Table</name>
<location>RecentPunishments.php</location>
<style>Style.css</style>
</element>
</page>
</settings>

XML File - Get specific child nodes in unlimited node depths

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>

Get value from xml using the attribute with xpath in php

I want to get the value '23452345235' of the parameter with name="userID" from this xml:
<?xml version="1.0" encoding="UTF-8"?>
<callout>
<parameter name="UserID">
23452345235
</parameter>
<parameter name="AccountID">
57674567567
</parameter>
<parameter name="NewUserID">
54745674566
</parameter>
</callout>
I'm using this code:
$xml = simplexml_load_string($data);
$myDataObject = $xml->xpath('//parameter[#name="UserID"]');
var_dump($myDataObject);
And I'm getting this:
array(1) {
[0] =>
class SimpleXMLElement#174 (1) {
public $#attributes =>
array(1) {
'name' =>
string(6) "UserID"
}
}
}
I actually want to get the value of '23452345235' or receive the parameter in order to get this value.
What I'm doing wrong?
Well you can (optionally) put it under a loop. Like this:
$myDataObject = $xml->xpath('//parameter[#name="UserID"]');
foreach($myDataObject as $element) {
echo $element;
}
Or directly:
echo $myDataObject[0];
Actually is quite straightforward, as seen on your var_dump(), its an array, so access it as such.
SimpleXMLElement::xpath() can only return an array of SimpleXMLElement objects, so it generates an element and attaches the fetched attribute to it.
DOMXpath::evaluate() can return scalar values from Xpath expressions:
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
var_dump($xpath->evaluate('normalize-space(//parameter[#name="UserID"])'));
Output:
string(11) "23452345235"

Pass container object in XSLTProcessor

is there any way to pass or bind Container object and call Service object's method in XSLTProcessor. some thing like.
XSLTProcessor::registerFunction(); //in php file.
in xsltStylesheet
<xslt:value-of select="php:function('serviceobject::serviceObjectMethod',string($xsltProcessingVariable))"/>
In "normal" php code you can do something like
<?php
class Foo {
public function __construct($prefix) {
$this->prefix = $prefix;
}
public function myMethod($id) {
return sprintf('%s#%s', $this->prefix, $id);
}
}
$fooA = new Foo('A');
$fooB = new Foo('B');
echo call_user_func_array( array($fooA, 'myMethod'), array('id1') ), "\r\n";
echo call_user_func_array( array($fooB, 'myMethod'), array('id1') ), "\r\n";
i.e. instead of giving call_user_func_array just the name of the function you pass an array($obj, 'methodName') to invoke an instance method.
Unfortunatley that doesn't seem to work with php:function(...) and I haven't found another easy/clean way to do it.
But you could register your objects in a lookup table string_id->object and then use something like
select="php:function('invoke', 'obj1', 'myMethod', string(#param1), string(#param2))"
in your stylesheet. function invoke($objectId, $methodName) now has to find the object that has been registered under $objectId and then invoke the method like in the previous example.
func_get_args() lets you retrieve all parameters passed to a function, even those that are not declared in the function signature. Cut off the first two elements (i.e. $objectId and $methodName) and pass the remaining array as arguments to call_user_func_array.
self-contained example:
<?php
class Foo {
public function __construct($prefix) {
$this->prefix = $prefix;
}
public function myMethod($id) {
return sprintf('%s#%s', $this->prefix, $id);
}
}
function invoke($objectId, $methodname)
{
static $lookup = array();
$args = func_get_args();
if ( is_null($methodname) ) {
$lookup[$objectId] = $args[2];
}
else {
$args = array_slice($args, 2);
return call_user_func_array( array($lookup[$objectId], $methodname), $args);
}
}
// second parameter null -> register object
// sorry, it's just a quick hack
// don't do this in production code, no one will remember after two weeks
invoke('obj1', null, new Foo('A'));
invoke('obj2', null, new Foo('B'));
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet(new SimpleXMLElement( style() ));
echo $proc->transformToXML(new SimpleXMLElement( document() ));
function document() {
return <<<EOB
<doc>
<element id="id1" />
<element id="id2" />
</doc>
EOB;
}
function style() {
return <<<EOB
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:output method="text"/>
<xsl:template match="element">
Obj1-<xsl:value-of select="php:function('invoke', 'obj1', 'myMethod', string(#id))"/>
|||
Obj2-<xsl:value-of select="php:function('invoke', 'obj2', 'myMethod', string(#id))"/>
</xsl:template>
</xsl:stylesheet>
EOB;
}
prints
Obj1-A#id1
|||
Obj2-B#id1
Obj1-A#id2
|||
Obj2-B#id2
btw: don't implement your invoke() function like I did in this example. I just failed to come up with a better way to implement a register()/invoke() functionality for this quick example ;-)

Categories