Perl: extracting data from text using regex
I am using Perl to process text with a regular expression. I have no control over the input. I have provided some input examples below.
As you can see, elements B and C can appear n times with different values in a string. I need to get all values as a backlink. Or, if you know otherwise, I am all ears.
I am trying to use the reset branch pattern (as stated in perldoc: "Advanced Templates" ) I have no luck matching string.
("Data" (Int "A" 22) (Int "B" 1) (Int "C" 2) (Int "D" 34896) (Int "E" 38046))
("Data" (Int "A" 22) (Int "B" 1) (Int "C" 2) (Int "B" 3) (Int "C" 4) (Int "B" 5) (Int "C "6) (Int" D "34896) (Int" E "38046))
("Data" (Int "A" 22) (Int "B" 22) (Int "C" 59) (Int "B" 1143) (Int "C" 1210) (Int "B" 1232) (Int "C "34896) (Int" D "34896) (Int" E "38046))
See my Perl below, any help would be great. Thanks for any help you can give.
if($inputString =~/\("Data" \(Int "A" ([0-9]+)\)(?:\(Int "B" ([0-9]+)\)\(Int "C" ([0-9]+)\))+\(Int "D" ([0-9]+)\)\(Int "E" ([0-9]+)\)\)/) {
print "\n\nmatched\n";
print "1: $1\n";
print "2: $2\n";
print "3: $3\n";
print "4: $4\n";
print "5: $5\n";
print "6: $6\n";
print "7: $7\n";
print "8: $8\n";
print "9: $9\n";
}
a source to share
Don't try to use a single regex, the set of regex and sections are easier to understand:
#!/usr/bin/perl
use strict;
use warnings;
while (<DATA>) {
next unless my ($data) = /\("Data" (.*)\)/;
print "on line $., I saw:\n";
for my $item ($data =~ /\((.*?)\)/g) {
my ($type, $var, $num) = split " ", $item;
print "\ttype $type var $var num $num\n";
}
}
__DATA__
("Data" (Int "A" 22)(Int "B" 1)(Int "C" 2)(Int "D" 34896)(Int "E" 38046))
("Data" (Int "A" 22)(Int "B" 1)(Int "C" 2)(Int "B" 3)(Int "C" 4)(Int "B" 5)(Int "C" 6)(Int "D" 34896)(Int "E" 38046))
("Data" (Int "A" 22)(Int "B" 22)(Int "C" 59)(Int "B" 1143)(Int "C" 1210)(Int "B" 1232)(Int "C" 34896)(Int "D" 34896)(Int "E" 38046))
If your data can stretch across lines, I would suggest using a parser instead of regex.
a source to share
I'm not sure what the use of getting the values as backlinks - who would you like to deal with the duplicate keys case (eg "C" in the second line). Also I'm not sure what you want to do with the values after retrieving.
But I would start with something like:
use Data::Dumper;
while (<DATA>)
{
my @a = m!\(Int "(.*?)" ([0-9]+)\)!g;
print Dumper(\@a);
}
__DATA__
("Data" (Int "A" 22)(Int "B" 1)(Int "C" 2)(Int "D" 34896)(Int "E" 38046))
("Data" (Int "A" 22)(Int "B" 1)(Int "C" 2)(Int "B" 3)(Int "C" 4)(Int "B" 5)(Int "C" 6)(Int "D" 34896)(Int "E" 38046))
("Data" (Int "A" 22)(Int "B" 22)(Int "C" 59)(Int "B" 1143)(Int "C" 1210)(Int "B" 1232)(Int "C" 34896)(Int "D" 34896)(Int "E" 38046))
This gives you an array of duplicate keys, values.
a source to share
My initial thought was to use named captures and to get values from %-
:
my $pattern = qr/
\(
"Data"\s+
\(Int\s+"A"\s+(?<A>[0-9]+)\)
(?:
\(Int\s+"B"\s+(?<B>[0-9]+)\)
\(Int\s+"C"\s+(?<C>[0-9]+)\)
)+
\(Int\s+"D"\s+(?<D>[0-9]+)\)
\(Int\s+"E"\s+(?<E>[0-9]+)\)
\)
/x;
Unfortunately grouping (?:...)
does not trigger multiple values for B and C. I suspect this is a bug. Doing this explicitly captures all values, but you will need to know the maximum number of instances ahead of time.
my $pattern = qr/
\(
"Data"\s+
\(Int\s+"A"\s+(?<A>[0-9]+)\)
\(Int\s+"B"\s+(?<B>[0-9]+)\)
\(Int\s+"C"\s+(?<C>[0-9]+)\)
(?:
\(Int\s+"B"\s+(?<B>[0-9]+)\)
\(Int\s+"C"\s+(?<C>[0-9]+)\)
)?
(?:
\(Int\s+"B"\s+(?<B>[0-9]+)\)
\(Int\s+"C"\s+(?<C>[0-9]+)\)
)?
# repeat (?:...) N times
\(Int\s+"D"\s+(?<D>[0-9]+)\)
\(Int\s+"E"\s+(?<E>[0-9]+)\)
\)
/x;
The easiest way is to use m//g
. You can either capture name / value pairs, as Beano suggests, or use multiple templates to capture each value:
my @b = m/Int "B" ([0-9]+)/g;
my @c = m/Int "C" ([0-9]+)/g;
# etc.
a source to share