How to create HTML tables dynamically using Perl? - perl

I am working on a project where I need to access CSV file form a web URL. I am able access the file and print the content from CSV file in the terminal, but I'm unable to produce HTML table (then I'll later send email using MIME).
Here is my code - I need complete CSV file as HTML table delivered to my email.
#!/usr/bin/perl
use strict;
use warnings;
use LWP::Simple;
use POSIX qw(strftime);
use MIME::Lite;
use Getopt::Long;
my $to="pankajdustbin\#gmail.com";
my $from="pankajdustbin\#gmail.com";
$subject="CSV File";
my $content = `curl -s "https:csvfile.com"`;
#output = split(/\n/,$content);
foreach my $line (#output) {
my ($col1, $col2, $col3, $col4, $col5, $col6) = split(/,/, $line);
#print "\n$col1, $col2, $col3, $col4, $col5, $col6\n";
$message = "<tr> <td>$col1</td> <td>$col2</td> <td>$col3</td> <td>$col4</td> <td>$col5</td> <td>$col6</td></tr>";
}
my $msg=MIME::Lite->new(
From => $from,
To => $to,
Subject => $subject,
Data => $message
);
$msg->attr('content-type' => 'text/html');
#MIME::Lite->send("smtp");
$msg->send;
With this code, the HTML table contains only the last row of the CSV. Can someone help me how I should do?
CSV has around 100 rows, and the sample output that I see in terminal as below:
1.2.3.4 03-04-2022. 03-08-2022. Red. 1%. Positive
5.2.3.4 03-05-2022. 04-08-2022. Blue. 1%. Neutral
and so on...

The problem is that you overwrite the contents of the $message variable each time through the foreach loop. This means that $message will only have the last value that you assign to it.
You could append to the contents of the variable using the .= operator:
my $message;
foreach my $line (#output) {
my ($col1, $col2, $col3, $col4, $col5, $col6) = split(/,/, $line);
$message .= "<tr> <td>$col1</td> <td>$col2</td> <td>$col3</td> <td>$col4</td> <td>$col5</td> <td>$col6</td></tr>";
}

Previous answer covered that you overwrite $message in the loop what is not you have intended.
Following snippet code demonstrates slightly different approach to build html table utilizing split and for loop.
Then table can be utilized anyway you desire -- send it by mail or generate html page. In this demo code complete html page generated.
Note #1: \n and \t optional and added for html readability only
Note #2: as no sample input CVS file was provided the content was assumed based on provided output in terminal
use strict;
use warnings;
use feature 'say';
my $table = '<table border=1>';
while( my $line = <DATA>) {
chomp $line;
$table .= "\n\t\t\t<tr>";
$table .= "\n\t\t\t\t<td>" . $_ . '</td>' for split(/,/,$line);
$table .= "\n\t\t\t</tr>";
}
$table .= "\n\t\t</table>";
my $html =
"<html lang='en'>
<head>
<meta charset='utf8' />
<link rel='stylesheet' href='css/styles.css'>
<title>
CVS table
</title>
</head>
<body>
$table
</body>
</html>
";
say $html;
__DATA__
1.2.3.4,03-04-2022,03-08-2022,Red,1%,Positive
5.2.3.4,03-05-2022,04-08-2022,Blue,1%,Neutral
1.2.3.4,03-04-2022,03-08-2022,Red,1%,Positive
5.2.3.4,03-05-2022,04-08-2022,Blue,1%,Neutral
1.2.3.4,03-04-2022,03-08-2022,Red,1%,Positive
5.2.3.4,03-05-2022,04-08-2022,Blue,1%,Neutral

Related

Perl script for Downloading the file from web

