PHP:如何在使用HTML Purifier的情况下保留使用nl2br()的换行符?

5

问题:使用HTML Purifier处理用户输入的内容时,换行符未被转换为<br />标签。

考虑以下用户输入的内容:

Lorem ipsum dolor sit amet.
This is another line.

<pre>
.my-css-class {
    color: blue;
}
</pre>

Lorem ipsum:

<ul>
<li>Lorem</li>
<li>Ipsum</li>
<li>Dolor</li>
</ul>

Dolor sit amet,
MyName

当使用HTML Purifier进行处理时,上述内容被更改为以下内容:

Lorem ipsum dolor sit amet. This is another line.

.my-css-class {
    color: blue;  
} 

Lorem ipsum:

  • Lorem
  • Ipsum
  • Dolor
Dolor sit amet, MyName

如您所见,用户想要将“MyName”放在单独一行显示,但它与前一行一起显示了。

如何修复?

当然可以使用 PHP nl2br() 函数。然而,无论我们是在净化内容之前还是之后使用它,都会出现新问题。
以下是在使用 HTML Purifier 之前使用 nl2br() 的示例:

Lorem ipsum dolor sit amet.
This is another line.

.my-css-class {

    color: blue; 

} 

Lorem ipsum:

  • Lorem
  • Ipsum
  • Dolor

Dolor sit amet,
MyName

发生的情况是nl2br()会为每个换行符添加<br />,因此即使在<pre>块中的换行符也会被处理,以及每个<li>标记后面的换行符。

我尝试过的方法

我尝试了一个自定义的nl2br()函数,它将换行符替换为<br />标签,然后从<pre>块中删除所有<br />标签。 它工作得很好,但<li>项目仍存在问题。

尝试对 <ul> 块使用相同的方法也会从 <li> 子元素中删除所有的 <br /> 标签,除非我们使用更复杂的正则表达式来删除在 <li> 元素之外但在 <ul> 元素内部的 <br /> 标签。但是如果一个嵌套的 <ul> 在一个 <li> 项中怎么办?为了处理所有这些情况,我们需要有一个更复杂的正则表达式!
  • 如果这是正确的方法,你能帮我写正则表达式吗?
  • 如果这不是正确的方法,我该如何解决这个问题?我也愿意尝试 HTML Purifier 的替代方案。

我已经查看过的其他资源:


1
当将纯文本放置在 HTML 上下文中时,应使用 nl2br。 在您的情况下,您已经有了 HTML。 为什么您的 HTML 没有正确地包含用于换行的 <br> - deceze
1
所以,如果用户基本上是在编写HTML,那么他也应该编写<br>标签。也许他正在使用HTML中的换行符,因为它们旨在使标记更易读,而不实际引入文本中的换行符。就我而言,你不能两者兼得。 :) 你真的需要解析HTML并仅在特定文本节点上应用nl2br,排除<pre>元素。 - deceze
1
关于“这太疯狂了”的评论:是的,确实如此。而且SO使用Markdown换行符!SO只是不删除某些HTML标签,但它具有用于基本文本格式化(包括换行)的Markdown。这就是我的观点:如果您要求用户仅编写HTML,则这就是权衡之一。 - deceze
1
你没有理解到一个Catch-22的关键点。:) 要将裸换行符转换为<br>标签,您需要解析HTML以仅在某些元素上执行此操作。但是,在对HTML进行清理之前,您必须这样做,这意味着您可能无法正确解析HTML。这是一个非常棘手的问题。我了解您尝试做什么,但它很棘手且容易出错的原因是Markdown和其他类似工具首次出现的原因。而SO并不是它有效的好例子,因为它并不起作用。 - deceze
1
好的,为了总结一下我对帮助方向的抱怨:你需要先对HTML进行净化,这很棘手。 HTML Purifier似乎是少数几个(如果不是唯一一个)声称做得正确的库之一。之后,您应该使用DOM处理器浏览HTML并应用nl2br。如果Purifier默认破坏了输入中固有的换行符,因此您无法在之后执行第二步,则需要自定义Purifier以以不同的方式运行和/或将nl2br直接卷入其处理中。您调查过这种可能性吗?我现在无法用代码给您提供解决方案。 - deceze
显示剩余8条评论
2个回答

