В статье "Избавляемся о ошибок xHTML-валидации при использовании JavaScript, Flash, <noindex>, CSS" рассказывается о четырёх основных источниках неправильной разметки в WordPress:

  1. JavaScript;
  2. Глобальные CSS, размещаемые в заголовке документа;
  3. Flash;
  4. Несуществующий по стандартам тег <noindex>, придуманный Яндексом

Естественно, после прочтения статьи я решил проверить свой, как я полагал, валидный, блог. В том-то и дело, что только плагал: нашёлся пятый источник неправильной разметки. Где бы Вы думали? В самом WordPress, в функции wpautop().

Сначала всё же пройдусь по первым четырём источникам. А точнее, трём — проблему с Яндексом я для себя давно уже решил, а именно: несуществующими тэгами я не балуюсь. И не могу не удержаться от камня в огород Яндекс: ну зачем нужно было придумывать абсолютно левый тэг, когда можно было использовать, например, комментарий:

[-]
View Code HTML
<!-- NOINDEX -->
Content not to be indexed
<!-- /NOINDEX -->

Во-первых, это решает проблему невалидного (X)HTML. Во-вторых, не надо было бы заботиться о том, где можно и где нельзя размещать тэг — вырезал регэкспом такой вот комментарий от начала и до конца и получил контент, который можно индексировать. Эх… Ладно, забыли.

Теперь по списку.
JavaScript. Решение простое: весь JavaScript заключаем в секцию CDATA. Например, так:

[-]
View Code HTML
<script type="text/javascript">
<!--//--><![CDATA[//><!--
//JavaScript goes here...
//--><!]]>
</script>

И не забываем, что атрибут type — обязательный. Решение выше, что называется, для любителей поизвращаться: помимо всего прочего, оно прячет JavaScript от антикварных браузеров, которые вообще не понимают тэг script. Если поддержка таких браузеров не нужна, то код можно значительно упростить:

[-]
View Code HTML
<script type="text/javascript">/*<![CDATA[*/
//JavaScript goes here...
/*]]>*/
</script>

А вот такое решение я не рекомендую:

[-]
View Code HTML
<script type="text/javascript"><!-- //<![CDATA[
//JavaScript goes here...
//-->
</script>

Причина простая: если мы планируем отдавать XHTML так, как это требует W3C (с MIME-типом application/xhtml+xml), то любой нормальный браузер воспримет HTML-комментарий как элемент, вложенный в script, и, как следствие, проигнорирует скрипт полностью (в XHTML парсер не знает о том, что существуют скрипты, таблицы стилей и прочие вещи).

С CSS всё аналогично. Метод для любителей поизвращаться:

[-]
View Code HTML
<style type="text/css">
<!--/*--><![CDATA[/*><!--*/
/* CSS goes here*/
/*]]>*/-->
</style>

И более простой метод:

[-]
View Code HTML
<style type="text/css">
/*<![CDATA[*/
/* CSS goes here*/
/*]]>*/
</style>

Да, у тэга style атрибут type тоже является обязательным.
Все браузеры, появившиеся после 2000 года понимают форму "без извращений", поэтому, наверное, имеет смысл использовать именно её. И еще небольшое замечание по поводу CDATA. По большому счету, секция CDATA нужна только тогда, когда контент содержит символы, которые могут быть интерпретированы как разметка XML, а именно: < и &. То есть в большинстве случаев, стили внутри тэга style помещать в CDATA особо не нужно.

Третий пункт — Flash. Здесь поможет Flash Satay. На нём подробно останавливаться не буду, лишь замечу, что правильный способ записи такого монстра:

[-]
View Code HTML
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" width="400" height="300" id="movie">
    <param name="movie" value="movie.swf"/>
    <embed src="movie.swf" quality="high" width="400" height="300" name="movie" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"/>
</object>

выглядит так:

[-]
View Code HTML
<object type="application/x-shockwave-flash" width="400" height="300" data="movie.swf">
    <param name="movie" value="movie.swf"/>
</object>

Достоинства и недостатки метода подробно описаны в оригинале, нет смысла на них останавливаться здесь.

По четвёртому вопросу я уже высказывался. У Дмитрия в статье есть решение, но лично для себя я его не считаю приемлемым.

Переходим к пятому пункту, ради которого, собственно говоря, данная статья и писалась :-) Как я уже сказал, во многих случаях в неправильной разметке виноват WordPress. Конкретно — функция wpautop(). Я всегда считал, что глобально менять разметку HTML, использую регулярные выражения — глупо, ибо в общем случае это работать не будет.

Допустим, Вы привыкли использовать хорошо структурированый код. Тогда вполне вероятно, что Вы можете написать нечто подобное (в целях упрощения восприятия я сократил код):

[-]
View Code HTML
<div>
    <strong>Test</strong>
    <div>Lorem ipsum dolor sit amet, иными словами, вложенный div.</div>
</div>

Дальнейшие рассуждения исходят из предположения, что Вы не пользуетесь WYSIWYG-редактором. Например, из-за того, что приходится вставлять разметку, которую TinyMCE никак не переваривает (исправляет её под себя), я им вообще не пользуюсь, предпочитая обыкновенный HTML-редактор без наворотов.

