【问题标题】:Complex PHP whitespace removal复杂的 PHP 空格去除
【发布时间】:2018-10-25 04:24:42
【问题描述】:

关于 SO 有许多关于删除空格的问题,通常用 preg_replace('/[\s]{2,}/, '', $string) 或类似答案来回答,该答案需要多个空格字符并将它们删除或替换为其中一个字符。

当允许某些空格重复时,这会变得更加复杂(例如,允许和相关的具有两个换行符和一个换行符的文本块),此外还结合空白字符(\n\r)。

这里有一些示例文本,虽然杂乱无章,但涵盖了我认为您最终可能会尝试以合理的方式呈现的内容(例如,以前使用 HTML 格式化但现在被剥离的用户输入)

$text = "\nDear Miss           Test McTestFace,\r\n  \n We  have received your customer support request about:\n \tA bug on our website\n \t \n \n \n We will be in touch by : \n\r\tNext Wednesday. \n   \r\n   \n     Thank you for your custom; \n   \r  \t     \n       If you have further questions please feel free to email us. \n     \n\r\n     \n     Sincerely \n \n    Customer service team \n \n";

如果我们的目标是采用以下格式:

亲爱的 Test McTestFace 小姐,

我们已收到您关于以下内容的客户支持请求: 网站

我们将在下周三之前与您联系。

感谢您的定制;

如果您还有其他问题,请随时给我们发送电子邮件。

真诚的

客户服务团队

我们将如何实现这一点 - 简单的正则表达式、更复杂的迭代,还是已经有库可以做到这一点?

还有什么方法可以让测试用例变得更复杂,从而提供更强大的整体算法?

【问题讨论】:

  • 就我个人而言,我会使用 HTML 并使用解析器来确定新行/段落是否合适。
  • 如果我们知道底层数据最初是 HTML,那肯定可以工作。在我的应用程序的特定情况下,我们已经在标签剥离后 存储了数据,因此需要一种方法来清理它以进行演示(在某些情况下,之后重新编码为 HTML)
  • 这很不幸。你肯定会更容易在源头而不是事后修复它......事后尝试你无法区分<p>P1</p><p>P2</p>(没有空格!)
  • 在这种情况下,我们知道原始过程采取了一种直截了当的方法,即先将每个块级结束标记替换为自身与“\n”连接。但是我想有一个一般情况下混乱的空白(例如解析 PDF 或 doc 文件,从用户程序粘贴的数据)类似于后 HTML 条的情况。

标签: php string text-parsing


【解决方案1】:

就我自己而言,我选择尝试一种迭代算法,其理念是如果我们知道当前上下文(我们是在一个段落中,还是在一系列换行符/空格中?)我们可以做出更好的决定。

在这种情况下,我选择忽略选项卡的问题,并有兴趣看看它们如何适应假设 - 在这种情况下,我只是将它们剥离出来。

function strip_whitespace($string){
    $string = trim($string);
    $string = str_replace(["\r\n", "\n\r"], "\n", $string);

    // These three could be done as one, but splitting out
    // is easier to read and modify/play with
    $string = str_replace("\r", "\n", $string);
    $string = str_replace(" \n", "\n", $string);
    $string = str_replace("\t", '', $string);

    $string_arr = str_split($string);
    $new_chars = [];

    $prev_char_return = 0;
    $prev_char_space = $had_space_recently = false;
    foreach ($string_arr as $char){
        switch ($char){
            case ' ':
                if ($prev_char_return || $prev_char_space){
                    continue 2;
                }
                $prev_char_space = true;
                $prev_char_return = 0;
            break;
            case "\n":
            case "\r":
                if ($prev_char_return>1 || $had_space_recently){
                    continue 2;
                }
                if ($prev_char_space){
                    $had_space_recently = true;
                }
                $prev_char_return += 1;
                $prev_char_space = false;
            break;
            default:
                $prev_char_space = $had_space_recently = false;
                $prev_char_return = 0;
        }
        $new_chars[] = $char;
    }

    $return = implode('', $new_chars);
    // Shouldn't be necessary as we trimmed to start, but may as well
    $return = trim($return);

    return $return;
}

我仍然有兴趣看到其他想法,尤其是对于此类函数的明显解释与该函数产生的内容不同的任何文本。

【讨论】:

    【解决方案2】:

    根据示例(而不是查看您的代码),看起来规则是:

    • 包含至少 2 个 LF 字符的空白范围 是段落分隔符(因此将其转换为空行);
    • 任何其他范围的空格都是单词分隔符 (所以将其转换为一个空格)。

    如果是这样,那么一种方法是:

    1. 找到段落分隔符并将它们转换为文本中不会出现的字符串(不涉及空格)。
    2. 将剩余的空格转换为单个空格。
    3. 将段落分隔符转换为\n\n。

    例如:

    $text = preg_replace(
        array('/\s*\n\s*\n\s*/', '/\s+/', '/<PARAGRAPH-SEP>/'),
        array('<PARAGRAPH-SEP>', ' ',     "\n\n"),
        trim($text)
    );
    

    如果规则比较复杂,最好使用preg_replace_callback,例如:

    $text = preg_replace_callback('/\s+/', 'handle_whitespace', trim($text));
    
    function handle_whitespace($matches)
    {
        $whitespace = $matches[0];
    
        if (substr_count($whitespace, "\n") >= 2)
        {
            // paragraph-separator: replace with blank line
            return "\n\n";
        }
        else
        {
            // everything else: replace with single space character
            return " ";
        }
    }
    

    【讨论】:

    • 谢谢你,我会在我的例子中检查一下。用占位符做两个阶段是个好主意;你可以更进一步,先生成一个随机值(甚至检查它不在文本中)
    猜你喜欢
    • 2014-05-01
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-14
    • 2015-12-01
    相关资源
    最近更新 更多