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

I need to take the following array:
my @array = ( 'Main//News', 'Main//Reviews//PC', 'Main//Reviews//PS2', 'Main//Reviews//XBox', 'Other Menu//Other//Sub//Menus' );
And magically turn it into: Thanks in advance and good luck!

--nutshell

Replies are listed 'Best First'.
(jeffa) Re: Dynamic menu generation
by jeffa (Bishop) on Oct 31, 2002 at 03:17 UTC
    use strict; my %menu; my @array = ( 'Main//Reviews//PC', 'Other Menu//Other//Sub//Menus', 'Main//News', 'Main//Reviews//PS2', 'Main//Reviews//XBox', ); for (@array) { $_ =~ s/^/\$menu{'/; $_ =~ s/\/\//'}->{'/g; $_ =~ s/$/'}/; eval "$_ = 1"; } list_em(\%menu); sub list_em { my $ref = shift; if (ref($ref) eq 'HASH') { print "<ul>\n"; for (keys %$ref) { print "<li>$_</li>\n"; list_em($ref->{$_}); } print "</ul>\n"; } }

    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: Dynamic menu generation
by djantzen (Priest) on Oct 31, 2002 at 03:59 UTC

    Warning: this is pretty darn evil, but since it's almost Halloween I'll post it anyway.

    my @array = ( 'Main//News', 'Main//Reviews//PC', 'Main//Reviews//PS2', 'Main//Reviews//XBox', 'Other Menu//Other//Sub//Menus' ); my %hash; foreach (@array) { s|(\w+\s+\w+)|\'$1\'|g; s|\/\/|\}\{|g; eval '$hash{' . "$_" . '} = 1'; } sub build { my $hash = shift(); while (my ($key, $value) = each %$hash) { if (ref $value eq 'HASH') { $html .= "<li>$key<ul>\n"; build($value); $html .= "<\/ul>\n"; } else { $html .= "<li>$key\n"; } } } my $html = "<ul>"; build(\%hash); $html .= "<\/ul>"; print $html;

    Update: Of course Jeffa beats me to it... harumph.

    Update: The ordering requirement can be satisfied using Tie::IxHash for all of the HoH-based methods in this thread.

Re: Dynamic menu generation
by tadman (Prior) on Oct 31, 2002 at 03:56 UTC
    This seems to do the trick:
    my @array = ( 'Main//News', 'Main//Reviews//PC', 'Main//Reviews//PS2', 'Main//Reviews//XBox', 'Other Menu//Other//Sub//Menus' ); sub menu { $_=q~;ir$w}Uw&Ii,jmcumUumbo#e_cjm>oImb}U|nyp@''@#l"ynrjmUtthU]h!U[$rhb +]h!U[11mU=mboI|_|mU#|wU"mcuL)'(*=L#/l"ynrjmU)mboI|wU"mUuhb]mU[#|wU"mc +uL)(*=)*<=hU]-s[)'*<=L#//f_y${sumc#/#tir$w~;y~L)oep<;@y>m}/Ushw![i=nb +$1*l#_&|{"]u('f,tjIcr~"<)ftI*'i_@=}s1$u#]m>lcn|Lw;obp!h[,U/jy&({re~;s +,.*,$&,ee;$_ } print menu(sort@array),$/;
    If you don't pass in a sorted array, it can create strange menus. It's also easily confused by similar leaf nodes.

      Okay, I am mystified (and if need be, humbled). I have seen this sort of genuinely obfuscated thing before (in someone's .sig file perhaps?) and even confirmed that it worked.

      What I am wondering though, is how the heck do you come up with code like,

      $_=q~;ir$w}Uw&Ii,jmcumUumbo#e_cjm>oImb}U|nyp@''@#l"ynrjmUtthU]h!U[$rhb +]h!U[11mU=mboI|_|mU#|wU"mcuL)'(*=L#/l"ynrjmU)mboI|wU"mUuhb]mU[#|wU"mc +uL)(*=)*<=hU]-s[)'*<=L#//f_y${sumc#/#tir$w~;y~L)oep<;@y>m}/Ushw![i=nb +$1*l#_&|{"]u('f,tjIcr~"<)ftI*'i_@=}s1$u#]m>lcn|Lw;obp!h[,U/jy&({re~;s +,.*,$&,ee;$_

      I mean, I can't even recognize many operators in that mess, let alone interpret jmcumUumbo#e_cjm into anything meaningful. Also, I could not get this to work.

      Clues for the clueless? Or perhaps I am missing some larger joke.



      PCS
        The 'mess' should not be interpreted literally.

        Hint 1: read perlop wrt to quoting operators.

        rdfield

        The first two statement in sub menu are:
        $_ = q~ stuff ~; y~ characters ~ other characters ~;
        If you insert a "print;" here you will be a lot less mystified.
        It works, but you'll have to be careful about how you copy it. Perl Monks inserts red plus signs (+) to break up the lines, that if pasted in to the program will cause it to fail. You can edit these out if you're careful, and join the program back in to one line, or use the "D/L Code" link to get a clean copy.

        Now, this is intentionally obfuscated, but the non-obfuscated source isn't that much clearer. Here's the encoded program that is being run:
        *menu=sub{my(@r,@s,@c);for(@_){@c=split'//';while(@s&&$s[$#s]ne$c[$#s]||@s>@c){pop@s;push@r,"</UL>";}while(@s<@c){push@s,$c[@s];push@r,"<UL><LI>$s[-1]</LI>";}}join!1,@r;};&menu
Re: Dynamic menu generation
by sauoq (Abbot) on Oct 31, 2002 at 03:02 UTC

    I'm not sure in what sense you have to turn it into a "menu." In your example, you indicate it should be turned into an unordered list. That said, You would probably be better off using a hash of nested hashes instead of an array of strings to hold the "menu" data.

    my %hash = ( Main => { Reviews => { 'PC' => A_LINK_OR_CALLBACK_FUNCTION, 'PS2' => A_LINK_OR_CALLBACK_FUNCTION, 'XBox' => A_LINK_OR_CALLBACK_FUNCTION, }, News => A_LINK_OR_CALLBACK_FUNCTION, }, 'Other Menu' => { Other => { Sub => { Menus => A_LINK_OR_CALLBACK_FUNCTION, }, }, }, );

    Note that I use A_LINK_OR_CALLBACK_FUNCTION to represent where you would probably store a URL (if this is for a web page) or a callback function (if this is for a GUI based program.)

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Dynamic menu generation
by nutshell (Beadle) on Oct 31, 2002 at 03:22 UTC
    No, no -- I'm simply trying to generate a HTML list that retains the order it was defined in. I also cannot change the format data is received in.

    Below are some examples of the array and the HTML that needs generated...

    Ex1:

    my @array = ( 'Main//News', 'Main//Reviews//PC', 'Main//Reviews//PS2', 'Main//Reviews//XBox', 'Other Menu//Other//Sub//Menus' );
    • Main
      • News
      • Reviews
        • PC
        • PS2
        • XBox
    • Other Menu
      • Other
        • Sub
          • Menus

    Ex2:

    my @array = ( 'Main//Reviews//PS2', 'Other Menu//Other//Sub//Menus', 'Main//Reviews//XBox', 'Main//News', 'Main//Reviews//PC' );
    • Main
      • Reviews
        • PS2
        • XBox
        • PC
      • News
    • Other Menu
      • Other
        • Sub
          • Menus

    Ex3:

    my @array = ( 'Other Menu//Other//Sub//Menus', 'Main//News', 'Main//Reviews//XBox', 'Main//Reviews//PS2', 'Main//Reviews//PC' );
    • Other Menu
      • Other
        • Sub
          • Menus
    • Main
      • News
      • Reviews
        • XBox
        • PS2
        • PC

    --nutshell

    Update 1: Didn't look at jeffa's code yet.
    Update 2: Jeffa's code is almost there... it just doesn't retain the order things are defined in.
    Update 3: Ok - I now know that I can't create a hash to simulate the menus tree but rather that I must use an array. I'm not sure how to do this... please help.

Re: Dynamic menu generation
by graff (Chancellor) on Oct 31, 2002 at 03:03 UTC
    Are you talking about menus in a Perl/Tk application? If so, it's easy to define menus based on, e.g. data read from a file; it's even easy to change menu contents on the fly depending on what the user choses to do.

    But I have to second tadman's request: show us some code that you have tried or would like to try. Without that, the question is too vague to give detailed help.

Re: Dynamic menu generation
by nutshell (Beadle) on Oct 31, 2002 at 02:19 UTC
    I forgot to add that the members in the array aren't necessarily going to be in order. For instance @array could look like:

    my @array = ( 'Main//Reviews//PC', 'Other Menu//Other//Sub//Menus', 'Main//Reviews//PS2', 'Main//News', 'Main//Reviews//XBox' );

    --nutshell

      You can always use sort @array so I don't see how this is an issue. What's your initial idea? Surely you have some code that was an attempt to solve this problem. What trouble did you have?
        I'm trying to take that array and generate the HTML shown above with it.

        --nutshell

Re: Dynamic menu generation
by dingus (Friar) on Oct 31, 2002 at 17:37 UTC
    #use Data::Dumper; my @array = ( 'Main//Reviews//PS2', 'Other Menu//Other//Sub//Menus', 'Main//Reviews//XBox', 'Main//News', 'Main//Reviews//PC' ); array2list(@array); sub array2list() { my %h; for (@_) { my $hr = \%h; for (split('//', $_ )) { $hr->{$_} = [(scalar keys %{$hr}), {}] unless (exists($hr->{$_})); $hr = $hr->{$_}[1]; } } #print Dumper(\%h),hr; displaylist (\%h); } sub displaylist() { print '<ul>'; my $hr = shift; for (sort {$hr->{$a}[0]<=>$hr->{$b}[0]} keys %{$hr}) { print '<li>',"$_"; # $hr->{$_}[0]\n"; displaylist ($hr->{$_}[1]) if (scalar keys %{$hr->{$_}[1]}); } print '</ul>'; }
    Some comments tomake this a little less confusing:

    The Data::Dumper bits can be uncommented if you want to know what the heck %h turns out to be. They are clearly not required.

    The ordering requirement means that yo can't just ahve a hash of hashrefs. So each hash value is an array ref containing a order created number and a sub hashref. At the twigs the sub hashref is empty.

    the subroutine displaylist() is called recursively to print the UL.../UL for a hashref