PHP: как сохранить разрывы строк с помощью nl2br () с HTML Purifier?

Проблема: при использовании 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. Это еще одна линия.

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

Lorem ipsum:

  • Lorem
  • Ипсум
  • Долор
Dolor sit amet, MyName

Как вы можете видеть, «MyName», который должен был отображаться пользователем в отдельной строке, отображается вместе с предыдущей строкой.

Как исправить?

Конечно, с помощью функции PHP nl2br(). Однако возникают новые проблемы, независимо от того, используем ли мы его до или после очистки содержимого.

Вот пример использования nl2br () перед очистителем HTML:

Lorem ipsum dolor sit amet.
Это еще одна строка.

.my-css-class {

    color: blue; 

} 

Lorem ipsum:

  • Lorem
  • Ipsum
  • Dolor

Долор сит амет,
MyName

Что происходит, так это то, что nl2br () добавляет <br /> для каждого разрыва строки, поэтому обрабатываются даже те, что в блоке <pre>, а также разрывы строк после каждого тега <li>.

Что я пробовал

Я попробовал настраиваемую функцию nl2br (), которая заменяет разрывы строк тегами <br />, а затем удаляет все теги <br /> из блоков <pre> . Он отлично работает, однако проблема остается для <li> элементов.

Использование того же подхода для блоков <ul> также приведет к удалению всех тегов <br /> из <li> дочерних элементов, если только мы не будем использовать более сложное регулярное выражение для удаления тегов <br />, которые находятся внутри элементов <ul>, но вне элементов <li>. Но как насчет вложенного <ul> в <li> элемент? Чтобы справиться со всеми этими ситуациями, нам понадобится еще более сложное регулярное выражение!

  • If this is the right approach, could you help me out with the regex?
  • Если это неправильный подход, как я могу решить эту проблему? Я также открыт для альтернатив HTML Purifier.

Другие ресурсы, которые я уже просматривал:


person Community    schedule 15.07.2013    source источник
comment
nl2br следует использовать с открытым текстом, когда он помещается в контекст HTML. В вашем случае у вас уже есть HTML. Почему ваш HTML-код не содержит уже правильно <br> для разрывов строк?   -  person deceze♦    schedule 15.07.2013
comment
@deceze контент поступает из простого текстового поля, где разрешены некоторые теги HTML. Разрешение использования некоторых HTML-тегов, таких как <strong>, <em>, <pre> или <ul>, является новой тенденцией, которая, как правило, заменяет BBCode, Markdown и Textile, например.   -  person Community    schedule 15.07.2013
comment
Так что, если пользователь в основном пишет HTML, он тоже должен писать теги <br>. Возможно, он использует разрывы строк в HTML так, как они были задуманы: чтобы сделать разметку более читаемой без фактического введения разрывов строк в текст. Насколько я понимаю, у вас не может быть обоих вариантов. :) Вам действительно нужно проанализировать HTML и применить nl2br только к определенным текстовым узлам, за исключением элементов <pre>.   -  person deceze♦    schedule 15.07.2013
comment
Итак, если пользователь в основном пишет HTML, он должен также писать теги ‹br›.: Это безумие! Сам Stack Overflow принимает как Markdown, так и некоторые HTML-теги, такие как <strong> и мне не нужно вручную писать <br /> теги! Это ерунда, ваше предложение совершенно недружелюбно к пользователю. Большинство людей не используют и не знают HTML, однако разрешение использования базовых тегов позволяет людям, которые знают, иметь возможность их использовать.   -  person Community    schedule 15.07.2013
comment
Вам действительно нужно проанализировать HTML и применить nl2br только к определенным текстовым узлам, исключая <pre> элементы.: Это именно то, что я хочу, любая идея, как я могу добиться этого с помощью HTML Purifier (или другого) ? Ваша помощь очень ценится;)   -  person Community    schedule 15.07.2013
comment
Re Это безумие: Да, это так. И SO использует разрывы строк в Markdown! SO просто не удаляет некоторые HTML-теги, но имеет Markdown для базового форматирования текста, включая разрывы строк. Вот моя точка зрения: если вы требуете, чтобы ваши пользователи писали только HTML и HTML, то это компромисс.   -  person deceze♦    schedule 15.07.2013
comment
Вы упускаете суть, моя дорогая, я не требую, чтобы мои пользователи писали HTML, я разрешаю им писать HTML! Это огромная разница. Сделайте это возможностью, а не требованием.   -  person Community    schedule 15.07.2013
comment
И вы упускаете из виду то, что у вас есть Уловка-22. :) Чтобы преобразовать пустые разрывы строк в теги <br>, вам нужно проанализировать HTML, чтобы сделать это только для определенных элементов. Но вы должны сделать это до того, как дезинфицируете HTML, что означает, что вы, вероятно, не сможете правильно проанализировать HTML. Это очень сложное предложение. Я понимаю, что вы пытаетесь сделать, но причина, по которой это сложно и подвержено ошибкам, - это причина, по которой Markdown & co. возникла в первую очередь. И SO не является хорошим примером того, как это работает, потому что это не так.   -  person deceze♦    schedule 15.07.2013
comment
@deceze Я понимаю, что это непростая задача, однако мне нужна техническая помощь, а не совет относительно первоначального выбора разрешения (не навязывания) некоторых HTML-тегов. Если вас интересует такой аргумент, см. Соответствующий вопрос о правильной разметке: stackoverflow.com/questions/342961/ Ура!   -  person Community    schedule 15.07.2013
comment
Итак, резюмируя мои разглагольствования в направлении помощи: вам нужно сначала дезинфицировать свой HTML, а это сложно. HTML Purifier кажется одной из немногих, если не единственной, если не единственной, библиотекой, которая якобы понимает это правильно. После этого вы должны использовать DOM-процессор, чтобы просмотреть HTML и применить nl2br. Если Purifier по умолчанию портит разрывы строк, присущие входным данным, поэтому вы не можете выполнить второй шаг после этого, вам необходимо настроить Purifier, чтобы он вел себя по-другому, и / или включите nl2br прямо в его обработку. Вы исследовали такую ​​возможность? Я не могу дать вам решение в коде ATM.   -  person deceze♦    schedule 15.07.2013
comment
Это не совсем то, о чем вы спрашиваете, но это может помочь вам по духу, по крайней мере: htmlpurifier.org/live/configdoc/ - посмотрите, работает ли это так, как вы ожидаете. (Также я рекомендую послушать Deceze, который искренне пытается спасти вас от сильной головной боли.)   -  person pinkgothic    schedule 18.07.2013
comment
@pinkgothic большое спасибо за ваш вклад. К сожалению, я уже взглянул на AutoParagraph, он просто обертывает текстовые блоки, разделенные двумя последовательными разрывами строки, тегами <p></p>. Все одиночные разрывы строк остаются необработанными. На самом деле меня не интересуют теги <p>, я просто хочу, чтобы все разрывы строк, которые пользователь намеревался добавить, оставались нетронутыми (т.е. преобразовывались в теги <br />, где это необходимо).   -  person Community    schedule 18.07.2013
comment
Кроме того, я рекомендую прислушаться к deceze, который искренне пытается спасти вас от огромной головной боли. Поиск решения, позволяющего сохранить пользовательский контент в неприкосновенности, но при этом разрешить использование некоторых HTML-тегов, - настоящая цель, которая делает много смысла, когда вы смотрите с точки зрения пользовательского опыта, и вам всем следует быть более внимательными к этому. При этом я ищу техническую помощь, как этого добиться, мне не нужно разочарований.   -  person Community    schedule 18.07.2013


Ответы (2)


Эта проблема может быть решена частично (если не полностью) с помощью специальной функции 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. Никогда не обрабатывайте очищенный контент повторно, если вы не знаете, что делаете.

Обратите внимание: поскольку каждый перенос строки и двойной перенос строки уже сохранены, вам не следует использовать AutoFormat.AutoParagraph функцию HTML Purifier:

// 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 Purifier.

HTML Purifier в настоящее время позволяет размещать код, но требует сложных маркеров 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);

Ваше здоровье!

person Community    schedule 05.08.2013

может это поможет.

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;
}
person NemanjaLazic    schedule 15.07.2013