6

这个问题可以通过自定义 nl2br() 函数来部分地(如果不是完全地)解决:

function nl2br_special($string){

    // Step 1: Add <br /> tags for each line-break
    $string = nl2br($string); 

    // Step 2: Remove the actual line-breaks
    $string = str_replace("\n", "", $string);
    $string = str_replace("\r", "", $string);

    // Step 3: Restore the line-breaks that are inside <pre></pre> tags
    if(preg_match_all('/\<pre\>(.*?)\<\/pre\>/', $string, $match)){
        foreach($match as $a){
            foreach($a as $b){
            $string = str_replace('<pre>'.$b.'</pre>', "<pre>".str_replace("<br />", PHP_EOL, $b)."</pre>", $string);
            }
        }
    }

    // Step 4: Removes extra <br /> tags

    // Before <pre> tags
    $string = str_replace("<br /><br /><br /><pre>", '<br /><br /><pre>', $string);
    // After </pre> tags
    $string = str_replace("</pre><br /><br />", '</pre><br />', $string);

    // Arround <ul></ul> tags
    $string = str_replace("<br /><br /><ul>", '<br /><ul>', $string);
    $string = str_replace("</ul><br /><br />", '</ul><br />', $string);
    // Inside <ul> </ul> tags
    $string = str_replace("<ul><br />", '<ul>', $string);
    $string = str_replace("<br /></ul>", '</ul>', $string);

    // Arround <ol></ol> tags
    $string = str_replace("<br /><br /><ol>", '<br /><ol>', $string);
    $string = str_replace("</ol><br /><br />", '</ol><br />', $string);
    // Inside <ol> </ol> tags
    $string = str_replace("<ol><br />", '<ol>', $string);
    $string = str_replace("<br /></ol>", '</ol>', $string);

    // Arround <li></li> tags
    $string = str_replace("<br /><li>", '<li>', $string);
    $string = str_replace("</li><br />", '</li>', $string);

    return $string;
}

在进行HTML净化之前,必须将此应用于内容。除非你知道自己在做什么,否则不要重新处理已净化的内容。

请注意,因为每个换行符和双重换行符已经保留,所以不应使用HTML Purifier的AutoFormat.AutoParagraph功能:

// Process line-breaks
$string = nl2br_special($string);

// Initiate HTML Purifier config
$purifier_config = HTMLPurifier_Config::createDefault();
$purifier_config->set('HTML.Allowed', 'p,ul,ol,li,strong,b,em,i,u,a[href],code,pre,blockquote,cite,img[src|alt],br,hr,h3,h4');
//$purifier_config->set('AutoFormat.AutoParagraph', true); // Make sure to NOT use this

// Initiate HTML Purifier
$purifier = new HTMLPurifier($purifier_config);

// Purify the content!
$string = $purifier->purify($string);

就是这样!


此外,允许基本的HTML标签最初是为了 通过不添加其他标记语法来提高用户体验,因此您可能希望允许用户发布代码,特别是HTML代码,这些代码将不会被HTML净化器解释/删除。
HTML净化器目前允许发布代码,但需要复杂的CDATA标记:
<![CDATA[
Place code here
]]>

很难记住和写下来。为了尽可能简化用户体验,我认为最好允许用户通过嵌入简单的<code>(用于内联代码)和<pre>(用于代码块)标签来添加代码。以下是如何实现:

function custom_code_tag_callback($code) {

    return '<code>'.trim(htmlspecialchars($code[1])).'</code>';
}
function custom_pre_tag_callback($code) {

    return '<pre><code>'.trim(htmlspecialchars($code[1])).'</code></pre>';
}

// Don't require HTMLPurifier's CDATA enclosing, instead allow simple <code> or <pre> tags
$string = preg_replace_callback("/\<code\>(.*?)\<\/code\>/is", 'custom_code_tag_callback', $string);
$string = preg_replace_callback("/\<pre\>(.*?)\<\/pre\>/is", 'custom_pre_tag_callback', $string);

