http://www.perlmonks.org?node_id=522117

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

I have what I thought was a fairly simple problem, but after several days of pondering I can't find a good solution. I have an array the contains a tree. It's a flat array, containing the results of a depth first iteration over the tree. Think find(1) read in to an array. Now each element in the array also contains it's depth, or what level of the tree it's on.

I want to display this array as a indented tree, with each level being indented further from the left. The cleanest way to do this in html/css I feel is to nest containers. Each container has, say, margin-left: 10, then you simply open a new one for each level and you get your nicely indented tree. But that's the problem, how would I accomplish that using the power of HTML::Template? I confess I've got no ideas that don't involve ugly hacks to my data and the template (such as storing a special variable in the array every time it changes level). Anyone got a better idea?
  • Comment on HTML::Template, pseudo trees and indention.

Replies are listed 'Best First'.
Re: HTML::Template, pseudo trees and indention.
by BrowserUk (Patriarch) on Jan 10, 2006 at 09:58 UTC

    This is one of those occasions when the markup & presentation logic is intimately intertwined with the content no matter how hard you try to separate them.

    Rather than trying to put complex logic within the template and it's highly restrictive mini-language, better to simply produce the html representing the tree as a string, using the full power of Perl, and the pass it into the template for incorporation with the rest of the page elements.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: HTML::Template, pseudo trees and indention.
