PHP字符串分割,但将引号中的单词视为一个单词

54

我如何拆分下面的字符串:

Lorem ipsum "dolor sit amet" consectetur "adipiscing elit" dolor

转化为

array("Lorem", "ipsum", "dolor sit amet", "consectetur", "adipiscing elit", "dolor")

为了将引号中的文本视为一个单词。

以下是我目前拥有的内容:

$mytext = "Lorem ipsum %22dolor sit amet%22 consectetur %22adipiscing elit%22 dolor"
$noquotes = str_replace("%22", "", $mytext");
$newarray = explode(" ", $noquotes);

但是我的代码将每个单词分成了一个数组。我该如何使引号内的单词被视为一个单词?


2
这听起来像是一个正则表达式的工作 - Earlz
5个回答

90

使用str_getcsv()函数将更为简便。

$test = 'Lorem ipsum "dolor sit amet" consectetur "adipiscing elit" dolor';
var_dump(str_getcsv($test, ' '));
提供以下信息:
array(6) {
  [0]=>
  string(5) "Lorem"
  [1]=>
  string(5) "ipsum"
  [2]=>
  string(14) "dolor sit amet"
  [3]=>
  string(11) "consectetur"
  [4]=>
  string(15) "adipiscing elit"
  [5]=>
  string(5) "dolor"
}

这在我的开发机上可以正常运行,但是在我的生产服务器上却不行。:-/ - Martin Ueding
4
str_getcsv需要PHP 5.3及以上版本。 - armakuni
5
请注意,它“忽略”引号。如果您需要在分割中保留引号,那么这种方法将不起作用。 - Gayan Dasanayake
我进行了一些速度测试,发现preg_match_all大约快3-5倍。对于大多数人来说可能不是问题,特别是如果不需要引号(在这种情况下使用更容易),但我认为值得一提。 - err
@err 你能分享一下你的测试吗? - Petah
没有什么特别的,只是用1到10000的循环包装了一下,并在之前和之后检查了微秒时间。两者都足够快,即使是在测试数量下使用也不会有问题,因此我提到这对我们大多数人来说可能不是问题。 - err

89

你可以使用 preg_match_all(...)

$text = 'Lorem ipsum "dolor sit amet" consectetur "adipiscing \\"elit" dolor';
preg_match_all('/"(?:\\\\.|[^\\\\"])*"|\S+/', $text, $matches);
print_r($matches);

这将产生:

Array
(
    [0] => Array
        (
            [0] => Lorem
            [1] => ipsum
            [2] => "dolor sit amet"
            [3] => consectetur
            [4] => "adipiscing \"elit"
            [5] => dolor
        )

)

正如您所看到的,它也考虑了引号字符串内部的转义引号。

编辑

简短解释:

"           # match the character '"'
(?:         # start non-capture group 1 
  \\        #   match the character '\'
  .         #   match any character except line breaks
  |         #   OR
  [^\\"]    #   match any character except '\' and '"'
)*          # end non-capture group 1 and repeat it zero or more times
"           # match the character '"'
|           # OR
\S+         # match a non-whitespace character: [^\s] and repeat it one or more times

如果要匹配 %22 而不是双引号,你可以这样做:

preg_match_all('/%22(?:\\\\.|(?!%22).)*%22|\S+/', $text, $matches);

1
有没有不使用preg_match_all而使用preg_split的理由?在我看来,它似乎更自然。 - prodigitalson
3
不,使用preg_split(...)无法考虑转义字符。preg_match_all(...)更像是一个解析器,这在这里是更自然的事情。此外,使用preg_split(...),您需要在每个空格上向前查看有多少引号在它前面,使其成为一个O(n ^ 2)操作:对于小字符串没有问题,但当涉及较大字符串时可能会降低运行时间。 - Bart Kiers
在单引号的php字符串中,''不会被转义,因此您不需要为一个\使用\\。 - Calmarius
为什么您的解决方案会将此字符串 http://pastebin.com/bhrnMGST 转换为包含“引用句子”的字符串? - madphp
@Bart Kiers,情况有些变化。很抱歉。在使用mysql_real_escape_string()之后,我得到了这个结果,-里面有一个被引用的句子。因此,我需要考虑这些额外的斜杠(我不知道是否有影响)以及单引号或双引号。 - madphp
显示剩余10条评论

4
你可以尝试使用这个多重分割函数。
function multiexplode ($delimiters,$string)
{

$ready = str_replace($delimiters, $delimiters[0], $string);
$launch = explode($delimiters[0], $ready);
return  $launch;
}

$text = "here is a sample: this text, and this will be exploded. this also | this one too :)";
$exploded = multiexplode(array(",",".","|",":"),$text);

print_r($exploded);

2
这个答案很好,但如果你要求它在空格和引号上分割,它会在引号内部的空格上进行分割。 - starbeamrainbowlabs

2

我遇到了一个类似于这样的复杂字符串分割问题,但这里的答案都不能完全满足我的需求 - 所以我写了自己的代码。

我在这里发布它,以防对其他人有用。

这可能是一种非常缓慢和低效的方法 - 但它对我有用。

function explode_adv($openers, $closers, $togglers, $delimiters, $str)
{
    $chars = str_split($str);
    $parts = [];
    $nextpart = "";
    $toggle_states = array_fill_keys($togglers, false); // true = now inside, false = now outside
    $depth = 0;
    foreach($chars as $char)
    {
        if(in_array($char, $openers))
            $depth++;
        elseif(in_array($char, $closers))
            $depth--;
        elseif(in_array($char, $togglers))
        {
            if($toggle_states[$char])
                $depth--; // we are inside a toggle block, leave it and decrease the depth
            else
                // we are outside a toggle block, enter it and increase the depth
                $depth++;

            // invert the toggle block state
            $toggle_states[$char] = !$toggle_states[$char];
        }
        else
            $nextpart .= $char;

        if($depth < 0) $depth = 0;

        if(in_array($char, $delimiters) &&
           $depth == 0 &&
           !in_array($char, $closers))
        {
            $parts[] = substr($nextpart, 0, -1);
            $nextpart = "";
        }
    }
    if(strlen($nextpart) > 0)
        $parts[] = $nextpart;

    return $parts;
}

使用方法如下。 explode_adv 接受 5 个参数:
  1. 一个开块字符数组 - 例如 [( 等。
  2. 一个闭块字符数组 - 例如 ]) 等。
  3. 一个切换块字符数组 - 例如 "' 等。
  4. 应该导致分割到下一部分的字符数组。
  5. 要处理的字符串。
这种方法可能存在缺陷 - 欢迎修改。

1

在某些情况下,鲜为人知的token_get_all()可能会很有用:

$tokens = token_get_all("<?php $text ?>");
$separator = ' ';
$items = array();
$item = "";
$last = count($tokens) - 1;
foreach($tokens as $index => $token) {
    if($index != 0 && $index != $last) {
        if(count($token) == 3) {
            if($token[0] == T_CONSTANT_ENCAPSED_STRING) {
                $token = substr($token[1], 1, -1);
            } else {
                $token = $token[1];
            }
        }
        if($token == $separator) {
            $items[] = $item;
            $item = "";
        } else {
            $item .= $token;
        }
    }
}

结果:

Array
(
    [0] => Lorem
    [1] => ipsum
    [2] => dolor sit amet
    [3] => consectetur
    [4] => adipiscing elit
    [5] => dolor
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接