Pass container object in XSLTProcessor - php

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 ;-)

Related

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

DOMDocument::loadXML() for parts of XML

Simple XML templates like these ones :
structure.xml :
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<document>
<book>first book</book>
<book>second book</book>
((other_books))
</document>
book_element.xml :
<book>((name))</book>
And this test :
<?php
Header("Content-type: text/xml; charset=UTF-8");
class XMLTemplate extends DOMDocument
{
private $_content_storage;
private $_filepath;
private $_tags;
public function XMLTemplate( $sFilePath )
{
if( !file_exists( $sFilePath ) ) throw new Exception("file not found");
$this->_filepath = $sFilePath;
$this->_tags = [];
$this->_content_storage = file_get_contents( $this->_filepath );
}
public function Get()
{
$this->merge();
$this->loadXML( $this->_content_storage );
return $this->saveXML();
}
public function SetTag( $sTagName, $sReplacement )
{
$this->_tags[ $sTagName ] = $sReplacement;
}
private function merge()
{
foreach( $this->_tags as $k=>$v)
{
$this->_content_storage = preg_replace(
"/\({2}". $k ."\){2}/i",
$v,
$this->_content_storage
);
}
$this->_content_storage = preg_replace(
"/\({2}[a-z0-9_\-]+\){2}/i",
"",
$this->_content_storage
);
}
}
$aBooks = [
"troisième livre",
"quatrième livre"
];
$Books = "";
foreach( $aBooks as $bookName )
{
$XMLBook = new XMLTemplate("book_element.xml");
$XMLBook->SetTag( "name", $bookName );
$Books .= $XMLBook->Get();
}
$XMLTemplate = new XMLTemplate("test.xml");
$XMLTemplate->SetTag("other_books", $Books);
echo $XMLTemplate->Get();
?>
Give me error :
Warning: DOMDocument::loadXML(): XML declaration allowed only at the start of the document in Entity, line: 5
Because loadXML() method add automatically the declaration to the content, but i need to inject parts of xml in the final template like above. How to disable this annoying auto adding and let me use my declaration ? Or another idea to conturn the problem ?
If you dislike the error and you want to save the document you'd like to merge without the XML declaration, just save the document element instead of the whole document.
See both variants in the following example-code (online-demo):
$doc = new DOMDocument();
$doc->loadXML('<root><child/></root>');
echo "The whole doc:\n\n";
echo $doc->saveXML();
echo "\n\nThe root element only:\n\n";
echo $doc->saveXML($doc->documentElement);
The output is as followed:
The whole doc:
<?xml version="1.0"?>
<root><child/></root>
The root element only:
<root><child/></root>
This probably should be already helpful for you. Additionally there is a constant for libxml which is said can be used to control whether or not the XML declaration is output. But I never used it:
LIBXML_NOXMLDECL (integer)
Drop the XML declaration when saving a document
Note: Only available in Libxml >= 2.6.21
From: http://php.net/libxml.constants
See the link for additional options, you might want to use the one or the other in the future.

Convert json to xml in php without an online converter

I've been trying hard on this for a while now, but can't find a solution on how to convert json to xml in php by file_get_contents. I don't want to use an online converter but want to have it in my website root directory.
Thanks in advance!
You can convert JSON to array/object by using json_decode(), once you have it inside array you can use any XML Manipulation method to build XML out of it.
For example you may build XML structure like this:
<array>
<data name="atrribute1">string 1</data>
<array name="array_atribute">
<data name="attribute2">string 2</data>
<data name="attribute2">string 2</data>
</array>
</array>
By DOMDocument, and method DOMDocument::createElement()
class XMLSerialize {
protected $dom = null;
public function serialize( $array)
{
$this->dom = new DOMDocument('1.0', 'utf-8');
$element = $this->dom->createElement('array');
$this->dom->appendChild($element);
$this->addData( $element, $array);
}
protected function addData( DOMElement &$element, $array)
{
foreach($array as $k => $v){
$e = null;
if( is_array( $v)){
// Add recursive data
$e = $this->dom->createElement( 'array', $v);
$e->setAttribute( 'name', $k);
$this->addData( $e, $v);
} else {
// Add linear data
$e = $this->dom->createElement( 'data', $v);
$e->setAttribute( 'name', $k);
}
$element->appendChild($e);
}
}
public function get_xml()
{
return $this->dom->saveXML();
}
}

Access first element of an array without index

i have an xml file:
<?xml version="1.0" encoding="utf-8" ?>
<transaction dsxml_version="1.08">
<action>action1</action>
<action>action2</action>
</transaction>
If i use simplexml i can access the first "action" with die following code
$xml = simplexml_load_string($xml_content);
echo $xml->action; // Write "action1"
echo $xml->action[0]; // Write "action1"
echo $xml->action[1]; // Write "action2"
Now i create an array and a try to access it on the same way. But it doesent work.
We have a huge php skipt which use simple xml which contains a logical error. If i can emulate simple xml i can fix this error on one position
try this:
current($xml->action);
echo array_shift(array_slice($xml->action, 0, 1));
or if you're not worried about damaging the original array, $xml->action you can use the following
echo array_shift($xml->action);
Using array_shift will guarantee you get the first element if it's numbered or associative.
You can create a fake or mock object that simulates the behaviour you are looking for:
$action = new SimpleXMLArrayMock($action_array);
$xml->action = $action;
echo "\nFake:\n";
echo $xml->action, "\n"; // Write "action1"
echo $xml->action[0], "\n"; // Write "action1"
echo $xml->action[1], "\n"; // Write "action2"
/**
* Mock SimpleXML array-like behavior
*/
class SimpleXMLArrayMock extends ArrayObject
{
private $first;
public function __construct(array $array)
{
$this->first = (string) $array[0];
parent::__construct($array);
}
public function __toString()
{
return $this->first;
}
}
Demo
use xpath so you can find the element
This would work:
foreach($myarray as $key => $val)
{
print $val;
//if you don't need the rest of the elements:
break;
}
Added:
You can even make it a function:
function get_first_element($myarray)
{
foreach($myarray as $key => $val)
{
return $val;
}
}

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