Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
923 views
in Technique[技术] by (71.8m points)

regex - UTF-8 characters in preg_match_all (PHP)

I have preg_match_all('/[a?e?io?uáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);

If $in = 'h?llo' $out is:

array(1) {
[0]=>
  array(2) {
  [0]=>
    array(2) {
      [0]=>
      string(2) "?"
  [1]=>
  int(1)
}
[1]=>
array(2) {
  [0]=>
  string(1) "o"
  [1]=>
  int(5)
  }
}
}

The position of o should be 4. I've read about this problem online (the ? gets counted as 2). Is there a solution for this? I've seen mb_substr and similar, but is there something like this for preg_match_all?

Kind of related: Is their an equivalent of preg_match_all in Python? (Returning an array of matches with their position in the string)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is not a bug, PREG_OFFSET_CAPTURE refers to the byte offset of the character in the string.

mb_ereg_search_pos behaves the same way. One possibility is to change the encoding to UTF-32 before and then divide the position by 4 (because all unicode code units are represented as 4-byte sequences in UTF-32):

mb_regex_encoding("UTF-32");
$string = mb_convert_encoding('h?llo', "UTF-32", "UTF-8");
$regex =  mb_convert_encoding('[a?e?io?uáéíóú]', "UTF-32", "UTF-8");
mb_ereg_search_init ($string, $regex);
$positions = array();
while ($r = mb_ereg_search_pos()) {
    $positions[] = reset($r)/4;
}
print_r($positions);

gives:

Array
(
    [0] => 1
    [1] => 4
)

You could also convert the binary positions into code unit positions. For UTF-8, a suboptimal implementation is:

function utf8_byte_offset_to_unit($string, $boff) {
    $result = 0;
    for ($i = 0; $i < $boff; ) {
        $result++;
        $byte = $string[$i];
        $base2 = str_pad(
            base_convert((string) ord($byte), 10, 2), 8, "0", STR_PAD_LEFT);
        $p = strpos($base2, "0");
        if ($p == 0) { $i++; }
        elseif ($p <= 4) { $i += $p; }
        else  { return FALSE; }
    }
    return $result;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...