rje has asked for the wisdom of the Perl Monks concerning the following question:

Greetings monks,

I've been working on an interesting task and decided to ask for comments about it. I don't know if I have specific questions exactly, but I'm trying to do things as maintenance-friendly as possible and am willing to listen to suggestions.

The fuzzy picture is:

Given: a perl structure (hashrefs of hashrefs).

Produce: a suite of web pages.

No, I haven't fully fleshed out the solution, but I have completed several parts and I know how I need to finish it. I'm just wondering if I'm overlooking something useful.

As an example, here's a sample structure in YAML (the actual config file will be XML, but YAML is easy for me to develop with, and the config format is not the issue here):

--- Benefits: Desc: your benefits Subcategories: Employee Recognition: Url: http://blah.com/recognition/index.html topics: Employee Service Awards: Url: http://blah.com/recognition/employee_service.html General Recognition and Performance-based Awards: Url: http://blah.com/recognition/awards.html Equity Plans: Url: Overridden Regional Content URL! topics: Incentive Plan: Url: http://blah.com/equity_plans/incentive/override.html Url: http://blah.com/index.html Compensation: Subcategories: {} Url: http://blah.com/comp/index.html Corporate Policies: Subcategories: {} Url: http://blah.com/corp_policies/index.html Early Retirement: Subcategories: {} Url: http://blah.com/ero/index.html
The structure above is actually the output of a process I've written that merges in regional content to default content (using Hash::Merge to great effect), which is the main purpose of generating HTML in the first place -- to route the employee automatically to regional content, rather than forcing her to navigate her way between multiple default and regional content pages.

However, there is still a heirarchy of pages to be built: the user will see a page of category links, with subcategory links subordinated under them. Clicking on a category or subcategory will route that person to static content, so maybe I'm making the problem out to be harder than it really is.

Anyway, in order to make the pages edit-friendly, I'm also looking at using Text::Template or TTT, so that dynamic content is encapsulated within a template. This way, our non-technical folks can edit static content on the template.

For the curious, the subs I've been working on to create the HTML pages are here. This is absolutely Code In Progress, and is not the finished product by any means.

sub createPages { my $filename = shift; my $content = shift; my $desc = $content->{Desc}; my $url = $content->{Url}; my $subcontent = $content->{Regions} || $content->{Categories} || $content->{Subcategories} || $content->{Topics}; open OUT, ">$filename.html"; foreach my $key ( sort keys %$subcontent ) { print OUT buildSubject( $filename, $key, $subcontent->{$key} ); } close OUT; } sub buildSubject { my $filename = shift; my $name = shift; my $content = shift; my $desc = $content->{Desc}; my $url = $content->{Url}; my $subcontent = $content->{Regions} || $content->{Categories} || $content->{Subcategories} || $content->{Topics}; my @out = (); foreach my $key ( sort keys %$subcontent ) { my $url = $subcontent->{$key}->{Url}; push @out, "<a href='$url'>$key</a>\n" if $url; push @out, $key unless $url; } my $header = "<a href='$filename.$name.html'>$name</a>"; $header = "<a href='$url'>$name</a>" if $subcontent; print $subcontent; return "<h2>$header</h2>\n<h3>$desc</h3>\n", join ' | ', @out; }

UPDATE Per Holli's suggestion, here is a Template Toolkit prototype I'm working on for the main display page.
<html> <body> # INTERPOLATE => 1 [% FOREACH name = Categories.keys %] <h2><a href="[% Categories.$name.Url %]">$name</a></h2> <h3>[% Categories.$name.Desc %]</h3> [% FOREACH subname = Categories.$name.Subcategories.keys %] <a href="[% Categories.$name.$subname.Url %]">$subname</a> [% END %] [% END %] </body> </html>

Replies are listed 'Best First'.
Re: Generating HTML from a Hashref of Hashrefs
by holli (Abbot) on Oct 07, 2005 at 18:20 UTC
    I think you're better off using a Templating engine. For example to create a htmlfile filled by a perl data structure using the fabulous Template-Toolkit:

    script:
    use strict; use warnings; use Template; #you would normally take this from a file my $template = join "", <DATA>; #some sample data my %data = ( title => "My Albums", albums => [ { artist=>"Some Musician", songs=> [ "Some Song", "Some Other Song", ] }, { artist=>"DocMarten", songs=> [ "The Doc goes wild", "Heavy Shoes", ] } ] ); #create template object and execute template my $tt = Template->new(); $tt->process (\$template, \%data, "myfile.html"); __DATA__ <html> <head> <title>[% title %]</title> </head> <body> [% FOR album = albums %] <h1>[% album.artist %]</h1> [% FOR song = album.songs %] <h2>[% song %]<h2> [% END %] [% END %] </body>

    Output (myfile.html):
    <html> <head> <title>My Albums</title> </head> <body> <h1>Some Musician</h1> <h2>Some Song<h2> <h2>Some Other Song<h2> <h1>DocMarten</h1> <h2>The Doc goes wild<h2> <h2>Heavy Shoes<h2> </body>


    holli, /regexed monk/
      As a matter of fact, I was planning on using Text::Template. However, TTT (The Template Toolkit) has quite a bit of oomf behind it, what with that "for X = foo.bar" metaphor...
