preg_split
имеет дополнительный PREG_SPLIT_DELIM_CAPTURE
флаг, который также возвращает все разделители в возвращаемом массиве. mb_split
не.
Есть ли способ разбить многобайтовую строку (не только UTF-8, но все виды) и захватить разделители?
Я пытаюсь сделать многобайтовый сплиттер разрыва строки, сохраняя разрывы строки, но предпочел бы более универсальное и пригодное для использования решение.
Решение
Благодаря пользователю Casimir et Hippolyte я создал решение и разместил его на github.
(https://github.com/vanderlee/PHP-multibyte-functions/blob/master/functions/mb_explode.php), который разрешает все флаги preg_split:
/**
* A cross between mb_split and preg_split, adding the preg_split flags
* to mb_split.
* @param string $pattern
* @param string $string
* @param int $limit
* @param int $flags
* @return array
*/
function mb_explode($pattern, $string, $limit = -1, $flags = 0) {
$strlen = strlen($string); // bytes!
mb_ereg_search_init($string);
$lengths = array();
$position = 0;
while (($array = mb_ereg_search_pos($pattern)) !== false) {
// capture split
$lengths[] = array($array[0] - $position, false, null);
// move position
$position = $array[0] + $array[1];
// capture delimiter
$regs = mb_ereg_search_getregs();
$lengths[] = array($array[1], true, isset($regs[1]) && $regs[1]);
// Continue on?
if ($position >= $strlen) {
break;
}
}
// Add last bit, if not ending with split
$lengths[] = array($strlen - $position, false, null);
// Substrings
$parts = array();
$position = 0;
$count = 1;
foreach ($lengths as $length) {
$is_delimiter = $length[1];
$is_captured = $length[2];
if ($limit > 0 && !$is_delimiter && ($length[0] || ~$flags & PREG_SPLIT_NO_EMPTY) && ++$count > $limit) {
if ($length[0] > 0 || ~$flags & PREG_SPLIT_NO_EMPTY) {
$parts[] = $flags & PREG_SPLIT_OFFSET_CAPTURE
? array(mb_strcut($string, $position), $position)
: mb_strcut($string, $position);
}
break;
} elseif ((!$is_delimiter || ($flags & PREG_SPLIT_DELIM_CAPTURE && $is_captured))
&& ($length[0] || ~$flags & PREG_SPLIT_NO_EMPTY)) {
$parts[] = $flags & PREG_SPLIT_OFFSET_CAPTURE
? array(mb_strcut($string, $position, $length[0]), $position)
: mb_strcut($string, $position, $length[0]);
}
$position += $length[0];
}
return $parts;
}
Захват разделителей возможен только с preg_split
и недоступен в других функциях.
Итак, три возможности:
1) преобразовать вашу строку в UTF8, используйте preg_split
с PREG_SPLIT_DELIM_CAPTURE
и использовать array_map
конвертировать каждый элемент в оригинальную кодировку.
Этот способ является более простым. Это не так во втором случае. (Обратите внимание, что в целом проще всегда работать в UTF8, а не работать с экзотическими кодировками)
2) вместо сплит-как функция, которую вы должны использовать, например, mb_ereg_search_regs
чтобы получить согласованные части и построить шаблон следующим образом:
delimiter|all_that_is_not_the_delimiter
(Обратите внимание, что две ветви чередования должны быть взаимоисключающими, и постарайтесь записать их таким образом, чтобы сделать невозможными пропуски между результатами. Первая часть должна быть в начале строки, а последняя часть должна быть в конце. Каждая часть должна быть смежной с предыдущей и т. Д.)
3) использование mb_split
с lookarounds. По определению, обходные пути являются утверждениями нулевой ширины и не соответствуют никаким символам, а только позициям в строке. Таким образом, вы можете использовать этот тип шаблона, который соответствует позициям после или перед разделителем:
(?=delimiter)|(<=delimiter)
(Ограничение этого способа заключается в том, что подшаблон в lookbehind не может иметь переменную длину (другими словами, вы не можете использовать квантификатор внутри), но это может быть чередование подшаблонов фиксированной длины: (?<=subpat1|subpat2|subpat3)
)
Других решений пока нет …