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

Thank you to the PerlMonks for taking the time to examine this newcomers question. I have a Putty CSV created from Putty Session Manager in Windows. I want to populate a XML that stores sessions in the KDE application 'konsole'. So I need to convert a CSV to a XML that konsole will understand. Thanks to others at comp.lang.perl.misc, I have gotten this far. Here is my code:

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $infile = '/media/Docs/Scripts/Perl/Putty/TEST.csv'; open (CSVFILE, $infile) || die ("Could not open $infile! $!"); my @final; while ( my $line = <CSVFILE> ) { $line =~ tr/"\r\n//d; my @cleanline = split /,/, $line; my ( undef, @prefinal ) = split /\\/, $cleanline[ 1 ]; push @final, [ @prefinal, @cleanline[ 0, 3 ] ]; } close $infile; my %hash; foreach my $leaf (@final) { my $ptr = \%hash; foreach my $i ( 0 .. $#$leaf - 1 ) { my $node = $leaf->[$i]; if( $i != $#$leaf - 1 ) { $ptr->{$node} = {} unless( exists $ptr->{$node} ); $ptr = $ptr->{$node}; } else { $ptr->{$node} = $leaf->[$i + 1]; } } } print qq(<SESSION>\n); foreach my $k1 (sort keys %hash) { print qq( <$k1>\n); foreach my $k2 (sort keys %{$hash{$k1}}) { print qq( <$k2>\n); foreach my $k3 (sort keys %{$hash{$k1}{$k2}}) { print qq( <$k3>\n); print qq( $hash{$k1}{$k2}{$k3}\n); print qq( </$k3>\n); } print qq( </$k2>\n); } print qq( </$k1>\n); } print qq(</SESSION>\n); exit(0);

The input in the csv file is as follows:

"Genview-EMS","Sessions\NOC-CO\Servers","","172.16.19.254" "Mayberry (external)","Sessions\NOC-CO\Servers","","216.196.75.8" "Mayberry (internall)","Sessions\NOC-CO\Servers","","172.16.18.103" "OPIE2 (SUN NTP SERVER)","Sessions\NOC-CO\Servers","","172.16.18.102" "10008PKRV_L","Sessions\NOC-INT\Core\Rotuers\Console","","donp:7001@10 +.10.17.68" "10008PKRV_R","Sessions\NOC-INT\Core\Rotuers\Console","","donp:7002@10 +.10.17.68" "6509PKRV_L","Sessions\NOC-INT\Core\Rotuers\Console","","donp:7003@10. +10.17.68" "6509PKRV_R","Sessions\NOC-INT\Core\Rotuers\Console","","donp:7004@10. +10.17.68"

The output generated from the script is (NOTED: Not worthy for konsole yet):

<SESSION> <NOC-CO> <Servers> <Genview-EMS> 172.16.19.254 </Genview-EMS> <Mayberry (external)> 216.196.75.8 </Mayberry (external)> <Mayberry (internall)> 172.16.18.103 </Mayberry (internall)> <OPIE2 (SUN NTP SERVER)> 172.16.18.102 </OPIE2 (SUN NTP SERVER)> <co-gateway> 10.70.64.33 </co-gateway> </Servers> </NOC-CO> <NOC-INT> <Core> <Rotuers> HASH(0x2100ec8) </Rotuers> </Core> </NOC-INT> </SESSION>

Note: The Routers portion of the XML has more elements, but it is represented by 'HASH(0x2100ce8)' because the foreach loops do not go deeper into the hashes. The whole issue is that my element count is not standard, and is variable. So if I want to print a hash that contains 4 elements, I need 3 foreach loops. If I want to print out a hash that contains 8 elements, I would need 7 foreach loops. My fear and inexperience makes me think I would need to get to the last leaf of the hash in order to calculate how many foreach loops I would need. This would defeat the entire purpose.

Does someone have a chance to give me some advice on what you think I should do?

Replies are listed 'Best First'.
Re: Veriable Length Array/Hash derived from CSV to populate an XML
by wind (Priest) on Mar 29, 2011 at 19:00 UTC

    It looks like you just need to use some recursion if %hash is of variable structure.

    print "<SESSION>\n"; print_hash(\%hash); print "</SESSION>\n"; sub print_hash { my $hashref = shift; my $prefix = (@_ ? shift : '') . ' '; foreach my $key (sort keys %$hashref) { print "$prefix<$key>\n"; if (ref $hashref->{$key}) { print_hash($hashref->{$key}, $prefix); } else { print "$prefix $hashref->{$key}\n" } print "$prefix</$key>\n"; } }

    Also, look at package solutions like XML::Simple to do all the xml generation for you.

    use XML::Simple; use strict; ... print XMLout(\%hash, RootName => 'SESSION');

    Update: Parameter processing updated to avoid warnings.

      Thank you wind for that answer. When I use your example, I have a couple of errors coming up.
      Use of uninitialized value within @_ in concatenation (.) or string at + ./scripts.pl line 42, <CSVFILE> line 1693. <Alvarion> Use of uninitialized value within @_ in concatenation (.) or string at + ./scripts.pl line 42, <CSVFILE> line 1693. <Nekoma> Use of uninitialized value within @_ in concatenation (.) or string at + ./scripts.pl line 42, <CSVFILE> line 1693.
      Line 42 of the script is
      my $prefix = shift . ' ';
      Line 1693 of my original csv is
      "3550MayvilleSwitch","Sessions\NOC-INT\Towers\Switches","","10.10.18.2 +1"

        Yeah, you were using a slightly older version of my post. Look at the code now.

        Regardless, you really should look into alternative ways of outputting the XML like I already listed, but the function might give you what you want if all you have is nested hashes.

Re: Veriable Length Array/Hash derived from CSV to populate an XML
by Tux (Canon) on Mar 30, 2011 at 06:08 UTC

    One flaw in you code is that you try to close $infile, where $infile is the file name, not the file handle.

    And I wonder how you can think that

    while (my $line = <CSVFILE>) { $line =~ tr/"\r\n//d;

    is ever going to do what you think it is doing. The diamond operator reads lines based on $/, which is most likely a newline, so if the line read contains a newline, it will be the last character of $line in this loop, and tr// is not what you want, but chomp would be.

    If your goal however is to delete \r and \n from the CSV fields after the line has been read, this will horribly fail. You should use a CSV parsing module instead, rendering the first loop into something like:

    use Text::CSV_XS; my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 }); open my $fh, "<", $infile or die "$infile: $!\n"; while (my $row = $csv->getline ($fh)) { $row->[1] =~ tr/"\r\n"//d; my (undef, @prefinal) = split m/\\/ => $row->[1]; push @final, [ @prefinal, $row->[0], $row->[3] ]; } close $fh;

    Text::CSV will work the same, but Text::CSV_XS is much faster.


    Enjoy, Have FUN! H.Merijn