简体   繁体   中英

How exactly do Regular Expression word boundaries work in PHP?

I'm currently writing a library for matching specific words in content.

Essentially the way it works is by compiling words into regular expressions, and running content through said regular expressions.

A feature I want to add is specifying whether a given word to match must start and/or end a word. For example, I have the word cat . I specify that it must start a word , so catering will match as cat is at the start, but ducat won't match as cat doesn't start the word.

I wanted to do this using word boundaries , but during some testing I found it doesn't work as I'd expect it to.

Take the following,

preg_match("/(^|\b)@nimal/i", "something@nimal", $match);
preg_match("/(^|\b)@nimal/i", "something!@nimal", $match);

In the statements above I would expect the following results,

> false
> 1 (@nimal)

But the result is instead the opposite,

> 1 (@nimal)
> false

In the first, I would expect it to fail as the group will eat the @ , leaving nimal to match against @nimal , which obviously it doesn't. Instead, the group matchs an empty string, so @nimal is matched, meaning @ is considered to be part of the word.

In the second, I would expect the group to eat the ! leaving @nimal to match the rest (which it should). Instead, it appears to combine the ! and @ together to form a word, which is confirmed by the following matching,

preg_match("/g\b!@\bn/i", "something!@nimal", $match);

Any ideas why regular expression does this?

I'd just love a page that clearly documents how word boundaries are determined, I just can't find one for the life of me.

The word boundary \b matches on a change from a \w (a word character) to a \W a non word character. You want to match if there is a \b before your @ which is a \W character. So to match you need a word character before your @

something@nimal
        ^^

==> Match because of the word boundary between g and @ .

something!@nimal
         ^^ 

==> NO match because between ! and @ there is no word boundary, both characters are \W

One problem I've encountered doing similar matching is words like can't and it's , where the apostrophe is considered a word/non-word boundary (as it is matched by \W and not \w ). If that is likely to be a problem for you, you should exclude the apostrophe (and all of the variants such as ' and ' that sometimes appear), for example by creating a class eg [\b^'] .

You might also experience problems with UTF8 characters that are genuinely part of the word (ie what us humans mean by a word), for example test your regex against how you encode a word such as Svašek .

It is therefore often easier when parsing normal "linguistic" text to look for "linguistic" boundaries such as space characters (not just literally spaces, but the full class including newlines and tabs), commas, colons, full-stops, etc (and angle-brackets if you are parsing HTML). YMMV.

@ is not part of a word character (in your locale probably it is, however, by default a "word" character is any letter or digit or the underscore character , Source - so @ is not a word character, therefore not \w but \W and as linked any \w\W or \W\w combination marks a \b position ), therefore it's always the word boundary that matches (in the OP's regex).

The following is similar to your regexes with the difference that instead of @ , a is used. And beginning of line is a word boundary as well, so no need to specify it as well:

$r = preg_match("/\b(animal)/i", "somethinganimal", $match);
var_dump($r, $match);

$r = preg_match("/\b(animal)/i", "something!animal", $match);
var_dump($r, $match);

Output:

int(0)
array(0) {
}
int(1)
array(2) {
  [0]=>
  string(6) "animal"
  [1]=>
  string(6) "animal"
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM