From my previous questions Why under locale-pragma word characters do not match? and How to change nested quotes I learnt that when dealing with UTF-8 data you can't trust \w
as word-char and you must use the Unicode character property \p{Word}
. Now I am in a situation where I found that zero-width word boundary \b
also does not work with UTF-8 (with locale enabled), but I did not find any equivalent in Unicode character properties. I thought I may construct it myself like: (?<=\P{Word})(\p{Word}+)(?=\P{Word})
, it should be equivalent to \b(\w+)\b
.
In the test script below I have two arrays to test two different regexes. The first based on \b
works fine when locale is not enabled. To get it to also work with locales I wrote another version with emulating boundary (?=\P{Word})
but it does not work as I expected (I show expected results in script too).
Do you see what is wrong and how to get emulated regex work as first with ASCII (or without locale)?
#!/usr/bin/perl
use 5.010;
use utf8::all;
use locale; # et_EE.UTF-8 in my case
$| = 1;
my @test_boundary = ( # EXPECTED RESULT:
'"abc def"', # '«abc def»'
'"abc "d e f" ghi"', # '«abc «d e f» ghi»'
'"abc "d e f""', # '«abc «d e f»»'
'"abc "d e f"', # '«abc "d e f»'
'"abc "d" "e" f"', # '«abc «d» «e» f»'
# below won't work with \b when locale enabled
'"100 Естонiï"', # '«100 Естонiï»'
'"äöõ "ä õ ü" ï"', # '«äöõ «ä õ ü» ï»'
'"äöõ "ä õ ü""', # '«äöõ «ä õ ü»»'
'"äöõ "ä õ ü"', # '«äöõ «ä õ ü»'
'"äöõ "ä" "õ" ï"', # '«äöõ «ä» «õ» ï»'
);
my @test_emulate = ( # EXPECTED RESULT:
'"100 Естонiï"', # '«100 Естонiï»'
'"äöõ "ä õ ü" ï"', # '«äöõ «ä õ ü» ï»'
'"äöõ "ä õ ü""', # '«äöõ «ä õ ü»»'
'"äöõ "ä õ ü"', # '«äöõ "ä õ ü»'
'"äöõ "ä" "õ" ï"', # '«äöõ «ä» «õ» ï»'
);
say "BOUNDARY";
for my $sentence ( @test_boundary ) {
my $quote_count = ( $sentence =~ tr/"/"/ );
for ( my $i = 0 ; $i <= $quote_count ; $i += 2 ) {
$sentence =~ s/
"( # first qoute, start capture
[\p{Word}\.]+? # suva word-char
.*?\b[\.,?!»]*? # any char followed boundary + opt. punctuation
)" # stop capture, ending quote
/«$1»/xg; # change to fancy
}
say $sentence;
}
say "EMULATE";
for my $sentence ( @test_emulate ) {
my $quote_count = ( $sentence =~ tr/"/"/ );
for ( my $i = 0 ; $i <= $quote_count ; $i += 2 ) {
$sentence =~ s/
"( # first qoute, start capture
[\p{Word}\.]+? # at least one word-char or point
.*?(?=\P{Word}) # any char followed boundary
[\.,?!»]*? # optional punctuation
)" # stop capture, ending quote
/«$1»/gx; # change to fancy
}
say $sentence;
}
Since the character after the position of the \b
is either some punctuation or "
(to be safe, please double check that \p{Word}
does not match any of them), it falls into the case \b\W
. Therefore, we can emulate \b
with:
(?<=\p{Word})
I am not familiar with Perl, but from what I tested here, it seems that \w
(and \b
) also works nicely when the encoding is set to UTF-8.
$sentence =~ s/
"(
[\w\.]+?
.*?\b[\.,?!»]*?
)"
/«$1»/xg;
If you move up to Perl 5.14 and above, you can set the character set to Unicode with u
flag.
You can use this general strategy to construct a boundary corresponding to a character class. (Like how \b
word boundary definition is based on the definition of \w
).
Let C
be the character class. We would like to define a boundary that is based on the character class C.
The construction below will emulate boundary in front when you know the current character belongs to C
character class (equivalent to (\b\w)
):
(?<!C)C
Or behind (equivalent to \w\b
):
C(?!C)
Why negative look-around? Because positive look-around (with the complementary character class) will also assert that there must be a character ahead/behind (assert width ahead/behind at least 1). Negative look-around will allow for the case of beginning/ending of the string without writing a cumbersome regex.
For \B\w
emulation:
(?<=C)C
and similarly \w\B
:
C(?=C)
\B
is the direct opposite of \b
, therefore, we can just flip the positive/negative look-around to emulate the effect. It also makes sense - a non-boundary can only be formed when there are more character ahead/behind.
Other emulations (let c
be the complement character class of C
):
\b\W
: (?<=C)c
\W\b
: c(?=C)
\B\W
: (?<!C)c
\W\B
: c(?!C)
For the emulation of a standalone boundary (equivalent to \b
):
(?:(?<!C)(?=C)|(?<=C)(?!C))
And standalone non-boundary (equivalent to \B
):
(?:(?<!C)(?!C)|(?<=C)(?=C))
You should be using negative lookarounds:
(?<!\p{Word})(\p{Word}+)(?!\p{Word})
The positive lookarounds fail at the start or end of the string because they require a non-word character to be present. The negative lookarounds work in both cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With