So I have a reporting tool that spits out job scheduling statistics in an HTML file, and I'm looking to consume this data using Perl. I don't know how to step through a HTML table though.
I know how to do this with jQuery using
$.find('<tr>').each(function(){
variable = $(this).find('<td>').text
});
But I don't know how to do this same logic with Perl. What should I do? Below is a sample of the HTML output. Each table row includes the three same stats: object name, status, and return code.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="UC4 Reporting Tool V8.00A">
<Title></Title>
<style type="text/css">
th,td {
font-family: arial;
font-size: 0.8em;
}
th {
background: rgb(77,148,255);
color: white;
}
td {
border: 1px solid rgb(208,213,217);
}
table {
border: 1px solid grey;
background: white;
}
body {
background: rgb(208,213,217);
}
</style>
</HEAD>
<BODY>
<table>
<tr>
<th>Object name</th>
<th>Status</th>
<th>Return code</th>
</tr>
<tr>
<td>JOBS.UNIX.S_SITEVIEW.WF_M_SITEVIEW_CHK_FACILITIES_REGISTRY</td>
<td>ENDED_OK - ended normally</td>
<td>0</td>
</tr>
<tr>
<td>JOBS.UNIX.ADMIN.INFA_CHK_REP_SERVICE</td>
<td>ENDED_OK - ended normally</td>
<td>0</td>
</tr>
<tr>
<td>JOBS.UNIX.S_SITEVIEW.WF_M_SITEVIEW_CHK_FACILITIES_REGISTRY</td>
<td>ENDED_OK - ended normally</td>
<td>0</td>
</tr>
The HTML::Query module is a wrapper around the HTML parser that provides a querying interface that is familiar to jQuery users. So you could write something like
use HTML::Query qw(Query);
my $docName = "test.html";
my $doc = Query(file => $docName);
for my $tr ($doc->query("td")) {
for my $td (Query($tr)->query("td")) {
# $td is now an HTML::Element object for the td element
print $td->as_text, "\n";
}
}
Read the HTML::Query documentation to get a better idea of how to use it--- the above is hardly the prettiest example.
You could use a RegExp but Perl already has modules built for this specific task. Check out HTML::TableContentParser
You would probably do something like this:
use HTML::TableContentParser;
$tcp = HTML::TableContentParser->new;
$tables = $tcp->parse($HTML);
foreach $table (#$tables) {
foreach $row (#{ $tables->{rows} }) {
foreach $col (#{ $row->{cols} }) {
# each <td>
$data = $col->{data};
}
}
}
Here I use the HTML::Parser, is a little verbose, but guaranteed to work. I am using the diamond operator so, you can use it as a filter. If you call this Perl source extractTd, here are a couple of ways to call it.
$ extractTd test.html
or
$ extractTd < test.html
will both work, output will go on standard output and you can redirect it to a file.
#!/usr/bin/perl -w
use strict;
package ExtractTd;
use 5.010;
use base "HTML::Parser";
my $td_flag = 0;
sub start {
my ($self, $tag, $attr, $attrseq, $origtext) = #_;
if ($tag =~ /^td$/i) {
$td_flag = 1;
}
}
sub end {
my ($self, $tag, $origtext) = #_;
if ($tag =~ /^td$/i) {
$td_flag = 0;
}
}
sub text {
my ($self, $text) = #_;
if ($td_flag) {
say $text;
}
}
my $extractTd = new ExtractTd;
while (<>) {
$extractTd->parse($_);
}
$extractTd->eof;
Have you tried looking at cpan for HTML libraries? This seems to do what your wanting
http://search.cpan.org/~msisk/HTML-TableExtract-2.11/lib/HTML/TableExtract.pm
Also here is a whole page of different HTML related libraries to use
http://search.cpan.org/search?m=all&q=html+&s=1&n=100
Perl CPAN module HTML::TreeBuilder.
I use it extensively to parse a lot of HTML documents.
The concept is that you get an HTML::Element (the root node by example).
From it, you can look for other nodes:
Get a list of children nodes with ->content_list()
Get the parent node with ->parent()
Disclaimer: The following code has not been tested, but it's the idea.
my $root = HTML::TreeBuilder->new;
$root->utf8_mode(1);
$root->parse($content);
$root->eof();
# This gets you an HTML::Element, of the root document
$root->elementify();
my #td = $root->look_down("_tag", "td");
foreach my $td_elem (#td)
{
printf "-> %s\n", $td_elem->as_trimmed_text();
}
If your table is more complex than that, you could first find the TABLE element,
then iterate over each TR children, and for each TR children, iterate over TD elements...
http://metacpan.org/pod/HTML::TreeBuilder
Related
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');
}
I have this file - all I need is the last five lines from the file.
I know that I am not supposed to parse html without a html module. but this is not really like
a program strict - I mean all I really need is the last five lines or so. Besides I cannot download
any modules. I do have access to the proxy server which allows me to curl files from the command line
so maybe there is a way to use cpan fromteh or through the proxy - but that is a nother matter.
the matter at hand is that when I parse out thelast file lines or so, I don't get the
"Names IN MY-DEPT that are restricted"
and I want it. it gets skipped.
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$ cat restricted.html.bak
To:DL-BANK#big_business.com
From:dl-dept?g-gsd-stm#big_business.com
Subject: Restricted List for 25-Nov-2014
Content-Type: text/html;
Content-Transfer-Encoding: quoted-print HTMLFILEable>
<HTML>
<HEAD>
<STYLE type="text/css">
body { font-family: verdana; font-size: 10pt }
td { font-size: 8pt; vertical-align: top }
td.cat { color: 6699FF ; background: 666699; text-align: right; vertical-align: bottom; height: 30 }
td.ind { width: 20pt }
td.link { }
td.desc { color: a0a0a0 }
a:visited { color: 800080; text-decoration: none }
</STYLE>
<TITLE>TRADES</TITLE>
</HEAD><BODY><TABLE width="80%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td colspan="3" align="center">Names IN MY-DEPT that are restricted</td>
</tr>
<tr>
<td><b>Restriction Code</b></td>
<td><b>Company</b></td>
<td><b>Ticker</b></td>
</tr><tr><td>RL5</td><td>First Trust Global Risk Managed Inc</td><td>ETP</td></tr><font color="red"><tr><td>RLMT</td><td>GT Advanced Technologies Inc</td><td nowrap>GTATQ (position only, not in MY-DEPT)</td></tr></font></TABLE></BODY</HTML>new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$ cat parse_restrict2
#!/usr/bin/perl
use strict;
use warnings ;
my #restrict_codes = qw(RL3 RL5 RL5H RL6 REGM RAF RLMT RTCA RTCAH RTCB RTCBH RTCI RTCIH RLSI RLHK RLJP RPROP RLCB RLCS RLBZ RLBZH RLSUS);
my $rest_dir = "/home/new_guy/hey/hit_BANK_restricted./";
my $restrict_file = "restricted.html.bak" ;
open my $fh_rest_codes, '<', "$rest_dir$restrict_file" or die "cannot load $! " ;
while (<$fh_rest_codes>) {
next unless $_ =~ m/Names/;
my #lines = <$fh_rest_codes> ;
}
foreach(#lines) {
s/td/ /g ;
s/<[^>]*>/ /g ;
foreach $restrict(#restrict_codes) {
s/$restrict/\n$restrict/g;
}
print $_ ;
sleep 1 ;
}
print "\n" ;
These are the results that I get:
They are Ok but I would like to format them and I do not know how.
new_gue#casper0170foo:~/hey/hit_BANK_restricted.$ cat parse_restrict^C
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$ ./parse_restrict2
Restriction Code
Company
Ticker
RL5 First Trust Global Risk Managed Inc ETP
RLMT GT Advanced Technologies Inc GTATQ (position only, not in MY-DEPT)
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
new_guy#casper0170foo:~/hey/hit_BANK_restricted.$
Would there be any way possible to get the lines in this kind of format.
Names IN MY-DEPT that are restricted
Restriction Code Company Ticker
RL5 First Trust Global Risk Managed Inc ETP
RLMT GT Advanced Technologies Inc GTATQ (position only, not in MY-DEPT)
Good question, you could try this workaround if you like:
my #lines;
while (<$fh_rest_codes>) {
next unless $_ =~ m/Names/;
push(#lines, $_);
push (#lines, <$fh_rest_codes>);
}
my $str=join ('',#lines);
$str=~m|<td.*?>(.*?)</td>|;
print "$1\n\n";
$str=~ m|<tr>(.*?)</tr>|msg;
my $fmt="%-24s%-40s%-40s\n";
printf ($fmt, $1=~ m{<td><b>(.*?)</b></td>}msg );
while ($str=~ m|<tr>(.*?)</tr>|msg) {
printf ($fmt, $1=~ m{<td.*?>(.*?)</td>}msg );
}
Output:
Names IN MY-DEPT that are restricted
Restriction Code Company Ticker
RL5 First Trust Global Risk Managed Inc ETP
RLMT GT Advanced Technologies Inc GTATQ (position only, not in MY-DEPT)
I'm an old-newbie in Perl, and Im trying to create a subroutine in perl using HTML::TokeParser and URI.
I need to extract ALL valid links enclosed within on div called "zone-extract"
This is my code:
#More perl above here... use strict and other subs
use HTML::TokeParser;
use URI;
sub extract_links_from_response {
my $response = $_[0];
my $base = URI->new( $response->base )->canonical;
# "canonical" returns it in the one "official" tidy form
my $stream = HTML::TokeParser->new( $response->content_ref );
my $page_url = URI->new( $response->request->uri );
print "Extracting links from: $page_url\n";
my($tag, $link_url);
while ( my $div = $stream->get_tag('div') ) {
my $id = $div->get_attr('id');
next unless defined($id) and $id eq 'zone-extract';
while( $tag = $stream->get_tag('a') ) {
next unless defined($link_url = $tag->[1]{'href'});
next if $link_url =~ m/\s/; # If it's got whitespace, it's a bad URL.
next unless length $link_url; # sanity check!
$link_url = URI->new_abs($link_url, $base)->canonical;
next unless $link_url->scheme eq 'http'; # sanity
$link_url->fragment(undef); # chop off any "#foo" part
print $link_url unless $link_url->eq($page_url); # Don't note links to itself!
}
}
return;
}
As you can see, I have 2 loops, first using get_tag 'div' and then look for id = 'zone-extract'. The second loop looks inside this div and retrieve all links (or that was my intention)...
The inner loop works, it extracts all links correctly working standalone, but I think there is some issues inside the first loop, looking for my desired div 'zone-extract'... Im using this post as a reference: How can I find the contents of a div using Perl's HTML modules, if I know a tag inside of it?
But all I have by the moment is this error:
Can't call method "get_attr" on unblessed reference
Some ideas? Help!
My HTML (Note URL_TO_EXTRACT_1 & 2):
<more html above here>
<div class="span-48 last">
<div class="span-37">
<div id="zone-extract" class="...">
<h2 class="genres"><img alt="extracting" class="png"></h2>
<li><a title="Extr 2" href="**URL_TO_EXTRACT_1**">2</a></li>
<li><a title="Con 1" class="sel" href="**URL_TO_EXTRACT_2**">1</a></li>
<li class="first">Pàg</li>
</div>
</div>
</div>
<more stuff from here>
I find that TokeParser is a very crude tool requiring too much code, its fault is that only supports the procedural style of programming.
A better alternatives which require less code due to declarative programming is Web::Query:
use Web::Query 'wq';
my $results = wq($response)->find('div#zone-extract a')->map(sub {
my (undef, $elem_a) = #_;
my $link_url = $elem_a->attr('href');
return unless $link_url && $link_url !~ m/\s/ && …
# Further checks like in the question go here.
return [$link_url => $elem_a->text];
});
Code is untested because there is no example HTML in the question.
Given a html with table data like the following...
<tr class=nbg1><td><A HREF=api.dll?pgm=cdq32&p1=oavmsd&p2=fg3m9s5d&p3=&cmd=w1d27d0id9654&hl=antibio&lstid=026>Nadifloxacin</A></td><td>Aknetherapeutikum Antibiotikum (Gyrasehemmer)</td><td>WST</td><td></td></tr>
<tr class=nbg2><td><A HREF=api.dll?pgm=cdq32&p1=oavmsd&p2=fg3m9s5d&p3=&cmd=w1d27d0id9728&hl=antibio&lstid=026>Ertapenem</A></td><td>Antibiotikum</td><td>WST</td><td></td></tr>
<tr class=nbg1><td><A HREF=api.dll?pgm=cdq32&p1=oavmsd&p2=fg3m9s5d&p3=&cmd=w1d27d0id9761&hl=antibio&lstid=026>Panipenem</A></td><td>Beta-Lactam-Antibiotikum</td><td>WST</td><td></td></tr>
<tr class=nbg2><td><A HREF=api.dll?pgm=cdq32&p1=oavmsd&p2=fg3m9s5d&p3=&cmd=w1d27d0id10302&hl=antibio&lstid=026>Prulifloxacin</A></td><td>Antibiotikum (Gyrasehemmer)</td><td>WST</td><td></td></tr>
</table></td>
<td width=15></td><td valign=top nowrap class=NBG1>
<TABLE width="200" border="0" cellspacing="0" cellpadding="2">
<TR><TD CLASS="NBG2">
</TD></TR></TABLE><BR>
I need to parse the url and the url description, where the extracted url will be used for further parsing the subpage. What would be a good practice to accomplish this, especially getting the url.
current code:
my $te = HTML::TableExtract->new( depth => 3, count => 0 );
$te->parse($mainpage);
my $ts = "";
my $row = "";
foreach $ts ($te->tables) {
foreach $row ($ts->rows) {
print #$row[0] . "\n";
}
}
if you want to extract only the href attribute from each a' element in that table, no need to use TableExtract, just use HTML::Query
my $qry = HTML::Query->new(text => $mainpage);
my #hrefs = map { $_->attr('href') } grep { m/api\.dll/i } $qry->query('tr > td > a')->get_elements();
no tested, but you get the idea...
HTML::TableExtract can help you exactly with dealing with tables.
I need to extract the largest values(number) of specific names from a webpage. consider the webpage as
http://earth.wifi.com/isos/preFCS5.3/upgrade/
and the URL content is
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<title>Index of /isos/preFCS5.3/upgrade</title>
</head>
<body>
<h1>Index of /isos/preFCS5.3/upgrade</h1>
<table><tr><th><img src="/icons/blank.gif" alt="[ICO]"></th><th>Name</th><th>Last modified</th><th>Size</th><th>Description</th></tr><tr><th colspan="5"><hr></th></tr>
<tr><td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td><td>Parent Directory</td><td> </td><td align="right"> - </td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>GTP-UPG-LATEST-5.3.0.160.iso</td><td align="right">29-Aug-2011 16:06 </td><td align="right">804M</td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>GTP-UPG-LATEST-5.3.0.169.iso</td><td align="right">31-Aug-2011 16:26 </td><td align="right">804M</td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>GTP-UPG-LATEST-5.3.0.172.iso</td><td align="right">01-Sep-2011 16:26 </td><td align="right">804M</td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>PRE-UPG-LATEST-5.3.0.157.iso</td><td align="right">29-Aug-2011 16:05 </td><td align="right">1.5G</td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>PRE-UPG-LATEST-5.3.0.165.iso</td><td align="right">31-Aug-2011 16:26 </td><td align="right">1.5G</td></tr>
<tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td>PRE-UPG-LATEST-5.3.0.168.iso</td><td align="right">01-Sep-2011 16:26 </td><td align="right">1.5G</td></tr>
<tr><th colspan="5"><hr></th></tr>
</table>
<address>Apache/2.2.3 (Red Hat) Server at earth.wifi.com Port 80</address>
</body></html>
In the above source you can see 172 is the largest for GTP-UPG-LATEST-5.3.0
and 168 is the largest for PRE-UPG-LATEST-5.3.0
How can I extract these values and put it to a varialble say $gtp and $pre in perl
Thanks so much in advance
#!/usr/bin/perl
use strict;
use warnings;
use LWP::Simple;
my $upgrade = 'http://earth.wifi.com/isos/preFCS5.3/upgrade/';
my $website_content = get($upgrade);
if ( $website_content =~ /href=\"PRE-UPG-LATEST-5.3.0(.*?)\.iso\"/ )
{
my $preversion = ${1};
print $preversion;
}
This is the code I tried with but its not getting the largest value. This is code is getting the first PRE-UPG-LATEST version value that it encounters . But I need the largest of the value
An if() executes only once. Since you want to get many, you need a loop
while ( m//g ) {
In your data it has "UPG" but your regex has "UGP", so it won't match
(you should copy/paste long strings rather than (attempt to) retype them!).
This will list the data you need, I'll leave it to you to figure out how to process it.
while ($website_content =~ /href="((?:PRE|GTP)-UPG-LATEST-.*?)\.(\d+)\.iso"/g) {
my($file, $version) = ($1, $2);
print "file=$file version=$version\n";
}
I would suggest that you not only use LWP::Simple, but XML::Simple too. This will allow you to example the data as a hash of hashes. It'll be a lot easier to find the largest version.
You can't parse HTML or XML with simple regular expressions because the XML data structure is too free form. Large structures can legally be broken up on separate lines. Take a look at this example:
The Foobar Page
It can also be expressed as:
<a
href="http://foo.com/bar/bar/">
The Foobar Page
</a>
If you were looking for a href, you'll never find it. Heck, you could even look for a\s+href and not find it.
There might be better modules to use for parsing HTML (I found HTML::Dom), but I've never used them and don't know which one is the best one to use.
As for finding the largest version number:
There's some difficulty because there are all sorts of strange and wacky rules with version numbering. For example:
2.2 < 2.10
Perl has something called V-Strings, but rumor has it that they've been deprecated. If this doesn't concern you, you can use Perl::Version.
Otherwise, here's a subroutine that does version comparison. Note that I also verify that each section is an integer via the /^\d+$/ regular expression. My subroutine can return four values:
0: Both are the same size
1: First Number is bigger
2: Second Number is bigger
undef: There is something wrong.
Here's the program:
my $minVersion = "10.3.1.3";
my $userVersion = "10.3.2";
# Create the version arrays
my $result = compare($minVersion, $userVersion);
if (not defined $results) {
print "Non-version string detected!\n";
}
elsif ($result == 0) {
print "$minVersion and $userVersion are the same\n";
}
elsif ($result == 1) {
print "$minVersion is bigger than $userVersion\n";
}
elsif ($result == 2) {
print "$userVersion is bigger than $minVersion\n";
}
else {
print "Something is wrong\n";
}
sub compare {
my $version1 = shift;
my $version2 = shift;
my #versionList1 = split /\./, $version1;
my #versionList2 = split /\./, $version2;
my $result;
while (1) {
# Shift off the first value for comparison
# Returns undef if there are no more values to parse
my $versionCompare1 = shift #versionList1;
my $versionCompare2 = shift #versionList2;
# If both are empty, Versions Matched
if (not defined $versionCompare1 and not defined $versionCompare2) {
return 0;
}
# If $versionCompare1 is empty $version2 is bigger
if (not defined $versionCompare1) {
return 2;
}
# If $versionCompare2 is empty $version1 is bigger
if (not defined $versionCompare2) {
return 1;
}
# Make sure both are numeric or else there's an error
if ($versionCompare1 !~ /\^d+$/ or $versionCompare2 !~ /\^\d+$/) {
return;
}
if ($versionCompare1 > $versionCompare2) {
return 1;
}
if ($versionCompare2 > $versionCompare1) {
return 2;
}
}
}