I'm using DOMDocument for DOM manipulation, but I'm encountering a problem with the usage of the getElementById() method. I'm looking for a div with the id 'content', but this returns NULL:
$element = $document->getElementById('content');
if($element instanceof DOMElement)
{
// do something
}
However, the following piece of code does return the element I'm looking for:
$elements = $document->getElementsByTagName('div');
foreach($elements as $element)
{
if($element->getAttribute('id') == 'content')
{
// do something
break;
}
}
I may be overlooking something simple, but those 2 pieces of code look identical to me. (Or at least should work identically.) The second piece of code is operational now, but it does feel wrong to keep it that way. I'd like to know why the first piece of code fails to return the correct element, when the second method succeeds.
I found this:
Please note that if your HTML does not contain a doctype declaration,
then getElementById will always return null.
By looking for tagname and then the ID of that tag name, it will return the id.
Source
Related
I'm basically trying to write a json_encode filter, by which I'm hoping to get a raw json object, but what I'm getting instead, is an escaped string of the json object.
Expected result:
{"foo":"bar"}
Actual result:
"{\"foo\":\"bar\"}"
Right now the only way I can get it the way I want, is by using the noescape filter, but this makes it unnecessarily more ugly
{$object|json_encode|noescape}
My json_encode filter
public static function json_encode(FilterInfo $info, mixed $value): string {
$info->contentType = ContentType::JavaScript;
return json_encode($value);
}
You can try something like {do printf('%s',json_encode($object)) } printf isn't restricted unlike echo and print within the context of the php and do tags. Even if it was restricted, you can define your own function and have it echo from there.
But it looks like noescape is a very special filter which doesn't actually appear in the core filter list, it is the compiler/nodes and essential/nodes sections which check in multiple places for the presence of the noescape filter within the filter list then make decisions based on it.
➜ latte grep -r 'noescape' .
./latte/src/Latte/Essential/Nodes/BlockNode.php: if ($node->modifier->hasFilter('noescape') && count($node->modifier->filters) === 1) {
./latte/src/Latte/Essential/Nodes/BlockNode.php: throw new CompileException('Filter |noescape is not expected here.', $tag->position);
./latte/src/Latte/Essential/Nodes/IncludeFileNode.php: $noEscape = $this->modifier->hasFilter('noescape');
./latte/src/Latte/Essential/Nodes/IncludeBlockNode.php: $noEscape = $this->modifier->hasFilter('noescape');
./latte/src/Latte/Compiler/Nodes/Php/FilterNode.php: NoEscape = 'noescape';
./latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php: } elseif ($name === 'noescape') {
./latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php: if ($name === 'noescape') {
./latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php: $noescape = true;
./latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php: if ($this->escape && empty($noescape)) {
➜ latte
I'm afraid trying to find a simple way to do this without having to use noescape would push latte to its limits.
It might be possible to dynamically slap on the noescape filter if your extension finds the json_encode filter being used. Otherwise it looks like the way to do this is like so:
{$object|escapeJs|noescape}
You could also try a different syntax like:
{do json_encode_output($object) }
and define a global function like so:
function json_encode_output($object)
{
echo json_encode($object);
}
It might also be possible to define your own tag like:
{json $output}
But considering how latte is designed, I'd probably just settle with the more verbose:
{$object|escapeJs|noescape}
Or I might actually reach for:
{json_encode($object)|noescape}
Edit:
I think I actually just figured it out. Simply echo from your function.
public static function json_encode(FilterInfo $info, mixed $value): string {
echo json_encode($value);
return '';
}
Found the answer in their forum.
This isn't very obvious from the documentation, but seems like since latte understands the context automatically, you don't really need any filter for json_encode, just need to write it as: var jsonObject = {$object} and it will automatically encode it to json
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");
Here is my original which works (I'm using simple-html-dom)
foreach(($filtered->find('a')) as $element) {
// do stuff
}
Problem is that now I may need to use 2 conditions... something like:
foreach(($filtered->find('a')) as $element || ($filtered->find('img')) as $element) {
// do stuff
}
But it doesn't work. Neither does:
foreach(($filtered->find('a') || $filtered->find('img')) as $element) {
// do stuff
}
How does one go about implementing both conditions so it runs the "do stuff" whenever EITHER an 'a' element OR an 'img' element is found.
Thank you very much for any help you can give.
foreach(array_merge($filtered->find('a'),$filtered->find('img')) as $element) {
// do stuff
}
foreach takes no conditions at all.
foreach takes ALL values from ONE array.
So if you want to take ALL values from TWO arrays, you have to merge TWO arrays into ONE array, and foreach over that.
Try this instead:
foreach( $filtered->find('a, img') as $element ) { ... }
Using the comma, this will gets you all a and img nodes...
For more informations, please refer to PHP Simple HTML DOM Parser Manual
In PHP, || is a boolean operator and just returns true or false. If you want the first non-false value in a series of expressions, use ?::
foreach(($filtered->find('a') ?: $filtered->find('img')) as $element) {
// do stuff
}
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');
}