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

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

I have a HoH whose keys are unknown at "programming time" (The structure is returned from XML::Simple after parsing an XML file), and I also have a "user string" that is passed at runtime which is a "text representation" of the value the user is looking to have returned.

I have a working PoC below which utilizes eval and I'm looking for feedback on whether I'm overlooking a way to achieve the same results without using eval.

#!/usr/bin/perl use strict; use warnings; my $usr_str = "{depTar}{name}"; my $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; my $dyn = '$xml->' . "$usr_str"; print eval $dyn;

Replies are listed 'Best First'.
Re: "Dynamically" Accessing a HoH
by dave0 (Friar) on Jun 02, 2005 at 15:03 UTC
    Why not just use normal hash access?
    my $user_str_toplevel = 'depTar'; my $user_str_secondlevel = 'name'; my $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; print $xml->{$user_str_toplevel}{$user_str_secondlevel};
    If you want the user to be able to enter their key in the format of "{depTar}{name}", it should just be a simple matter of parsing to convert that into single keys for hash access.

    Given that your source format is XML, XML::XPath might be worth looking at instead.

Re: "Dynamically" Accessing a HoH
by merlyn (Sage) on Jun 02, 2005 at 15:32 UTC
Re: "Dynamically" Accessing a HoH
by monkey_boy (Priest) on Jun 02, 2005 at 15:10 UTC

    the 1st post would do it if you always only have two keys, if an unknown number of keys can be given try this...

    #!/usr/bin/perl use strict; use warnings; my $usr_str = "{depTar}{name}"; my @keys = $usr_str =~ m/\{(.*?)\}/g; my $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; my $href = $xml; for my $i (0 .. $#keys) { $href = $href->{$keys[$i]}; }; print $href;



    Update: fixed spelling mistake

    This is not a Signature...
Re: "Dynamically" Accessing a HoH
by fishbot_v2 (Chaplain) on Jun 02, 2005 at 15:11 UTC

    Maybe I am missing something obvious, but why wouldn't something like this work?

    my @hashloc = qw{ depTar name }; my $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; print $xml->{ $hashloc[0] }{ $hashloc[1] };

    If no, then I think that you need to give more information about what the actual user input is going to look like, and why.

Re: "Dynamically" Accessing a HoH
by ysth (Canon) on Jun 02, 2005 at 15:39 UTC
    To make merlyn happy:

    Why not just keep using eval, but validate your $usr_str?

    die "horribly" if $usr_str !~ /^(\{\w+\})+\z/; my $val = eval "\$xml->$usr_str"; print $val;
    If you do change to parsing out string keys and looping through them, which is really easy to do as others have demonstrated, be aware that some things will actually work differently, e.g. $usr_str = "{0x2a}{052}", which with eval is equivalent to {42}{42}.
Re: "Dynamically" Accessing a HoH
by duelafn (Vicar) on Jun 02, 2005 at 15:45 UTC

    I'd say, just suck it up and recurse,

    sub deep_hash { my $h = shift; return $h unless @_; deep_hash($h->{shift()}, @_); } $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; print deep_hash($xml, qw/depTar name/);

    Good Day,
        Dean

Re: "Dynamically" Accessing a HoH
by ChemBoy (Priest) on Jun 02, 2005 at 16:10 UTC

    If I were a user, I'd be annoyed by that format for $usr_string, so I'm going to assume you're not tied to it, and can have them supply something (slightly) more intuitive like depTar/name instead.

    Using the power of unjustified assumptions makes coding much easier, you see... <grin>

    my @keys = split '/', $usr_string; my $ref = $xml; $ref = $ref->{$_} for @keys; print $ref;

    Obviously, this completely fails to check for error conditions of any kind (invalid keys owing to typos being the most obvious case). I'm assuming you know how to do that already. :-)



    If God had meant us to fly, he would *never* have given us the railroads.
        --Michael Flanders

      Given that the input data was originally XML parsed with XML::Simple, if you're going to use a format like depTar/name for the user input, you can use XML::XPath to extract the results:
      use XML::XPath; use XML::XPath::XMLParser; my $usr_string = "depTar/name"; my $xml = "<depTar><name>c_p20</name><loc>srvr1</loc></depTar>"; my $xp = XML::XPath->new(xml => $xml); my $nodeset = $xp->find($usr_string); foreach my $node ($nodeset->get_nodelist) { print $node->string_value(), "\n"; }
Re: "Dynamically" Accessing a HoH
by djohnston (Monk) on Jun 02, 2005 at 19:13 UTC
    I used to do something similar, but I eventually adopted a unique syntax for traversing structures when my requirements grew to include array traversal. My solution (which may be overkill for your needs) involves dot and colon meta characters - the dot represents a hash key (e.g. depTar.name), and the colon represents an array element (e.g. foobar:0). This works great unless you expect to have hash keys that might contain dot or colon characters, in which case support for escaped meta characters is required. I've been working on a module that uses this scheme for quite some time now (see my home node for the url).

    sub lookup { my( $ref, $str ) = @_; return $ref unless length $str; my( $v, $r, $arr ); while ( $str =~ s/^((?:[^\\]|\\.)*?)([:.])//o ){ $r = $2; ( $v = $1 ) =~ s/\\([.:\\])/$1/og; $ref = $arr ? $ref->[$v] : $ref->{$v}; $arr = $r eq ':'; } $str =~ s/\\([.:\\])/$1/g; return $arr ? $ref->[$str] : $ref->{$str}; } my $usr_str = "depTar.name"; my $xml = { 'depTar' => { 'name' => 'c_p20', 'loc' => 'srvr1' }}; print lookup( $xml, $usr_str ); # c_p20
    Note: This code snippet was modified slightly to exclude elements which are irrelevant to this discussion (hopefully I didn't break anything in the process). I should probably also point out that it will (by design) traverse blessed references.