请注意,与nl2br处理类似,必须在内容被HTML净化之前完成。此外,请记住,如果用户在自己发布的代码中放置了<code><pre>标签,则会关闭包含其代码的父<code><pre>标签。这无法解决,并且适用于原始CDATA标记或任何标记,甚至是在StackOverflow上使用的标记(例如,在代码示例中使用`符号将关闭代码标记)。
最后,为了获得良好的用户体验,我们可能希望自动化其他一些事情,例如我们希望链接可以点击。幸运的是,这可以通过HTML Purifier的AutoFormat.Linkify功能来实现。
以下是包括所有内容的最终代码,以获取终极设置:
// === Declare functions ===

function nl2br_special($string){

    // Step 1: Add <br /> tags for each line-break
    $string = nl2br($string); 

    // Step 2: Remove the actual line-breaks
    $string = str_replace("\n", "", $string);
    $string = str_replace("\r", "", $string);

    // Step 3: Restore the line-breaks that are inside <pre></pre> tags
    if(preg_match_all('/\<pre\>(.*?)\<\/pre\>/', $string, $match)){
        foreach($match as $a){
            foreach($a as $b){
            $string = str_replace('<pre>'.$b.'</pre>', "<pre>".str_replace("<br />", PHP_EOL, $b)."</pre>", $string);
            }
        }
    }

    // Step 4: Removes extra <br /> tags

    // Before <pre> tags
    $string = str_replace("<br /><br /><br /><pre>", '<br /><br /><pre>', $string);
    // After </pre> tags
    $string = str_replace("</pre><br /><br />", '</pre><br />', $string);

    // Arround <ul></ul> tags
    $string = str_replace("<br /><br /><ul>", '<br /><ul>', $string);
    $string = str_replace("</ul><br /><br />", '</ul><br />', $string);
    // Inside <ul> </ul> tags
    $string = str_replace("<ul><br />", '<ul>', $string);
    $string = str_replace("<br /></ul>", '</ul>', $string);

    // Arround <ol></ol> tags
    $string = str_replace("<br /><br /><ol>", '<br /><ol>', $string);
    $string = str_replace("</ol><br /><br />", '</ol><br />', $string);
    // Inside <ol> </ol> tags
    $string = str_replace("<ol><br />", '<ol>', $string);
    $string = str_replace("<br /></ol>", '</ol>', $string);

    // Arround <li></li> tags
    $string = str_replace("<br /><li>", '<li>', $string);
    $string = str_replace("</li><br />", '</li>', $string);

    return $string;
}


function custom_code_tag_callback($code) {

    return '<code>'.trim(htmlspecialchars($code[1])).'</code>';
}

function custom_pre_tag_callback($code) {

    return '<pre><code>'.trim(htmlspecialchars($code[1])).'</code></pre>';
}



// === Process user's input ===

// Process line-breaks
$string = nl2br_special($string);

// Allow simple <code> or <pre> tags for posting code
$string = preg_replace_callback("/\<code\>(.*?)\<\/code\>/is", 'custom_code_tag_callback', $string);
$string = preg_replace_callback("/\<pre\>(.*?)\<\/pre\>/is", 'custom_pre_tag_callback', $string);


// Initiate HTML Purifier config
$purifier_config = HTMLPurifier_Config::createDefault();
$purifier_config->set('HTML.Allowed', 'p,ul,ol,li,strong,b,em,i,u,a[href],code,pre,blockquote,cite,img[src|alt],br,hr,h3,h4');
$purifier_config->set('AutoFormat.Linkify', true); // Make links clickable
//$purifier_config->set('HTML.TargetBlank', true); // Uncomment if you want links to open new tabs
//$purifier_config->set('AutoFormat.AutoParagraph', true); // Leave this commented as it conflicts with nl2br


// Initiate HTML Purifier
$purifier = new HTMLPurifier($purifier_config);

// Purify the content!
$string = $purifier->purify($string);

干杯!


1

也许这会有所帮助。

function custom_nl2br($html) {
    $pattern = "/<ul>(.*?)<\/ul>/s";
    preg_match($pattern, $html, $matches);

    $html = nl2br(str_replace($matches[0], '[placeholder]', $html));
    $html = str_replace('[placeholder]',$matches[0], $html);

    return $html;
}

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