To clarify the "limit" parameter and the PREG_SPLIT_DELIM_CAPTURE option,
<?php
$preg_split('(/ /)', '1 2 3 4 5 6 7 8', 4 ,PREG_SPLIT_DELIM_CAPTURE );
?>
returns:
('1', ' ', '2', ' ' , '3', ' ', '4 5 6 7 8')
So you actually get 7 array items not 4
preg_split
(PHP 4, PHP 5)
preg_split — 正規表現で文字列を分割する
説明
$pattern
, string $subject
[, int $limit = -1
[, int $flags = 0
]] )指定した文字列を、正規表現で分割します。
パラメータ
-
pattern -
検索するパターンを表す文字列。
-
subject -
入力文字列。
-
limit -
これを指定した場合、最大
limit個の部分文字列を返します。 残りの文字列は、最後の部分文字列に含めて返されます。limitが -1、0 あるいはNULLの場合は「制限が無い」ことを意味します。 制限を指定せずにflagsパラメータを指定したい場合などにNULLを使用します。 -
flags -
flagsは、次のフラグを組み合わせたものとする (ビット和演算子 | で組み合わせる)ことが可能です。-
PREG_SPLIT_NO_EMPTY - このフラグを設定すると、空文字列でないものだけが preg_split() により返されます。
-
PREG_SPLIT_DELIM_CAPTURE - このフラグを設定すると、文字列分割用のパターン中の カッコによるサブパターンでキャプチャされた値も同時に返されます。
-
PREG_SPLIT_OFFSET_CAPTURE -
このフラグを設定した場合、各マッチに対応する文字列のオフセットも返されます。 これにより、返り値は配列となり、配列の要素 0 はマッチした文字列、 要素 1 は
subjectにおけるマッチした文字列のオフセット値となることに 注意してください。
-
返り値
pattern にマッチした境界で分割した
subject の部分文字列の配列を返します。
変更履歴
| バージョン | 説明 |
|---|---|
| 4.3.0 |
PREG_SPLIT_OFFSET_CAPTURE が追加されました。
|
| 4.0.5 |
PREG_SPLIT_DELIM_CAPTURE が追加されました。
|
例
例1 preg_split() の例 : 検索文字列のある部分を取得
<?php
// カンマまたは " ", \r, \t, \n , \f などの空白文字で句を分割する。
$keywords = preg_split("/[\s,]+/", "hypertext language, programming");
print_r($keywords);
?>
上の例の出力は以下となります。
Array
(
[0] => hypertext
[1] => language
[2] => programming
)
例2 文字列を文字要素に分割
<?php
$str = 'string';
$chars = preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY);
print_r($chars);
?>
上の例の出力は以下となります。
Array
(
[0] => s
[1] => t
[2] => r
[3] => i
[4] => n
[5] => g
)
例3 文字列をマッチするものとそのオフセットに分割
<?php
$str = 'hypertext language programming';
$chars = preg_split('/ /', $str, -1, PREG_SPLIT_OFFSET_CAPTURE);
print_r($chars);
?>
上の例の出力は以下となります。
Array
(
[0] => Array
(
[0] => hypertext
[1] => 0
)
[1] => Array
(
[0] => language
[1] => 10
)
[2] => Array
(
[0] => programming
[1] => 19
)
)
注意
正規表現の威力を必要としないのなら、より高速な (機能はシンプルですが) 代替関数として explode() あるいは str_split() のような選択肢があります。
参考
- PCRE のパターン
- implode() - 配列要素を文字列により連結する
- preg_match() - 正規表現によるマッチングを行う
- preg_match_all() - 繰り返し正規表現検索を行う
- preg_replace() - 正規表現検索および置換を行う
- preg_last_error() - 直近の PCRE 正規表現処理のエラーコードを返す
Here is another way to split a CamelCase string, which is a simpler expression than the one using lookaheads and lookbehinds:
preg_split('/([[:upper:]][[:lower:]]+)/', $last, null, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY)
It makes the entire CamelCased word the delimiter, then returns the delimiters (PREG_SPLIT_DELIM_CAPTURE) and omits the empty values between the delimiters (PREG_SPLIT_NO_EMPTY)
Sometimes PREG_SPLIT_DELIM_CAPTURE does strange results.
<?php
$content = '<strong>Lorem ipsum dolor</strong> sit <img src="test.png" />amet <span class="test" style="color:red">consec<i>tet</i>uer</span>.';
$chars = preg_split('/<[^>]*[^\/]>/i', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
print_r($chars);
?>
Produces:
Array
(
[0] => Lorem ipsum dolor
[1] => sit <img src="test.png" />amet
[2] => consec
[3] => tet
[4] => uer
)
So that the delimiter patterns are missing. If you wanna get these patters remember to use parentheses.
<?php
$chars = preg_split('/(<[^>]*[^\/]>)/i', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
print_r($chars); //parentheses added
?>
Produces:
Array
(
[0] => <strong>
[1] => Lorem ipsum dolor
[2] => </strong>
[3] => sit <img src="test.png" />amet
[4] => <span class="test" style="color:red">
[5] => consec
[6] => <i>
[7] => tet
[8] => </i>
[9] => uer
[10] => </span>
[11] => .
)
You must be caution when using lookbehind to a variable match.
For example:
'/(?<!\\\)\r?\n)/'
to match a new line when not \ is before it don't go as spected as it match \r as the lookbehind (becouse isn't a \) and is optional before \n.
You must use this for example:
'/((?<!\\\|\r)\n)|((?<!\\\)\r\n)/'
That match a alone \n (not preceded by \r or \) or a \r\n not preceded by a \.
preg_split() behaves differently from perl's split() if the string ends with a delimiter. This perl snippet will print 5:
my @a = split(/ /, "a b c d e ");
print scalar @a;
The corresponding php code prints 6:
<?php print count(preg_split("/ /", "a b c d e ")); ?>
This is not necessarily a bug (nowhere does the documentation say that preg_split() behaves the same as perl's split()) but it might surprise perl programmers.
Extending m.timmermans's solution, you can use the following code as a search expression parser:
<?php
$search_expression = "apple bear \"Tom Cruise\" or 'Mickey Mouse' another word";
$words = preg_split("/[\s,]*\\\"([^\\\"]+)\\\"[\s,]*|" . "[\s,]*'([^']+)'[\s,]*|" . "[\s,]+/", $search_expression, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
print_r($words);
?>
The result will be:
Array
(
[0] => apple
[1] => bear
[2] => Tom Cruise
[3] => or
[4] => Mickey Mouse
[5] => another
[6] => word
)
1. Accepted delimiters: white spaces (space, tab, new line etc.) and commas.
2. You can use either simple (') or double (") quotes for expressions which contains more than one word.
Spacing out your CamelCase using preg_replace:
<?php
function spacify($camel, $glue = ' ') {
return preg_replace( '/([a-z0-9])([A-Z])/', "$1$glue$2", $camel );
}
echo spacify('CamelCaseWords'), "\n"; // 'Camel Case Words'
echo spacify('camelCaseWords'), "\n"; // 'camel Case Words'
?>
Hope this helps someone...
<?php
/**
* Split a string into groups of words with a line no longer than $max
* characters.
*
* @param string $string
* @param integer $max
* @return array
**/
function split_words($string, $max = 1)
{
$words = preg_split('/\s/', $string);
$lines = array();
$line = '';
foreach ($words as $k => $word) {
$length = strlen($line . ' ' . $word);
if ($length <= $max) {
$line .= ' ' . $word;
} else if ($length > $max) {
if (!empty($line)) $lines[] = trim($line);
$line = $word;
} else {
$lines[] = trim($line) . ' ' . $word;
$line = '';
}
}
$lines[] = ($line = trim($line)) ? $line : $word;
return $lines;
}
?>
To split a camel-cased string using preg_split() with lookaheads and lookbehinds:
<?php
function splitCamelCase($str) {
return preg_split('/(?<=\\w)(?=[A-Z])/', $str);
}
?>
If you want to use something like explode(PHP_EOL, $string) but for all combinations of \r and \n, try this one:
<?php
$text = "A\nB\rC\r\nD\r\rE\n\nF";
$texts = preg_split("/((\r(?!\n))|((?<!\r)\n)|(\r\n))/", $text);
?>
result:
array("A", "B", "C", "D", "", "E", "", "F");
If you need convert function arguments without default default values and references, you can try this code:
<?php
$func_args = '$node, $op, $a3 = NULL, $form = array(), $a4 = NULL'
$call_arg = preg_match_all('@(?<func_arg>\$[^,= ]+)@i', $func_args, $matches);
$call_arg = implode(',', $matches['func_arg']);
?>
Result: string = "$node,$op,$a3,$form,$a4"
If the task is too complicated for preg_split, preg_match_all might come in handy, since preg_split is essentially a special case.
I wanted to split a string on a certain character (asterisk), but only if it wasn't escaped (by a preceding backslash). Thus, I should ensure an even number of backslashes before any asterisk meant as a splitter. Look-behind in a regular expression wouldn't work since the length of the preceding backslash sequence can't be fixed. So I turned to preg_match_all:
<?php
// split a string at unescaped asterisks
// where backslash is the escape character
$splitter = "/\\*((?:[^\\\\*]|\\\\.)*)/";
preg_match_all($splitter, "*$string", $aPieces, PREG_PATTERN_ORDER);
$aPieces = $aPieces[1];
// $aPieces now contains the exploded string
// and unescaping can be safely done on each piece
foreach ($aPieces as $idx=>$piece)
$aPieces[$idx] = preg_replace("/\\\\(.)/s", "$1", $piece);
?>
<?php
$s = '<p>bleh blah</p><p style="one">one two three</p>';
$htmlbits = preg_split('/(<p( style="[-:a-z0-9 ]+")?>|<\/p>)/i', $s, -1, PREG_SPLIT_DELIM_CAPTURE);
print_r($htmlbits);
?>
Array
(
[0] =>
[1] => <p>
[2] => bleh blah
[3] => </p>
[4] =>
[5] => <p style="one">
[6] => style="one"
[7] => one two three
[8] => </p>
[9] =>
)
two interesting bits:
1. When using PREG_SPLIT_DELIM_CAPTURE, if you use more than one pair of parentheses, the result array can have members representing all pairs. See array indexes 5 and 6 to see two adjacent delimiter results in which the second is a subset match of the first.
2. If a parenthesised sub-expression is made optional by a following question mark (ex: '/abc (optional subregex)?/') some split delimiters may be captured in the result while others are not. See array indexes 1 and 2 to see an instance where the overall match succeeded and returned a delimiter while the optional sub-expression '( style="[-:a-z0-9 ]+")?' did not match, and did not return a delimiter. This means it's possible to have a result with an unpredictable number of delimiters in the result array.
This second aspect is true irrespective of the number of pairs of parentheses in the regex. This means: in a regular expression with a single optional parenthesised sub-expression, the overall expression can match without generating a corresponding delimiter in the result.
PREG_SPLIT_OFFSET_CAPTURE should be maintained for UTF-8 characters, because it produces wrong results as if it is using strlen() internally, instead of using mb_strlen(), which is the right one...
Limit = 1 may be confusing. The important thing is that in case of limit equals to 1 will produce only ONE substring. Ergo the only one substring will be the first one as well as the last one. Tnat the rest of the string (after the first delimiter) will be placed to the last substring. But last is the first and only one.
<?php
$output = $preg_split('(/ /)', '1 2 3 4 5 6 7 8', 1);
echo $output[0] //will return whole string!;
$output = $preg_split('(/ /)', '1 2 3 4 5 6 7 8', 2);
echo $output[0] //will return 1;
echo $output[1] //will return '2 3 4 5 6 7 8';
?>
If you want to split by a char, but want to ignore that char in case it is escaped, use a lookbehind assertion.
In this example a string will be split by ":" but "\:" will be ignored:
<?php
$string='a:b:c\:d';
$array=preg_split('#(?<!\\\)\:#',$string);
print_r($array);
?>
Results into:
Array
(
[0] => a
[1] => b
[2] => c\:d
)
If you need to split a list of "tags" while allowing for user error, you'll find this more useful than the manual's first example.
<?php
$string = 'one, two,three, four , five,six seven';
$array = preg_split("/[\s]*[,][\s]*/", $string);
print_r($array);
// Array ( [0] => one [1] => two [2] => three [3] => four [4] => five [5] => six seven )
?>
This splits the string *only* by commas, regardless of how many spaces there are on either side of any comma.
The above description for PREG_SPLIT_OFFSET_CAPTURE may be a bit confusing.
When the flag is or'd into the 'flags' parameter of preg_split, each match is returned in the form of a two-element array. For each of the two-element arrays, the first element is the matched string, while the second is the match's zero-based offset in the input string.
For example, if you called preg_split like this:
preg_split('/foo/', 'matchfoomatch', -1, PREG_SPLIT_OFFSET_CAPTURE);
it would return an array of the form:
Array(
[0] => Array([0] => "match", [1] => 0),
[1] => Array([1] => "match", [1] => 8)
)
Note that or'ing in PREG_DELIM_CAPTURE along with PREG_SPLIT_OFFSET_CAPTURE works as well.
Here's a helpful function to space out your CamelCase using preg_split:
<?php
function spacify($camel, $glue = ' ') {
return $camel[0] . substr(implode($glue, array_map('implode', array_chunk(preg_split('/([A-Z])/',
ucfirst($camel), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE), 2))), 1);
}
echo spacify('CamelCaseWords'); // 'Camel Case Words'
echo spacify('camelCaseWords'); // 'camel Case Words'
?>
how to display a shortened text string with an elipsis, but on word boundaries only.
<?php
function truncate($string, $max = 70, $rep = '...') {
$words = preg_split("/[\s]+/", $string);
$newstring = '';
$numwords = 0;
foreach ($words as $word) {
if ((strlen($newstring) + 1 + strlen($word)) < $max) {
$newstring .= ' '.$word;
++$numwords;
} else {
break;
}
}
if ($numwords < count($words)) {
$newstring .= $rep;
}
return $newstring;
}
?>
hope this helps someone! thanks for all the help from everyone else!!
For people who want to use the double quote to group words/fields, kind of like CSV does, you can use the following expression:
<?php
$keywords = preg_split( "/[\s,]*\\\"([^\\\"]+)\\\"[\s,]*|[\s,]+/", "textline with, commas and \"quoted text\" inserted", 0, PREG_SPLIT_DELIM_CAPTURE );
?>
Which will result in:
Array
(
[0] => textline
[1] => with
[2] => commas
[3] => and
[4] => quoted text
[5] => inserted
)
I was having trouble getting the PREG_SPLIT_DELIM_CAPTURE flag to work because I missed reading the "parenthesized expression" in the documentation :-(
So the pattern should look like:
/(A)/
not just
/A/
and it works as described/expected.
