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

Say you have a hash like the following...
$our_hash = { 'Cows' => { 'Brown' => undef, 'Green' => undef, 'Strawberry' => { 'Spotted' => undef, 'Solid' => undef }, 'Orange' => undef }, 'Dogs' => { 'Purple' => { 'Spotted' => undef, 'Solid' => undef } } };
Each key in this hash can have any number of child hashes, which really complicates things :|

My problem is, as the topic states, that I need to be able to write this hash in HTML format :)

It needs to be printed out like the following:

- Cows - Brown - Green - Strawberry - Spotted - Solid - Orange - Dogs - Purple - Spotted - Solid
Please help, I dont really know where to start (Data::Dumper maybe?)!

Replies are listed 'Best First'.
•Re: Hash to HTML display
by merlyn (Sage) on Jul 18, 2002 at 07:16 UTC
    Not nicely indented, but close enough:
    sub hash_to_html { my $x = shift; return "" unless ref($x) eq "HASH"; return "<ul>".join("", map { "<li>$_\n".hash_to_html($x->{$_}) } sor +t keys %$x )."</ul>\n"; } $our_hash = { 'Cows' => { 'Brown' => undef, 'Green' => undef, 'Strawberry' => { 'Spotted' => undef, 'Solid' => undef }, 'Orange' => undef }, 'Dogs' => { 'Purple' => { 'Spotted' => undef, 'Solid' => undef } } }; print hash_to_html($our_hash);
    which generates:
    <ul><li>Cows <ul><li>Brown <li>Green <li>Orange <li>Strawberry <ul><li>Solid <li>Spotted </ul> </ul> <li>Dogs <ul><li>Purple <ul><li>Solid <li>Spotted </ul> </ul> </ul>

    -- Randal L. Schwartz, Perl hacker

      Maybe I'm a little too picky here, but I want to encourage everybody to write "right" HTML; with XHTML in mind, of course.. I edited merlyn's code so it creates XHTML conform output:

      sub hash_to_html ($;$) { my $x = shift; my $i = @_ ? shift : ''; return "" unless ref ($x) eq "HASH"; return "$i<ul>\n" . join ('', map { "$i <li>$_</li>\n" . hash_to_html ($x->{$_}, "$ +i ") } sort (keys (%$x))) . "$i</ul>\n"; }

      Which prints (with the input above):

      <ul> <li>Cows</li> <ul> <li>Brown</li> <li>Green</li> <li>Orange</li> <li>Strawberry</li> <ul> <li>Solid</li> <li>Spotted</li> </ul> </ul> <li>Dogs</li> <ul> <li>Purple</li> <ul> <li>Solid</li> <li>Spotted</li> </ul> </ul> </ul>

      Regards,
      -octo

        No, you've got the /LI in the wrong place. Change
        join ('', map { "$i <li>$_</li>\n" . hash_to_html ($x->{$_}, "$i " +) } sort (keys (%$x)))
        to
        join ('', map { "$i <li>$_\n" . hash_to_html ($x->{$_}, "$i ")."$i + </li>" } sort (keys (%$x)))

        -- Randal L. Schwartz, Perl hacker

        but I want to encourage everybody to write "right" HTML
        Hmm. You are aware that in HTML, the close tag on an LI element is completely optional, and still validates? So it's "right" HTML. Truly. It's not tag soup with error correction. It really is "right" HTML.

        Surely it's not XHTML, but the rules are different there. No optional close tags, for example. And all your attributes must be quoted. And a few other miscellaneous things.

        But the original request was for HTML, so I kept my version of the program simple.

        -- Randal L. Schwartz, Perl hacker

      Thanks a ton!

      I don't know what I'd do without you guys :)

Re: Hash to HTML display
by grummerX (Pilgrim) on Jul 18, 2002 at 10:30 UTC
    Merlyn has already posted a better solution, but in the interest of TMTOWTDI here's one using Data::Dumper:
    #!c:/perl/bin/perl -w use strict; use Data::Dumper; my $our_hash = { 'Cows' => { 'Brown' => undef, 'Green' => undef, 'Strawberry' => { 'Spotted' => undef, 'Solid' => undef }, 'Orange' => undef }, 'Dogs' => { 'Purple' => { 'Spotted' => undef, 'Solid' => undef } } }; $Data::Dumper::Quotekeys = 0; for (Dumper $our_hash){ s/\$VAR1 = //; s/{/<UL>/g; s/}/<\/UL>/g; s/(undef)?[,;]?\n/\n/g; s/\n *([^\n]*)=>/\n<LI>$1<\/LI>/g; print; }
    Not really pretty or robust, but it works with a simple hash like your sample ... you'd need to flesh it out a little if you wanted it to be able to handle quotes, etc. Like I said, Merlyn's solution is better, but I couldn't resist giving this a try...

    Update: Added closing </LI> tag per flocto's encouragement.

    -- grummerX

