Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot

How to set variable names using Text::CSV?

by Lady_Aleena (Curate)
on Oct 15, 2009 at 22:03 UTC ( #801445=perlquestion: print w/replies, xml ) Need Help??
Lady_Aleena has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to combine two scripts into one, but I can't figure out how to get the variable names to work with Text::CSV. Everything I try to combine these two scripts doesn't work. I will spare you the csv file, but if you want to see it, it is here.

Script 1

#!/usr/bin/perl use strict; use warnings; use diagnostics; use Text::CSV; use Data::Dumper; open(my $io, "player_characters.csv") || die("can't open player_charac +ters.csv: $!"); my $csv = Text::CSV_XS->new({ sep_char => '|', allow_whitespace => 1, always_quote => 1, blank_is_undef => 1, }); my @names = $csv->getline($io); $csv->column_names (@names); while (defined (my $hr = $csv->getline_hr($io))) { print Dumper($hr); }

Script 2

#!/usr/bin/perl use strict; use warnings; use diagnostics; use Tie::IxHash; tie my %general_information, qw(Tie::IxHash); tie my %ability_scores, qw(Tie::IxHash); my ($id_name, $filename, $last_name, $first_name, $alignment, $generic +_class_name, $class_name, $generic_race, $race, $gender, $experience, + $strength, $dexterity, $constitution, $intelligence, $wisdom, $chari +sma) = qw(test) x 17; %general_information = ( "class(es)" => $class_name, alignment => $alignment, race => $race, gender => $gender, ); %ability_scores = ( strength => $strength, dexterity => $dexterity, constitution => $constitution, intelligence => $intelligence, wisdom => $wisdom, charisma => $charisma, ); sub list_loop { my $hash_ref = shift; print "<ul>\n"; for my $key (keys(%$hash_ref)) { my $value = $hash_ref->{$key}; print "\t<li><strong>".ucfirst $key.":</strong> ".$value."</li>\n" +; } print "</ul>\n"; } print qq{<h2><a href="$filename" id="$id_name">$first_name $last_name< +/a></h2>\n}; list_loop(\%general_information); list_loop(\%ability_scores);
Have a nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: How to set variable names using Text::CSV?
by Bloodnok (Vicar) on Oct 15, 2009 at 22:14 UTC
    Hmmm ,

    One wonders: why bother ?

    Why not utilise the hashrefs that Text::CSV can generate for you ?

    That being said, in your code, shouldn't the values of %general_information & %ability_scores be scalar ref's - not scalars as you've declared ...or is it too late here and I'm missing something too obvious to see ?

    A user level that continues to overstate my experience :-))

      It is never too late. Your experience is far and away better than mine. I wrote them the way I did for testing the individual code to see some sort of result for both seperately. All the placeholder stuff will be gone in the final code, such as the test x 17 and all of the Data::Dumper stuff.

      Have a nice day!
      Lady Aleena