by dragonchild (Archbishop) on Jan 10, 2006 at 14:19 UTC
    You can't do it within HTML::Template, period. You can do it within Template Toolkit simply because TT allows you to create recursive functions which are the easiest way to handle recursive data structures, such as trees.

    Furthermore, is there a reason you're using a flat array vs. one of the CPAN solutions like Tree or Tree::Simple? Tree::Simple provides for a huge number of visitors, some of which handle layout in terms of spaces. Tree has an persistence mode that writes to an XML file that you can use XSL to transform into HTML. Heck, it would be extremely simple to write a "persistence" layer for Tree that persists to HTML vs. XML and writes to an IO::Scalar object vs. a filehandle.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      quote: You can't do it within HTML::Template, period

      According to whom?

      #!/usr/bin/perl use strict; use HTML::Template; my $template = " <ul> <TMPL_LOOP tree> <li><TMPL_VAR value> (depth=<TMPL_VAR depth>) <TMPL_LOOP open><ul></TMPL_LOOP> <TMPL_IF close> <TMPL_LOOP close> </li></ul> <TMPL_IF __LAST__></li></TMPL_ +IF> </TMPL_LOOP> <TMPL_ELSE> <TMPL_UNLESS open> </li> </TMPL_UNLESS> </TMPL_IF> </TMPL_LOOP> </ul> "; my $tree = [ { 'value' => 'a', 'depth' => 1 }, { 'value' => 'b', 'depth' => 1 }, { 'value' => 'b1', 'depth' => 2 }, { 'value' => 'b2', 'depth' => 2 }, { 'value' => 'c', 'depth' => 1 }, { 'value' => 'c1', 'depth' => 2 }, { 'value' => 'c1.1', 'depth' => 3 }, { 'value' => 'd', 'depth' => 1 } ]; for my $i (0 .. $#$tree) { my $delta; if (defined($tree->[$i+1])) { $delta = $tree->[$i+1]->{'depth'} - $tree->[$i]->{'dep +th'}; } else { $delta = - $tree->[$i]->{'depth'}; } if ($delta > 0) { push(@{$tree->[$i]->{'open'}},{}) for (1 .. $delta); } elsif ($delta < 0) { push(@{$tree->[$i]->{'close'}},{}) for ($delta .. -1); } } my $html_template = HTML::Template->new( scalarref => \$template, loop_context_vars => 1); $html_template->param('tree' => $tree); print $html_template->output;
      Produces the following html:
      • a (depth=1)
      • b (depth=1)
        • b1 (depth=2)
        • b2 (depth=2)
      • c (depth=1)
        • c1 (depth=2)
          • c1.1 (depth=3)
      • d (depth=1)

    Isn't there some law of comp-sci that says that say any algorithm that can be written recusively, can also be written iteratively? er...or something to that effect.

    Update: fixted sprelling erors

    /\/\averick

        *claps sardonically* I was waiting for that response. You did it with H::T, not within H::T. Your node is an excellent example of twisting the problem to suit the answer. Not only is your template nearly unreadable the first 3 times one reads it, but your code is nearly impossible to follow as well. The point wasn't to see if you could code a solution that involves H::T. The point was to see if H::T could provide a solution.

        And, yes, any recursive solution can be written iteratively. That doesn't mean that H::T can handle it any better.


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

        Here is a bit simpler version. I'm not sure this doesn't miss some special case you where planning on though. It just makes an up or a down loop to raise or level the level as needed.

        #!/usr/bin/perl use strict; use HTML::Template; use Data::Dumper; my $template = " <ul> <TMPL_LOOP tree> <li><TMPL_VAR value> (depth=<TMPL_VAR depth>) <TMPL_LOOP up><ul></TMPL_LOOP> <TMPL_LOOP down></ul></TMPL_LOOP> </li> </TMPL_LOOP> </ul> "; my $tree = [ { 'value' => 'a', 'depth' => 1 }, { 'value' => 'b', 'depth' => 1 }, { 'value' => 'b1', 'depth' => 2 }, { 'value' => 'b2', 'depth' => 2 }, { 'value' => 'c', 'depth' => 1 }, { 'value' => 'c1', 'depth' => 2 }, { 'value' => 'c1.1', 'depth' => 3 }, { 'value' => 'd', 'depth' => 1 } ]; for my $i (0 .. $#$tree) { if (defined $tree->[$i+1]) { my $diff = $tree->[$i+1]->{depth} - $tree->[$i]->{depth}; if ($diff > 0) { $tree->[$i]->{up} = [({}) x $diff]; } elsif ( $diff < 0 ) { $tree->[$i]->{down} = [({}) x -$diff]; } } } my $html_template = HTML::Template->new( scalarref => \$template, loop_context_vars => 1); $html_template->param('tree' => $tree); print $html_template->output;


        ___________
        Eric Hodges
      Heh, the reason I'm using a flat array is because that's how it comes out of the database =]. I was attempting to be as lazy as possible and just pass the data to my template, but I have to admit the contortions involved are really fairly ugly (Sorry guys). I'm now leaning strongly towards one of the Tree modules, thanks.
        Getting Tree::Persist::DB to work with your schema should be relatively easy. Good luck!

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: HTML::Template, pseudo trees and indention.
by talexb (Chancellor) on Jan 10, 2006 at 09:19 UTC

    Can you do recursive includes using HTML::Template? That would do it, I think.

    Alex / talexb / Toronto

    "Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds

Re: HTML::Template, pseudo trees and indention.
by dws (Chancellor) on Jan 10, 2006 at 15:02 UTC

    I confess I've got no ideas that don't involve ugly hacks to my data and the template (such as storing a special variable in the array every time it changes level.

    Don't think of it as an ugly hack. Think of it as one of life's many trade-offs. You're doing some set-up in code so that you don't have to contort your template.

    If the depth of the tree you're dealing with can't be bounded, you can generate indentations in code, and do something like

    <tmpl_loop tree> <div class="leaf" style="margin-left: <tmpl_var margin>px"> ... </di +v>
    This has the upside of giving you fine-grain control over indentation, with the downside of that control being in code rather than in your template.

    If the depth of the tree is bounded, you can move presentation into the stylesheet by generating a simple depth count in code. In the template, do something like

    <tmpl_loop tree> <div class="leaf depth<tmpl_var depth>"> ... </div>
    and provide corresponding depth0, depth1, etc. targets in your stylesheet, with the generic style in leaf.