正则表达式匹配空格,但不匹配“字符串”中的空格。

5

我正在寻找一个正则表达式,只匹配那些没有被双引号(")包裹起来的空格。例如,在下面的文本中:

Mary had "a little lamb"

它应该匹配第一个和第二个空格,但不是其他的。

我想在没有双引号的空格处分割字符串,而不是在引号处分割。

我正在使用带有Qt工具包的C ++并希望使用QString :: split(QRegExp)。 QString与std :: string非常相似,QRegExp基本上是封装在类中的POSIX regex。如果存在这样的regex,则分割将很简单。

示例:

Mary had "a little lamb"     =>   Mary,had,"a little lamb"
1" 2 "3                      =>   1" 2 "3 (no splitting at ")
abc def="g h i" "j k" = 12   =>   abc,def="g h i","j k",=,12

对于我的编辑表示抱歉,我在最初提问时表述不够精确。希望现在更加清晰明了。


问题在这里得到了回答:使用正则表达式在Ruby中替换所有不在引号内的空格 - Ron Gejman
5个回答

9

我知道你刚刚发布了几乎完全相同的答案,但我无法忍受把所有这些都扔掉。 :-/

如果可以使用正则表达式拆分操作解决您的问题,则正则表达式必须匹配偶数引号,就像MSalters所说的那样。 但是,拆分正则表达式应仅匹配您要拆分的空格,因此其余的工作必须在前瞻中完成。 这是我会使用的内容:

" +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"

如果文本格式良好,则向前查找偶数引号就足以确定刚匹配的空格不在引号序列内。也就是说,不需要回顾,这很好,因为QRegExp似乎不支持它们。转义引号也可以被容纳,但正则表达式会变得更大更丑陋。但是,如果您不能确定文本格式良好,那么使用split()解决问题的可能性极小。
顺便说一下,QRegExp没有实现 POSIX正则表达式,如果它实现了,它将不支持前瞻或后顾。相反,它属于兼容Perl的正则表达式风格的松散定义类别。

好的,我基本上写了POSIX ;-) 当我搜索回顾时,我注意到有些东西丢失了。但很快我就注意到,只要假设总是成对的引号,我甚至不需要它们。也许我应该在Qt上报告一个错误。无论如何,为正则表达式的改进加1。 - Gunther Piez
1
我意识到你在宽泛地使用“POSIX”这个术语;我只是想指出其他人可能不知道。我不得不查找QRegExp以确保它不是 POSIX标准(或者更具体地说,我可以推荐基于前瞻的解决方案)。 - Alan Moore

4

MSalters引导我找到了正确的方向。他的答案存在问题,即给出的正则表达式总是匹配整个字符串,因此不适合split()函数,但这可以通过前瞻匹配来部分解决。假设引号始终成对出现(确实如此),我可以在每个由偶数个引号跟随的空格处进行拆分。

没有C转义符并且使用单引号的正则表达式如下:

' (?=[^"]*("[^"]*"[^"]*)*$)'

在源代码中,最终看起来是这样的(使用Qt和C++)。
QString buf("Mary had \"a little lamb\""); // string we want to split
QStringList splitted = buf.split( QRegExp(" (?=[^\"]*(\"[^\"]*\"[^\"]*)*$)") );

简单,对吧?

就性能而言,这些字符串在程序开始时只被解析一次,它们只有几十个字符,不到一百个。我会测试长字符串的运行时间,以确保不会发生任何问题;-)


4

“a b c”这个子字符串应该如何处理?

请注意,在子字符串“ b ”中,空格位于引号之间。

-- 编辑 --

如果一个空格被奇数个标准引号(即U+0022,我忽略那些有趣的Unicode“引号”)包围,则我认为这个空格是“在引号之间”的。

这意味着您需要以下正则表达式:^[^"]*("[^"]*"[^"]*)*"[^"]* [^"]*"[^"]*("[^"]*"[^"]*)*$

("[^"]*"[^"]*)表示一对引号。("[^"]*"[^"]*)*表示偶数数量的引号,("[^"]"[^"]*)*"表示奇数数量的引号。然后是实际的带引号字符串部分,后面跟着另一个奇数数量的引号。 ^$锚点是必需的,因为您需要从字符串开头计算每个引号。 这通过永远不查看子字符串来回答了上述" b "子字符串的问题。代价是必须将输入中的每个字符与整个字符串匹配,从而将其转换为O(N * N)拆分操作。

之所以可以在正则表达式中执行此操作,是因为只需有限量的内存即可。实际上只需要一个位;“我到目前为止看到了奇数还是偶数引号?”。您实际上不必匹配单个""对。

但这并非唯一可能的解释。如果您包括应该成对出现的“有趣的Unicode引号”,则还需要处理““双引号””字符串。这反过来意味着您需要计算打开的的数量,这意味着您需要无限的存储空间,这又意味着它不再是正则语言,这意味着您不能使用正则表达式。证毕。

无论如何,即使可能性存在,您仍然需要一个适当的解析器。O(N * N)行为以计算每个字符之前的引号数并不好玩。 如果您已经知道 Str[N] 之前有 X 个引号,则确定 Str[N+1] 之前有多少引号应该是O(1)操作,而不是O(N)。 可能的答案毕竟只有 X 或 X+1!


那是一个问题,而不是答案。请使用注释。 - Gumbo
这是一个带问号的回答 ;) 问题在于他正在使用错误的工具 (正则表达式而不是基于栈的解析器) 来解决他的问题。并且没有“关闭原因:问题无法用正则表达式解决”。 - MSalters
我问这个问题的原因是因为我想避免使用解析器。我想要一个“便宜”的解决方案。如果没有使用正则表达式的解决方案,请提供数学证明,我会接受它作为答案 :-)甚至不需要严格和严谨 :-) - Gunther Piez
1
让我们试一试。定义:如果一个空格前后都有奇数个标准引号(即U+0022,我会忽略那些有趣的Unicode“引号”),则该空格位于“引号之间”。这意味着您需要以下正则表达式:^.(".".)".* .".(".".)*$ 嗯,我最好把它变成一个答案 :P - MSalters
奇偶计数的想法很棒 :-) 但是第二个闭合括号后面应该有一个 * 吗?你试过匹配引号之间的空格吗?我确实想要其他的。但无论如何,这是一个起点 :-) - Gunther Piez
我确实错过了 *,已经修复。 - MSalters

1

如果字符串中的引号很简单(就像你的例子一样),你可以使用交替。这个正则表达式首先寻找一个简单的带引号的字符串;如果失败,它会找到空格。

/(\"[^\"]*\"| +)/

在 Perl 中,如果您在调用 split() 时在正则表达式中使用分组,则该函数不仅返回元素,还返回捕获的组(在本例中为分隔符)。然后,如果过滤掉空白和仅包含空格的分隔符,您将得到所需的元素列表。我不知道类似的策略是否适用于 C++,但是以下 Perl 代码可以工作:
use strict;
use warnings;
while (<DATA>){
    chomp;
    my @elements = split /(\"[^\"]*\"| +)/, $_;
    @elements = grep {length and /[^ ]/} @elements;
    # Do stuff with @elements
}

__DATA__
Mary had "a little lamb"
1" 2 "3
abc def="g h i" "j k" = 12

-2

最简单的正则表达式解决方案:匹配整个空格和引号。稍后过滤引号。

"[^"]*"|\s

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