Re: How to set variable names using Text::CSV?
by Tanktalus (Canon) on Oct 15, 2009 at 23:14 UTC

    I'll second Bloodnok's suggestion, but take it a bit further.

    First off, you probably shouldn't have "my ($id_name, $filename, $last_name, $first_name, $alignment, $generic_class_name, $class_name, $generic_race, $race, $gender, $experience, $strength, $dexterity, $constitution, $intelligence, $wisdom, $charisma) = qw(test) x 17;". You probably should just use a hash: "my %character;", or, if you want to initialise it all, "my %character = map { $_ => 'test' } qw/id_name filename last_name first_name alignment generic_class_name class_name generic_race race gender experience strength dexterity constitution intelligence wisdom charisma/;" (and if you do this, you can use one of the Hash modules to lock the keys, though I don't bother).

    Once you do this, then accepting what Text::CSV gives you shouldn't be such a big deal.

    What's more, I generally don't use Text::CSV or it's bigger brother, Text::CSV_XS. Well, not directly. I use DBD::CSV (which uses Text::CSV_XS under the covers). By treating your CSV file as a table in a database, some things get a bit easier. Like letting the DBI/DBD code do the looping for you when you want to extract certain character(s). Or just certain elements of the character.

    I have to admit ... I'm curious as to why you need Tie::IxHash. Nevermind - you want to control the order that everything comes out, I see that now.

    PS: if you want your ability scores, you can just do this:

    my %ability_scores = map { $_ => $character{$_} } qw/strength dexter +ity constitution intelligence wisdom charisma/;


      my ($id_name, $filename, $last_name, $first_name, $alignment, $generic_class_name, $class_name, $generic_race, $race, $gender, $experience, $strength, $dexterity, $constitution, $intelligence, $wisdom, $charisma) = qw(test) x 17; is just a placeholder, and will be gone in the final code as will all the Data::Dumper stuff since it isn't doing me any good at the moment.

      I have read, reread, and reread Text::CSV and Text::CSV_XS, and they do not tell me what I need to know, how to use the data once I get it. I tried to bind the columns, and that didn't work. I am at my wits end trying to make this work. I just don't get what Text::CSV is doing.

      I can't even figure out why tie %csv, qw(Tie::IxHash); doesn't work.

      I have written the two hashes in two ways, and they don't work.

      %general_information = ( "class(es)" => $csv{"class_name"}, alignment => $csv{"alignment"}, race => $csv{"race"}, gender => $csv{"gender"}, ); %ability_scores = ( strength => $csv{"strength"}, dexterity => $csv{"dexterity"}, constitution => $csv{"constitution"}, intelligence => $csv{"intelligence"}, wisdom => $csv{"wisdom"}, charisma => $csv{"charisma"}, ); %general_information = ( "class(es)" => $csv->{class_name}, alignment => $csv->{alignment}, race => $csv->{race}, gender => $csv->{gender}, ); %ability_scores = ( strength => $csv->{strength}, dexterity => $csv->{dexterity}, constitution => $csv->{constitution}, intelligence => $csv->{intelligence}, wisdom => $csv->{wisdom}, charisma => $csv->{charisma}, );

      I am missing a big piece of this puzzle and don't know what it is.

      Have a nice day!
      Lady Aleena

        You're not really explaining what you want to do. Because, as far as I can tell, your first script works just fine. Once you get the $hr, you print Dumper($hr), and the output you see to your screen will look like:

        $VAR1 = { 'class_name' => '1st level Fighter/1st level Thief/1st level + Mage', 'intelligence' => '18', 'wisdom' => '14', 'charisma' => '15', 'experience' => '0', 'dexterity' => '18', 'strength' => '18(93)', 'last_name' => 'Zendelic', 'generic_class_name' => 'Fighter-Thief-Mage', 'filename' => '!ZeKy', 'generic_race' => 'Half-Elf', 'constitution' => '17', 'race' => 'Half-Elf', 'alignment' => 'Neutral Good', 'gender' => 'Female', 'id_name' => 'Kymaria_Zendelic', 'first_name' => 'Kymaria' };
        That means that $hr is a reference to a hash (see the braces? Just like in your perl code when you want a reference to an anonymous hash). You could print ref($hr) to see that it says HASH. That means that if you put the rest of your character-handling-code inside that loop, and use $hr as a reference to a hash containing all the variables you care about, you should be off and running:
        while (defined (my $hr = $csv->getline_hr($io))) { # use $hr->{id_name} to get the id_name }
        You can't use Tie::IxHash because, well, you're not populating the hash. You'll probably have to extract things yourself in the order you want, but that's ok, you already have the order you want as @names. (Using DBD::CSV allows you to do this, too, but we'll stick with what you have for now.)

        So, you'll have this loop:

        while (defined (my $hr = $csv->getline_hr($io))) { my %general_information = ( "class(es)" => $hr->{class_name}, alignment => $hr->{alignment}, race => $hr->{race}, gender => $hr->{gender}, ); my %ability_scores = ( strength => $hr->{strength}, dexterity => $hr->{dexterity}, constitution => $hr->{constitution}, intelligence => $hr->{intelligence}, wisdom => $hr->{wisdom}, charisma => $hr->{charisma}, ); # do stuff with the above hashes. }
        Mind you, that does seem somewhat repetitive, and I'm not a fan of repetition, so ...
        while (defined (my $hr = $csv->getline_hr($io))) { my %general_information = ( "class(es)" => $hr->{class_name}, map { $_ => $hr->{$_} } qw/alignment ra +ce gender/; ); my %ability_scores = ( map { $_ => $hr->{$_} } qw/strength dexterity constitution intelligence wisdom charisma/ ); # here you can use them, same as previous example. list_loop(\%general_information); list_loop(\%ability_scores); }
        Now, in these cases, you can use Tie::IxHash if you want to control the order of the keys, otherwise the order will be different each time you run (possibly).

      I didn't follow all the details in this thread re Text::CVS, but your comment about DBD::CSV stands out as a good idea!

      I have a project with a CSV file that I am starting soon and plan to go with the DBI approach. I had played with this on Unix a while back and it seemed to work great The problem was that the CSV module for the DBI wasn't available on ActiveState and I couldn't use it for Windows based applications - so this idea went on back burner. That problem has evidently been solved with Perl 5.10 and current modules!

      If I'm right about this, using the DBI will allow me to write code that could be used with some other DB at some later time if necessary.

      Oh well, not directly applicable to the OP's problem at hand, but I thought that this DBI with CSV is now possible on Windows was worth a mention. A thought for other projects.

      The code looks like connecting to other underlying Databases:

      use DBI; #need to have DBD::CSV installed for this script my $db = DBI->connect("DBI:CSV:f_dir=./DEMO.db") or die "Cannot connect: $DBI::errstr"; DBI SQL stuff ...

      Update:There is a thing that I've found out with using CSV files...I have some users who haven't written any code in their life, never seen an SQL or a Perl statement, but yet can do magic with modern spreadsheets! A lot of the old limits are gone like number of lines, and there are multiple pages, etc. Sometimes sending CSV spreadsheet data works to everyone's advantage. The users can sometimes do amazing stuff!

        my $dbh = DBI->connect ("dbi:CSV:", undef, undef, { f_dir => "DEMO.db:", f_ext => ".csv/r", f_schema => undef, PrintError => 1, RaiseError => 1, });

        The disadvantage of this when the data set grows bigger is that all of your CSV data is kept in memory, and probably even twice. That, plus some meta-data will quickly gobble up most of your available valuable memory. If you want an intermediate database to make a move to something real later on, and still want portability, skip DBD::CSV and use DBD::SQLite instead. It is lightweight, fast and portable and supports a lot more than CSV.

        Enjoy, Have FUN! H.Merijn
Re: How to set variable names using Text::CSV?
by Tux (Abbot) on Oct 16, 2009 at 06:13 UTC

    I will only comment on the flaws in script 1, as that deals with Text::CSV

    • my @names = $csv->getline ($io); assigns an array ref to a list. That's not what you want.
      my @names = @{$csv->getline ($io)}; is the coreect mantra.
    • always_quote is void on parsing. That is a generator option.
    • make it a habit to always pass binary => 1
    • Use auto_diag => 1 on csv initiation or $csv->oef or $csv->error_diag () after parsing
    use strict; use warnings; use Text::CSV; use Data::Dumper; open my $io, "<", "player_characters.csv" or die "player_characters.cs +v: $!"; my $csv = Text::CSV_XS->new ({ sep_char => "|", allow_whitespace => 1, blank_is_undef => 1, binary => 1, auto_diag => 1, }); my @names = @{$csv->getline ($io)}; $csv->column_names (@names); while (defined (my $hr = $csv->getline_hr ($io))) { print Dumper ($hr); }

    If you do not use the @names after you set it, other than to assign to to column names, you can combine the in one call, as column_names () accepts both a list or a list ref

    $csv->column_names ($csv->getline ($io));

    Enjoy, Have FUN! H.Merijn

      Why should I always pass binary => 1 when I am dealing with ASCII characters? I don't know what auto_diag => 1 does, so I would prefer not to use it. I also noticed that you changed the || to or, would you please tell me why? It also looks like @names keeps the order that I want, so I think I will keep it.

      Have a nice day!
      Lady Aleena

        IMHO binary should have been the default when the module was created, but that was before I took over. Now we have to keep backward compatibility. There are two reasons to always pass binary

        1. You don't forget the attribute syntax (people still get that wrong)
        2. You never know when your-all-ASCII suddenly uses binary characters

        Of course, when it is your own data, and you generate it yourself, feel free to leave it out, but it is the most given solution I have to give to people that sen me mail with "Text::CSV_XS is broken" subjects.

        auto_diag is a rather new attribute that calls error_diag () at the moment the error occurs, so you will (hopefully) never wonder why something went wrong.

        There is no difference between

        my @names = @{$csv->getline ($io)}; # note the @{...} $csv->column_names (@names);


        my @names = @{$csv->getline ($io)}; # note the @{...} $csv->column_names ( [ @names ] ); # extra whitespace for attention

        which is essentially the same as

        $csv->column_names ($csv->getline ($io));

        when you do not intend to use @names after the column assignment. The order is the same for all. If you need the headers later, you can still use

        my @names = $csv->column_names ();

        Enjoy, Have FUN! H.Merijn

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://801445]
Approved by Bloodnok
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2018-06-23 04:56 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (125 votes). Check out past polls.