【问题标题】:PCRE php regex to match groups correctlyPCRE php正则表达式正确匹配组
【发布时间】:2017-04-11 16:40:16
【问题描述】:

我有以下示例文本:

tabela de Preço 18654 TONER XEROX 106R01632 MA(6000/6010 117.90 129.90 18656 TONER XEROX 106R01634 PR 6000/6010 179.00 199.00 UDP COMPUT ADORES IBYTE 32607 UDP A - GCL(CDCP 2.41,2,500) 747.00 829.90 32148 UDP A - GCL(CDCP 2.41,2,500) 747.00 829.90 32149 UDP A - GCL(CDCP 2.41,4,500,DVD) 769.90 879.00 32555 UDP A - GCL(CDCP 2.41,4,500,DVD) 769.90 879.00 32490 UDP A - ICL(CDCP 2.41,2,500) 747.00 829.90 32150 UDP A - ICL(CDCP 2.41,2,500) 747.00 829.90 32024 UDP A - ICW10(CDC 2.8,4,500,DVD) 1 260.001 399.90 32445 UDP A - ICW10(CDC 2.8,4,500,DVD) 1 260.001 399.90 31060 UDP A - ISW10PRO(CDCP 2.41,4,500)SLI1 349.901 549.90 32356 UDP F - GCL(I3 6G 3.7,4,500,DVD,LT) 1 699.001 929.90 

并且我必须将其匹配到以下组中:

code, description,value1,value2

使用该摘录作为来源:

"18654 TONER XEROX 106R01632 MA(6000/6010 117.90 129.90"

它是一个产品,我需要按如下方式解析它:

"18654" is the code
"TONER XEROX 106R01632 MA(6000/6010" is the description
"117.90" is the value1
"129.90" is the value2

但描述、value1 和 value2 的长度各不相同,虽然我的产品具有 value1,例如“117.90”,但我也有“1 699.00”和“90.00”。

我正在尝试使用以下正则表达式来捕获组,但它正确匹配了一些而不是整个源字符串:

(?<code>\d{5})\s{1}(?<description>.{20,35})\s{1}(?<value1>\d{2,3}\.\d{2})\s{1}(?<value2>\d{2,3}\.\d{2})

如何使用 pcre (php) 正确捕获此示例源字符串中每个产品的组?

我有以下 regex101.com 网址来显示我尝试过的内容 https://regex101.com/r/Smh2KA/3

提前致谢。

【问题讨论】:

  • 您需要解释应该如何识别比赛的不同部分。
  • 从来没有必要在正则表达式中写{1}。这是默认设置,添加它只会让 RE 看起来更复杂。
  • 为什么所有数据都在一行?
  • @CasimiretHippolyte 我无法控制数据源。这个字符串来自一个解析/转换为纯文本的pdf,所以一行大字符串
  • @Barmar 我需要的正则表达式应该解析整个输入字符串并捕获如下组:代码是启动产品的 5 个数字固定组。 description 是以下组,前面有 1 个空格字符。 value1 是下一个,可以是“117.90”、“1 699.00”或“90.00”,value2 也是最后一个。然后,序列重复直到输入字符串的结尾

标签: php regex pcre


【解决方案1】:

你可以使用这个模式:

$pattern = '~\b (?<id>\d{5}) \s
           (?<desc>.*?) \s*+
           (?<val1>
               (?: \d \s*(?=[\d\s]*\.\d\s?\d\s*(?<c>(?(c)\g{c})\s*\d)) )+
               \.\d\s?\d
           ) \s*
           (?<val2>\g{c}\d?\.\d{2})~x';

demo

val1 中的子模式检查 val1 的整数部分中的每个数字是否存在 val2 中整数部分的数字。这就是为什么这部分有点复杂。但优点是描述部分和第一个值之间不再可能混淆。

val1 子模式详情:

(?:
    \d \s* # 1 digit in val1 (and an eventual space)
    (?= # lookahead that checks if for this digit there's also
        # a digit in val2
        [\d\s]*\.\d\s?\d\s* # reach val2
        (?<c> # open a capture group c
             (?(c)\g{c}) # conditional: if the capture group c has already captured
                         # something then start the group with the backreference \g{c}
                         # (this means that the non-captured group has been repeated
                         # at least once)
             \s*\d       # add the next digit to c
        )
    )
)+ # repeat the non-capturing group
\.\d\s?\d

请注意,此模式需要很多步骤才能成功。如果您需要在大输入上使用它,我建议在每个代码之前拆分字符串,然后使用 preg_match 和前一个模式搜索每个部分(您可以使用 ^ 锚而不是 \b 开始它):

$parts = preg_split('~\b(?=\d{5}\b)~', $str);
$result = [];
foreach ($parts as $part) {
    preg_match($pattern, $part, $m);
    $result[] = [$m['id'], $m['desc'], $m['val1'], $m['val2']];
}

【讨论】:

  • 感谢您的帮助,您的正则表达式似乎是迄今为止最准确的,我现在正在根据更大的输入字符串检查它。
  • @CaioMaia:我将添加一个关于大输入的注释。
【解决方案2】:

我建议像这样的正则表达式

\b(?<code>\d{5})\s+(?<description>.*?)\s+(?<value1>\d[,\d\s]*\.\d{2})\s*(?<value2>\d[,\d\s]*\.\d{2})

regex demo

带有 cmets 的版本:

\b                           # leading word boundary
(?<code>\d{5})               # 5 digits
\s+                          # 1+ whitespaces
(?<description>.*?)          # any 0+ non-line break chars
\s+                          # 1+ whitespaces
(?<value1>\d[,\d\s]*\.\d{2}) # a float number with 2-digit fractional part
\s*                          # 0+ whitespaces
(?<value2>\d[,\d\s]*\.\d{2}) # a float number

注意:如果您的浮点值(value1 和 value2)包含 , 作为千位分隔符和 . 作为小数分隔符,请将它们的模式调整为 \d[,\d]*\.\d+。如果千位分隔符是空格,请使用\d[\d\s]*\.\d+。如果千位分隔符是空格,小数分隔符是逗号,请使用\d[\d\s]*,\d+。以此类推。

【讨论】:

  • 为什么,[,\d\s]* 中有?问题不是说value1 可以包含逗号。
  • @Barmar:以防万一。没有的话可以去掉。
  • @Wiktor Stribiżew 再次感谢您帮助我,但正如您所见,这非常具有挑战性!好吧,问题仍然存在,因为在这种情况下:“32365 UDP N - IWL(PQCP 2.67,4,500) 989.901 099.90” value1 被解析为 989.901,这是一个无效数字(应该是 989.90 - 两位小数),所以 value2 是结果,也没有正确解析( 099.90 )。有什么其他想法可以解决这个问题吗?
  • @WiktorStribiżew 千位分隔符实际上是一个空格,值没有逗号。问题是当 value2 大于 1000 时,空间被添加为千位分隔符,因此 value2 的第一个数字成为 value1 的最后一个数字的后面,所以在这种情况下它们之间没有空格,但 value1 和 value2 更少然后 1000
  • 现在有什么不匹配的?
【解决方案3】:

这个应该可以的:

(?&lt;code&gt;\d{5})\s+(?&lt;description&gt;((?!\d{2,}\.\d{1,}).)*)\s+(?&lt;value1&gt;\d{2,3}\.\d{1,})((?!\d{2,}\.\d{1,}).)*(?&lt;value2&gt;\d{2,}\.\d{1,})

这是一个基于您的初始文本的Demo 和一个更简单的here

它按预期返回 35 个匹配项,包括这个有点棘手的匹配项,因为 value1 和 value2 没有用简单的空格分隔:

31069 UDP GAMER - IGW10(I7 3.4,8,1,DVD,PV)4 499.0 04 999.90

【讨论】:

  • 感谢您的帮助,但这不是我想要的,因为需要解析整个输入字符串,而且它几乎是混合了一些虚拟字符串的整个产品。您的正则表达式与预期的大部分源不匹配 35 匹配而不是 17。
  • @CaioMaia 我已经更新了我的答案,这个应该符合您的需求。 ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-02
  • 2016-10-20
  • 2018-10-01
  • 1970-01-01
  • 2012-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多