Цапля

Публикация листингов html-кода на страницах сайта

Решение, которое подходит для вывода мнемоник, не применимо для случаев вывода листингов с html-кодом. У задачи появляются новые особенности.

  1. После открывающего тэга <code html> и перед закрывающим </code> могут идти пробельные символы. Паттерн @<code.*?>(.*?)code>@ не учитывает этот момент.
  2. Листинг содержит несколько строк кода и это разбиение на строки нужно сохранить.

Иллюстрация работы итоговой функции.

Этапы поиска решения

1. Составить хороший пример, иллюстрирующий все тонкие моменты.

  • Самый простой вариант — пробельных символов после <code html> и перед </code> нет, html-тэга всего два — открывающий и закрывающий.
  • <code html><p>Обрамляющие тэги на одной строке с теми, что нужно экранировать. Нет вложенных тэгов.</p></code>
  • После открывающего тэга идёт пробельный символ (перевод строки), перед открывающим — нет. В строке встречается несколько последовательных пар тэгов.
  • <code html>
    <b>Закрывающий тэг</b> на одной строке с теми, что надо <em>экранировать</em>. Нет вложенных тэгов, но есть следующие друг за другом.</code>
  • Тэги <code html> и <code> отделены пробельными символами, листинг содержит несколько строк и вложенные тэги.
  • <code html>
    <ul><li>Первый пункт</li>
    <li>Второй пункт</li></ul>
    </code>
  • Открывающий тэг <code html> находится на одной строке с экранируемым текстом, закрывающий — отделен пробельным символом (перевод строки). В листинге встречаются мнемоники — внутри html-тэгов и за их пределами, а также спецсимволы. Мнемоника должна отображаться в итоге в виде кода мнемоники, а спецсимвол должен отображаться спецсимволом.
  • <code html><b>Попугаям&nbsp;&#151; свободу!</b> А компании 'Horns & Hoofs'&nbsp;&#151; хороших адвокатов.
    </code>

2. Функция htmlentities(), увы, не вполне подходит. Нам нужна замена всего двух символов: < на &lt; и & на &amp;. Создадим свою функцию, которая будет выполнять эту задачу.

function changesymbols ($somecontent) {

preg_match_all("@\&@",$somecontent,$smatches); //собираем массив вхождений амперсанда
$nums=sizeof($smatches[0]); //считаем количество амперсандов в тексте
$somecontent=preg_replace("@\&@","&amp;",$somecontent,$nums); //производим замену в $somecontent несколько раз, а именно $nums раз

preg_match_all("@\<@",$somecontent,$fmatches);
$nums=sizeof($fmatches[0]);
$somecontent=preg_replace("@\<@","&lt;",$somecontent,$nums);

return $somecontent;
};

3. Выполняем замену с сохранением разбиения по строкам.

Посчитаем число отрывков исходного кода для экранирования.

$iteration=substr_count($content, "<code html");

Создадим цикл с нужным числом итераций.

for ($i=0; $i<$iteration; $i++) {}

Внутри цикла проверяем условие.

if ( preg_match("<code html>(.*?)</code>@s", $content, $matches)>0 ) {}

Здесь паттерн @<code html>(.*?)</code>@s позволяет найти и те случаи, когда после открывающего тэга или перед закрывающим идёт пробельный символ. Можно было бы его записать ещё так: @(?|<code html>|<code html>\s)(.*?)(?|</code>|\s</code>)@

Далее перезаписываем вначале каждой итерации значение переменных: $difflines=''; $resultcode='';

С помощью функции preg_split() разбиваем исходный текст по регулярному выражению — символу новой строки, таким образом строки исходного текста записываются в массив.

$difflines=preg_split('#\n#', $matches[1]);

Считаем количество строк.

$difflines_size=sizeof($difflines);

Производим замену символов в каждой строке по отдельности, а потом измененную строку прибавляем конкатенацией к результирующей переменной $resultcode.


for ($k=0; $k<$difflines_size; $k++) {
$difflines[$k]=changesymbols($difflines[$k]);
$resultcode.=$difflines[$k].'<br>';
};

В конце делаем замену тэгов <code html> на <htmlcode>, чтобы в следующей итерации внешнего цикла не обрабатывать этот текст вновь.

Для WordPress нужно отключить функцию автоматического добавления абзацев, чтобы функция работала корректно. Результат для WordPress выглядит так:

remove_filter('the_content', 'wpautop');
function true_code ($content) {
if ( is_single() ) {
$iteration=substr_count($content, "<code html");

function changesymbols ($somecontent) {
preg_match_all("@\&@",$somecontent,$smatches);
$nums=sizeof($smatches[0]);
$somecontent=preg_replace("@\&@","&amp;",$somecontent,$nums);

preg_match_all("@\&lt;@",$somecontent,$fmatches);
$nums=sizeof($fmatches[0]);
$somecontent=preg_replace("@\<@","&lt;",$somecontent,$nums);

return $somecontent;
};

for ($i=0; $i<$iteration; $i++) {
if ( preg_match("@<code html>(.*?)</code>@s", $content, $matches)>0 ) {
$difflines='';
$difflines=preg_split('#\n#', $matches[1]);
$difflines_size=sizeof($difflines);
$resultcode='';
for ($k=0; $k<$difflines_size; $k++) {
$difflines[$k]=changesymbols($difflines[$k]);
$resultcode.=$difflines[$k].'<br>';
};

$content=preg_replace("@<code html>(.*?)</code>@s", "<htmlcode>".$resultcode."</htmlcode>", $content, 1);
};
};
return $content;
} else return $content;
};
add_filter('the_content', 'true_code');

Для всех остальных случаев надо убрать первую, третью и последнюю строки.