Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Re: opposite of format?

by dataDrone (Acolyte)
on Jun 27, 2003 at 22:31 UTC ( [id://269767]=note: print w/replies, xml ) Need Help??


in reply to opposite of format?

I think you're on the right track with unpack. Remember that you can use x in the templet to throw characters away so no need to undef them later. A couple questions to clarify, is you file arranged with one field name/value pair per line, and one file per record? Do the field-name / value pairs have a defined separator character or string? Can you show use an example of a file ( or the first couple lines or so ). Perhaps loading the file into an array (if the files are small in size) and then using split to break apart the name value pairs. Don't forget to filter the data for illegal characters that would cause problems when loading into the database or by sure to quote them properly.

Replies are listed 'Best First'.
Re: Re: opposite of format?
by xChauncey (Scribe) on Jun 27, 2003 at 23:26 UTC
    Thanks for the tip on going with unpack.

    The files that I have to use are text captures of account information screens at work (with personally identifiable info changed of course), so I can't post an example here, but here's a bit of a mockup...

    LastName, FirstName Home <hm phone> field <value> Address1 Work <wk phone> field <value> Address2 field <value> City, State, zip field <value> field <value> field <value> field <value> field <value> field <value> field <value> field <value> field <value> field <value> field <value> ...etc...
    So the bottom portion of the screen is kind of columnur, except for the fact that in some of the colums there are more than one field-value pair. There is no separator char between the fieldname and the value, and the amount of whitespace between a field and the value varies. (all whitespace is \040).

    Each file will have the data for one record in the db. Some of the fieldnames have spaces in them, so I can't really think of a way to split the file into field-value pairs off of the top of my head.

    Also, I'm liking unpack because of the top part of the screen, which has the name, address, etc. without any fieldname (which I forgot to mention in my original post).

    Feeling like I'm making progress.

    Thanks.

      If the lines in your file are all the same length (even the blank ones) and each field is also fixed length then you can use the fact that unpack formats ignore whitespace in conjuction with the @nnn absolute position specifier to lay out the format in a manner that is relatively easy to write and later read.

      By fixing the start of each line using @nnn and then using relative lengths and xxx to position stuff within each line greatly simplifies working out your format. In the example below, I've left the original 'specification' lines interleaved for readability and then used a regex to strip them out before using the format. There are several ways of doing this, and the much-maligned HEREDOC might actually be a better way. You could also omit them if you preferred.

      Using qw// in conjunction with a hash slice to name the individual fields is a nice way of laying out the field names in a clear and concise manner. You can the assign a ref to the hash into another hash indexed by personnel id (or lastname etc.), giving you a HoH which is probably the best structure for this kind of work.

      my $unpack_format = ' @0 A8 xxA9 xxxxxxxxA8 xxxxxxxxxxxa5 LastName, FirstName Home <hm phone> field <value> @53 A19 xxxxxxxxA8 xxxxxxxxxxxa5 Address1 Work <wk phone> field <value> @105A38 xxxxxxxxA5 Address2 field <value> @157A4 xxA5 xxA8 xxxxxxxxA5 xxxxxxxxxA5 City, State, zip field <value> field <value> @261xxxxxxxA5 xxxxxxxxxxxxxxA5 xxxxxxxxxxxxxxxxA5 field <value> field <value> field <value> @313xxxxxxxA5 xxxxxxxxxxxxxxA5 xxxxxxxxxxxxxxxxA5 field <value> field <value> field <value> @365xxxxxxxA5 xxxxxxxxxxxxxxA5 xxxxxxxxxxxxxxxxA5 field <value> field <value> field <value> '; ## remove the "documentation" lines. $unpack_format = s[^\s.*?\n][]mg; my %personnel; # Assuming that the Personnel number is an integral part of the filena +me for my $filename ( <PID*.rec> ) { my %record; @record{ qw[ lastname firstname homephone field1 address1 workphone field2 address2 field3 city state zip field4 field5 field6 field7 field8 field9 field10 field10 field11 field12 field13 field14 ] } = unpack $unpack_format, slurp_file( $filename ); # Slurp file could be a function or do{ local (*ARGV, $/) = $filename, + <> }; # Search for [Juerd]s "Cheap idioms" node for details $personnel{ $filename =~ m[PID(\d+)\.rec] } = \%record; }

      However, I strongly suspect that lines like

      City, State, zip     field <value> field <value>

      nn your specification don't indicate that City is a 4-byte field or State 5-bytes, but that within the overall 21-byte field allocated to them, the parts are variable length seperated by commas.

      unpack has no mechanism for dealing with this variable-width-within-a-fixed-length-field type of data. You would need to unpack the 3 as a single field and then use split or a regex to subdivide it later, which is a pain.

      In this case, I would probably go for a big-regex, though that doesn't mean it has to be complicated or hard to write. And using the /x modifier and embedded comments, it can become self documenting. Using your record specification as a starting point, and then interleaving the elements of the regex lined up as best you can with those lines makes for a reasonably readable layout. The need to add \s(*|+) between the elements when using /x has the effect of making the regex look 'noisy', but it's a trade off against the self-documentation provided by the embedded comments and alignment. Overall I find this quite readable, but YMMV.

      my $re_record = qr[ #LastName, FirstName Home <hm phone> field <value> ([^,]+) ,([^ ]+) \sHome <([^>]+) >\s+field\s<([^>]+)> \s*\n #Address1 Work <wk phone> field\s<value> (.*) Work <([^>]+) >\s+field\s<([^>]+)> \s*\n #Address2 field <value> (.*) field\s<([^>]+)> \s*\n #City, State, zip field <value> field <value> ([^,]+),([^,]+),(.*) field\s<([^>]+)>\sfield\s<([^>]+)> \s*\n \s* \n #field <value> field <value> field <value> field\s<([^>]+)> \s+field\s<([^>]+)> \s+ field\s<([^>]+)> \s*\n #field <value> field <value> field <value> field\s<([^>]+)> \s+field\s<([^>]+)> \s+ field\s<([^>]+)> \s*\n #field <value> field <value> field <value> field\s<([^>]+)> \s+field\s<([^>]+)> \s+ field\s<([^>]+)> \s*\n ]x; my $record = slurp_file( $filename ); my %record; @record{ qw[ .... ] } = ( $record =~ $re_record ); $personnel{ .... } = \%record;

      You could use a repeat count for the last 3-lines/9-fields, but if the data has a fixed number of fields, I think that using cut&paste makes things clearer in this instance.

      One tip: If you decide to go the 'big regex' route, start by commenting out everything but the first line, capture to an array and print the results. Once you have that capturing the right things, uncomment the second and repeat. Simple advice, but it took me a while to work it out.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller


        Wow!
        Thanks alot for the thoughtful response and the code.

        Right now, I think that for the project I am going to go with unpack, but since I am still learning, I may just try to develop two different versions with both methods as an academic exercise.(never one to pass up a learning opportunity)

        My fields aren't necessarily a constant width, so some of the advice I have gotten won't work for this project (although it is being filed in the ol' head for the future)
        Thanks for the tip with the @nnn thing, it is something that I should have known (I should have read the docs on unpack before ever using the word in my post, but I just read about in the camel...live and learn).

        I think that I am on the right track now, thanks to the monks. The community for Perl programmers is unequaled!

        Thanks again,
        XC
      The alternative to unpack is to use a regex (see perlre). Unpack and formats are both rather obscure bits of perl - you might find learning regexes more useful at this stage. In fact, it sounds as if you only need a very simple split on whitespace. For the bottom part of the screen, which is just pairs of fields and values, separated by whitespace, you could do

      my %fields = split(/\s+/, $line);

      for each line. For the earlier bits, if you know that whitespace will be in the right place, you could do:

      my ($last_name, $first_name, undef, $home_phone, %fields) = split(/\s+ +/, $line);
      A massive flamewar beneath your chosen depth has not been shown here

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://269767]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-04-18 19:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found