Problem matching a list with multi-line items

I'm trying to break a chunk of text containing a numbered list into individual items, but am having trouble with items that continue to the second line. I'm hoping more pairs of eyes will see what I'm doing wrong.

Data looks like this:
1. This is the first item
2. This is a longer second item that wraps down
to the next line, but it's still one item.
3. This is the last time which is also long and
wraps to the next line as well

Open in new window

Perl code looks like this:
$/ = undef;
$_ = <>;

while (/^ \d+ [ \.]* (.*?) $/gmsx) {
  print "line[$1]\n";
}

Open in new window

Current output looks like this:
l
ine[This is the first item]
line[This is a longer second item that wraps down]
line[This is the last time which is also long and ]

Open in new window

As you can see I'm missing the second line of items two and three. I've tried various combinations of options and pattern greediness to get what I want, but so far no success.

jkfrenchAsked:
Who is Participating?
 
ozoConnect With a Mentor Commented:
perl -0777 -ne 's/\n/ /g,s/\s+$//,print "line[$_]\n" for grep/./,split/^\d+[. ]*/m'
also works
\. is not necessary in []
0
 
farzanjCommented:
Obviously.  You don't have a number in the second line.

Try this.
$/ = undef;
$_ = <>;

while (/^ \d+ [ \.]* (.*?) $/gmsx) {
  print "line[$1]\n";
}

Open in new window

0
 
jkfrenchAuthor Commented:
farzanj,

Thanks for the reply, but I don't see any difference between your example and the code I posted.

And I realize there is no number on the second line. I was trying to get the (.*?) pattern to match across lines to pick up the rest of the item. It looks like the $ is anchoring the pattern to the end of the line, although I was trying to use the 's' option to allow . to match newline.
0
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

 
farzanjCommented:
Sorry,  I replaced + with *
0
 
farzanjCommented:

$/ = undef;
$_ = <>;

while (/^ \d+ [ \.]* (.*?) $/gmsx) {
  print "line[$1]\n";
}

Open in new window

0
 
farzanjCommented:
SORRY ONCE AGAIN.  Problem with my clipboard.

$/ = undef;
$_ = <>;

while (/^\d* [ \.]* (.*?) $/gmsx) {
  print "line[$1]\n";
}
0
 
jkfrenchAuthor Commented:
No problem. That gets the additional text, but as separate items. So I'm now getting:

line[This is the first item]
line[This is a longer second item that wraps down]
line[to the next line, but it's still one item.]
line[This is the last time which is also long and ]
line[wraps to the next line as well]

Open in new window

but am trying to get:
line[This is the first item]
line[This is a longer second item that wraps down to the next line, but it's still one item.]
line[This is the last time which is also long and wraps to the next line as well]

Open in new window

0
 
farzanjCommented:
Ok.

here is the idea.  Change input record separator to

$/= '\d+';

This solves part of the problem

For the second part.  You have to delete new line character from the line before printing.
0
 
farzanjCommented:
And yes, you can keep \d+ the one I had changed to \d*
0
 
ozoCommented:
$/ is a string, not a regex.

s/\n/ /g,print "line[$_]\n" for /^\d+ [ .]* ([\s\S]*?) (?=\n\d|\Z)/gmx;
0
 
tel2Connect With a Mentor Commented:
Have you tested that code, ozo?

This seems produce the output you've specified, jk.

$/ = undef;
@lines = split(/\d+[\. ]*/, <>);

foreach $line (@lines)
{
        $lineno ++;
        next if $lineno == 1;
        chop $line;
        $line =~ s/\n/ /sg;
        print "line[$line]\n";
}

Open in new window

0
 
tel2Commented:
PS: Where I wrote:
    $line =~ s/\n/ /sg;
the 's' modifier is not required.  It could simply be:
    $line =~ s/\n/ /g;
0
 
ozoCommented:
> Have you tested that code, ozo?
Yes, it produces

line[This is the first item]
line[This is a longer second item that wraps down to the next line, but it's still one item.]
line[This is the last time which is also long and wraps to the next line as well]

Have you tested it?
0
 
tel2Commented:
Hi ozo,

Well, when I test it like this:
    perl -ne 's/\n/ /g,print "line[$_]\n" for /^\d+ [ .]* ([\s\S]*?) (?=\n\d|\Z)/gmx' inputfile
I get this:
    line[This is the first item]
    line[This is a longer second item that wraps down]
    line[This is the last time which is also long and]

What am I doing wrong?
0
 
ozoCommented:
perl -0777 -ne 's/\n/ /g,print "line[$_]\n" for /^\d+ [ .]* ([\s\S]*?) (?=\n\d|\Z)/gmx'

-0777 has the effect of $/ = undef;
0
 
tel2Commented:
Thanks ozo,

Sorry - wasn't thinking.  Should have realised that's what was needed.

BTW, in your code, should this:
    [ .]*
be this:
    [ \.]*
?
0
 
tel2Commented:
Thanks ozo.  Sorry - I forgot about that, too!

jkfrench, pls take note of that.  If you decide to use my solution, you can also change the:
    @lines = split(/\d+[\. ]*/, <>);
to:
    @lines = split(/\d+[. ]*/, <>);
0
 
jkfrenchAuthor Commented:
Thank you all for your help. I'm using ozo's one-liner, because it also works if there is a number embedded in the text. Since tel2's solution also worked for the data I posted, I'm awarding assist points.
0
 
tel2Commented:
Hi ozo,

Can you please explain your use of "([\s\S]*?)".  It obviously works, but it just looks a tad strange to say "match zero or more spaces or non-spaces".  I'm guessing it's something to do with avoiding newlines?


Hi jkf,

Thanks for the points.  I suggest you be careful in future about giving people lower marks for failing to meet unspecified requirements, but if you are interested in an adjusted version which caters for that new requirement, here's one at no extra charge:
$/ = undef;
@lines = split(/\d+\. */, <>);

foreach $line (@lines)
{
  next unless $started++;
  chop $line;
  $line =~ s/\n/ /g;
  print "line[$line]\n";
}

Open in new window

For the future, I would also suggest you provide your expected output in your original post, so that experts like farzanj don't waste your time or theirs posting solutions until they know they work.

Thanks.
0
 
ozoCommented:
/[\s\S]/ matches any character including \n
I could have used /./s
but I also wanted to use a . that doesn't match \n elsewhere in the same regex
I could have used (?s:.) and (?-s:.) but [\s\S] and . seemed simpler
0
 
tel2Commented:
Thanks heaps, ozo!
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.