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

I am trying to parse information out of a large collection of directory names. This includes things like dates, status indicators, blah blah blah. It also includes product names, which are the data least easily picked out. An example would be "MMF - Pretendin Coarse L, Codes 0114, 6114, 7504 - FINAL - 07.10.15". The names include all the same kinds of information, but do not have a predictable internal structure.

My notion has been to use a switch with fall-through rules, thusly (edited in the interests of brevity):

switch ($folder) { case m/(do not use|not used|delete(d?))/i { $include = 0; DEBUG("This one shouldn't be included"); next; } case m/(FINAL|DRAFT)/ { ($status) = $folder =~ m/(FINAL|DRAFT)/; DEBUG("case 0 = the status is '$status'"); next; } case m/($m_dateMonth)\s\d{2,4}/i { ($date) = $folder =~ m/(($m_dateMonth)\s\d{2,4})/i; DEBUG("case 1 = the date is '$date'"); next; } case m/\(($m_regions)\)/i { ($region) = $folder =~ m/\(($m_regions)\)/i; DEBUG("case 2 = the region is '$region'"); next; } case m/(MMF|ANDA|NDA|IND)/i { ($applicationType) = $folder =~ m/(MMF|ANDA|NDA|IND)/i; DEBUG("case 4 = the application type is '$applicationType'"); next; } case m/(\d{2,3}-?\d{3,4})/ { ($applicationNumber) = $folder =~ m/(\d{2,3}-?\d{3,4})/; DEBUG("case 6 = the application number is '$applicationNumber' +"); next; } }

Whatever doesn't get matched will be the product name (and some extraneous bits I can dispose of without much fuss.) However, I'm unsure of how to get this unmatched substring.

Suggestions and criticism welcomed equally.

Replies are listed 'Best First'.
Re: Grab everything not matched by regexp
by jethro (Monsignor) on Feb 22, 2010 at 22:53 UTC

    You might substitue away anything you match, i.e. instead of

    ($status) = $folder =~ m/(FINAL|DRAFT)/;

    use

    $folder =~ s/(FINAL|DRAFT)//; $status=$1;

      Yes, I think that's it. Thanks very much; and for incidentally pointing out the use of $1, which I wasn't familiar with.

Re: Grab everything not matched by regexp
by Xiong (Hermit) on Feb 23, 2010 at 19:29 UTC

    Mm, this feels like an issue not appropriate for a quick fix. You may wake up at any time to decide you need to extend it in an unexpected direction. If your data were better behaved, I'd look for the elegant solution. But here I'd incline to more of a heavy-duty, ugly strategy. I'm advocating a procedural, rather than a functional or logical approach. Given your sample code, you may, I dare say, prefer procedural.

    This reminds me of a task I worked several years ago, parsing Shakespeare's works from a badly proofed text. I needed to throw out misspellings, accept the Bard's eccentric spellings, discard stage directions, and otherwise deal with unpredictable data. There are, if I recall, a couple tens of thousands of lines and I had to see results from one attempt before modifying my code for the next; a quick eyeball of the data failed to reveal some of the most annoying exceptions. I believe I made the task more difficult by tying myself early on to a functional approach.

    Let's say you concentrate first on breaking each raw record into fields. These fields may or may not correspond to actual desired content; rather, you determine how to split up the raw record based on what you already know (without too much regexing). For instance, you might define a set of delimiters and split on them, bearing in mind that you probably want to append the delimiter to the field, in case it wasn't a throwaway after all.

    I'd look for a rough cut here and be willing to entertain overlapping fields. Keep in mind that you can have the same data in more than one field: "cats", "paw", and "cats paw". Some mix of small tokens and medium chunks will probably work best.

    Store all the fields associated with each record in some sort of structure, not necessarily a full-blown object. An array might be enough, or a hash.

    In the second pass, you examine rough-cut fields in detail and decide whether to throw them away or munge them further. You may decide what to do with one field by testing another. Don't delete anything until you've created the sanitized record full of clean fields in a second structure. Then toss the entire rough record.

    You might like to make the intermediate, rough-cut structure something like an array of hashes. The array index just tells the (possibly unimportant) sequence in which each chunk was pulled out; for each chunk, there are key/value pairs telling the chunk data itself, the split or other technique used to pull it, and whether the chunk has been incorporated into the final, clean record. Your final rule will sweep up any unused chunks and concatenate them into a 'notes' field in the clean record.

    This will seem rather a muddle when the code is reviewed but it's forgiving. You can add rules to the first pass until you're sure, by dumping the rough records, that you've chopped the raw records into small enough chunks.

    Then again, on the second pass, you can add rules one at a time until you've squeezed out everything you can identify. This is forgiving, because fooling with one rule will not break another or damage the rough record.

    Your code will come out looking like an accumulation of unplanned kludges, which is exactly what it will be. It's a one-off fix. Don't build a superandroid to herd a few cats.

    Many Monks will post much more elegant solutions but if your data is as jumbled as I fear, these may prove fragile and difficult to amend. On the other hand, the approach I offer will be difficult to maintain and reuse.

    I do strongly suggest that you spend goodly time browsing CPAN for tools you like for this job. But there are over 4000 modules that "parse" something, somehow; don't get lost in the woods.

      Xiong's experience sounds familiar. I recommend you consider his suggestions.

      I would also recommend studying the perldoc for regular expressions.

      Named captures are also very helpful. If you can install Perl 5.10.1, I would recommend using them. Perl 5.10.0 has a memory leak associated with using them, but you might get away with it. Anyway, 5.10.0 is the first release that allows using named-captures.

      UPDATE:

      You probably also will want to look at Parse::RecDescent as mentioned by Anonymous Monk above.
      I appreciate your thoughts here. You've led me to rethink my approach somewhat. I have been discovering the kinds of issues you anticipated, and I think now that you're correct about not being hasty to discard things, or in thinking that I can get away with a tidy solution.
Re: Grab everything not matched by regexp
by Anonymous Monk on Feb 23, 2010 at 08:04 UTC