如何使正则表达式不使用引号进行匹配?

7

如何避免出现无法匹配 - preg_match_all('/"[\p{L}\p{Nd}а-яА-ЯёЁ -_\.\+]+"/ui', $outStr, $matches);


1
我相信通用术语是“惰性”。 - josh.trow
1
@josh:实际上,应该是“贪心算法”。 - Lightness Races in Orbit
3
实际上,“贪婪”和“懒惰”这样的术语只是更长、更技术的术语的口头简称,有时这些简称会掩盖真正发生的事情。更技术性的术语是量词可以进行“最大匹配”、“最小匹配”或“占有匹配”,其中*+?{n,m}是“最大集合”;*?+???{n,m}?是“最小集合”;而*+++{n,m}+则是“占有集合”。为了完整起见,我想加上?+,但它不会改变其功能:请认真思考。 - tchrist
1
@Tomalak:我相信我说得对- 'not hungry' == '懒惰'。我认为你在想 'hungry' == '贪婪'。 - josh.trow
3个回答

11

你是说非贪心模式,也就是找到最短的匹配而不是最长的匹配吗?*+?这些量词默认情况下是贪心的,会尽可能匹配更多字符。在它们后面加上问号可以让它们变成非贪心的。

preg_match_all('/"[\p{L}\p{Nd}а-яА-ЯёЁ -_\.\+]+?"/ui', $outStr, $matches);

贪婪匹配:

"foo" and "bar"
^^^^^^^^^^^^^^^

非贪婪匹配:

"foo" and "bar"
^^^^^

是的,谢谢。我尝试在引号后面设置“?”符号,但效果不佳。 - Arthur Kushman

3

请参考:http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

U (PCRE_UNGREEDY)

该修饰符颠倒了量词的"贪婪性",使它们默认情况下不是贪婪的,但如果在?后面跟随,它们会变得贪婪。它与Perl不兼容。它也可以通过模式中的(?U)修改器设置或在量词后面加上一个问号(例如.*?)设置。


2
糟糕!这意味着PHP和Java在使用(?U)标志时有所不同。在PHP中,它打开了PCRE_UNGREEDY正则表达式编译标志,但在JDK7中,它打开了UNICODE_CHARACTER_CLASS正则表达式编译标志,以使字符类符合Unicode正则表达式规范 - 这是PHP已经默认执行的(我相信!),因为Perl已经执行了。嗯,阅读pcrepattern手册让我有点怀疑。看起来只有[\pL\pN_],这并不完全符合上面引用的RL1.2要求。但比ASCII好。 - tchrist
5
如果你不知道自己在做什么,通常使用U(翻转量词行为)是一个坏主意。更清晰并且可以更好地控制每个量词的翻转,建议针对每个量词单独使用问号。 - CrayonViolent
这个答案已经被添加到了Stack Overflow正则表达式FAQ中的“修饰符”一节。 - aliteralmind
注意,(?U)修饰符是PCRE(以及PHP和R等衍生品)独有的,而在JavaScript,Python或Perl等编程语言中找不到它。早期的评论指出它在Java中的行为完全不同。 - Adam Katz

2
你建议了。
/"[\p{L}\p{Nd}а-яА-ЯёЁ -_\.\+]+"/ui

我认为这等同于:
/"[\pL\p{Nd}а-яА-ЯёЁ -_.+]+"/ui

为了向人们展示您使用的非ASCII字符(如果不明显),可以使用\x{⋯}转义字符:
/"[\pL\p{Nd}\x{430}-\x{44F}\x{410}-\x{42F}\x{451}\x{401} -_.+]+"/ui

使用命名字符是:

/"[\pL\p{Nd}\N{CYRILLIC SMALL LETTER A}-\N{CYRILLIC SMALL LETTER YA}\N{CYRILLIC CAPITAL LETTER A}-\N{CYRILLIC CAPITAL LETTER YA}\N{CYRILLIC SMALL LETTER IO}\N{CYRILLIC CAPITAL LETTER IO} -_.+]+"/ui

顺便说一句,这些是通过运行它们通过 uniquote 脚本 生成的,第一个使用 uniquote -x,第二个使用 uniquote -v
是的,我知道或至少相信 PHP 还不支持命名字符,但这使得谈论更容易。此外,它确保它们不会混淆看起来相似的字符。
U+0410 ‹А› \N{CYRILLIC CAPITAL LETTER A}
U+0430 ‹а› \N{CYRILLIC SMALL LETTER A}
U+0401 ‹Ё› \N{CYRILLIC CAPITAL LETTER IO}
U+0451 ‹ё› \N{CYRILLIC SMALL LETTER IO}

for:

U+0041 ‹A› \N{LATIN CAPITAL LETTER A}
U+0061 ‹a› \N{LATIN SMALL LETTER A}
U+00CB ‹Ë› \N{LATIN CAPITAL LETTER E WITH DIAERESIS}
U+00EB ‹ë› \N{LATIN SMALL LETTER E WITH DIAERESIS}

现在我想起来了,那些都是字母,所以我不明白为什么你要枚举西里尔字母表。是因为你不需要所有的西里尔字母,而只需要其中一部分吗?否则,我会这样做:
/"[\pL\p{Nd} -_.+]+"/ui

