Coltrane58
Coltrane58

Reputation: 327

hash with array of hashes in perl

I know this topic has been covered but other posts usually has static hashes and arrays and the don't show how to load the hashes and arrays. I am trying to process a music library. I have a hash with album name and an array of hashes that contain track no, song title and artist. This is loaded from an XML file generated by iTunes.

the pared down code follows:

use strict;
use warnings;
use utf8;
use feature 'unicode_strings';
use feature qw( say );
use XML::LibXML qw( );
use URI::Escape;

my $source = "Library.xml";
binmode STDOUT, ":utf8";

# load the xml doc

my $doc = XML::LibXML->load_xml( location => $source )
    or warn $! ? "Error loading XML file: $source $!"
    : "Exit status $?";

    my %hCompilations;
    my %track;

    # extract xml fields
    my @album_nodes = $doc->findnodes('/plist/dict/dict/dict');
    for my $album_idx (0..$#album_nodes) {
        my $album_node = $album_nodes[$album_idx];
        my $trackName = $album_node->findvalue('key[text()="Name"]/following-sibling::*[position()=1]');
        my $artist = $album_node->findvalue('key[text()="Artist"]/following-sibling::*[position()=1]');
        my $album = $album_node->findvalue('key[text()="Album"]/following-sibling::*[position()=1]');
        my $compilation = $album_node->exists('key[text()="Compilation"]');

        # I only want compilations
        if( ! $compilation ) { next; }

        %track = (
                trackName => $trackName,
                trackArtist => $artist,
                );

        push @{$hCompilations{$album}} , %track;    
    }
#loop through each album access the album name field and get what should be the array of tracks
foreach my $albumName ( sort keys %hCompilations ) {
    print "$albumName\n";
    my @trackRecs = @{$hCompilations{$albumName}};

    # how do I loop through the trackrecs?
}

Upvotes: 0

Views: 80

Answers (2)

Dave Cross
Dave Cross

Reputation: 69224

This line isn't doing what you think it is:

push @{$hCompilations{$album}} , %track;

This will unwrap your hash into a list of key/value pairs and will push each of those individually onto your array. What you want is to push a reference to your hash onto the array.

You could do that by creating a new copy of the hash:

push @{$hCompilations{$album}} , { %track };

But that takes an unnecessary copy of the hash - which will have an effect on your program's performance. A better idea is to move the declaration of that variable (my %track) inside the loop (so you get a new variable each time round the loop) and then just push a reference to the hash onto your array.

push @{$hCompilations{$album}} , \%track;

You already have the code to get the array of tracks, so iterating across that array is simple.

my @trackRecs = @{$hCompilations{$albumName}};

foreach my $track (@trackRecs) {
   print "$track->{trackName}/$track->{trackArtist}\n";
}

Note that you don't need the intermediate array:

foreach my $track (@{$hCompilations{$albumName}}) {
   print "$track->{trackName}/$track->{trackArtist}\n";
}

Upvotes: 2

Georg Mavridis
Georg Mavridis

Reputation: 2331

first of all you want to push the hash as a single element, so instead of

    push @{$hCompilations{$album}} , %track;    

use

    push @{$hCompilations{$album}} , {%track};

in the loop you can access the tracks with:

foreach my $albumName ( sort keys %hCompilations ) {
    print "$albumName\n";
    my @trackRecs = @{$hCompilations{$albumName}};

    # how do I loop through the trackrecs?
    foreach my $track (@trackRecs) {
       print $track->{trackName} . "/" . $track->{trackArtist} . "\n";
    }
}

Upvotes: 2

Related Questions