В этом случае функция wpautop() преобразует его следующим образом:

  1. Добавление символов перевода строки:
    [-]
    View Code HTML
    <div><strong>Test</strong>
           
    <div>Lorem ipsum dolor sit amet, иными словами, вложенный div.</div>

    </div>
  2. Добавление параграфов куда можно и куда нельзя:
    [-]
    View Code HTML
    <p><div><strong>Test</strong></p>
    <p><div>Lorem ipsum dolor sit amet, иными словами, вложенный div.</div></p>
    <p></div></p>
  3. Финальная очистка:
    [-]
    View Code HTML
    <div><strong>Test</strong></p>
    <div>Lorem ipsum dolor sit amet, иными словами, вложенный div.</div>
    </div>

Как видим, остается лишний закрывающий тэг p. Кстати, если написать так (строчный и блочный элементы на одной строке):

[-]
View Code HTML
<div>
    <strong>Test</strong><div>Lorem ipsum dolor sit amet, иными словами, вложенный div.</div>
</div>

то разметка будет нормальной.

В общем случае, ошибка вылезает при такой разметке:

[-]
View Code XML
<blocktag>
    <inlinetag></inlinetag>
    <blocktag></blocktag>
</blocktag>

Править все статьи — абсолютно не метод русского программиста :-) Проще автоматизировать.
Открываем файл wp-includes/formatting.php:

[-]
View Code PHP
  1. function wpautop($pee, $br = 1) {
  2.     $pee = $pee . "\n"; // just to make things a little easier, pad the end
  3.     $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee);
  4.     // Space things out a little
  5.     $allblocks = '(?:table|thead|tfoot|caption|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|form|map|area|blockquote|address|math|style|input|p|h[1-6]|hr)';
  6.     $pee = preg_replace('!(<' . $allblocks . '[^>]*>)!', "\n$1", $pee);
  7.     $pee = preg_replace('!(</' . $allblocks . '>)!', "$1\n\n", $pee);
  8.     $pee = str_replace(array("\r\n", "\r"), "\n", $pee); // cross-platform newlines
  9.     if ( strpos($pee, '<object') !== false ) {
  10.         $pee = preg_replace('|\s*<param([^>]*)>\s*|', "<param$1>", $pee); // no pee inside object/embed
  11.         $pee = preg_replace('|\s*</embed>\s*|', '</embed>', $pee);
  12.     }
  13.     $pee = preg_replace("/\n\n+/", "\n\n", $pee); // take care of duplicates
  14.     $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end
  15.     $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace
  16.     $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee);
  17.     $pee = preg_replace( '|<p>|', "$1<p>", $pee );
  18.     $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag
  19.     $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists
  20.     $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
  21.     $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
  22.     $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
  23.     $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
  24.     if ($br) {
  25.         $pee = preg_replace_callback('/<(script|style).*?<\/\\1>/s', create_function('$matches', 'return str_replace("\n", "<WPPreserveNewline />", $matches[0]);'), $pee);
  26.         $pee = preg_replace('|(?<!<br />)\s*\n|', "<br />\n", $pee); // optionally make line breaks
  27.         $pee = str_replace('<WPPreserveNewline />', "\n", $pee);
  28.     }
  29.     $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*<br />!', "$1", $pee);
  30.     $pee = preg_replace('!<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)!', '$1', $pee);
  31.     if (strpos($pee, '<pre') !== false)
  32.         $pee = preg_replace_callback('!(<pre.*?>)(.*?)<\/pre>!is', 'clean_pre', $pee );
  33.     $pee = preg_replace( "|\n</p>$|", '</p>', $pee );
  34.     $pee = preg_replace('/<p>\s*?(' . get_shortcode_regex() . ')\s*<\/p>/s', '$1', $pee); // don't auto-p wrap shortcodes that stand alone
  35.  
  36.     return $pee;
  37. }

После строки 77 вставляем:

[-]
View Code PHP
  1.     $pee = preg_replace("/<p>\\s*(<{$allblocks}.*?)<\\/p>/ism", '$1', $pee);

Эта строка кода убирает параграф вокруг открывающего тэга блочного элемента. Такой простой вот трюк (я на него убил минут 30 отладки) решает проблему с форматированием.

Но, как оказалось, это еще не всё. Второй случай более редкий:

[-]
View Code HTML
    <li>text
<div></div>

text</li>

Что характерно, перед открывающим <li> должен стоять по крайней мере пробел (редактор WordPress его добавляет, так что условие вполне выполнимое).
Так выглядит результат работы неизменённого wpautop():

[-]
View Code HTML
<li>text</p>
<div></div>
<p>text</li>

А так — исправленного:

[-]
View Code HTML
<li>text
<div></div>
<p>text</li>

В исправленном варианте на одну ошибку меньше :-) Но всё равно нужно исправлять.

Здесь лечение более сложное: во-первых, нужно добавить одну функцию:

[-]
View Code PHP
function wpautop_replace_callback($matches)
{
    if ('</p>' == $matches[2]) {
        return '<p>' . $matches[1] . $matches[2];
    }

    return $matches[1] . $matches[2];
}