Re: Generating HTML from a Hashref of Hashrefs
by InfiniteSilence (Curate) on Oct 07, 2005 at 21:45 UTC
    I liked holli's post but I just didn't like the format of your input file. If you want structure that both a program and a human can work with, you need XML:

    Your data file, transformed a bit:

    <?xml version="1.0" ?> <BASENODE> <HEAD name="Benefits"> <Desc> your benefits</Desc> <Subcategories> <category name="Employee Recognition"> <Url> http://blah.com/recognition/index.html</Url> <topics> <topic name="Employee Service Awards"> <Url> http://blah.com/recognition/employee_service.html</Url +> </topic> <topic name="General Recognition and Performance-based Awards" +> <Url> http://blah.com/recognition/awards.html</Url> </topic> </topics> </category> <category name="Equity Plans"> <Url> Overridden Regional Content URL</Url> <topics> <topic name="Incentive Plan"> <Url> http://blah.com/equity_plans/incentive/override.html</ +Url> </topic> </topics> </category> </Subcategories> <Url> http://blah.com/index.html</Url> </HEAD> <HEAD name="Compensation"> <Subcategories /> <Url> http://blah.com/comp/index.html</Url> </HEAD> <HEAD name="Corporate Policies"> <Subcategories /> <Url> http://blah.com/corp_policies/index.html</Url> </HEAD> <HEAD name="Early Retirement"> <Subcategories /> <Url> http://blah.com/ero/index.html</Url> </HEAD> </BASENODE>

    Code using XML::Simple:

    #!/usr/bin/perl -w use strict; use XML::Simple; use Data::Dumper; local $/; open(H,'subcontent2.xml') or die $!; my $wholefile =<H>; close(H); my $xmlref = XMLin($wholefile, ForceArray=>['topic']); foreach(keys %{$xmlref->{'HEAD'}}){ if (!$xmlref->{HEAD}->{$_}->{Subcategories}->{category}){ print qq|GENERAL URL($_): | . $xmlref->{HEAD}->{$_}->{Url} . qq|\n +|; } else { foreach my $cat($xmlref->{HEAD}->{$_}->{Subcategories}->{category} +){ for (keys %$cat){ print qq|CATEGORY URL($_): | . $cat->{$_}->{Url} +. qq|\n|; foreach my $topics ( values %{$cat->{$_}->{topics} +}){ foreach(keys %{$topics}){ print qq|TOPIC URL($_): | . $topics->{$_}->{Url} . qq| +\n|; } } } } } } #print Dumper $xmlref; 1;

    Spits out:

    perl subcontent.pl GENERAL URL(Corporate Policies): http://blah.com/corp_policies/index. +html GENERAL URL(Compensation): http://blah.com/comp/index.html GENERAL URL(Early Retirement): http://blah.com/ero/index.html CATEGORY URL(Employee Recognition): http://blah.com/recognition/index +.html TOPIC URL(Employee Service Awards): http://blah.com/recognition/emplo +yee_service.html TOPIC URL(General Recognition and Performance-based Awards): http://b +lah.com/recognition/awards.html CATEGORY URL(Equity Plans): Overridden Regional Content URL TOPIC URL(Incentive Plan): http://blah.com/equity_plans/incentive/ove +rride.html

    What's my point? By using XML (possibly with a schema or a DTD) you will notice whether or not your nested data is well-formed. A tabbed text file as a data source is likely to cause you trouble down the road.

    Celebrate Intellectual Diversity

      I.S.:

      As I mentioned, the input structure was not the issue at hand; however, perhaps I did not make it clear enough that the targetted input structure is, in fact, XML, and yes we will be using XML::Simple. I am currently using YAML for development because I am very familiar with it.

      And in fact, I have encapsulated the configuration reading and writing, so you see, changing the input format really is of no consequence at all.