我想知道那个 /i 到底是干嘛的。我看不出它的作用,所以会写成这样:
/"[\pL\p{Nd} -_.+]+"/u

正如已经提到的那样,将最大量化符号+替换为其对应的最小版本+?即可:
/"[\pL\p{Nd} -_.+]+?"/u

然而,我对于[ -_]这个范围感到担忧,也就是说,\p{SPACE}-\p{LOW LINE}。 我觉得这是一个非常奇怪的范围。它意味着任何以下的字符:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_

一方面,您又包含了大写的 ASCII 字母。另一方面,您遗漏了一些符号和标点符号:
% unichars -g '\p{ASCII}' '[\pS\pP]' 'ord() < ord(" ") || ord() > ord("_")'
 `  U+0060 GC=Sk GRAVE ACCENT
 {  U+007B GC=Ps LEFT CURLY BRACKET
 |  U+007C GC=Sm VERTICAL LINE
 }  U+007D GC=Pe RIGHT CURLY BRACKET
 ~  U+007E GC=Sm TILDE

这个输出来自于 unichars 脚本,如果你感到好奇的话。
这似乎有些武断。所以我想知道这是否对你来说不够好:
/"[\pL\p{Nd}\s\pS\pP]+?"/u

现在我想起来了,这两个可能会引起其他问题:
U+0401 ‹Ё› \N{CYRILLIC CAPITAL LETTER IO}
U+0451 ‹ё› \N{CYRILLIC SMALL LETTER IO}

这假设它们是以 NFC 形式存在的(由规范分解的规范组合形成)。如果你处理的数据可能没有被规范化为 NFC 形式,那么你就需要考虑。
NFD("\N{CYRILLIC CAPITAL LETTER IO}") => "\N{CYRILLIC SMALL LETTER IE}\N{COMBINING DIAERESIS}"
NFD("\N{CYRILLIC SMALL LETTER IO}")   => "\N{CYRILLIC CAPITAL LETTER IE}\N{COMBINING DIAERESIS}"

现在你有了非字母字符!
% uniprops "COMBINING DIAERESIS"
U+0308 ‹◌̈› \N{COMBINING DIAERESIS}
    \w \pM \p{Mn}
    All Any Assigned InCombiningDiacriticalMarks Case_Ignorable CI Combining_Diacritical_Marks Dia Diacritic M Mn Gr_Ext Grapheme_Extend Graph GrExt ID_Continue IDC Inherited Zinh Mark Nonspacing_Mark Print Qaai Word XID_Continue XIDC

也许你真的想要:
/"[\pL\pM\p{Nd}\s\pS\pP]+?"/u

如果您想将字符串限制为仅包含拉丁或西里尔脚本中的字符(而不是希腊语或片假名等其他字符),则需要添加一个前瞻来实现这一点。
/"(?:(?=[\p{Latin}\p{Cyrillic}])[\pL\pM\p{Nd}\s\pS\pP])+?"/u

除此之外,您还需要使用Common来获取数字和各种标点符号和符号,以及需要使用Inherited来处理跟随字母的组合标记。这就把我们带到了这里:

/"(?:(?=[\p{Latin}\p{Cyrillic}\p{Common}\p{Inherited}])[\pL\pM\p{Nd}\s\pS\pP])+?"/u

现在这提供了另一种方法来实现双引号之间的最小匹配:
/"(?:(?!")(?=[\p{Latin}\p{Cyrillic}\p{Common}\p{Inherited}])[\pL\pM\p{Nd}\s\pS\pP])+"/u

这句话的意思是:“如果不使用/x模式,情况会变得非常复杂。”
/
    "               # literal double quote
    (?:
  ### This group specifies a single char with
  ### three separate constraints:

        # Constraint 1: next char must NOT be a double quote
        (?!")

        # Constraint 2: next char must be from one of these four scripts
        (?=[\p{Latin}\p{Cyrillic}\p{Common}\p{Inherited}])

        # Constraint 3: match one of either Letter, Mark, Decimal Number,
        #               whitespace, Symbol, or Punctuation:
        [\pL\pM\p{Nd}\s\pS\pP]

    )       # end constraint group
    +       # repeat entire group 1 or more times
    "       # and finally match another double-quote
/ux

如果是 Perl 的话,我会用 m{⋯}xu 来写。
m{
    "               # literal double quote
    (?:
  ### This group specifies a single char with
  ### three separate constraints:

        # Constraint 1: next char must NOT be a double quote
        (?!")

        # Constraint 2: next char must be from one of these four scripts
        (?=[\p{Latin}\p{Cyrillic}\p{Common}\p{Inherited}])

        # Constraint 3: match one of either Letter, Mark, Decimal Number,
        #               whitespace, Symbol, or Punctuation:
        [\pL\pM\p{Nd}\s\pS\pP]

    )       # end constraint group
    +       # repeat entire group 1 or more times
    "       # and finally match another double-quote
}ux

但我不知道你是否能在PHP中使用成对的括号分隔符。希望这可以帮到你!

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