I am trying to automate one of my task where i have to download a last 5 releases of some softwares let say Google talk from http://www.filehippo.com/download_google_talk/.
I have never done such type of programming i mean, to interact with Web through perl .I have just read and came to know that through CGI module we can implement this thing so i tried with this module.
If some body can give me better advice then please you are welcome :)
My code :
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use CGI::Carp qw/fatalsToBrowser/;
my $path_to_files = 'http://www.filehippo.com/download_google_talk/download/298ba15362f425c3ac48ffbda96a6156';
my $q = CGI->new;
my $file = $q->param('file') or error('Error: No file selected.');
print "$file\n";
if ($file =~ /^(\w+[\w.-]+\.\w+)$/) {
$file = $1;
}
else {
error('Error: Unexpected characters in filename.');
}
if ($file) {
download($file) or error('Error: an unknown error has occured. Try again.');
}
sub download
{
open(DLFILE, '<', "$path_to_files/$file") or return(0);
print $q->header(-type => 'application/x-download',
-attachment => $file,
'Content-length' => -s "$path_to_files/$file",
);
binmode DLFILE;
print while <DLFILE>;
close (DLFILE);
return(1);
}
sub error {
print $q->header(),
$q->start_html(-title=>'Error'),
$q->h1($_[0]),
$q->end_html;
exit(0);
}
In above code i am trying to print the file name which i wan to download but it is displaying error message.I am not able to figure it out why this error "Error: No file selected." is comming.
Sorry, but you are in the wrong track. Your best bet is this module: http://metacpan.org/pod/WWW::Mechanize
This page contain a lot of example to start with: http://metacpan.org/pod/WWW::Mechanize::Examples
It could be more elegant but I think this code easier to understand.
use strict;
use warnings;
my $path_to_files = 'http://www.filehippo.com/download_google_talk/download/298ba15362f425c3ac48ffbda96a6156';
my $mech = WWW::Mechanize->new();
$mech->get( $path_to_files );
$mech->save_content( "download_google_talk.html" );#save the base to see how it looks like
foreach my $link ( $mech->links() ){ #walk all links
print "link: $link\n";
if ($link =~ m!what_you_want!i){ #if it match
my $fname = $link;
$fname =~ s!\A.*/!! if $link =~ m!/!;
$fname .= ".zip"; #add extension
print "Download $link to $fname\n";
$mech->get($link,":content_file" => "$fname" );#download the file and stoore it in a fname.
}
}

download url content of the cgi web page

