如何使用 preg_replace 去除过多的单个空格

3
我们正在从PDF文件中提取文本,但是结果中存在大量包含格式不正确的文本。具体来说,单词字符之间添加了空格,例如SEATTLE被返回为S E A T T L E
是否有一个正则表达式可以通过preg_replace移除任何单个字符“单词”数量的空格?特别是,从任何由空格分隔的超过3个单个字母字符组成的字符串的任何出现中删除空格?
我已经在Google上搜索了一段时间,但是无法想象如何构建这个表达式。正如在评论中所述,我不想删除所有空格,而是只有当存在超过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 替换空格。 - Wiktor Stribiżew
这看起来很有前途,但正如你所知道的,正则表达式对我来说就像魔法一样。你能否提供一个工作示例作为答案? - GDP
3个回答

2
您可以使用一个简单的方法,通过 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);

请查看正则表达式演示

如果只想匹配大写字母序列,请从模式中删除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会在第一次出现时停止。 - GDP
我恰好正在等待一个长时间运行的CLI脚本,同时使用我的调试器,所以在此期间进行了这项研究。 - GDP
1
@GDP 你看,其他所有答案都使用了更复杂的正则表达式。这就是我所说的“简单方法”。我也考虑过基于\G的正则表达式,但当你手头有整个PHP语言的强大功能时,这种方法是最好的,因为模式非常易读且易于增强,如果需要避免特定上下文。 - Wiktor Stribiżew

2
您可以一次完成此操作:
(?i:(?<!\S)([a-z]) +((?1))|\G(?!\A) +((?1))\b)

请查看此处的演示

说明:

(?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);

这个方法看起来也可以,但是我必须接受其他答案,因为他先回答了我的问题,并让我朝着正确的方向开始解决问题,但还是非常感谢你! - GDP

1
你可以使用这个纯正的正则表达式方法,其中包括前后查找和\G
$re = '~\b(?:(?=(?:\pL\h+){3}\pL\b)|(?<!^)\G)(\pL)\h+(?=\pL\b)~';

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

RegEx演示

正则表达式详情:

  • \b:匹配单词边界
  • (?::开始非捕获组
    • (?=(?:\pL\h+){3}\pL\b):顺序预查以确保我们有三个或更多个由一个或多个空格分隔的单个字母
    • |:或
    • (?<!^)\G\G断言位置在前一个匹配的末尾。(?<!^)确保我们不会在第一次匹配时匹配字符串的开头
  • ):结束非捕获组
  • (\pL):匹配并捕获单个字母
  • \h+:后面跟着一个或多个水平空白字符
  • (?=\pL\b):确保我们只有一个字母在前面
  • 替换中,我们使用$1,它是我们捕获的空格左侧的字母

1
这个方法看起来也可以,但是我必须接受其他答案,因为他先回答了我的问题,并让我朝着正确的方向开始解决问题,但还是非常感谢你! - GDP

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