【问题标题】:How to use preg_replace to remove excessive single spaces如何使用 preg_replace 删除过多的单个空格
【发布时间】:2018-10-03 16:51:21
【问题描述】:

我们正在从 PDF 文件中提取文本,结果中包含格式错误的文本的频率很高。特别是在单词的字符之间添加空格。例如SEATTLE 将作为 S E A T T L E 返回。

n 单个字符“单词”的数量的情况下,preg_replace 是否有一个 RegEx 表达式可以删除任何空格?具体来说,从任何超过 3 个单个字母字符且以空格分隔的字符串中删除空格?

如果用谷歌搜索了一段时间,但甚至无法想象如何构造表达式。如评论中所述,我不希望删除所有空格,但仅当出现> 3个单个字母字符时,例如Welcome to the Greater S E A T T L E area 应该变成 Welcome to the Greater SEATTLE area。结果将用于全文搜索,因此不区分大小写。

【问题讨论】:

  • 您应该使用preg_replace_callback 的简单方法。匹配匿名函数中的'~\b[A-Za-z](?: [A-Za-z]){2,}\b~'str_replace 空格。
  • 这看起来很有希望,但正如您可能知道的那样,RegEx 对我来说是魔法。您能否在工作示例中提供该表达式作为答案?

标签: php regex preg-replace


【解决方案1】:

您可以使用preg_replace_callback 的简单方法。匹配匿名函数中的'~\b[A-Za-z](?: [A-Za-z]){2,}\b~'str_replace 空格:

$regex = '~\b[A-Za-z](?: [A-Za-z]){2,}\b~';
$result = preg_replace_callback($regex, function($m) {
     return str_replace(" ", "", $m[0]);
}, $s);

请参阅regex demo

要仅匹配大写字母序列,请从模式中删除 a-z

$regex = '~\b[A-Z](?: [A-Z]){2,}\b~';

还有一件事:可能有软/硬空格、制表符、其他类型的空格。然后,使用

$regex = '~\b[A-Za-z](?:\h[A-Za-z]){2,}\b~u';
                        ^^                ^

最后,要匹配任何 Unicode 字母,请使用 \p{L}(仅匹配大写字母 \p{Lu})而不是 [a-zA-Z]

$regex = '~\b\p{L}(?:\h\p{L}){2,}\b~u';

注意:它很可能在某些情况下无法工作,例如当有一个字母的单词时。您将不得不单独/手动处理这些情况。无论如何,没有安全的纯正则表达式来解决 OCR 问题。

模式详情

  • \b - 单词边界
  • [A-Za-z] - 一个字母
  • (?: [A-Za-z]){2,} - 出现 2 次或更多
    • - 一个空格(\h 匹配任何类型的水平空格)
    • [A-Za-z] - 一个字母
  • \b - 单词边界

当使用 u 修饰符时,\h 变为可识别 Unicode。

【讨论】:

  • 谢谢...在我删除 Regtest101.com 上的 ~ 后,您的评论确实有效。这会取代所有出现的情况吗? RegTester 在第一次出现时停止。
  • 我碰巧在等待一个很长的 CLI 脚本,它与我的调试器一起运行,同时也在做这个研究。
  • @GDP 如您所见,所有其他答案都使用更复杂的正则表达式。这就是我说“一种简单的方法”时的意思。我还考虑过基于\G 的正则表达式,但是当你手头拥有整个 PHP 语言的能力时,这种方法是最好的,恕我直言,因为如果你需要避免特定的上下文,这种模式非常易读并且易于增强。跨度>
【解决方案2】:

您可以一次性完成:

(?i:(?<!\S)([a-z]) +((?1))|\G(?!\A) +((?1))\b)

live demo here

解释:

(?i: # Start of non-capturing group with case-insensitive modifier on
    (?<!\S) # Negative lookbehind to ensure there is no leading non-whitespace character
    ([a-z]) + # Capture one letter and at least one space
    ((?1)) # Capture one letter in 2nd capturing group
    | # Or
    \G(?!\A) + # Start match from where previous match ends 
               # with matching spaces
    ((?1))\b # Match a letter at word boundary
) # End of non-capturing group

PHP 代码:

$str = preg_replace('~(?i:(?<!\S)([a-z]) +((?1))|\G(?!\A) +((?1))\b)~', '$1$2$3', $str);

【讨论】:

  • 这似乎也有效,但我必须接受他首先回答的另一个答案,让我从正确的方向开始,但谢谢!
【解决方案3】:

您可以使用这种带有环视和\G 的纯正则表达式方法:

$re = '~\b(?:(?=(?:\pL\h+){3}\pL\b)|(?<!^)\G)(\pL)\h+(?=\pL\b)~';

$repl = preg_replace($re, '$1', $str);

RegEx Demo

正则表达式详细信息:

  • \b: 匹配单词边界
  • (?:: 启动非捕获组
    • (?=(?:\pL\h+){3}\pL\b):提前断言我们有 3+ 个单个字母,由 1+ 个空格分隔
    • |: 或者
    • (?&lt;!^)\G\G 在上一场比赛结束时断言位置。 (?&lt;!^) 确保我们不匹配第一个匹配的字符串开头
  • ): 结束非捕获组
  • (\pL):匹配单个字母并捕获它
  • \h+:后跟1+水平空格
  • (?=\pL\b): 断言我们前面只有一个字母
  • 替换中,我们使用$1,这是我们捕获的空格左侧的字母

【讨论】:

  • 这似乎也有效,但我必须接受他首先回答的另一个答案,让我从正确的方向开始,但谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多