и в функцию wpautop() под строку

[-]
View Code PHP
    $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);

добавить

[-]
View Code PHP
    $pee = preg_replace_callback("/<p>(.*?)(<\\/{$allblocks}>)/s", 'wpautop_replace_callback', $pee);

После этого вроде всё хорошо. За исключением элементов <ins> и <del>. Так как они могут иметь как блочный, так и строчный контекст, решение я пока не придумал. Единственное, что я могу посоветовать: если нужен <ins> или <del> в блочном котексте, его нудно поместить в <div>:

[-]
View Code HTML
<div><ins>
whatever
</ins></div>

По традиции, патч в формате unified diff, который исправляет проблему:

[-]
--- formatting.php.original 2008-04-25 03:46:17.000000000 +0300
+++ formatting.php  2008-07-07 05:44:52.000000000 +0300
@@ -59,6 +59,15 @@
    return $text;
 }

+function wpautop_replace_callback($matches)
+{
+    if ('</p>' == $matches[2]) {
+       return '<p>' . $matches[1] . $matches[2];
+   }
+
+   return $matches[1] . $matches[2];
+}
+
 function wpautop($pee, $br = 1) {
    $pee = $pee . "\n"; // just to make things a little easier, pad the end
    $pee = preg_replace('|<br />\s*<br />|', "\n\n", $pee);
@@ -75,11 +84,13 @@
    $pee = preg_replace('/\n?(.+?)(?:\n\s*\n|\z)/s', "<p>$1</p>\n", $pee); // make paragraphs, including one at the end
    $pee = preg_replace('|<p>\s*?</p>|', '', $pee); // under certain strange conditions it could create a P of entirely whitespace
    $pee = preg_replace('!<p>([^<]+)\s*?(</(?:div|address|form)[^>]*>)!', "<p>$1</p>$2", $pee);
+   $pee = preg_replace("/<p>\\s*(<{$allblocks}.*?)<\\/p>/ism", '$1', $pee);
    $pee = preg_replace( '|<p>|', "$1<p>", $pee );
    $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee); // don't pee all over a tag
    $pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // problem with nested lists
    $pee = preg_replace('|<p><blockquote([^>]*)>|i', "<blockquote$1><p>", $pee);
    $pee = str_replace('</blockquote></p>', '</p></blockquote>', $pee);
+   $pee = preg_replace_callback("/<p>(.*?)(<\\/{$allblocks}>)/s", 'wpautop_replace_callback', $pee);
    $pee = preg_replace('!<p>\s*(</?' . $allblocks . '[^>]*>)!', "$1", $pee);
    $pee = preg_replace('!(</?' . $allblocks . '[^>]*>)\s*</p>!', "$1", $pee);
    if ($br) {
Добавить в закладки
  • del.ici.ous
  • Digg
  • Furl
  • Google
  • Simpy
  • Spurl
  • Y! MyWeb
  • БобрДобр
  • Мистер Вонг
  • Yandex.Закладки
  • Текст 2.0
  • News2
  • AddScoop
  • RuSpace
  • RUmarkz
  • Memori
  • Google Bookmarks
  • Писали
  • СМИ 2
  • Моё Место
  • 100 Закладок
  • Ваау!
  • Technorati
  • RuCity
  • LinkStore
  • NewsLand
  • Lopas
  • Закладки - IN.UA
  • Connotea
  • Bibsonomy
  • Trucking Bookmarks
  • Communizm
  • UCA
  • Slashdot
  • Magnolia
  • Blogmarks
  • Current
  • Meneame
  • Oknotizie
  • Diigo
  • Funp
  • Hugg
  • Dealspl.us
  • N4G
  • Mister Wong
  • Faves
  • Yigg
  • Fresqui
  • Care2
  • Kirtsy
  • Sphinn

Связанные записи

7
Июль
2008

Комментарии к статье «Основные источники неправильной разметки в WordPress» (9)  »

  1. Dimox says:

    Да, жаль, конечно, что разработчики WordPress не позаботились о данных моментах. Однако, для меня такой проблемы, в общем-то, и не существует, т.к. я использую структуру кода, которую WordPress не ломает :)

  2. Макисим Покровский says:

    А я просто проверяю код в редакторе, а за идею спасибо.

  3. Сергей М. says:

    Та же проблема с , если хоть сколько отбивать переводами строки после текст, написанный в HTML (а не визуальном), то он также забудет поставить в начале. Вообще, решать это надо не исправлением wautop, а добавлением фильтра с приоритетом ниже.

  4. Сергей М. says:

    О, сейчас посмотрим, что там. Ничего что добавлю исправление обработки из твоего плагина в свой типограф? С указанием, конечно.

  5. webalexan says:

    Полезная заметка про noindex.

Подписаться на RSS-ленту комментариев к статье «Основные источники неправильной разметки в WordPress» Trackback URL: http://blog.sjinks.org.ua/wordpress/222-main-sources-of-invalid-markup-in-wordpress/trackback/

Оставить комментарий к записи «Основные источники неправильной разметки в WordPress»

Вы можете использовать данные тэги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Оставляя комментарий, Вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя