Element tree: .tail() always returns NONE - elementtree

parser = etree.HTMLParser()
tree = etree.parse(StringIO(input), parser)
for target in tree.findall("//tr[#class='error']"):
print target.tail
I want to execute the code above on this an fetch everything after
trclass="error"id="Testcase_5">
<tr class="error" id="Testcase_5"><td>Hello</td><td>test</td><td>test</td> <td>test</td><td>Failed</td><td></td><td></td></tr>
However all I get is NONE

tail, by definition from lxml, is:
the text that directly follows the element, up to the next element in the XML tree
Since your tr class does not have any text beyond the <tr> .... </tr> and we just have one element, it returns None.
Let's say the input is:
'<tr class="error" id="Testcase_5"><td>Hello</td><td>test</td><td>test</td> <td>test</td><td>Failed</td><td></td><td></td></tr>i am the tail'
then the output would be i am the tail
Coming back to your question, if you want to extract all the text within the <tr> node you could do something like this:
parser = etree.HTMLParser()
tree = etree.parse(StringIO(input), parser)
for target in tree.findall(".//tr[#class='error']"):
#print target.tail
print target.xpath("//text()")
This will print:
['Hello', 'test', 'test', ' ', 'test', 'Failed']
Check lxml docs

Related

DOMXPath multiple contain selectors not working

