如何使正则表达式不要用引号挨饿?

时间:2011-05-12 12:58:38

标签: php regex unicode

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

3 个答案:

答案 0 :(得分:11)

你的意思是非贪婪,因为找到最短的比赛而不是最长的比赛?默认情况下,*+?量词是贪婪的,并且会尽可能匹配。在它们后面添加一个问号,使它们不贪婪。

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

贪婪的比赛:

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

非贪婪的比赛:

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

答案 1 :(得分:3)

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

  

U(PCRE_UNGREEDY)

     

这个修饰语反映了量词的“贪婪”   他们默认不贪心   但如果紧随其后会变得贪婪吗?它   与Perl不兼容。它可以   也可以通过(?U)修改器设置来设置   在模式内或通过问题   在量词后面标记(例如。*?)。

答案 2 :(得分: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

BTW,这些是通过uniquote script运行它们产生的,第一个使用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}

有:

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 script。)

这似乎很奇怪。所以我想知道这对你来说是否不够好:

/"[\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来获取数字和各种puntuation和符号,以及 你需要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中使用这样的配对包围分隔符。

希望这有帮助!