http://qs1969.pair.com?node_id=143791

In XSLT vs Templating?, I asked fellow monks for their opinion on using either a templating solution, like Template Toolkit 2, for the processing of output from a DB or similar prior to page display, or using XML output along with XSLT to work up the page display. Both have benefits and disadvantages, and no real forerunner came out of that discussion per se, beyond giving everyone else food for thought.

Now, I've been playing with XSLT over the last few days, and I thought that I could use a quick test to bookmark which methods might be better than others. Thus, I took 6 different ways one could work up output from a database that typically would be displayed as a web page, and timed the results to see which is the most efficient.

The database is Postgres, and I am using the monk locations as compiled by jcwren to fill it. The application that I do is to call the following query on the db:
SELECT id, name, xp, lat, long FROM monks ORDER BY lat LIMIT 25
The output then must be compiled into a table, as demonstrated below:

16309j.a.p.h.616-47-45
22313puck179-41.3174.783333
65703rob_au3136-37.816667144.95
116014pjf827- 37.6725144.843333
...and so forth.

The 6 methods that I tried were:

  • Strict use of DBI and the builtin print command
  • Use of DBI and CGI's generation functions, after massaging the data into a form usable for these calls
  • Use of DBI and TT2 with no extra modules to process the data (Do note that with all the TT2-based methods, I used the non-XS variable cache, partially because the XS one appears to be broken in the current version)
  • Use of XML::Generator::DBI, TT2, and TT2's XML::Simple plugin to parse the XML. Note that because of the way XML::Simple read in the data, the order of the data was not explicitly kept, and a sort statement was required in the template file (and even then, it sorted on text, not numbers)
  • Use of XML::Generator::DBI, TT2, and TT2's XML::XPath plugin to parse the XML
  • Use of XML::Generator::DBI and XML::LibXSLT to transform the XML. I do realize that there are other XML/XSLT engines out there, and strictly speaking, this method employs non-pure Perl parts (as it piggybacks on GNOME's libxslt library), but this is still not an unreasonable test to perform.
Note in the code below that I tried to prep each method as much as possible before running the tests, including prepping the TT2 variable cache, creating the DBI handle, creating the XML parser, etc. The only thing that each subroutine should do is execute the SQL statement, process the data, and print it out to a target (here being /dev/null since I didn't want to flood my fs with a hugh file of the same text over and over).

In addition to the code, I've included the various templates that I used for the TT2 and XSLT methods.

The results from timing 50 runs a piece of each method are below (Box is Linux, perl 5.6.1, 200Mhz with 128Megs Ram):

Benchmark: timing 50 iterations of DBI and CGI, DBI and Print, DBI and + TT2, XML and TT2/Simple, XML and TT2/XPath, XML and XSLT... DBI and CGI: 5 wallclock secs ( 2.02 usr + 0.02 sys = 2.04 CPU) @ 2 +4.51/s (n=50) DBI and Print: 2 wallclock secs ( 0.23 usr + 0.02 sys = 0.25 CPU) @ + 200.00/s (n=50) (warning: too few iterations for a reliable count) DBI and TT2: 13 wallclock secs (10.91 usr + 0.07 sys = 10.98 CPU) @ +4.55/s (n=50) XML and TT2/Simple: 56 wallclock secs (43.87 usr + 0.23 sys = 44.10 C +PU) @ 1.13/s (n=50) XML and TT2/XPath: 154 wallclock secs (144.17 usr + 0.73 sys = 144.90 + CPU) @ 0.35/s (n=50) XML and XSLT: 27 wallclock secs (24.23 usr + 0.11 sys = 24.34 CPU) @ + 2.05/s (n=50) XML and TT2/XPath 0.345/s -- -70% -83% -92% -99% -100% XML and TT2/Simple 1.13/s 229% -- -45% -75% -95% -99% XML and XSLT 2.05/s 495% 81% -- -55% -92% -99% DBI and TT2 4.55/s 1220% 302% 122% -- -81% -98% DBI and CGI 24.5/s 7003% 2062% 1093% 438% -- -88% DBI and Print 200/s 57860% 17540% 9636% 4292% 716% --
To no surpise, straightforward solutions overwhelm those that do extra things (like generating XML). Of course, when you look at the code, and think of separation of presentation and content, this is only a CPU efficiency, and not a development efficiency.

Typically, the XML solutions fared worse than the straight DBI solutions. Of course, there is probably overhead in the XML::Generator::DBI methods, however, given the fair distance between the two XML/TT2 solutions and XML/XSLT, compared with the DBI solutions, this isn't probably very large. Instead, probably much of the overhead for the XML/TT2 solutions is the fact that they are using pure perl libraries to tackle the XML, and that means a lot of inching forward regexs. With the XML/XSLT solution, we have a precompiled library, and while there's probaby inch-by-inch regexes there as well, the lack of time needed to recompile the code is very significant.

What I do find surprising and yet satisfactory is that performace-wise, the XML/XSLT solution isn't terribly off from the best solution in terms of bother CPU and development efficiency, the DBI/TT2 solution. Yes, we are looking at what appears to be between 200-250% increase in time per cycle for the XSLT solution, but not insurmountable. For the purposes that I would be considering XML for (non-commerical, low traffic site), that would certainly acceptable.

Certainly, this is a simple test, but I think it does show that any solution of content generation that tried to separate content from the logic is going to run into CPU bottlenecks, obviously. And here, in the decision to go with a template solution vs XML, I think both are still up in the air, though templating has the edge in CPU usage.

However, I do read about many benefits of XSLT. For example, the process that I transform the data here is as follows:

XSLT Transform DBI --> XML -----------------> XHTML
which is about as easy as you can get. But because transforms can go to any space, not just XHTML, it's possible to insert special steps along the way to get where you need. For example, if I was going to allow to customization in which data was to be present (a 'fast' view vs a 'full' view) I could simply add a few more steps to this process to get to that:
CGI-->userinfo -----| | Data Pruning XSLT DBI--> DataXML -----+--> XML ------------------> XML2 | XHTML Transform | | V XHTML
whereby the data pruning transformation would take the various <monk> elements, and using info from the userinfo, convert those into a new <tablerow> tag; at this point, regardless of the customization of the user, we have several <tablerow>s in the XML. Then we simply plug those into the XHTML transformer, taking the <tablerow>s into alternating colored <tr>s. Other customizations can be done here as well, and furthermore, other levels of transforms can be easily chained to make a complex but easily servicable web page. While you can coerce TT2 to do a similar functionality, it's just not as simple as chaining XSLT transformations.

So the end result of this study is that both Templating and XSLT have their place. If CPU efficiency is absolutely critical, it's much better to go with TT2 (or any other template solution). On the other hand, if you want to have a lot more flexibility in the transformations, using XML/XSLT may be the right way, but as pointed out in recent threads, this is still new to a lot of people and may take a lot of getting used to.

And now for the code:

test.pl - The actual program
#!/usr/bin/perl -w use strict; use DBI; use CGI qw/-no_xhtml :standard/; use XML::Generator::DBI; use XML::Handler::YAWriter; use XML::LibXML; use XML::LibXSLT; use Template; use Data::Dumper; use Benchmark qw( cmpthese ); $Template::Config::STASH = 'Template::Stash'; my $dbh = DBI->connect( "dbi:Pg:dbname=monksdb", "", "" ) or die $DBI::errstr; my $query = "SELECT id, name, xp, lat, long FROM monks ORDER BY lat LI +MIT 25"; my $sth = $dbh->prepare( $query ) or die $DBI::errstr; my $ya = XML::Handler::YAWriter->new( AsString => 1 ); my $generator = XML::Generator::DBI->new( Handler => $ya, dbh => $dbh, RowElement => "monk" ); my $tt2 = Template->new; my $tt2_nonXML = "template1.tt2"; my $tt2_XML = "template2.tt2"; my $tt2_XPath = "template3.tt2"; my $parser = new XML::LibXML; my $xslt = new XML::LibXSLT; my $sheet = "xslt_sheet.xsl"; my $slt = $parser->parse_file( $sheet ); my $stylesheet = $xslt->parse_stylesheet( $slt ); open FILE, ">/dev/null" or die "Cannot write out: $!"; my $target = \*FILE; cmpthese( 50, { "DBI and Print" => \&generate_from_straight_dbi_and_print, "DBI and CGI" => \&generate_from_straight_dbi_and_cgi, "DBI and TT2" => \&generate_from_straight_dbi_and_tt2, "XML and TT2/Simple" => \&generate_from_xml_and_tt2_and_xmlsimp +le, "XML and TT2/XPath" => \&generate_from_xml_and_tt2_and_xpath, "XML and XSLT" => \&generate_from_xml_and_xslt } ); close FILE; # Here, we use straight DBI calls and print calls to mark up # the table sub generate_from_straight_dbi_and_print { # my $target = shift; $sth->execute() or die $DBI::errstr; print $target "Content-Type: text/html\n\n"; print $target "<html><body><table>\n"; my $colorrow = 0; while ( my ( $id, $name, $xp, $lat, $long ) = $sth->fetchrow_array() + ) { $colorrow = !$colorrow; my $color = ( $colorrow ) ? "#FFFFFF" : "#D0D0FF"; print $target <<ROW; <tr> <td bgcolor="$color">$id</td> <td bgcolor="$color">$name</td> <td bgcolor="$color">$xp</td> <td bgcolor="$color">$lat</td> <td bgcolor="$color">$long</td> </tr> ROW ; } print $target "</table></body></html>"; } # Here, we group the results as to make it easier for CGI # to print out (avoiding large HERE docs...) sub generate_from_straight_dbi_and_cgi { # my $target = shift; $sth->execute() or die $DBI::errstr; my @data; while ( my @row = $sth->fetchrow_array() ) { push @data, \@row; } my $colorrow = 0; print $target header('text/html'), start_html, table( map { $colorrow = !$colorrow; my $color = ( $colorrow ) ? "#FFFFFF" : "#D0D0FF"; Tr( td( {-bgcolor=>$color}, $_ ) ) } @data ), end_html; } # Here, we pass the results to Template Toolkit for printing sub generate_from_straight_dbi_and_tt2 { # my $target = shift; $sth->execute() or die $DBI::errstr; my @data; while ( my @row = $sth->fetchrow_array() ) { push @data, \@row; } print $target header; $tt2->process( $tt2_nonXML, { monks => \@data }, $target ) or die $tt2->error(),"\n"; } # Use TT2 again, but now pass it XML and use the XPath module # for parsing sub generate_from_xml_and_tt2_and_xmlsimple { # my $target = shift; my $xml = $generator->execute( $query ); print $target header; $tt2->process( $tt2_XML, { results => $xml }, $target ) or die $tt2->error(), "\n"; } # Use TT2 again, but now pass it XML and use the XPath module # for parsing sub generate_from_xml_and_tt2_and_xpath { # my $target = shift; my $xml = $generator->execute( $query ); print $target header; $tt2->process( $tt2_XPath, { results => $xml }, $target ) or die $tt2->error(), "\n"; } # Use LibXML/LibXSLT to parse the results sub generate_from_xml_and_xslt { # my $target = shift; my $xml = $generator->execute( $query ); print $target header; my $source = $parser->parse_string( $xml ); my $results = $stylesheet->transform( $source ); print $target $stylesheet->output_string( $results ); }
template1.tt2 - The straight-forward TT2 Template
[% colorrow = 0 %] <html> <body> <table> [% FOREACH monkinfo = monks %] <tr> [% colorrow = !colorrow %] [% IF colorrow %] [% color = "#FFFFFF" %] [% ELSE %] [% color = "#D0D0FF" %] [% END %] [% FOREACH item = monkinfo %] <td bgcolor="[% color %]"> [% item %] </td> [% END %] </tr> [% END %] </table> </body> </html>
template2.tt2 - The TT2/XML::Simple Template
[% USE xml = XML.Simple( results ) %] [% xml %] [% colorrow = 0 %] <html> <body> <table> [% orderedmonks = xml.select.monk.sort(keys.lat) %] [% FOREACH monkinfo = orderedmonks %] <tr> [% colorrow = !colorrow %] [% IF colorrow %] [% color = "#FFFFFF" %] [% ELSE %] [% color = "#D0D0FF" %] [% END %] <td bgcolor="[% color %]"> [% xml.select.monk.$monkinfo.id %] </td> <td bgcolor="[% color %]"> [% monkinfo %] </td> <td bgcolor="[% color %]"> [% xml.select.monk.$monkinfo.xp %] </td> <td bgcolor="[% color %]"> [% xml.select.monk.$monkinfo.lat %] </td> <td bgcolor="[% color %]"> [% xml.select.monk.$monkinfo.long %] </td> </tr> [% END %] </table> </body> </html>
template3.tt2 - The TT2/XPath Template
[% USE xpath= XML.XPath( results ) %] [% colorrow = 0 %] <html> <body> <table> [% FOREACH monk = xpath.findnodes('/database/select/monk') %] <tr> [% colorrow = !colorrow %] [% IF colorrow %] [% color = "#FFFFFF" %] [% ELSE %] [% color = "#D0D0FF" %] [% END %] <td bgcolor="[% color %]"> [% xpath.find('id',monk) %] </td> <td bgcolor="[% color %]"> [% xpath.find('name',monk) %] <td> <td bgcolor="[% color %]"> [% xpath.find('xp',monk) %] </td> <td bgcolor="[% color %]"> [% xpath.find('lat',monk) %] </td> <td bgcolor="[% color %]"> [% xpath.find('long',monk) %] </td> </tr> [% END %] </table> </body> </html>
xslt_sheet.xsl - The XSLT Tranform
<xsl:stylesheet version = '1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> <xsl:template match="/database/select"> <table> <xsl:for-each select="//monk"> <tr> <td> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#D0D0FF</xsl:attribute> </xsl:if> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#FFFFFF</xsl:attribute> </xsl:if> <xsl:value-of select="id"/> </td> <td> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#D0D0FF</xsl:attribute> </xsl:if> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#FFFFFF</xsl:attribute> </xsl:if> <xsl:value-of select="name"/> </td> <td> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#D0D0FF</xsl:attribute> </xsl:if> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#FFFFFF</xsl:attribute> </xsl:if> <xsl:value-of select="xp"/> </td> <td> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#D0D0FF</xsl:attribute> </xsl:if> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#FFFFFF</xsl:attribute> </xsl:if> <xsl:value-of select="lat"/> </td> <td> <xsl:if test="position() mod 2 = 0"> <xsl:attribute name="bgcolor">#D0D0FF</xsl:attribute> </xsl:if> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#FFFFFF</xsl:attribute> </xsl:if> <xsl:value-of select="long"/> </td> </tr> </xsl:for-each> </table> </xsl:template> </xsl:stylesheet>

-----------------------------------------------------
Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
"I can see my house from here!"
It's not what you know, but knowing how to find it if you don't know that's important

Replies are listed 'Best First'.
Re: XSLT vs Templating, Part 2
by Matts (Deacon) on Feb 07, 2002 at 10:37 UTC
    Wow, nice set of tests!

    It's a little unfair on the XSLT stuff, since you're going from DBI to XML to String to XML to XSLT. I need to update XML::Generator::DBI to output SAX2 (which I'm doing right now), after which you'll be able to go direct from XML::Generator::DBI into XML::LibXML::SAX::Builder, and skip the string phase completely.

    I may even run the tests to see how it fares for me. I'll reply here after I've made those updates!

      Alternatively you can cause XML::Generator::DBI to use the XML::Handler::BuildDOM as a handler in order to generate a DOM object that can then be fed to XML::XSLT again cutting out the intermediate reparse of the XML.

      I am toying with the idea of providing a utility to create modules that encapsulate preparsed DOM objects from XSLT so that step can be taken out as well if anyone is interested

      /J\

      OK, I hacked on this a bit. It seems that almost without a doubt the bottleneck is in XML::Generator::DBI (I added a tiny benchmark to the single sub, and generate took 11u, xslt took 1u and output took 1u. So I'm going to update it to SAX2, and make sure it goes faster. There's a lot of cruft in there, and I've learnt some about how to make DBI calls faster since I wrote it.

      You can make things faster by simply changing your generator to:

      my $generator = XML::Generator::DBI->new( Handler => XML::LibXML::SAX::Builder->new(), dbh => $dbh, RowElement => "monk" );
      Which seems to work (even without updating XML::Generator::DBI). Then you have to add ->toString to your calls for the TT level stuff. It makes all three faster - by about 40%.
      Ah, it makes a big difference if you also turn off indenting on XML::Generator::DBI (pass NoIndent => 1 to new()). I think that should be the default!
Re: XSLT vs Templating, Part 2
by gellyfish (Monsignor) on Feb 07, 2002 at 14:41 UTC

    I do realize that there are other XML/XSLT engines out there, and strictly speaking, this method employs non-pure Perl parts

    I think that XML::XSLT would have come out very badly in speed terms - I have been favouring ease of maintenance and consistency in my recent refactoring with little concern for speed and efficiency :) Also of course it does ultimately depend on XML::Parser which has a non-perl dependency

    /J\

      I didn't realize this existed as well; however, I would suspect that in the DBI->XML->XSLT path, the choice of which parser or XSLT library that you use is going to have some, but not a significant impact on the overall speed, assuming that, as with LibXSLT and XML::Parser, there's a non-perl component. As demonstrated by the two pure-perl routes, any significant processing of XML is going to need a boost by having pre-compiled code available for at least parsing the system.

      However, I think I'll add the XML::XSLT case as well as skipping the DOM->string->DOM conversion that I do as gellyfish mentioned in reply to Matts response above, as additional tests, just for completeness. (I could also probably improve the xslt sheet itself, for the row coloring code doesn't seem to be overly efficient).

      I also understand that GNOME's LibXML (which XML::LibXML uses) is not fully complient with recent W3C specs, so that may be a notch against it, as I'd expect a fully complient library to be a bit more rigorous and thus more CPU demanding.

      -----------------------------------------------------
      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      "I can see my house from here!"
      It's not what you know, but knowing how to find it if you don't know that's important

        There are no known areas where LibXML/LibXSLT are not fully compliant with the relevant W3C specs (they're even authored by the W3C!). If you do know of areas where they fall down on this please do let me know and I'll forward the info on to Daniel.
Re: XSLT vs Templating, Part 2
by perrin (Chancellor) on Feb 07, 2002 at 17:16 UTC
    Two general DBI performance tips:

    1) Use prepare_cached() when you can. In this case, you can.
    2) Use bind_columns(). It's faster and saves some memory.

      I agree, in general you'd use these, but to be fair to XML::Generator::DBI, which to the best of my knowledge you cannot take advantage of these features, I kept them off. Given the baseline performance of the DBI/print system, which did fifty iters without blinking, I would suspect these tricks would only slighty improve the non-XML systems (which all were faster to start with).

      -----------------------------------------------------
      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      "I can see my house from here!"
      It's not what you know, but knowing how to find it if you don't know that's important

        Actually I just uploaded 0.02 of XML::Generator::DBI to CPAN, which can take a prepared statement instead of as string as a parameter to execute(). And it already did the bind_columns trick. It also now makes Indent off the default, so it should be about as fast as I can make it.

        The other thing I did was change your '//monks' in your XSLT to just 'monks', which is much faster, since it doesn't examine every single child node.

        Technically, you could do the XML side of things without XML::Generator::DBI, and it would be faster since you could code if for your specific case rather than making a general DBI-->XML tool. Then you could take full advantage of all DBI speed tweaks. However, you would probably end up writing a lot more code.

        When comparing performance, I do think that being unable to fully use DBI's performance features matters. Personally though, I would be more interested in knowing how easy it is to code the different approaches, since they all seem to perform well enough.

(Updates) Re: XSLT vs Templating, Part 2
by Masem (Monsignor) on Feb 08, 2002 at 01:32 UTC
    I've added the various suggestions in this thread: using bind_columns and prepare_cached from perrin, the use of XML::LibXML::SAX::Builder to avoid creating a temporary string, and using matts' new XML::Generator::DBI version to take advantage of the sth handler. I've also added code that uses XML::XSLT, which surprising is VERY bad at least as I've used it (did I use it wrong? I couldn't get a pure XML DOM document to work with it...)

    The results are:

    XML and XSLT, String Intermediate, XML::XSLT 0.159/s + -- -62% -88% -94% -95% -96% -99% + -100% XML and TT2/XPath 0.422/s + 166% -- -67% -84% -87% -90% -98% - +100% XML and TT2/Simple 1.29/s + 713% 205% -- -51% -60% -69% -95% - +99% XML and XSLT, String Intermediate, LibXSLT 2.66/s + 1573% 529% 106% -- -17% -36% -90% - +98% XML and XSLT, XML Intermediate, LibXSLT 3.20/s + 1915% 657% 148% 20% -- -23% -87% - +98% DBI and TT2 4.13/s + 2505% 879% 221% 56% 29% -- -84% - +98% DBI and CGI 25.4/s + 15931% 5924% 1873% 858% 696% 516% -- - +85% DBI and Print 172/s + 108526% 40717% 13269% 6391% 5291% 4071% 578% - +-
    Nothing surprising about the order, but as you can see, with the various improvements, the behavior of LibXSLT getting close to that of DBI and TT2. There may be other improves that one could do, but as it stands, I think some of the benefits of using XSLT can outweight the small performance penalty compared to TT2 usage.

    Except for changing the XSLT template as Matts indicated, the rest of the files remain unchanged. Here's the updated test code, however...

    #!/usr/bin/perl -w use strict; use DBI; use CGI qw/-no_xhtml :standard/; use XML::Generator::DBI; use XML::Handler::YAWriter; use XML::LibXML::SAX::Builder; use XML::LibXML; use XML::LibXSLT; use XML::XSLT; use Template; use Data::Dumper; use Benchmark qw( cmpthese ); $Template::Config::STASH = 'Template::Stash'; my $dbh = DBI->connect( "dbi:Pg:dbname=monksdb", "", "" ) or die $DBI::errstr; my $query = "SELECT id, name, xp, lat, long FROM monks ORDER BY lat LI +MIT 25"; my $sth = $dbh->prepare_cached( $query ) or die $DBI::errstr; my $ya = XML::Handler::YAWriter->new( AsString => 1 ); my $generator = XML::Generator::DBI->new( Handler => $ya, dbh => $dbh, RowElement => "monk" ); my $generator2 = XML::Generator::DBI->new( Handler => XML::LibXML::SAX::Builder->new(), dbh => $dbh, RowElement => "monk" ); my $tt2 = Template->new; my $tt2_nonXML = "template1.tt2"; my $tt2_XML = "template2.tt2"; my $tt2_XPath = "template3.tt2"; my $parser = new XML::LibXML; my $xslt = new XML::LibXSLT; my $sheet = "xslt_sheet.xsl"; my $slt = $parser->parse_file( $sheet ); my $stylesheet = $xslt->parse_stylesheet( $slt ); my $stylesheet2 = XML::XSLT->new( $sheet, warnings => 1 ); open FILE, ">/dev/null" or die "Cannot write out: $!"; my $target = \*FILE; cmpthese( 100, { "DBI and Print" => \&generate_from_straight_dbi_and_print, "DBI and CGI" => \&generate_from_straight_dbi_and_cgi, "DBI and TT2" => \&generate_from_straight_dbi_and_tt2, "XML and TT2/Simple" => \&generate_from_xml_and_tt2_and_xmlsimp +le, "XML and TT2/XPath" => \&generate_from_xml_and_tt2_and_xpath, "XML and XSLT, String Intermediate, LibXSLT " => \&generate_fro +m_xml_and_xslt_string, "XML and XSLT, XML Intermediate, LibXSLT" => \&generate_from_xm +l_and_xslt_xml, "XML and XSLT, String Intermediate, XML::XSLT" => \&generate_fr +om_xmlxslt_xml } ); close FILE; # Here, we use straight DBI calls and print calls to mark up # the table sub generate_from_straight_dbi_and_print { # my $target = shift; $sth->execute() or die $DBI::errstr; my ( $id, $name, $xp, $lat, $long ); $sth->bind_columns( \$id, \$name, \$xp, \$lat, \$long ); print $target "Content-Type: text/html\n\n"; print $target "<html><body><table>\n"; my $colorrow = 0; while ( $sth->fetch() ) { $colorrow = !$colorrow; my $color = ( $colorrow ) ? "#FFFFFF" : "#D0D0FF"; print $target <<ROW; <tr> <td bgcolor="$color">$id</td> <td bgcolor="$color">$name</td> <td bgcolor="$color">$xp</td> <td bgcolor="$color">$lat</td> <td bgcolor="$color">$long</td> </tr> ROW ; } print $target "</table></body></html>"; } # Here, we group the results as to make it easier for CGI # to print out (avoiding large HERE docs...) sub generate_from_straight_dbi_and_cgi { # my $target = shift; $sth->execute() or die $DBI::errstr; my ( $id, $name, $xp, $lat, $long ); $sth->bind_columns( \$id, \$name, \$xp, \$lat, \$long ); my @data; while ( $sth->fetch ) { push @data, [$id, $name, $xp, $lat, $long]; +} my $colorrow = 0; print $target header('text/html'), start_html, table( map { $colorrow = !$colorrow; my $color = ( $colorrow ) ? "#FFFFFF" : "#D0D0FF"; Tr( td( {-bgcolor=>$color}, $_ ) ) } @data ), end_html; } # Here, we pass the results to Template Toolkit for printing sub generate_from_straight_dbi_and_tt2 { # my $target = shift; $sth->execute() or die $DBI::errstr; my ( $id, $name, $xp, $lat, $long ); $sth->bind_columns( \$id, \$name, \$xp, \$lat, \$long ); my @data; while ( $sth->fetch ) { push @data, [$id, $name, $xp, $lat, $long]; +} print $target header; $tt2->process( $tt2_nonXML, { monks => \@data }, $target ) or die $tt2->error(),"\n"; } # Use TT2 again, but now pass it XML and use the XPath module # for parsing sub generate_from_xml_and_tt2_and_xmlsimple { # my $target = shift; my $xml = $generator->execute( $query ); print $target header; $tt2->process( $tt2_XML, { results => $xml }, $target ) or die $tt2->error(), "\n"; } # Use TT2 again, but now pass it XML and use the XPath module # for parsing sub generate_from_xml_and_tt2_and_xpath { # my $target = shift; my $xml = $generator->execute( $query ); print $target header; $tt2->process( $tt2_XPath, { results => $xml }, $target ) or die $tt2->error(), "\n"; } # Use LibXML/LibXSLT to parse the results sub generate_from_xml_and_xslt_string { # my $target = shift; my $xml = $generator->execute( $sth ); print $target header; my $source = $parser->parse_string( $xml ); my $results = $stylesheet->transform( $source ); print $target $stylesheet->output_string( $results ); } sub generate_from_xml_and_xslt_xml { # my $target = shift; my $xml = $generator2->execute( $sth ); print $target header; my $results = $stylesheet->transform( $xml ); print $target $stylesheet->output_string( $results ); } sub generate_from_xmlxslt_xml { # my $target = shift; my $xml = $generator->execute( $sth ); print $target header; print $target $stylesheet2->serve( $xml ); }

    -----------------------------------------------------
    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
    "I can see my house from here!"
    It's not what you know, but knowing how to find it if you don't know that's important

      I've also added code that uses XML::XSLT , which surprising is VERY bad at least as I've used it (did I use it wrong? I couldn't get a pure XML DOM document to work with it...)

      Heh, I discovered that there was an evil interaction between XML::Generator::DBI and XML::Handler::BuildDOM - I have sent patches to both Matt and Tim ;-}

      I'll update this later with an example of how you might do this when I get a minute

      Update: Matt has applied a fix to XML::Generator::DBI and this code now works as billed :

      #!/usr/bin/perl -w use strict; use XML::XSLT; use XML::Generator::DBI; use XML::Handler::BuildDOM; use XML::DOM; use DBI; my $ya = XML::Handler::BuildDOM->new(); my $dbh = DBI->connect("dbi:Informix:tdcusers",'','',{ChopBlanks => 1} +); my $generator = XML::Generator::DBI->new( Handler => $ya, dbh => $dbh ); my $style =<<EOFOO; <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" versi +on="1.0"> <xsl:output encoding = "iso-8859-1"/> <xsl:template match = "select"> <html> <head> <title> Test </title> </head> <body> <table> <xsl:for-each select="row"> <tr> <td> <xsl:value-of select="prefix" /> </td> <td> <xsl:value-of select="code" /> </td> <td> <xsl:value-of select="ctext" /> </td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet> EOFOO my $stylesheet = XML::XSLT->new($style); my $sql = 'select * from text_codes'; my $dom = $generator->execute($sql); $stylesheet->transform(Source => $dom); $stylesheet->toString();

      You will obviously want to use your own database and an appropriate stylesheet :)

      /J\