XHTML, шепчу…

Oct 11, 2007 21:08

Сегодня утром вернулся к выводу RSS-ленты на нашем сайте. И обнаружил, что innerHTML меня не спасает. Что же делать?

С недавних пор мы решили верстать сайты в XHTML. Затея не нова, скажете вы, - сейчас каждый уважающий себя верстальщик все делает по стандартам и о валидности не забывает. Однако, как мы можем узнать из статьи Ивана Сагалаева «XHTML, говорите?», не все так просто, и многие разработчики не подозревают, что верстают вовсе не в режиме XHTML, а в старом добром HTML-режиме. Дело в HTTP-заголовке Content-type, и вся свистопляска начинается только тогда, когда вы вместо привычного text/html дадите браузеру нюхнуть application/xhtml+xml. Или text/xml на худой конец. Браузер сразу обнаружит, что вы, наконец, одумались и выбрали самый современный отформатированный формат документов и сопутствующие ему самые стандартные стандарты. Однако  прежней свою страницу вы не увидите. Делов-то, сейчас слеши в конце
и
поставим, и все. А не все. Когда вы начнете программировать, предварительно исправив разметку, указав пространство имен и разобравшись с кодировкой, вас настигнут бонусные неприятности с передовым форматом. Самое время вернуться к теме поста.

Классическим примером разницы в движках HTML и XHTML было отсутствие  у XHTML-элементов свойства innerHTML. То есть загрузить что-то аморфное аяксом и вывести в div стало нельзя. Свойство присваивается, JavaScript ведь этого не запрещает, но реакции никакой нет. Народ стал мучаться с интерфейсами DOMParser и DOMSerializer, чтобы эмулировать любимое свойство. И, честно говоря, некоторый успех был. Позже браузеры научились и сами имитировать innerHTML в режиме XHTML (Ьщяшддф вероятнее всего тем же способом, Opera тоже умеет) и сейчас можно не опасаясь писать нечто вроде $('content').innerHTML = 'Сильно лень городить createElement() и appendChild()
'. Хотя свойство и называется innerHTML на самом деле HTML ему скормить нельзя. Это скорее innerXHTML. То есть такое дело не пройдет: mailDiv.innerHTML = '
забудем закрыть "p", поставим неразрывный пробел, а еще картинку вставим
'. И что произойдет? В обычном режиме браузер напряжет свои HTML-извилины и догадается о том, что вы имели ввиду. В новом мире XML умен должен быть не браузер, а мы. А сам браузер, как ему и положено, откажется парсить такую гадость и выбросит исключение.

Вот какие исключения бывают (на тот случай, если кто-то, как и я, гуглить начинает с текста ошибки целиком):
Firefox 2: An invalid or illegal string was specified
Firefox 3: Component returned failure code: 0x80004003 (NS_ERROR_INVALID_POINTER) [nsIDOMNSHTMLElement.innerHTML]
Safari 3: NO_MODIFICATION_ALLOWED_ERR: DOM Exception 7

Opera 9 умеет парсить HTML сама. За что нижайший ей поклон.

Первое решение, которое пришло мне на ум, это поправить разметку простенькими регулярками.

markup = markup.replace(/<(img|br|hr|link|input)(.+?)\/?>/g, '<$1$2/>')
markup = markup.replace(/( )/g, ' ')

После этого чать кода заработала. Потом из любопытства поглядел на список сущностей в HTML. А потом попался фрагмент кода с перемешанными тегами. Писать парсер перехотелось. Помучавшись в раздумиях, решил попробовать распарсить HTML на сервере, а потом выдать его как HTML. Идея мне понравилась, по натуре  я серверный программист и раньше воспринимал браузер только как дисплей. Потом, правда, одумался.

Одуматься удалось и на этот раз. Зачем, спрашиввается, мне парсить HTML на сервере, если у меня в руках самый совершенный парсер HTML - собственно браузер. Не найдя рационального ответа на этот вопрос, решил парсить кривой HTML на клиенте. Вот только куда тиранический XML-режим сослал этот самый парсер - не понятно.

Отступлю немного и отвечу на вопрос, ответ на который вас, возможно, интересует. Почему у меня HTML весь такой кривой и неправильный. Ответ прост как шланг - на нашем блоге мы решили парсить сырую RSS-выдачу. А получаем мы очень вольно составленный HTML.

Так вот. Где же cпрятан парсер HTML, когда у нас все кругом в XHTML? Сегодня получить парсер в лисе, скриптом невозможно. Про вебкит и оперу по запросу "new HTMLDocument()" тоже ничего найти не удалось (искал, правда, спустя рукава - любимчик-то мой уже облажался). XMLParser, как видно из названия, парсит только XML (правда это может быть и XUL, и MathML, и SVG и…). Создать новый HTML-документ с помощью document.implementation.createDocument(…) можно, но будет он XHTML. Конструкция (new DOMParser()).parseFromString("...", "text/html") не сработает, так как DOMParser не поддерживает text/html. Прямо так и пишет: not implemented.

Промучавшись полдня с «крутыми» технологиями, решил попробовать самую простую - спрятать iframe  и в него загрузить html-файл. Как мы и рассчитываем window.onload срабатывает когда все загрутся, в том числе и фрейм. Добавлять фрейм можно  динамически, но главное успеть это сделать до события load и не забыть включить его в дерево документа через body.appendChild(...). Так как я успел и не забыл, браузеры меня вознаградили. Наградой явился объект document, который умеет парсить HTML. Ура!

Как это выгладит в коде? Как всегда было в HTML:
var html = $('htmliframe').contentDocument.firstChild
html.innerHTML = '
' + markup + ''
Родные, милые черты...

Осталось решить последнюю задачу, а именно извлечь полученное дерево и доставить его на место назначения. Это уже совсем легко.
// почистим ноду назначения
while (destination.firstChild)
    destination.removeChild(destination.firstChild)
// склонируем HTML-дерево (в нашем случае див с детьми)
clone = html.firstChild.cloneNode(true)
// вставим див кда надо
content.appendChild(clone)

В будущих версиях браузеров, возможно, надо будет адаптировать ноды clone = document.adoptNode(clone), но пока они на это ругаются.

Теперь нам можно завернуть полученные знания в модуль и распространять, наслаждаясь славой и признанием.

Итог. HTML парсится и выводится в Firefox 2, 3 и Safari 3. Opera 9 все умеет сама. IE и Safari 2 не умеет XHTML совсем, но все же умеют клиентский XSL через xml-stylesheet. Получается, мостик между HTML и XML перебросили.

xsl, xhtml, ajax, html

Next post
Up