this works
TABLE users
userid firstname lastname
1 JOHN DEO
2 JANE DEO
TABLE msg
msg_id msg_from msg_to received
1 userid(1) userid(2) null
$janedeo_id = 2;
my $data = $DBH->prepare("SELECT SND.userid, SND.firstname, SND.lastname
FROM msg as M
JOIN users as SND
ON SND.userid = M.msg_from
WHERE M.msg_to = ?
AND M.received IS NULL");
$data->execute($janedeo_id);
while (my $row = $data->fetchrow_hashref) {
foreach $row ( #$data) {
($userid, $snd_firstname, $snd_lastname) = #$data;
}
}
my $templ = <<START_HTML;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1
+" />
<title>Untitled Document</title>
</head>
<body>
[% FOREACH name IN list %]
<p>userid [% name.0 %] </p>
<p>firstname [% name.1 %] </p>
<p>lastname [% name.2 %] </p>
[% END %]
</body>
</html>
START_HTML
$template->process (\$templ, { list => \#$data })
or die $template->error;
this doesn't work. when i try to add age city country and results it fails cant works
TABLE users
userid firstname lastname
1 JOHN DEO
2 JANE DEO
TABLE msg
msg_id msg_from msg_to received age city country
1 userid(1) userid(2) null 26 any any
$janedeo_id = 2;
my $data = $DBH->prepare("SELECT SND.userid, SND.firstname, SND.lastname, SND.age, SND.city, SND.country
FROM msg as M
JOIN users as SND
ON SND.userid = M.msg_from
WHERE M.msg_to = ?
AND M.received IS NULL");
$data->execute($janedeo_id);
while (my $row = $data->fetchrow_hashref) {
foreach $row ( #$data) {
($userid, $snd_firstname, $snd_lastname, $snd.age, $snd.city, $snd.country) = #$data;
}
}
my $templ = <<START_HTML;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>Untitled Document</title>
</head>
<body>
[% FOREACH name IN list %]
<p>userid [% name.0 %] </p>
<p>firstname [% name.1 %] </p>
<p>lastname [% name.2 %] </p>
<p>city [% name.3 %] </p>
<p>age [% name.4 %] </p>
<p>country [% name.5 %] </p>
[% END %]
</body>
</html>
START_HTML
$template->process (\$templ, { list => \#$data })
or die $template->error;
am failing to get results when i try to add city age country to table msg and getting them printed out. am just getting blank response. even the script just printing error. cant find anything in database. so am just confused i don't know the problem
This is really weird:
while (my $row = $data->fetchrow_hashref) {
foreach $row ( #$data) {
($userid, $snd_firstname, $snd_lastname, $snd.age, $snd.city, $snd.country) = #$data;
}
}
You fetch one row from the result as a hash reference. You than iterate over #$data which is a statement handle - this can't work. Use the fetched $row in the loop.
Also, . is the concatenation operator. What do you mean by $snd.city?
You can use Data::Dumper to see what structure is returned:
use Data::Dumper;
...
while (my $row = $data->fetchrow_hashref) {
print Dumper $row;
}
Here's a standalone example:
#!/usr/bin/perl
use warnings;
use strict;
use DBI;
use Template;
# Fake the database
my $dbh = 'DBI'->connect('dbi:SQLite:dbname=:memory:', "", "");
$dbh->do('CREATE TABLE USERS (id INT, firstname VARCHAR(20), lastname VARCHAR(20), age INT, city VARCHAR(20), country VARCHAR(20))');
$dbh->do('INSERT INTO USERS VALUES(1, "John", "Doe", 30, "Mumbai", "India")');
$dbh->do('INSERT INTO USERS VALUES(2, "Jane", "Doe", 20, "New York", "USA")');
$dbh->do('CREATE TABLE msg (id INT, msg_from VARCHAR(20), msg_to VARCHAR(20), received BOOL)');
$dbh->do('INSERT INTO msg VALUES (1, 2, 1, NULL)');
# Template.
my $templ = << '__HTML__';
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>Untitled Document</title>
</head>
<body>
[% FOREACH user IN list %]
<p>userid [% user.id %] </p>
<p>firstname [% user.firstname %] </p>
<p>lastname [% user.lastname %] </p>
<p>city [% user.city %] </p>
<p>age [% user.age %] </p>
<p>country [% user.country %] </p>
[% END %]
</body>
</html>
__HTML__
my $data = $dbh->prepare(<< '__SQL__');
SELECT u.id, u.firstname, u.lastname, age, u.city, u.country
FROM msg as m
JOIN users as u
ON u.id = m.msg_from
WHERE m.msg_to = ?
AND m.received IS NULL
__SQL__
$data->execute(1);
my #output;
while (my $row = $data->fetchrow_hashref) {
push #output, $row;
}
'Template'->new->process (\$templ, { list => \#output })
or die $templ->error;
You seem very, very confused. I don't think you are actually showing us the code that you are running. Look at this code:
my $data = $DBH->prepare("SELECT SND.userid, SND.firstname,
SND.lastname
FROM msg as M
JOIN users as SND
ON SND.userid = M.msg_from
WHERE M.msg_to = ?
AND M.received IS NULL");
$data->execute($janedeo_id);
while (my $row = $data->fetchrow_hashref) {
foreach $row ( #$data) {
($userid, $snd_firstname, $snd_lastname,
$snd.age, $snd.city, $snd.country) = #$data;
}
}
This is your first code example - the code that you claim works. But it can't possibly work.
You have a variable called $data which contains your DBI statement handle (I know that because it's returned from a $DBH->prepare() call). It would be more usual to call this variable $sth.
You, correctly, call execute() on this object and then start to call fetchrow_hashref() in a loop. You store the hash reference you get in a variable called $row. So far so good.
But then it all goes wrong.
You ignore the hash reference you have and overwrite it with the data that is stored in $data. But there's no data in $data - it's a statement handle. You treat it as an array reference (#$data) but it's not an array reference, so this would throw a fatal runtime error.
You then ignore the second value you have put in $row and switch to using individual variables to, presumably, store the data that you're getting back from the database. Once again, you treat $data as an array reference and once again, it won't work and will throw a fatal runtime error. Oh, and you're using variable names (for example, $snd.age) which are invalid in Perl and which would almost certainly give you a syntax error.
By claiming that this code works, you are wasting our time. It doesn't work. It doesn't even compile. We are very happy to help people here, but you need to show us actual code that we can run.
I don't know what your programming background is. But in order to be successful in this career, you are going to have to pay a lot more attention to detail.
i got this fixed by doing
SELECT SND.userid, SND.firstname, SND.lastname, M.age, M.city, M.country
FROM msg as M
JOIN users as SND
ON SND.userid = M.msg_from
WHERE M.msg_to = 'userid -1'
AND M.received IS NULL
Related
I'm guessing this is relatively simple, but I can't find the answer.
From a string such as '"John Doe" <email#example.com>' - how can I extract the email portion from it using Template Tookit?
An example string to parse is this:
$VAR1 = {
'date' => '2021-03-25',
'time' => '03:58:18',
'href' => 'https://example.com',
'from' => 'fezius#evrostroyserov.ru on behalf of Caroline <fezius#evrostroyserov.ru>',
'bytes' => 13620,
'pmail' => 'user#example.com',
'sender' => 'sender#example.com',
'subject' => 'Some Email Subject'
};
My code, based on #dave-cross help below where $VAR1 is the output of dumper.dump(item.from)
[% text = item.from -%]
[% IF (matches = text.match('(.*?)(\s)?+<(.*?)>')) -%]
<td>[% matches.1 %]</td>
[% ELSE -%]
<td>[% text %]</td>
[% END %]
However, it's still not matching against $VAR1
This does what you want, but it's pretty fragile and this really isn't the kind of thing that you should be doing in TT code. You should either get the data parsed outside of the template and passed into variables, or you should pass in a parsing subroutine that can be called from inside the template.
But, having given you the caveats, if you still insist this is what you want to do, then this is how you might do it:
In test.tt:
[% text = '"John Doe" <email#example.com>';
matches = text.match('"(.*?)"\s+<(.*?)>');
IF matches -%]
Name: [% matches.0 %]
Email: [% matches.1 %]
[% ELSE -%]
No match found
[% END -%]
Then, testing using tpage:
$ tpage test.tt
Name: John Doe
Email: email#example.com
But I cannot emphasise enough that you should not be doing it like this.
Update: I've used this test template to investigate your further problem.
[% item = { from => '"John Doe" <email#example.com>' };
text = item.from -%]
[% IF (matches = text.match('(.*?)(\s)?+<(.*?)>')) -%]
<td>[% matches.1 %]</td>
[% ELSE -%]
<td>[% text %]</td>
[% END %]
And running it, I get this:
$ tpage test2.tt
<td> </td>
That's what I'd expect to see for a match. You're printing matches.1. That's the second item from the matches array. And the second match group is (\s). So I'm getting the space between the name and the opening angle bracket.
You probably don't want that whitespace match in your matches array, so I'd remove the parentheses around it, to make the regex (.*?)\s*<(.*?)> (note that \s* is a simpler way to say "zero or more whitespace characters").
You can now use matches.0 to get the name and matches.1 to get the email address.
Oh, and there's no need to copy items.from into text. You can call the matches vmethod on any scalar variable, so it's probably simpler to just use:
[% matches = item.from.match(...) -%]
Did I mention that this is all a really terrible idea? :-)
Update2:
This is all going to be far easier if you give me complete, runnable code examples in the same way that I am doing for you. Any time I have to edit something in order to get an example running, we run the risk that I'm guessing incorrectly how your code works.
But, bearing that in mind, here's my latest test template:
[% item = {
'date' => '2021-03-25',
'time' => '03:58:18',
'href' => 'https://example.com',
'from' => 'fezius#evrostroyserov.ru on behalf of Caroline <fezius#evrostroyserov.ru>',
'bytes' => 13620,
'pmail' => 'user#example.com',
'sender' => 'sender#example.com',
'subject' => 'Some Email Subject'
};
text = item.from -%]
[% IF (matches = text.match('(.*?)(\s)?<(.*?)>')) -%]
<td>[% matches.2 %]</td>
[% ELSE -%]
<td>[% text %]</td>
[% END %]
I've changed the definition of item to have your full example. I've left the regex as it was before my suggestions. And (because I haven't changed the regex) I've changed the output to print matches.2 instead of matches.1.
And here's what happens:
$ tpage test3.tt
<td>fezius#evrostroyserov.ru</td>
So it works.
If yours doesn't work, then you need to identify the differences between my (working) code and your (non-working) code. I'm happy to help you identify those differences, but you have to give my your non-working example in order for me to do that.
Update3:
Again I've tried to incorporate the changes that you're talking about. But again, I've had to guess at stuff because you're not sharing complete runnable examples. And again, my code works as expected.
[% USE dumper -%]
[% item = {
'date' => '2021-03-25',
'time' => '03:58:18',
'href' => 'https://example.com',
'from' => 'fezius#evrostroyserov.ru on behalf of Caroline <fezius#evrostroyserov.ru>',
'bytes' => 13620,
'pmail' => 'user#example.com',
'sender' => 'sender#example.com',
'subject' => 'Some Email Subject'
};
-%]
[% matches = item.from.match('(.*?)(\s)?<(.*?)>') -%]
[% dumper.dump(matches) %]
And testing it:
$ tpage test4.tt
$VAR1 = [
'fezius#evrostroyserov.ru on behalf of Caroline',
' ',
'fezius#evrostroyserov.ru'
];
So that works. If you want any more help, then send a complete runnable example. If you don't do that, I won't be able to help you any more.
There's a very old (and unmaintained) module, Template::Extract, that let's you define a template, then work backward from a string that might have been produced by that template:
use Template::Extract;
use Data::Dumper;
my $obj = Template::Extract->new;
my $template = qq("[% name %]" <[% email %]>);
my $string = '"John Doe" <email#example.com>';
my $extracted = $obj->extract($template, $string);
print Dumper( $extracted );
The output is:
$VAR1 = {
'email' => 'email#example.com',
'name' => 'John Doe'
};
However, there are modules that already do this job for you and will handle many more situations
I have no idea how Template Toolkit can help you. Use Email::Address or Email::Address::XS to parse an e-mail address.
I am using Perl template toolkit for rendering my data.
This is the hash that I am passing to the template
'location' => {
'1' => {
'nmi' => 'QB13557343'
},
'2' => {
'nmi' => 'QB13559843'
},
},
and in the template I am looping this hash to get the result but its not displaying
The code in the template is:
[% FOREACH loc IN location %]
<p>NMI: [% loc.nmi %][% location.loc.nmi %]
[% END %]
I tried both loc.nmi and location.loc.nmi but not getting any result.
Any help would be greatly appreciated.
It's a hash and not an array. Try to iterate over the keys.
[% FOREACH key IN location.keys %]
<p>NMI: [% location.$key.nmi %]</p>
[% END %]
I am creating a simple perl script to create a web page to register users. This is just a learning program for me. It is very simple. I will display a page on the browser. The user enters name, user name, and password. After the user presses submit, I will check the user name against the database. If the user name exists in the database, I just want to display an error and bring up the register page again. I am using the cgi->redirect function. I am not sure if that is how I should use the redirection function. It does not work like I thought. It display "The document has moved here". Please point me to the right way. Thanks.
Here is the scripts
registeruser.pl
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print <<PAGE;
<html>
<head>
<link rel="stylesheet" type="text/css" href="tracker.css"/>
</head>
<body>
<div id="header">
<h1> Register New User</h1>
</div>
<div id="content">
<form action="adduser.pl" method="POST">
<b>Name:</b> <input type="text" name="name"><br>
<b>UserName:</b> <input type="text" name="username"><br>
<b>Password:</b> <input type="password" name="password"><br>
<input type="submit">
</div>
</body>
<html>
PAGE
adduser.pl
#!/usr/bin/perl
use CGI;
use DBI;
$cgiObj = CGI->new;
print $cgiObj->header ('text/html');
# get post data
$newUser = $cgiObj->param('username');
$newName = $cgiObj->param('name');
$newPass = $cgiObj->param('password');
# set up sql connection
$param = 'DBI:mysql:Tracker:localhost';
$user = 'madison';
$pass = 'qwerty';
$connect = DBI->connect ($param, $user, $pass);
$sql = 'select user from users where user = "' . $newUser . '"';
$query = $connect->prepare ($sql);
$query->execute;
$found = 0;
while (#row = $query->fetchrow_array)
{
$found = 1;
}
if ($found == 0)
{
# no user found add new user
$sql = 'insert into users (user, name, passwd) values (?, ?, ?)';
$insert = $connect->prepare ($sql);
$insert->execute ($newUser, $newName, $newPass);
}
else
{
# user already exists, get new user name
# What do I do here ????
print $cgiObj->redirect ("registerusr.pl");
}
One thing to look out for, SQL Injection. For an illustrated example, Little Bobby Tables.
As it stands your code is inescure, and can allow people to do bad things to your database. DBI provides placeholders as a secure way of querying a database with user input. Example http://bobby-tables.com/perl.html
Also, in this day and age even the CGI module warns you not to use it:
The rational for this decision is that CGI.pm is no longer considered good practice for developing web applications, including quick prototyping and small web scripts. There are far better, cleaner, quicker, easier, safer, more scalable, more extensible, more modern alternatives available at this point in time. These will be documented with CGI::Alternatives.
I suggest you use Dancer to make your life easier.
Three things
Include use strict; and use warnings; in EVERY perl script. No exceptions.
This is the #1 thing that you can do to be a better perl programmer. It will save you an incalculable amount of time during both development and testing.
Don't use redirects to switch between form processing and form display
Keep your form display and form processing in the same script. This enables you to display error messages in the form and only move on to a new step upon a successfully processed form.
You simply need to test the request_method to determine if the form is needing to be processed or just displayed.
CGI works for learning perl, but look at CGI::Alternatives for live code.
The following is your form refactored with the first 2 guidelines in mind:
register.pl:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = CGI->new;
my $name = $q->param('name') // '';
my $username = $q->param('username') // '';
my $password = $q->param('password') // '';
# Process Form
my #errors;
if ( $q->request_method() eq 'POST' ) {
if ( $username =~ /^\s*$/ ) {
push #errors, "No username specified.";
}
if ( $password =~ /^\s*$/ ) {
push #errors, "No password specified.";
}
# Successful Processing
if ( !#errors ) {
# Obfuscate for display
$password =~ s/./*/g;
print $q->header();
print <<"END_PAGE";
<html>
<head><title>Success</title></head>
<body>
<p>Name = $name</p>
<p>Username = $username</p>
<p>Password = $password</p>
</body>
</html>
END_PAGE
exit;
}
}
# Display Form
print $q->header();
print <<"END_PAGE";
<html>
<head>
<link rel="stylesheet" type="text/css" href="tracker.css"/>
</head>
<body>
<div id="header">
<h1>Register New User</h1>
</div>
#{[ #errors ? join("\n", map "<p>Error: $_</p>", #errors) : '' ]}
<div id="content">
<form action="register.pl" method="POST">
<b>Name:</b> #{[ $q->textfield( -name => 'name' ) ]}<br>
<b>UserName:</b> #{[ $q->textfield( -name => 'username' ) ]}<br>
<b>Password:</b> #{[ $q->password_field( -name => 'password' ) ]}<br>
<input type="submit">
</div>
</body>
<html>
END_PAGE
__DATA__
I installed bugzilla 4.4 on linux server, and finished to define all groups and products.
I also defined kinds of OS and Hardware platforms. I am wondering if it is possible do define default OS or default hardware platform for specific Product.
I coudn't find any such option in the administartion page.
Thanks
You should do the next steps:
cd ..../bugzilla-4.4/template/en/custom/bug/create/
then you have to edit this file:
vi create.html.tmpl
inside the file you have to find this peace of code:
<tr>
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.op_sys, editable = 1,
value = default.op_sys %]
</tr>
replace it with this code:
<tr>
[% IF product.name == "ProductA" %]
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.op_sys, editable = 1,
value = "Android" %]
[% ELSIF product.name == "ProductB" %]
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.op_sys, editable = 1,
value = "iOS" %]
[% ELSE %]
[% INCLUDE bug/field.html.tmpl
bug = default, field = bug_fields.op_sys, editable = 1,
value = default.op_sys %]
[% END %]
</tr>
Imagine an HTML page that is a report with repetitive structure:
<html>
<body>
<h1>Big Hairy Report Page</h1>
<div class="customer">
<div class="customer_id">001</div>
<div class="customer_name">Joe Blough</div>
<div class="customer_addr">123 That Road</div>
<div class="customer_city">Smallville</div>
<div class="customer_state">Nebraska</div>
<div class="order_info">
<div class="shipping_details">
<ul>
<li>Large crate</li>
<li>Fragile</li>
<li>Express</li>
</ul>
</div>
<div class="order_item">Deluxe Hoodie</div>
<div class="payment">35.95</div>
<div class="order_id">000123456789</div>
</div>
<div class="comment">StackOverflow rocks!</div>
</div>
<div class="customer">
<div class="customer_id">002</div>
.... and so forth for a list of 150 customers
This kind of report page appears often. My goal is to extract each customer's related information into some reasonable data structure using HTML::TreeBuilder::XPath.
I know to do the basics and get the file read into $tree. But how can one concisely loop through that tree and get associated clusters of information per each customer? How, for example, would I create a list of address labels sorted by customer number based on this information? What if I want to sort all my customer information by state?
I'm not asking for the whole perl (I can read my file, output to file, etc). I just need help understanding how to ask HTML::TreeBuilder::XPath for those bundles of related data, and then how to dereference them. If it's easier to express this in terms of an output statement (i.e., Joe Blough ordered 1 Deluxe Hoodie and left 1 comment) then that's cool, too.
Thank you very much for those of you who tackle this one, it seems a bit overwhelming to me.
This will do what you need.
It starts by pulling all the <div class="customer"> elements into array #customers and extracting the information from there.
I have taken your example of the address label, sorted by the customer number (by which I assume you mean the field with class="customer_id"). All of the address values are pulled from the array into the hash %customers, keyed by the customer ID and the name of the element class. The information is then printed in the order of the ID.
use strict;
use warnings;
use HTML::TreeBuilder::XPath;
my $tree = HTML::TreeBuilder::XPath->new_from_file('html.html');
my #customers = $tree->findnodes('/html/body/div[#class="customer"');
my %customers;
for my $cust (#customers) {
my $id = $cust->findvalue('div[#class="customer_id"]');
for my $field (qw/ customer_name customer_addr customer_city customer_state /) {
my $xpath = "div[\#class='$field']";
my $val = $cust->findvalue($xpath);
$customers{$id}{$field} = $val;
}
}
for my $id (sort keys %customers) {
my $info = $customers{$id};
print "Customer ID $id\n";
print $info->{customer_name}, "\n";
print $info->{customer_addr}, "\n";
print $info->{customer_city}, "\n";
print $info->{customer_state}, "\n";
print "\n";
}
output
Customer ID 001
Joe Blough
123 That Road
Smallville
Nebraska
use HTML::TreeBuilder::XPath;
...
my #customers;
my $tree = HTML::TreeBuilder::XPath->new_from_content( $mech->content() );
foreach my $customer_section_node ( $tree->findnodes('//div[ #class = "customer" ]') ) {
my $customer = {};
$customer->{id} = find_customer_id($customer_section_node);
$customer->{name} = find_customer_name($customer_section_node);
...
push #customers, $customer;
}
$tree->delete();
sub find_customer_id {
my $node = shift;
my ($id) = $node->findvalues('.//div[ #class = "customer_id" ]');
return $id
}
I'll use XML::LibXML since it's faster and I'm familiar with it, but it should be pretty straightforward to convert what I post to from XML::LibXML to HTML::TreeBuilder::XPath if you so desire.
use XML::LibXML qw( );
sub get_text { defined($_[0]) ? $_[0]->textContent() : undef }
my $doc = XML::LibXML->load_html(...);
my #customers;
for my $cust_node ($doc->findnodes('/html/body/div[#class="customer"]')) {
my $id = get_text( $cust_node->findnodes('div[#class="customer_id"]') );
my $name = get_text( $cust_node->findnodes('div[#class="customer_name"]') );
...
push #customers, {
id => $id,
name => $name,
...
};
}
Actually, given the regularity of the data, you don't have to hardcode the field names.
use XML::LibXML qw( );
sub parse_list {
my ($node) = #_;
return [
map parse_field($_),
$node->findnodes('li')
];
}
sub parse_field {
my ($node) = #_;
my #children = $node->findnodes('*');
return $node->textContent() if !#children;
return parse_list($children[0]) if $children[0]->nodeName() eq 'ul';
return {
map { $_->getAttribute('class') => parse_field($_) }
#children
};
}
{
my $doc = XML::LibXML->load_html( ... );
my #customers =
map parse_field($_),
$doc->findnodes('/html/body/div[#class="customer"]');
...
}