DOM: how to import nodes and give them different namespace prefix - php

I'm familiar with the DOMDocument::importNode method for importing a tree of nodes from some other document element.
However, what I was wondering is if I can automatically change the namespace prefix on a tree of nodes as I import them, that is, specify a new prefix for all nodes of that namespace.
Say the nodes, in their existing document, all have names like "name", "identity", and so on. When importing them into my new document they will be alongside other namespaces, so I'd like them to appear as "nicnames:name", "nicnames:identity" and so on. I'd like to be able to change this prefix programmatically so that in another context I may be able to import them as, for instance, "myprefix:name", "myprefix:identity" depending on the document they're imported into.
Edit: as per the explanation in my answer, I figured out I don't actually need to do this. I was misunderstanding namespaces in XML.

You probably have to write your own import code then. E.g.
function importNS(DOMNode $target, DOMNode $source, $fnImportElement, $fnImportAttribute) {
switch($source->nodeType) {
case XML_ELEMENT_NODE:
// invoke the callback that creates the new DOMElement node
$newNode = $fnImportElement($target->ownerDocument, $source);
if ( !is_null($newNode) && !is_null($source->attributes) ) {
foreach( $source->attributes as $attr) {
importNS($newNode, $attr, $fnImportElement, $fnImportAttribute);
}
}
break;
case XML_ATTRIBUTE_NODE:
// invoke the callback that creates the new DOMAttribute node
$newNode = $fnImportAttribute($target->ownerDocument, $source);
break;
default:
// flat copy
$newNode = $target->ownerDocument->importNode($source);
}
if ( !is_null($newNode) ) {
// import all child nodes
if ( !is_null($source->childNodes) ) {
foreach( $source->childNodes as $c) {
importNS($newNode, $c, $fnImportElement, $fnImportAttribute);
}
}
$target->appendChild($newNode);
}
}
$target = new DOMDocument;
$target->loadxml('<foo xmlns:myprefix="myprefixUri"></foo>');
$source = new DOMDocument;
$source->loadxml('<a>
<b x="123">...</b>
</a>');
$fnImportElement = function(DOMDocument $newOwnerDoc, DOMElement $e) {
return $newOwnerDoc->createElement('myprefix:'.$e->localName);
};
$fnImportAttribute = function(DOMDocument $newOwnerDoc, DOMAttr $a) {
// could use namespace here, too....
return $newOwnerDoc->createAttribute($a->name);
};
importNS($target->documentElement, $source->documentElement, $fnImportElement, $fnImportAttribute);
echo $target->savexml();
prints
<?xml version="1.0"?>
<foo xmlns:myprefix="myprefixUri"><myprefix:a>
<myprefix:b x="123">...</myprefix:b>
</myprefix:a></foo>

I discovered I'd misunderstood XML namespaces. They are actually much better than I thought they were.
I'd thought that each XML namespace used in a single document had to have a different namespace prefix. This is not true.
You can use different namespaces throughout your document even without namespace prefixes, just by including the xmlns attribute where appropriate, and that xmlns attribute only has effect for that element and its descendants, overriding the namespace for that prefix which may have been set higher up the tree.
For example, to have one namespace within another, you don't have to do:
<record xmlns="namespace1">
<person:surname xmlns:person="namespace2">Smith</person:surname>
</record>
You can just do
<record xmlns="namespace1">
<surname xmlns="namespace2">Smith</person>
</record>
Namespace prefixes are a good shortcut in certain situations, but not necessary when just including one document inside another of a different namespace.

Related

Change Namespace of Childern Nodes SimpleXMLElement

<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I want to know how can I do the following? The namespace of children are getting changed by setting the xsi:type attribute.
My Code:
$ret2FormFields = $ret2FileBody->addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addAttribute("xsi:type", "ret1:FormFieldsType", 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addChild('ret1:isReverseReplace', false);
$ret2FormFields->addChild('ret1:payDayDate', '2018-04-10'); /** #todo date will be dynamic */
Expected XML:
<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
My Incorrect XML:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance" ret2:type="ret1:FormFieldsType">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
I am stuck how to change the children namespaces ret1 without changing the parent namespace ret2
You are passing the wrong namespace to addChild here:
addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
This tells SimpleXML that you want the element to have prefix ret2 and namespace URI http://www.w3.org/2001/XMLSchema-instance, so it will generate:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance">
You don't show in your example what the prefix ret2 has been assigned elsewhere in the document, but that's what you need to provide, e.g.:
addChild('ret2:formFields', null, 'http://example.org/mynamespaces/ret2');

Automatically loading the classes

I've built "core" class that loads another classes, and I want to load automatically all the classes in spesific folder named "class", I've started to build something, but I have no idea if it's good.
In the construct function at the core class, I'm getting an array with the class names to load.
The construct function calls to function named _loadClasses, and in the _loadClasses function, I'm loading the classes by using require_once() fucntion.
Then at the top of the page, i'm adding public variable with the name of the class.
For example, "public $example;"
Now, what left is to create the ocject of the class, so that's what I did.
Example of the _loadClasses method:
require_once("class/user.class.php");
self::$user = new User();
Here comes the "automat" part.
I want the function _loadClasses to get an array, for example:
private function _loadClasses( $classesToLoad = array('security', 'is') );
and now, I'm using glob to load the classes from the folder "class", the name syntax of the classes files in the folder "class" is classname.class.php.
$classesArray = array(); // initialize the variable of all the web classes
$classesFiles = glob("class/*.php"); // gets all the web classes from the folder 'class'
foreach($classesFiles as $file) { // loop on the classes in the folder 'class'
$filename = explode('class/', $file);
$filename = $filename[1];
$className = explode('.class.php', $filename);
$className = $className[0];
if($className != 'index.php' || $className != 'database') {
array_push( $classesArray, $className ); // adds the class name into the array 'classesArray'
}
}
foreach( $classesArray as $className ) {
if( in_array($className, $classesToLoad) ) {
require_once("class/$className.class.php");
$classLines = file( "class/$className.class.php" );
$classNameLine = $classLines[1];
$classNameLine = explode(' ', $classNameLine);
$classObjectName = $classNameLine[1];
$classObjectName = explode(" ", $classObjectName);
self::$$classObjectName = new $classObjectName();
}
}
I need something like that, of curse it doesn't work, it's just to show you what I wanna do with an example. Thanks in advance.
For this particular approach I'd suggest something like:
// Your custom class dir
define('CLASS_DIR', 'class/')
// Add your class dir to include path
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
// You can use this trick to make autoloader look for commonly used "My.class.php" type filenames
spl_autoload_extensions('.class.php');
// Use default autoload implementation
spl_autoload_register();
To get started there's no need to implement a parent class autoloading functionality for "core" objects since they should only be aware of their role functionality. Use php standard library.
For this purposes you can use the __autoload() function. It will be called when you create a new object.
__autoload($class)
{
require 'dir/to/your/classes/'. $class. '.php'
}
You have to use one class per file and name the files the same as the class they describe.
"it doesn't work" is not a useful diagnostic. What doesn't work? What's the error message? Which line does it fail at? Looking at the code, the first loop, though a little messy (why do you have a file named index.php in this directory? Why don't you just glob for *.class.php?) should probably work OK. But the second loop is horrible.
To start with, why load all the filenames into an array in one loop, then use a second loop to load some of them? As for reading the class file to determine the object name....words fail me. Simply name it as the filename without the .class.php
$classesFiles = glob("class/*.class.php"); // gets all the web classes from the folder 'class'
foreach($classesFiles as $file) {
$prefix=array_shift(explode('.', basename($file)));
if (in_array($prefix, $classestoload)
&& !isset($this->$prefix)) {
if (!class_exists($prefix)) {
require($file);
}
$this->$prefix=new $prefix();
}
}
You could use the autoloader, but this will get messy if you integrate with other code which also uses the autoloader, but in a different way.

Create an RSS Element with Namespace using SimpleXML in PHP

I'm trying to create a rss feed and one element is
<content:encoded></content:encoded>
But, when i use this code:
$item->addChild('content:encoded',htmlspecialchars($itemdata->description));
I get this as a result:
<encoded> .................. </encoded>
I don't get the content namespace, and how would I be able to?
As you can see in the documentation, you need to provide the namespace URI as 3rd argument of addChild() to create element in namespace correctly :
$item->addChild(
'content:encoded',
htmlspecialchars($itemdata->description),
'namespace-URI-for-content-prefix-here'
);
Quick demo :
$raw = '<root xmlns:content="mynamespace"></root>';
$item = new SimpleXMLElement($raw);
$item->addChild(
'content:encoded',
'foo bar baz',
'mynamespace'
);
echo $item->asXML();
eval.in demo
output :
<?xml version="1.0"?>
<root xmlns:content="mynamespace"><content:encoded>foo bar baz</content:encoded></root>

Add multiple attributes to a child in SimpleXMLElement

I want to add multiple attributes to a child in SimpleXMLElement so it will look like this:
<data>
<photo>
<file size="3309519" size="JPG">P1270081</file>
</photo>
</data>
As it is right now in my code, I can only add one attribute per child as the code below shows.
$xml = new SimpleXMLElement('<data/>');
$photo = $xml->addChild('photo');
$photo->addChild('file', 'P1270081')->addAttribute('size', '3309519');
$photo->addChild('uploaded', '2013-09-01 15:23:10')->addAttribute('by', 'edgren');
If I change the third line to $photo->addChild('file', 'P1270081')->addAttribute('size', '3309519')->addAttribute('type', 'JPG'); I'm getting this error message:
Fatal error: Call to a member function addAttribute() on a non-object in ...
I am new to creating XML files on the fly with SimpleXMLElement so I don't know how I shall fix this issue. What should I do to fix it?
addAttribute returns void. If you want to add more attributes you have to something like this:
$file = $photo->addChild('file', 'P1270081');
$file->addAttribute('size', '3309519');
$file->addAttribute('type', 'JPG');
$photo->addChild(..) // returns the created XML component .. and you can chain a action(one) directly one it.
But addAttribute(..) returns nothing .. so you get an error if you try to chain event after it.
$photo = $xml->addChild('photo');
$photo->addChild('file', 'P1270081')->addAttribute('size', '3309519');
$theNewChild = $photo->addChild('uploaded', '2013-09-01 15:23:10')
$theNewChild ->addAttribute('by', 'edgren');
$theNewChild ->addAttribute('type', 'JPG');

How do we iterate over the object that implements iterator

Iam rendering a menu (using Zend framework) (zend_navigation)
what iam doing is getting the page as label if the page has the value "myPage"
than then iam setting the new URI with the page as expected
$it = new RecursiveIteratorIterator(
$container, RecursiveIteratorIterator::SELF_FIRST);
foreach ($it as &$page) {
$label = $page->label;
if($label = "MyPage"){
$newuri = "mypage.php?stcode=".$stcode."&cde=".$cde;
$page->setUri($newuri);
}
}
In the above statement iam getting an error
"An iterator cannot be used with foreach by reference".
I want to use reference so that based on the label i can point the page to new uri
Now my problem and all the menu items in the menu are getting the same URI .
Does it work without the & ? Objects are passed by reference by default in PHP, so calling setUri should (in theory) modify the original object. Also note that your if statement is doing an assignment ($label = "MyPage") rather than a comparison ($label == "MyPage").
Assuming $container is your Zend Navigation object, the component has methods to make this easier anyway, so you should be able to simplify your code to:
$page = $container->findByLabel('MyPage');
$page->setUri("mypage.php?stcode=".$stcode."&cde=".$cde);
See http://framework.zend.com/manual/en/zend.navigation.containers.html#zend.navigation.containers.finding for some more examples.

Categories