Re: Hash to HTML display
by vladb (Vicar) on Jul 18, 2002 at 07:54 UTC
    You could make use of a recursive traverse subroutine like so:
    use CGI; my $cgi = new CGI; my $our_hash = { 'Cows' => { 'Brown' => undef, 'Green' => undef, 'Strawberry' => { 'Spotted' => undef, 'Solid' => undef }, 'Orange' => undef }, 'Dogs' => { 'Purple' => { 'Spotted' => undef, 'Solid' => undef } } }; use Data::Dumper; print $cgi->header(); print "<html><title>Page Title</title><body>"; #print Dumper($our_hash); sub traverse_hash { my $ahash = $_[0]; my $r; print "<ul>\n"; for (keys %$ahash) { $r = $ahash->{$_}; print "<li> $_\n"; traverse_hash($r) if (ref $r eq 'HASH'); } print "</ul>"; } $DB::single = 1; print traverse_hash($our_hash); $DB::single = 1; print "</body></html>";
    Which yields this output:
    Content-Type: text/html; charset=ISO-8859-1 <html><title>Page Title</title><body><ul> <li> Dogs <ul> <li> Purple <ul> <li> Spotted <li> Solid </ul></ul><li> Cows <ul> <li> Green <li> Brown <li> Strawberry <ul> <li> Spotted <li> Solid </ul><li> Orange </ul></ul>1</body></html>
    Update: darn! merlyn beat me to it ;-)...

    _____________________
    # Under Construction
      Using CGI.pm and not using ul() and li()? Here is a version that does, and i will admit that i peeked and am using merlyn's form for the recursive subroutine. Temptation--
      use CGI qw(ul li); use CGI::Pretty; sub hash2html { my $r = shift; return '' unless ref $r eq 'HASH'; return ul(li[map {$_.hash2html($r->{$_})} sort keys %$r]); }

      jeffa

      L-LL-L--L-LL-L--L-LL-L--
      -R--R-RR-R--R-RR-R--R-RR
      B--B--B--B--B--B--B--B--
      H---H---H---H---H---H---
      (the triplet paradiddle with high-hat)
      
Re: Hash to HTML display
by Anonymous Monk on Jul 18, 2002 at 10:24 UTC
    Well, now I have a sight change in my problem.

    The array @path needs to keep a record of all the keys it went through to get to level it is on the tree. Below I got this kind of working. The array @path needs to be poped again if the sub is on the last item in a group. I can't explain this very well at all so you might want to try the sub out yourself (with the above hash) :)

    { my @path; sub hash_gen { my $cats = shift; unless (ref $cats eq 'HASH') { pop @path; return undef; } my ($output, $level) = ('', 0); foreach my $cat (sort {uc($a) cmp uc($b)} keys % $cats) { push @path, $cat; $output .= '<li>' . join(' -> ', @path) . "</li>\n"; pop @path; $output .= hash_gen($cats->{$cat}) . "\n"; } return "<ul>$output</ul>"; } }
      Oh, opps! I got it working! I just had to move the pop in the foreach one line down :)
Re: Hash to HTML display
by Anonymous Monk on Jul 18, 2002 at 10:56 UTC
    I tried this script 
    
    ---
    use Data::Dumper;
    
    $our_hash = {
        'Cows' => {
            'Brown' => undef,
            'Green' => undef,
            'Strawberry' => {
                'Spotted' => undef,
                'Solid' => undef
            },
            'Orange' => undef
        },
        'Dogs' => {
            'Purple' => {
                'Spotted' => undef,
                'Solid' => undef
            }
        }
    };
    
    print Dumper(\$our_hash);
    ---
    
    and it prints out what you want
    
    ---
    perl test.pl
    $VAR1 = \{
                'Dogs' => {
                            'Purple' => {
                                          'Spotted' => undef,
                                          'Solid' => undef
                                        }
                          },
                'Cows' => {
                            'Green' => undef,
                            'Brown' => undef,
                            'Strawberry' => {
                                              'Spotted' => undef,
                                              'Solid' => undef
                                            },
                            'Orange' => undef
                          }
              };
    ----
    
    This means you have to install Data::Dumper, but isn't it
    allready there, e.g. at <yourPerlDir>/lib/Data/Dumper.pm?
    
    Hagen
Why a hash?
by simeon2000 (Monk) on Jul 18, 2002 at 17:12 UTC
    ... pardon my asking, but if each of these hash keys don't really seem to be storing anything, would it not be better to make a large multidimensional array instead of a multidimensional hash? I think it'd be more compact in memory and such as well.

    Of course, if you need to be able to traverse that hash for other reasons instead of just printing it, then never mind.

Re: Hash to HTML display
by Anonymous Monk on Jul 18, 2002 at 17:40 UTC
    $our_hash = { 'Cows' => { 'Brown' => undef, 'Green' => undef, 'Strawberry' => { 'Spotted' => undef, 'Solid' => undef }, 'Orange' => undef }, 'Dogs' => { 'Purple' => { 'Spotted' => undef, 'Solid' => undef } } }; &print_hash_text(0, %$our_hash); sub print_hash_text { my ($indent, %h) = @_; foreach (keys %h) { print ' 'x$indent,'-', $_, "\n"; if (%{$h{$_}}) { print &print_hash_text($indent+1,%{$h{$_}}); } } } sub print_hash_html { my ($indent, %h) = @_; foreach (keys %h) { print '&nbsp;'x$indent,'-', $_, "<br>\n"; if (%{$h{$_}}) { print &print_hash_html($indent+1,%{$h{$_}}); } } }
    mi_cuenta@yahoo.com