I'm iterating through a set of SimpleXML objects, and I can't figure out how to access each object's parent node. Here's what I want:
$divs = simplexml->xpath("//div");
foreach ($divs as $div)
{
$parent_div = $div->get_parent_node(); // Sadly, there's no such function.
}
Seems like there must be a fairly easy way to do this.
You could run a simple XPath query to get it:
$parent_div = $div->xpath("parent::*");
And as this is Simplexml and it only has element and attribute nodes and a parent node can only be an element and never an attribute, the abbreviated syntax can be used:
$parent_div = $div->xpath("..");
(via: Common Xpath Cheats - SimpleXML Type Cheatsheet (Feb 2013; by hakre) )
$div->get_parent_node(); // Sadly, there's no such function.
Note that you can extend SimpleXML to make it so. For example:
class my_xml extends SimpleXMLElement
{
public function get_parent_node()
{
return current($this->xpath('parent::*'));
}
}
And now all you have to do is modify the code you use to create your SimpleXMLElement in the first place:
$foo = new SimpleXMLElement('<foo/>');
// becomes
$foo = new my_xml('<foo/>');
$foo = simplexml_load_string('<foo/>');
// becomes
$foo = simplexml_load_string('<foo/>', 'my_xml');
$foo = simplexml_load_file('foo.xml');
// becomes
$foo = simplexml_load_file('foo.xml', 'my_xml');
The best part is that SimpleXML will automatically and transparently return my_xml objects for this document, so you don't have to change anything else, which makes your get_parent_node() method chainable:
// returns $grandchild's parent's parent
$grandchild->get_parent_node()->get_parent_node();
If memory serves, an xpath() call returns one or more SimpleXMLElements. If that's the case, then you may be able to use something like:
$div->xpath( '..' );
# or
$div->xpath( 'parent::*' );
Related
Im using https://github.com/nikic/PHP-Parser. What is a good strategy when wanting to INSERT a node in the AST? With the traverser I can UPDATE and DELETE nodes easily using a NodeTraverser class. But how can I "INSERT before" or "INSERT after" a node?
Example: When traversing an AST namespace I want to INSERT a Use statement just before the first non-use statement.
I started working with beforeTraverse and afterTraverse to find indexes of arrays but it seems overly complicated. Any ideas?
It is possible to replace one node with multiple nodes. This only works inside leaveNode and only if the parent structure is an array.
public function leaveNode(Node $node) {
if ($node instanceof Node\Stmt\Return_ && $node->expr !== null) {
// Convert "return foo();" into "$retval = foo(); return $retval;"
$var = new Node\Expr\Variable('retval');
return [
new Node\Stmt\Expression(new Node\Expr\Assign($var, $node->expr)),
new Node\Stmt\Return_($var),
];
}
}
See last section in Modyfing the AST
I have a XML and I want to retrieve the value inside <p> tag in PHP. How do I do that? I am trying getElementbyId, getAttribute but I am unable to get the data.
$xmlDoc = new DOMDocument();$path_to_dir = "/var/www/html/Dalai/Annotation/0001.xml";
$xmlDoc->load($path_to_dir);$elements1 = $xmlDoc->getElementsByTagName('text');
foreach($elements1 as $node)
{
foreach($node->childNodes as $child)
{
if($child->nodeName=='p')
{
$path=$child->getAttribute();
echo $path;
}
}
}
I think in this line $path=$child->getAttribute(); getAttribute should take a string as a parameter, which then returns a string.
Assuming that, by the value inside <p>, you mean the text content like foo in <p>foo</p>, then you can access it via textContent or nodeValue * :
$path = $child->textContent;
//or $path = $child->nodeValue;
echo $path;
*) either one will do since it will be called on a DOMElement in this case. For more subtle differences between textContent and nodeValue, see : PHP DOM textContent vs nodeValue?
I want to know how can I concatenate [und][0][value].
I don't want to write every time [und][0][value]. So I have do like this:
<?php
$und_value = $load->field_testimonial_location['und'][0]['value'];
$query = db_select('node','n');
$query->fields('n',array('nid'));
$query->condition('n.type','testimonial','=');
$result = $testimonial_query->execute();
while($fetch = $result->fetchObject()){
$load = node_load($fetch->nid);
// $location = $load->field_testimonial_location['und'][0]['value'];
$location = $load->field_testimonial_location.$und_value;
echo $location;
}
But its not working. It outputs Array Array So have any idia for this problem? How can I do? Full code here
Why don't you make some function which will take node field as parameter and return it's value
function field_value($field){
return $field['und'][0]['value'];
}
Something like that (not tested).
But if you don't want to use function try using curly braces like:
$location = $load->{field_testimonial_location.$und_value};
That should work...
Extending answer posted by MilanG, to make function more generic
function field_value($field, $index = 0 ){
return $field['und'][$index]['value'];
}
There are time when you have multi value fields, in that case you have to pass index of the value also. For example
$field['und'][3]['value'];
Please do not use such abbreviations, they will not suit all cases and eventually break your code.
Instead, there is already a tool do create custom code with easier syntax: Entity Metadata Wrapper.
Basically, instead of
$node = node_load($nid);
$field_value = $node->field_name['und'][0]['value'];
you can then do something like
$node = node_load($nid);
$node_wrapper = entity_metadata_wrapper('node', $node);
$field_value = $node_wrapper->field_name->value();
With the node wrapper you can also set values of a node, it's way easier and even works in multilingual environments, no need to get the language first ($node->language) or use constants (LANGUAGE_NONE).
In my custom module, I often use $node for the node object and $enode for the wrapper object. It's equally short and still know which object I am working on.
I have a complex xml with nested namespaces for which I'm trying to do the following:
1) Open XML File
2) Validate against a XSD Schema
3) Parse it
4) Change nodes (1 at the time, setting them either to null or other variables)
5) Saves changed xml into a new file
5) Ri-validate it against same schema as 2) and make sure an error pops up.
Now, points 1-2-3 and 5-6 are not an issue. The Change + saving into a new xml is.
XML Snippet:
<Movie creationDateTime="2014-05-14T13:42:52Z" endDateTime="2015-05-14T00:00:00Z" providerVersionNum="5" startDateTime="2014-05-14T00:00:00Z" uriId="disney.chlsd.com/MOOT0000000000020902">
<core:Ext>
<ext:MovieExtensions analogueOff="true" mediaId="CGOT0000000000020902">
<ext:assetPart partNum="1">
<ext:SourceUrl>DSNY0000000000020902.mxf</ext:SourceUrl>
<ext:ContentFileSize>46166173874</ext:ContentFileSize>
<ext:ContentCheckSum>4da3e4cafd4f3262d136c519311a7b53</ext:ContentCheckSum>
<ext:SOE>PT09H59M30S00F</ext:SOE>
<ext:SOM>PT10H00M00S00F</ext:SOM>
<ext:EOM>PT10H46M02S11F</ext:EOM>
</ext:assetPart>
<ext:playlistSupportOnly>false</ext:playlistSupportOnly>
</ext:MovieExtensions>
</core:Ext>
<content:AudioType>Stereo</content:AudioType>
<content:FrameRate>25</content:FrameRate>
<content:Codec>H.264</content:Codec>
<content:AVContainer>MXF</content:AVContainer>
<content:Duration>PT00H46M02S</content:Duration>
<content:IsHDContent>false</content:IsHDContent>
</Movie>
I do the parsing on attributes using ($mypix is the XmlSimpleObject where I load the Xml):
$xmlfile = "prova.xml";
$mypix = simplexml_load_file($xmlfile);
[...]
foreach ($mypix->children() as $parent => $child)
{
echo "<br/>Main Node: ".(String)$parent."<br/>";
foreach ($mypix->children()->attributes() as $a => $b)
{
echo "Main attribute: ".(String)$a. " with value: ".(String)$b."<br/>";
if ($a == "endDateTime")
{
echo "Entering node: ".$a." and eliminating: ".$b." <br/>";
$b=NULL;
echo "<br/><pre>";
echo $mypix->asXML("t.xml");
echo "<br/></pre>";
}
}
}
The parsing gives me:
Main Node: Movie
Main attribute: creationDateTime with value: 2014-05-16T14:40:41Z
Main attribute: endDateTime with value: 2015-05-16T00:00:00Z
Entering node: endDateTime and eliminating: 2015-05-16T00:00:00Z
Problem is, when I open t.xml, endDateTime is still a valid tag (definitely not empty).
=========================================================================
Things I've tried:
alternative approach using Xpath:
$namespaces = $mypix->getNameSpaces(true);
$mypix->registerXPathNamespace('ext', 'URN:NNDS:CMS:ADI3:01');
$mypix->registerXPathNamespace('title', 'http://www.cablelabs.com/namespaces/metadata/xsd/title/1');
$mypix->registerXPathNamespace('core', 'http://www.cablelabs.com/namespaces/metadata/xsd/core/1');
echo "<br/><br/>";
// Getting Episode Name
$xtring = ($mypix->xpath('//core:Ext/ext:LocalizableTitleExt/ext:EpisodeName'));
echo "<br/><b>EpisodeName: </b>".$xtring[0]."<br/>";
$xtring[0] = NULL;
echo $mypix->asXML("t.xml"); // Nothing again
Here the xpath query returns a valid value, but changing & writing to a new file fails
2nd try: save to the same file ('prova.xml') instead of 't.xml' (in case I screwed up with SimpleXMlObjects)...nothing...
Any help please?
Setting a variable to null does not remove, destroy, or edit the object that variable used to point to.
You may have seen examples where this is a valid way of "cleaning up" something like a database connection object, because when you remove all references to an object, its destructor will be called. However, this is not the case here, because the object pointed at by $b is still accessible, e.g. from another call to $mypix->children()->attributes().
The other thing you will have seen in examples is assigning a new value to a child element or attribute using syntax like $element->someChild = 'new value'; or $element['someAttribute'] = 'new value';. However, this works because SimpleXML overloads the property access (->) and array element access ([...]), in the same way as implementing __set() and ArrayAccess::offsetSet(), and your code uses neither of those.
There is a way of using the array-access overload to delete or blank an element which you have a variable pointing at directly, which is that the offset [0] points back at the current element. Thus, you can write unset($b[0]); to delete an element or attribute completely; you can also write $b[0] = ''; to blank an element, but with an attribute as here, that leads to a fatal error (which I suspect is a bug).
Note that when you use XPath, you are not actually reaching this self-reference, or an overloaded operator because SimpleXMLElement::xpath returns a plain array, so $xtring[0] is just a normal PHP variable. Since it's an element in that example, you could delete it using the self-reference, by writing unset($xtring[0][0]); or blank it with $xtring[0][0] = '';
However, all that being said, your code can actually be massively simplified in order to avoid any of this being necessary. Let's take it apart line by line:
foreach ($mypix->children() as $parent => $child)
The variable $mypix here is for a larger document than you show in your sample, the sample apparently being just one entry in this loop. Note that $parent => $child here would be more appropriately named $childName => $child.
It's also quite likely that you're only interested in children with a particular name, so the most common form of loop is foreach ($mypix->Movie as $child)
foreach ($mypix->children()->attributes() as $a => $b)
Here you ignore the progress around the outer loop completely, and go back to the whole document. SimpleXML will interpret $mypix->children()->... as $mypix->children()[0]->..., that is only ever look at the first child. You actually want foreach ($child->attributes() ....
if ($a == "endDateTime")
Since you are looking for an attribute with a particular name, you don't actually need to loop over attributes() at all, you can just access it directly as $child['endDateTime']. Note that since we're now using the overloaded [...] operator, we can make use of it to write back to or delete the attribute.
echo $mypix->asXML("t.xml");
SimpleXMLElement::asXML either returns the document as a string or saves to a file, not both. Since in the latter case it returns a boolean, echoing that result isn't likely to be very useful.
You are also calling this function every time around the inner loop, thus saving the same file several times. You only need to do it once, when you've finished making all your modifications.
So, here is how I would write that code:
foreach ( $mypix->Movie as $child )
{
$child['endDateTime'] = null;
// or to remove the attribute completely: unset($child['endDateTime']);
}
$mypix->asXML('t.xml');
Or, for the second example but without XPath (long-winded, but useful if you are changing several things at once, so don't want to "jump" to the deepest descendants in the document). Note the use of ->children($ns_uri) to switch to a different namespace.
// Constants for handier but implementation-independent reference to namespaces
define('XMLNS_EXT', 'URN:NNDS:CMS:ADI3:01');
define('XMLNS_TITLE', 'http://www.cablelabs.com/namespaces/metadata/xsd/title/1');
define('XMLNS_CORE', 'http://www.cablelabs.com/namespaces/metadata/xsd/core/1');
foreach ( $mypix->children() as $child )
{
foreach ( $child->children(XMLNS_CORE)->Ext as $ext )
{
foreach ( $ext->children(XMLNS_EXT)->LocalizableTitleExt as $title )
{
// Delete a child node; note not ->children() as "ext" namespace already selected
unset($title->EpisodeName);
}
}
}
$mypix->asXML("t.xml");
I wana to assign a variable such as 'heloo' to an address such as ->system_settings->settings->hostname and i write a function for.now when i write that address manually this function work correctly and assign 'hello' to that address,but,when i wana to gave address dynamically it doesn't work.
my function :
<?php
write_xml("->system_settings->settings->hostname",'Helloooooooo');
function write_xml($tag_address,$value) {
$xml = simplexml_load_file("test.xml")
or die("Error: Cannot create object");
// $xml->system_settings->settings->hostname = $value;
$xml->$tag_address=$value;
$xml->asXML("test.xml");
}
?>
when i run the command line it works but in dynamical mode it doesn't work and identifies $tag_address in this line $xml->$tag_address=$value; as a string,not as an address.
what should i do?
TNX
The solution is not that easy.
The easiest, but least secure, is to use eval() function so you can write something like this:
eval('$xml'.$tag_address.' = $value;'); // note ' as quotation marks
The most correct way can be that you split your text and create a chained object manually. You can't just refer to all chained elements in one string, but you can do this step-by-step with one element.
For example something like
$splitted_text = explode('->', $tag_address);
$node = $xml;
foreach($splitted_text as $object)
$node = &$node -> {$object};
// at the moment $node = $xml->system_settings->settings->hostname
$node = $value;
$xml->asXML("test.xml");
should work. I haven't tested it, but the idea is that in each iteration you prepare $node variable going deeper into the $xml variable. At the end you modify only the $node, but as objects are only references, so $xml should change accordingly.
This example assumes that there is no -> in the beginning of $tag_address. If it is, there would be a problem with explode() function because this would create empty string as the first element of the $splitted_text array.
So you might need to remove this empty element or apply calling as
write_xml("system_settings->settings->hostname",'Helloooooooo');
without the first ->.
Use XPath to select the node, then update the value. For that, you need the proper systax for your tag address.
write_xml("system_settings/settings/hostname", 'Helloooooooo');
function write_xml($tag_address, $value)
{
$xml = simplexml_load_file('test.xml') or die("Error: Cannot create object");
$nodes = $xml->xpath($tag_address);
$nodes[0][0] = $value;
$xml->asXML('test.xml');
}