I have the following XPath query that a kind user on SO helped me with:
$xpath->query(".//*[not(self::textarea or self::select or self::input) and contains(., '{{{')]/text()") as $node)
Its purpose is to replace certain placeholders with a value, and correctly catches occurences such as the below that should not be replaced:
<textarea id="testtextarea" name="testtextarea">{{{variable:test}}}</textarea>
And replaces correctly occurrences like this:
<div>{{{variable:test}}}</div>
Now I want to exclude elements that are of type <div> that contain the class name note-editable in that query, e.g., <div class="note-editable mayhaveanotherclasstoo">, in addition to textareas, selects or inputs.
I have tried:
$xpath->query(".//*[not(self::textarea or self::select or self::input) and not(contains(#class, 'note-editable')) and contains(., '{{{')]/text()") as $node)
and:
$xpath->query(".//*[not(self::textarea or self::select or self::input or contains(#class, 'note-editable')) and contains(., '{{{')]/text()") as $node)
I have followed the advice on some questions similar to this: PHP xpath contains class and does not contain class, and I do not get PHP errors, but the note-editable <div> tags are still having their placeholders replaced.
Any idea what's wrong with my attempted queries?
EDIT
Minimum reproducible DOM sample:
<div class="note-editing-area">
<textarea class="note-codable"></textarea>
<div class="note-editable panel-body" contenteditable="true" style="height: 350px;">{{{variable:system_url}}</div>
</div>
Code that does the replacement:
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
$xpath = new DOMXpath($dom);
foreach ($xpath->query(".//*[not(self::textarea or self::select or self::input or self::div[contains(#class,'note-editable')]) and contains(., '{{{')]/text()") as $node) {
$node->nodeValue = preg_replace_callback('~{{{([^:]+):([^}]+)}}}~', function($m) use ($placeholders) {
return $placeholders[$m[1]][$m[2]] ?? '';
},
$node->nodeValue);
}
$html = $dom->saveHTML();
echo html_entity_decode($html);
Use this below xpath.
.//*[not(self::textarea or self::select or self::input or self::div[contains(#class,'note-editable')]) and contains(., '{{{')]

Find and extract content of division of certain class using DomXPath

I am trying to extract and save into PHP string (or array) the content of a certain section of a remote page. That particular section looks like:
<section class="intro">
<div class="container">
<h1>Student Club</h1>
<h2>Subtitle</h2>
<p>Lore ipsum paragraph.</p>
</div>
</section>
And since I can't narrow down using class container because there are several other sections of class "container" on the same page and because there is the only section of class "intro", I use the following code to find the right division:
$doc = new DOMDocument;
$doc->preserveWhiteSpace = FALSE;
#$doc->loadHTMLFile("https://www.remotesite.tld/remotepage.html");
$finder = new DomXPath($doc);
$intro = $finder->query("//*[contains(#class, 'intro')]");
And at this point, I'm hitting a problem - can't extract the content of $intro as PHP string.
Trying further the following code
foreach ($intro as $item) {
$string = $item->nodeValue;
echo $string;
}
gives only the text value, all the tags are stripped and I really need all those divs, h1 and h2 and p tags preserved for further manipulation needs.
Trying:
foreach ($intro->attributes as $attr) {
$name = $attr->nodeName;
$value = $attr->nodeValue;
echo $name;
echo $value;
}
is giving the error:
Notice: Undefined property: DOMNodeList::$attributes in
So how could I extract the full HTML code of the found DOM elements?
I knew I was so close... I just needed to do:
foreach ($intro as $item) {
$h1= $item->getElementsByTagName('h1');
$h2= $item->getElementsByTagName('h2');
$p= $item->getElementsByTagName('p');
}

domxpath- how to get content for parent tag only instead of child tags

I, am using domxpath query for fetching content for parent tag only that is (td[class='s']) instead of including div content which is nested inside that td as given below in my code.
<?php
$second_trim='<td class="s" style="line-height:18px;">THIS TEXT IS REQUIRED and <div id="a" style="display:none;background-color:black;border:1px solid #ddd;padding:5px;color:black;">THIS TEXT IS NOT REQUIRED </div></td>';
$dom = new DOMDocument();
$doc->validateOnParse = true;
#$dom->loadHTML($second_trim);
libxml_clear_errors();
$xpath = new DOMXpath($dom);
$b = $xpath->query('//td[#class="s"]');
echo "<p style='font-size:14px;color:red;'><b style='font-size:18px;color:gray;'>cONTENT :- </b>".$b->item(0)->nodeValue."</p>";
?>
so how to remove content of that div tag and fetching only td's content any ideas !!
EDIT
If you are only interested in the direct text content modify your xpath query:
$b = $xpath->query('//td[#class="s"]/text()');
echo '<p style="font-size:14px;color:red;">'
.'<b style="font-size:18px;color:gray;">cONTENT :- </b>'
.$b->item(0)->nodeValue
.'</p>';
Right now the result is very specific to the example:
If more than one direct text node exists, its not gone be displayed. To do that foreach through the DOMNodeList $b and echo every selected node value.

how to get value from java script function to perl

I want to get the value of check box selected and pass it to a query for deleting the value from database.
java script code
function deleteRows(){
isTable = document.getElementById('dataTbl');
nBoxes = document.getElementsByName('delBox');
for (i=nBoxes.length-1; i>=0; i--)
{
if (nBoxes[i].checked === true)
{
var a =nBoxes[i].value;
alert("Do you want to delete this row?"+a);
isTable.deleteRow(i+1);
}
}
}
i need the var a value in perl so that i can pass it to the delete query and delete the selected row.
html code
<Table id='dataTbl' border='1' >
<tr>
<td><input type=checkbox name='delBox' value=#data></td>
<td bgcolor=#0099FF>$pid</td>
<td bgcolor=#99CCFF>$project_name</td>
<td bgcolor=#3399FF> $variant_no</td>
<td bgcolor=#99CCFF> $variant_name</td>
<td bgcolor=#3399FF>$vehicle_class</td>
<td bgcolor=#99CCFF> $vc_no</td>
</table>
<input type=button value="Delete Rows" onclick="deleteRows()" id="delbtn">
perl query
my $sth = $dbh->prepare("delete form table name col1,col2,col3 where id='$a'");
$sth->execute() or die "$!";
You have to do POST request (or DELETE to be precise) towards server where your perl script runs.
e.g.
After you get a variable set (let's say you are using jquery):
$.ajax({
type: "POST",
url: url, // where your script lives
data: {'a' : a},
success: function(data) {
console.log(data);
}
dataType: 'json'
});
in your script you will then get 'a' variable from post request.
Sample scenario:
JS working on Your Client PC. Users selected some id.
JS sends selected id to server side Perl CGI script.
Perl parses GET request get id.
Perl checks if id is number not some string to hack your server.
Perl executes delete in MySQL.
Resourses:
JS send GET request with Param:
HTTP GET request in JavaScript?
Perl Read GET Parameter
How can I read the URL parameter in a Perl CGI program?
Perl MySQL Tutorial:
http://perl.about.com/od/perltutorials/a/perlmysql_3.htm

xpath query to parse html tags

I need to parse the following sample html using xpath query..
<td id="msgcontents">
<div class="user-data">Just seeing if I can post a link... please ignore post
http://finance.yahoo.com
</div>
</td>
<td id="msgcontents">
<div class="user-data">some text2...
http://abc.com
</div>
</td>
<td id="msgcontents">
<div class="user-data">some text3...
</div>
</td>
The above html may repeat n no of times in a page.
Also sometimes the ..... portion may be absent as shown in the above html blocks.
What I need is the xpath syntax so that I can get the parsed strings as
array1[0]= "Just seeing if I can post a link... please ignore post ttp://finance.yahoo.com"
array[1]="some text2 htp://abc.com"
array[2]="sometext3"
Maybe something like the following:
$remote = file_get_contents('http://www.sitename.com');
$dom = new DOMDocument();
//Error suppression unfortunately, as an invalid xhtml document throws up warnings.
$file = #$dom->loadHTML($remote);
$xpath = new DOMXpath($dom);
//Get all data with the user-data class.
$userdata = $xpath->query('//*[contains(#class, \'user-data\')]');
//get links
$links = $xpath->query('//a/#href');
So to access one of these variables, you need to use nodeValue:
$ret = array();
foreach($userdata as $data) {
$ret[] = $data->nodeValue;
}
Edit: I thought I'd mention that this will get all the links on a given page, I assume this is what you wanted?
Use:
concat(/td/div/text[1], ' ', /td/div/a)
You can use instead of the ' ' above, whatever delimiter you'd like to appear between the two strings.