3
my $str = "<SampleElement oldattribs=\"sa1 sa2 sa3\">";

$str =~ s#<SampleElement[^>]*oldattribs="([^"]*)"#
          my $fulcnt=$&;
          my $afids=$1;
          my @affs = ();
          if($afids =~ m/\s+/) {
              @affs = split /\s/, $afids; 
              my $jnafs = join ",", map { $_=~s/[a-z]*//i, } @affs;
              ($fulcnt." newattribs=\"$jnafs\"");
          }
          else {
              ($fulcnt);
          }
         #eg;

My Output:

<SampleElement oldattribs="sa1 sa2 sa3" newattribs="1,1,1">

Expected Output:

<SampleElement oldattribs="sa1 sa2 sa3" newattribs="1,2,3">

Someone could point out me where I am doing wrong. Thanks in advance.

Zaid
  • 36,680
  • 16
  • 86
  • 155
ssr1012
  • 2,573
  • 1
  • 18
  • 30
  • 4
    Don't try to parse [XML/HTML](http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) with regexps yourself, use a [XML](http://search.cpan.org/~mirod/XML-Twig/Twig.pm) parser instead. – dgw Dec 02 '16 at 08:12
  • 3
    A one-liner: [`$str =~ s{]*oldattribs="\K(?[^"]+)} ["$+{v}\" newattribs=\"" . $+{v}=~s!\pL*(?\d+)$|\pL*(?\d+)\s*! $+{f} ? $+{f} : $+{s}.","!egr]e;`](https://ideone.com/KwEapL). – Wiktor Stribiżew Dec 02 '16 at 08:21

3 Answers3

4

Where you're going wrong is earlier than you think - you're parsing XML using regular expressions. XML is contextual, and regex isn't, so it's NEVER going to be better than a dirty hack.

#!/usr/bin/env perl
use strict;
use warnings;

use XML::Twig;
my $twig = XML::Twig -> parse ( \*DATA );

my $sample_elt = $twig -> get_xpath('//SampleElement',0); 
my @old_att = split ( ' ', $sample_elt -> att('oldattribs') );
$sample_elt -> set_att('newattribs', join " ", map { /(\d+)/ } @old_att);

$twig -> set_pretty_print ( 'indented_a' );
$twig -> print;


__DATA__
<XML>
    <SampleElement oldattribs="sa1 sa2 sa3">
    </SampleElement>
</XML>

But to answer the core of your problem - you're misusing map as an iterator here.

map { $_=~s/[a-z]*//i, } @affs;

Because what that is doing is iterating all the elements in @affs and modifying those... but map is just returning the result of the expression - which is 1 because it worked.

If you want to change @affs you'd:

s/[a-z]*//i for @affs; 

But if you didn't want to, then the easy answer is to use the r regex flag:

map { s/[a-z]*//ir } @affs;

Or as I've done in my example:

map { /(\d+)/ } @affs; 

Which regex matches and captures the numeric part of the string, but as a result the 'captured' text is what's returned.

Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
1

Here is a simple way to build shown output from the input $str.

Note: The input is in single quotes, not double. Then the \" isn't a problem in the regex.

my $str = '<SampleElement oldattribs=\"sa1 sa2 sa3\">';

# Pull 'sa1 sa2 sa3' string out of it
my ($attrs) = $str =~ /=\\"([^\\]+)/;    # " # (turn off bad syntax highlight)

# Build '1,2,3' string from it
my $indices = join ',', map { /(\d+)/ } split ' ', $attrs;

# Extract content between < > so to add to it, put it back together
my ($content) = $str =~ /<(.*)>/;    
my $outout = '<' . $content . " newattribs=\"$indices\"" . '>';

This gives the required output.

Some of these can be combined into single statements, if you are into that. For example

my $indices = 
    join ',', map { /(\d+)/ } split ' ', ($str =~ /"([^\\]+)/)[0];   # "

$str =~ s/<(.*)>/<$1 newattribs=\"$indices\">/;

All of this can be rolled into one regex, but it becomes just unwieldy and hard to maintain.


Above all – this appears to be XML or such ... please don't do it by hand, unless there is literally just a snippet or two. There are excellent parsers.

zdim
  • 64,580
  • 5
  • 52
  • 81
0

Found solution on this by searching map function:

my $str = "<SampleElement oldattribs=\"sa1 sa2 sa3\">";

$str=~s#<SampleElement[^>]*oldattribs="([^"]*)"#my $fulcnt=$&; my $afids=$1;
my @affs = ();
if($afids=~m/\s+/)
{
    @affs = split /\s/, $afids; 
    my @newas = join ",", map { (my $foo = $_) =~ s/[a-z]*//i; $foo; } @affs ;
    ($fulcnt." newattribs=\"@newas\"");
}
else
{
    ($fulcnt);
}
#eg;

I have updated the below line on my code:

my @newas = join ",", map { (my $foo = $_) =~ s/[a-z]*//i; $foo; } @affs ;

Instead of

my $jnafs = join ",", map { $_=~s/[a-z]*//i, } @affs;

Its working thanks for all.

ssr1012
  • 2,573
  • 1
  • 18
  • 30
  • You don't need the variable `$foo`, just use `map { s/[a-z]*//i ; $_ }` – dgw Dec 02 '16 at 08:07
  • 2
    Since perl 5.14 you could even use `map { s/[a-z]*//ir }` – dgw Dec 02 '16 at 08:14
  • 1
    That's good, but why strip off letters -- can you not match numbers instead, `map { /(\d+)/ }` (see my answer)? Also, this certainly misses things -- the regex line is broken syntax wise (it does not work). Can you please fix it, to have working code posted? – zdim Dec 02 '16 at 08:21
  • 1
    IMO it's really bad form to use a `s` regex in a `map` (without the `r` flag) because it's modifying the source array, and the point of `map` is to return a new one. That sort of modifications should be done via `for` IMO - it's as efficient, and _much_ clearer what you're doing. – Sobrique Dec 02 '16 at 09:05