I have cgi search engine for local flat database search and I would like to add option the user able to export/download the search result. is that possible with cgi?
this is the code.
#!/usr/bin/perl
read(STDIN, $buffer,$ENV{'CONTENT_LENGTH'});
# Split the name-value pairs
#pairs = split(/&/, $buffer);
foreach $pair (#pairs) {
($key, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$formdata{$key}.= "$value";
}
$search = $formdata{'search'};
open(INFO, "/test.txt");
#array=<INFO>;
close (INFO);
print "Content-type:text/html\n\n";
print "<html>\n";
print "<head><title>Search result</title></head>\n";
print "<body>\n";
print "<h4><font color=#990000>This is your search result!</h4>\n";
$search_url = 'https://test.php';
foreach $line (#array) {
if ($line =~ /$search/){
($host,$ip)=split(/\|/,$line);
$records= ++$counter;
#result =($host,$ip);
print "<font color=#7a378b><b><p>";
print "<table border=0 cellpadding=0 cellspacing=0 style=border-collapse: collapse
bordercolor=#111111 width=20% bgcolor=#C0C0C0>";
print "</tr>";
foreach (#result) {
#words = split ;
print "<tr><td width=33% bgcolor=#DCDCDC><b><font color=#000080 size=1 face=Courier New>$words[0]</font></b></td>";
print "<td width=36% bgcolor=#DCDCDC><b><font color=#000080 size=1 face=Courier
New>$words[1]</font></b></td>";
print "</table>";
}
}
}
if ($records== 0) {
print " Sorry! No records found\n";
}
Yes. The conventional way is to output the appropriate content type header, e.g.
Content-type: text/csv
Content-type: text/xml
and optionally to specify a "content disposition" as well. In conforming browsers, this will give the client the option to save the server's output to a local file:
Content-disposition: attachment;filename="myfilename.csv"
(Edit to use friedo's wise suggestion)

Random element order in XML document using XML::LibXML

I have a Perl script that reads a simple .csv file like below-
"header1","header2","header3","header4"
"12","12-JUL-2012","Active","Processed"
"13","11-JUL-2012","In Process","Pending"
"32","10-JUL-2012","Active","Processed"
"24","08-JUL-2012","Active","Processed"
.....
The aim is to convert this .csv to an .xml file something like below-
<ORDERS>
<LIST_G_ROWS>
<G_ROWS>
<header1>12</header1>
<header2>12-JUL-2012</header2>
<header3>Active</header3>
<header4>Processed</header4>
</G_ROWS>
<G_ROWS>
<header1>13</header1>
<header2>11-JUL-2012</header2>
<header3>In Process</header3>
<header4>Pending</header4>
</G_ROWS>
....
....
</LIST_G_ROWS>
</ORDERS>
I know that there is XML::CSV available in CPAN which will make my life easier but I want to make use of already installed XML::LibXML to create the XML, instead of installing XML::CSV. I was able to read the CSV and create the XML file as above without any issues, but I am getting a random order of the elements in the XML i.e. something like below. I need to have the order of the elements (child nodes) to be in sync with the .csv file as shown above, but I am not quite sure how do go around that. I am using a hash and sort() ing the hash didn't quite solve the problem either.
<ORDERS>
<LIST_G_ROWS>
<G_ROWS>
<header3>Active</header3>
<header1>12</header1>
<header4>Processed</header4>
<header2>12-JUL-2012</header2>
</G_ROWS>
......
and so on. Below is the snippet from my perl code
use XML::LibXML;
use strict;
my $outcsv="/path/to/data.csv";
my $$xmlFile="/path/to/data.xml";
my $headers = 0;
my $doc = XML::LibXML::Document->new('1.0', 'UTF-8');
my $root = $doc->createElement("ORDERS");
my $list = $doc->createElement("LIST_G_ROWS");
$root->appendChild($list);
open(IN,"$outcsv") || die "can not open $outcsv: $!\n";
while(<IN>){
chomp($_);
if ($headers == 0)
{
$_ =~ s/^\"//g; #remove starting (")
$_ =~ s/\"$//g; #remove trailing (")
#keys = split(/\",\"/,$_); #split per ","
s{^\s+|\s+$}{}g foreach #keys; #remove leading and trailing spaces from each field
$headers = 1;
}
else{
$_ =~ s/^\"//g; #remove starting (")
$_ =~ s/\"$//g; #remove trailing (")
#vals = split(/\",\"/,$_); #split per ","
s{^\s+|\s+$}{}g foreach #vals; #remove leading and trailing spaces from each field
my %tags = map {$keys[$_] => $vals[$_]} (0..#keys-1);
my $row = $doc->createElement("G_ROWS");
$list->appendChild($row);
for my $name (keys %tags) {
my $tag = $doc->createElement($name);
my $value = $tags{$name};
$tag->appendTextNode($value);
$row->appendChild($tag);
}
}
}
close(IN);
$doc->setDocumentElement($root);
open(OUT,">$xmlFile") || die "can not open $xmlFile: $!\n";
print OUT $doc->toString();
close(OUT);
You could forget the %tags hash entirely. Instead, loop over the indices of #keys:
for my $i (0 .. #keys - 1) {
my $key = $keys[$i];
my $value = $values[$i];
my $tag = $doc->createElement($key);
$tag->appendTextNode($value);
$row->appendChild($tag);
}
That way, the ordering of your keys is preserved. When a hash is used, the ordering is indeterminate.
Your program is far more involved than it needs to be. For convenience and reliability you should use Text::CSV to parse your CSV file.
The program below does what you need.
use strict;
use warnings;
use Text::CSV;
use XML::LibXML;
open my $csv_fh, '<', '/path/to/data.csv' or die $!;
my $csv = Text::CSV->new;
my $headers = $csv->getline($csv_fh);
my $doc = XML::LibXML::Document->new('1.0', 'UTF-8');
my $orders = $doc->createElement('ORDERS');
$doc->setDocumentElement($orders);
my $list = $orders->appendChild($doc->createElement('LIST_G_ROWS'));
while ( my $data = $csv->getline($csv_fh) ) {
my $rows = $list->appendChild($doc->createElement('G_ROWS'));
for my $i (0 .. $#$data) {
$rows->appendTextChild($headers->[$i], $data->[$i]);
}
}
print $doc->toFile('/path/to/data.xml', 1);
output
<?xml version="1.0" encoding="UTF-8"?>
<ORDERS>
<LIST_G_ROWS>
<G_ROWS>
<header1>12</header1>
<header2>12-JUL-2012</header2>
<header3>Active</header3>
<header4>Processed</header4>
</G_ROWS>
<G_ROWS>
<header1>13</header1>
<header2>11-JUL-2012</header2>
<header3>In Process</header3>
<header4>Pending</header4>
</G_ROWS>
<G_ROWS>
<header1>32</header1>
<header2>10-JUL-2012</header2>
<header3>Active</header3>
<header4>Processed</header4>
</G_ROWS>
<G_ROWS>
<header1>24</header1>
<header2>08-JUL-2012</header2>
<header3>Active</header3>
<header4>Processed</header4>
</G_ROWS>
</LIST_G_ROWS>
</ORDERS>
Update
Without the exotic options that Text::CSV provides, its functionality is fairly simple if its options are fixed. This alternative provides a subroutine csv_readline to replace the Text::CSV method readline. It works mostly in the same way as the module.
The output of this program is identical to that above.
use strict;
use warnings;
use XML::LibXML;
open my $csv_fh, '<', '/path/to/data.csv' or die $!;
my $doc = XML::LibXML::Document->new('1.0', 'UTF-8');
my $orders = $doc->createElement('ORDERS');
$doc->setDocumentElement($orders);
my $list = $orders->appendChild($doc->createElement('LIST_G_ROWS'));
my $headers = csv_getline($csv_fh);
while ( my $data = csv_getline($csv_fh) ) {
my $rows = $list->appendChild($doc->createElement('G_ROWS'));
for my $i (0 .. $#$data) {
$rows->appendTextChild($headers->[$i], $data->[$i]);
}
}
print $doc->toFile('/path/to/data.xml', 1);
sub csv_getline {
my $fh = shift;
defined (my $line = <$fh>) or return;
$line =~ s/\s*\z/,/;
[ map { /"(.*)"/ ? $1 : $_ } $line =~ /( " [^"]* " | [^,]* ) , /gx ];
}
Seems like something that XML::LibXml is an overkill for, just use XML::Simple and build the proper hash which will describe that XML structure, than dump it with XMLOut to an XML file

How convert text into XML using perl?

input text file contain the following:
....
ponies B-pro
were I-pro
used I-pro
A O
report O
of O
indirect B-cd
were O
. O
...
output XML file
<sen>
<base id="pro">
<w id="1">ponies</w>
<w id="2">were</w>
<w id="3">were</w>
</base>A report of
<base id="cd">indirect</base> were
</sen>
i want to make an XML file by reading the text file, B- means the begining of my tag and I- means an include words inside the tag while "O" means outside the base tag which means it only exist in the tag.
i try the following codes:
#!/usr/local/bin/perl -w
open(my $f, "input.txt") or die "Can't";
open(my $o, ">output.xml") or die "Can't";
my $c;
sub read_line {
my $fh = shift;
if ($fh and my $line = <$fh>) {
chomp($line);
my #words = split(/\t/, $line);
my $word = $words[0];
my $group = $words[1];
if($word eq "."){
return;
}
else{
if($group ne 'O'){
my #b = split(/\-/, $group);
if($b[0] eq 'B'){
my $e = "<e id=\"";
$e .= " . $b[1] . "\">";
$e .= $word . "</e>";
return $e;
}
if($b[0] eq 'I'){
my $w = "<w id=\"";
$w .= $c . "\">";
$w .= $word . "</w>";
$c++;
return $w;
}
}
else{
$c = 2;
return $word;
}
}
}
return;
}
sub get_text(){
my $txt = "";
my $r = read_line($f);
while($r){
if($r =~ m/[[:punct:]]/){
chop($txt);
$txt .= " " . $r . " ";
}
else{
$txt .= $r . " ";
}
$r = read_line($f);
}
chop($txt);
return "<sen>" . $txt . ".</sen>";
}
instead im getting as output:
<sen>
<base id="pro"> ponies </base>
<w id="2">were</w>
<w id="3">were</w>
A report of
<base id="cd">indirect</base> were
</sen>
i really need help.
Thanks
Writing XML "by hand" will only get you in trouble. Use a module from CPAN.
In your case, I would first put the data in a proper Perl data structure (maybe a hash containing some arrays, or something similar) and then using a module (i.e. XML::Simple for starters) to output to a file.
As Javs said, you want to use a module rather than do this by hand. For your purposes, since you have mixed content, I recommend XML::LibXML. Here is an example I made to test that you can indeed to mixed content like you've got:
use XML::LibXML;
my $doc = XML::LibXML::Document->new();
my $root = $doc->createElement('html');
$doc->setDocumentElement($root);
my $body = $doc->createElement('body');
$root->appendChild($body);
my $link = $doc->createElement('a');
$link->setAttribute('href', 'http://google.com');
$link->appendText('Google');
$body->appendChild($link);
$body->appendText('Inline Text');
print $doc->toString;

How can I change a rowspan attribute within my CGI program?

I am writing a CGI script that will process form data, and it should print the name of the input, along with its value, in a table. When I have one or more values with the same name, the name should span rows to accommodate all values that correspond to that name. For example, if I have a name "color" with its values at "red", "green", "blue", then color should span 3 rows in my table. My question is, how would i change the rowspan attribute in my script to accommodate this:
#!/usr/bin/perl --
use strict;
use CGI;
print <<HTTP;
Status: 200 OK
Content-Type: text/html
HTTP
print <<HTML;
<html>
<head>
<title>Parameters<title>
<head>
<body>
<table border="1" cellpadding="5" cellspacing="1">
<tr>
<th>Name</th>
<th>Value</th>
</tr>
HTML
my $query = new CGI;
my($name, $value);
foreach $name ( $query->param)
{
print "<tr>";
print "<td>$name</td>";
foreach $value($query->param($name))
{
print "<td>$value</td>";
print "</tr>";
}
}
Try this:
my $query = new CGI;
my($name, $value);
foreach $name ($query->param) {
my #values = $query->param($name);
my $count = #values;
print "<tr>";
print "<td rowspan='$count'>$name</td>";
print "<td>".shift(#values)."</td>";
print "</tr>";
foreach $value (#values) {
print "<tr>";
print "<td>$value</td>";
print "</tr>";
}
}
BTW, I would suggest you to consider using some template processing system, e.g. Template Toolkit.