mhtml
Кроссбраузерный data URI в CSS-файлах (подробно)
Кстати, всё забываю порекомендовать статью Banderlog’а, который написал её по мотивам моей статьи «Кроссбраузерный data URI
Статья на Хабре называется «К вопросу о кроссбраузерных Data URI».
Кроссбраузерный data URI в CSS-файлах (многабукв)
Прелюдия
Некоторое время назад, я нашёл способ, использования data URI в Internet Explorer. Data URI — специальный вид URL, который включает в себя код самого объекта. Например, таким образом можно внедрять изображения прямо в код
Выглядит такой HTML вот так:
<img src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP…и так далее" />Где «data» — это протокол URI, image/gif — тип объекта (картинка GIF), base64 — тип кодирования, после запятой идёт сам код картинки.
Такой вид URI понимают сейчас все современные браузеры, включая Internet Explorer 8 (правда, со многими ограничениями). Плохо то, что Internet Explorer 7 и ниже этого не поддерживает, а доля этих версий настолько велика (около 50% на данный момент по данным LiveInternet), что до недавнего времени data URI использовалась крайне редко.
Я, увлёкшись исследованием малодокументированного протокола mhtml (MIME HTML), поддерживаемого в браузерах Microsoft с пятой версии, в одном из экспериментов разобрался как внедрить объект в тело
Вообще mhtml был придумал для адресации внутри так называемых
Типичный
mhtml:http://example.net/index.html!fhhryregdy4gereГде после «mhtml» идёт обычный
В итоге, когда мне пришла эта идея, я совместил оба способа так, чтобы Internet Explorer видел файл как
Специфика HTML допускает переносы, в том числе и внутри data URI, поэтому мне удалось задуманное относительно быстро. В CSS всё сложнее, а тема использования data URI в CSS, как оказалось, актуальнее, чем для HTML. Например, в книге Николая Мациевского «Разгони свой сайт» сделана попытка расширения моего способа на CSS, к сожалению, менее удачная, чем хотелось бы: требуется включение закодированного объекта два раза.
Фуга
Итак, мне захотелось посмотреть, не получится ли расширить мой способ на CSS, не прибегая к включению закодированного файла два раза. Причём, сфера тут уже, подключаемый к CSS объект это, в 99,99% — картинка.
После немногочисленных экспериментов, оказалось, что оторвать закодированный объект от начала data URI не представляется возможным, что мешает использованить mhtml — там заголовок и кодированный объект должны отделяться пустой строкой.
Тем не менее, других способов побороть IE я не видел и потому продолжал эксперименты. Несколько слов о том, что такое base64. Base64, по сути, анахронизм, доставшийся нам от электронной почты на заре её зарождения. Письма тогда ходили исключительно в латинице и все почтовые системы полагали, что никаких других символов в письмах не бывает.
Таким образом, когда встала задача передавать в письмах
Хорошие новости, заключаются в том, что браузеры игнорируют в кодированной строке символы, которые не входят в упомянутые 64. Можно было ожидать, что браузеры вообще откажутся разбирать такую строку.
Итак, строка data URI начинается
Из вышесказанного следует, что нужно
Оставим в покое XBM, BMP, ART, WMF/EMF, JPEG200 и прочую экзотику, поддерживаемую браузерами, и остановимся на триплете JPEG, GIF, PNG. Честно сказать с форматом GIF я знаком плохо, а JPEG и PNG исследовал несколько лет назад довольно подробно.
JPEG и PNG состоят из секций. В общих чертах, сначала идёт заголовок файла, потом сами секции, в формате (с некоторыми вариациями) «имя секции», «длина секции», «конец секции». И у JPEG, и у PNG есть секции для произвольного содержимого. У JPEG это COM и APPn, у PNG — iTXt, tEXt и zTXt. Кстати, и у GIF тоже есть похожее место — Application Extension.
Отсюда идея: Internet Explorer, декодировав файл с начала строки, получает заголовок с секцией из содержимого, куда помещается заголовок для остальных браузеров и мусор, оставшийся от декодирования начала data URI, остальные браузеры должны пропустить заголовок для IE и начать декодирование с data URI.
Получаем, на примере JPEG:
FF D8 — заголовок JPEG для IE FF E0 xx xx — секция APP0, куда прячется всё до данных изображения, «;background-color:url(data:image/jpeg;base64,» — это видят остальные браузеры FF D8 — начало JPEG для остальных браузеров «данные изображения» — это место видят уже все браузеры
Все бинарные данные выравниваются и кодируются (строка начала
В моём примере такой CSS выглядит вот так:
/9j/4AAs;background-image:url(data:image/jpeg;base64;BOLKNOTE.RU,/9j/4AAC…Легко увидеть, как видит этот стиль браузер: «/9j/4AAs» кажется ему ошибочным атрибутом (его, кстати, можно взять в комментарий), а «;» — разделителем атрибутов. И хорошо просматривается («/9j/AA») двойной старт заголовка
Остальное должно быть понятно из примера («!ie» — CSS hack, делающий стиль видимым только для IE), как это выглядит в браузере, можно увидеть в разделе «Храню» у меня на сайте.
data URL в IE ★
Пару лет назад я занимался проблемой data URL в Internet Explorer, добился определённых результатов, но то, что получилось, использовать было невозможно. Data URL (иногда его ещё называют «протокол data:») — возможность вставлять ресурсы (графику, CSS, JavaScript и так далее) в HTML код.
Выглядит это примерно так:
<img
src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw
AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz
ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp
a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl
ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis
F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH
hhx4dbgYKAAA7" />В Data URL указывает тип содержимого и способ его кодирования. Способа кодирования два — base64 (именно он и указан в примере), увеличивающий содержимое на треть и URL encoding (в этом случае способ кодирования не указывается) — привычный многим способ кодирования при помощи %xx, в лучшем случае вообще не увеличивает размер содержимого, в худшем — увеличивает в три раза.
Кстати, в настоящее время IE8b1 поддерживает data URL, но, увы, длиной не более 32Кб.
Теперь амбула.
Вчера ночью мне пришла в голову идея как можно попытаться корректно совместить data URL и включение картинок через протокол mhtml. То, что у меня не получилось два года назад, получилось сейчас.
Результат — готовый код на PHP из двух функций. Первую функцию («bolk_data_uri_header») нужно вызвать в самом начале перед выводом любого вашего кода, вторую («bolk_data_uri») собственно для включения картинки в код. Надеюсь на примерах всё понятно:
bolk_data_uri_header();
bolk_data_uri('myjpeg.jpg');
bolk_data_uri('ourpng.png', 'border: 2px dotted red');Код самой библиотеки:
function bolk_data_uri_header()
{
echo "<!--\n"
."Content-Type: multipart/related; boundary=\"=_NextPart_01C6A9B1.539AB070\"\n\n"
."--=_NextPart_01C6A9B1.539AB070\n"
."Content-Transfer-Encoding: base64\n"
."Content-Type: text/html\n"
."-->\n\n";
}
function bolk_data_uri($file, $style = '')
{
if (!( file_exists($file) && ($data = @getimagesize($file)) )) return false;
$name = uniqid('', true);
if ($style <> '') $style = ' style="'.htmlspecialchars($style).'"';
echo "<!--\n"
."--=_NextPart_01C6A9B1.539AB070\n"
."Content-Location: {$name}\n"
."Content-Transfer-Encoding: base64\n"
."Content-Type: {$data['mime']}; -->\n"
."<object data='data:{$data['mime']};base64,\n\n";
echo base64_encode(file_get_contents($file));
echo "' {$data[3]}{$style} type='\n{$data['mime']}'><img "
."src='mhtml:http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}!{$name}' {$data[3]}{$style} /></object>\n\n"
."<!--\n"
."--=_NextPart_01C6A9B1.539AB070-->";
return true;
}У данного метода, по сравнению с обычными data URL есть масса ограничений: необходим специальный заголовок в начале файла, т. е. этот метод невозможно использовать на чужих сайтах, этим методом нельзя воспользоваться (по крайней мере не в таком виде) для включения ресурсов внутрь CSS или JavaScript. В принципе, тут есть достаточно большое поле для экспериментов, возможно все или некоторые из проблем можно решить.
В разделе «храню» есть пример, который можно потестировать на имеющихся у вас браузерах. Уже протестировано на Opera 9.25 и 9.50b, FF 2.0.0.13 и 4 (Minefield), Safari 3.1 (Safari под iPhone — тоже), Internet Explorer 6.0SP2 и 7.0, Sony PSP browser, Opera mini, Netscape Navigator 9.0.0.5.
MHTML
В предыдущей статье я описывал способ для Internet Explorer, позволяющий внедрить картинки
Если подумать, это не сильно меняет дело —
<img src="mhtml:http://sample.com/package1.mht!image.jpg" />
<img src="mhtml:http://sample.com/package1.mht!another_image.gif" />и так далее. Основное достоинство способа — объединение нескольких картинок в одном соединении и его удаётся сохранить.
Сегодня я расскажу как добиться мультибраузерности при помощи SSI. В других скриптовых языках такой проблемы, понятно, не возникает. Я буду рассматривать SSI
Символом «собачки» (@) я буду выделять места, где перевод строки не нужно ставить — просто мне приходится разбивать длинные места на части. Описание, если не интересно, можно и не читать, просто скопировать всё в нужный файл и положить в каталог /images/.
В настройках Apache или .htaccess должна быть включена обработка SSI в файлах .html и .mht:
AddHandler server-parsed .html .mhtПервый файл (или файлы), который понадобится — это файл самой картинки. Он должен начинаться с полного имени картинки, плюс расширение .html. Например, «1.jpg.html», внутри нужно указать
<!--#set var="NAME" value="$QUERY_STRING" -->@
<!--#set var="TYPE" value="image/jpeg" -->@
<!--#include virtual="pict.html" -->@
4AAQSkZJRgABAgAAZABkAAD...код картинки...mJiBFoxCGkShUxyGCMYMRcSwwRiMdRZСледующий файл — img.html. В нём проверяется из какого браузера его вызывают — Internet Explorer или всех остальных (вторым условием убирается Opera, которая иногда притворяется IE) и, в зависимости от этого, выводит, либо ссылку
<!--#if expr="$HTTP_USER_AGENT=/MSIE/ && $HTTP_USER_AGENT!=/Opera/" -->@
mhtml:http://<!--#echo var="HTTP_HOST" -->/images/package1.mht!<!--#echo var="QUERY_STRING" -->@
<!--#else -->@
<!--#include virtual="$QUERY_STRING.html?$QUERY_STRING" -->@
<!--#endif -->Внутри файла package1.mht ссылки на файлы картинок (обратите внимание на параметры вызова в include virtual), в константе BOUND задаётся boundary (разделительная строка), о которой я говорил в прошлый раз. В include надо перечислить (пример показан для двух файлов) все файлы картинок, которые будут входить в этот набор.
<!--#set var="BOUND" value="Next_Section" --><!--
MIME-Version: 1.0
Content-Type: multipart/related; boundary="<!--#echo var="BOUND" -->"
--<!--#echo var="BOUND" -->
Content-Location: index.htm
Content-Transfer-Encoding: base64
Content-Type: text/html
<!--#set var="MHT" value="1" -->
<!--#include virtual="1.jpg.html?1.jpg" -->
<!--#include virtual="2.jpg.html?2.jpg" -->
--<!--#echo var="BOUND" -->-->Последний файл — pict.html. Вызывается он из файлов картинок. Если он вызывается из mht, то выдаёт кусок
<!--#if expr="$MHT" -->--<!--#echo var="BOUND" -->
Content-Location: <!--#echo var="NAME" -->
Content-Transfer-Encoding: base64
Content-Type: <!--#echo var="TYPE" -->
<!--#else -->data://<!--#echo var="TYPE" -->;base64,<!--#endif -->Теперь, как применять эту груду файлов. Всё очень просто — просто вставляем в нужно место следующий тег:
<img src="<!--#include virtual="/images/img.html?2.jpg" -->" />И смотрим на результат. В исходном коде в IE мы должны увидеть ссылку на package1.mht, в остальных браузерах — data URI, во всех браузерах — картинку.
Колдуем с MHTML
Позже, после того, как я уже написал эту статью, мне прислали необходимое уточнение. Я провёл несколько экспериментов, попросил своих друзей и знакомых опросить своих друзей и знакомых и вот что выяснилось:
В некоторых версиях Windows XP, 2000 (закономерность не выявлена, но, видимо, дело в наличии
каких-то Service Pack) и, кажется, во всех 2003, способ не работает, если расширение файла не mht. Это несколько сужает сферу применения данного способа.
Обход этой проблемы читайте в следующей статье. Далее сама статья:
Сегодня немного поколдуем, ок? Тем, кому не интересна кухня, где готовят внутренности сайтов, могут перейти к просмотру
Браузеры всё больше превращаются в комбайны, которые поддерживают десятки схем доступа к данным, от всем известных file, ftp, http, https, до полузабытых gopher, finger, news и так далее. Среди этого списка есть интересная схема — data, который поддерживают уже, кажется, все браузеры, кроме Internet Explorer (включая беты седьмых версий). Для тех, кто незнаком с ним, общий вид строки запроса в этой схеме выглядит так:
смысл в том, что данные (картинки, javascript, другие ресурсы) можно помещать внутрь HTML. Эта схема является частью стандарта HTML 4.0, если я не ошибаюсь, но, в силу того, что она не поддерживается основным игроком на рынке браузеров, используется лишь эпизодически — чаще всего
<img src="data:image/png;base64,iVBORw0KGgoAAAAN...и так далее">Эта схема, кроме возможности помещать различные данные в HTML, имеет и более очевидные преимущества. Обычно, браузер открывает не более
Мы, в нашей студии, обычно стараемся использовать карту ссылок, вместо нескольких мелких кнопок, например, для ускорения загрузки. Я очень много разговаривал с поисковиками Google и Yandex, но они уверены, что в IE внедрение ресурсов в HTML невозможно. Так ли это?
Нет. По крайней мере мне удалось найти минимум один способ сделать это. В интернете я ничего подобного не встретил, так что будет считать, что его изобрёл я. Натолкнула меня на эту мысль одна из кошмарных домашних страниц, которую сверстали в Microsoft Word и сохранили… в формате MHTML.
Этот формат первым начали использовать в браузере от Microsoft (поэтому его иногда расшифровывают как «Microsoft HTML») для того, чтобы можно было сохранять текст со всем содержимым — стилями, изображениями в одном файле, поэтому этот формат ещё называют
MIME HTML сильно отличается от обычного HTML форматом — внутри заголовок, информация об именах и форматах файлов, кодированные файлы изображений и так далее — более всего это похоже на файлы писем с вложениями (посмотрите исходный текст письма в своём почтовом клиенте). Нам было бы интересней, если бы в остальных браузерах показывалось бы
Просматривая
mhtml:http://example.com/file.mht!image.jpg
Причём, оказывается, браузер довольно вольно подходит к формату файла, лишь бы внутри были необходимые секции, что позволяет легко заключить архив внутрь
<!--
Content-Type: multipart/related; boundary="=_NextPart_01C6A9B1.539AB070"
--=_NextPart_01C6A9B1.539AB070
Content-Location: index.htm
Content-Transfer-Encoding: base64
Content-Type: text/html
--=_NextPart_01C6A9B1.539AB070
Content-Location: img.jpg
Content-Transfer-Encoding: base64
Content-Type: image/jpeg
4AAQSkZJRgA....закодированная...base64...картинка...BAgAAZABkAADMdRZ
--=_NextPart_01C6A9B1.539AB070-->
<body>
<img src="mhtml:http://www.test/index.html!img.jpg">
</body>Те, кто знает как устроено обычное электронное письмо изнутри, разберутся и сами, для остальных расскажу. Строка, которую вы видите после слова «boundary», разделяет файл на секции. Секции начинаются с двух минусов и разделяющей строки. Заканчивается
если вы не знаете, что такое base64 и
Я не уверен насчёт четвёртой версии браузера, но 5.0 и выше должны отобразить такой HTML нормально. У этого способа есть преимущество перед схемой data — на файл можно ссылаться несколько раз. Организация вывода разного контента для двух групп браузеров — сейчас довольно простая задача. Лучше всего, конечно, использовать для этого язык на стороне сервера (для JavaScript объёмы данных слишком значительные) — например, SSI или PHP.
Кстати, Opera 9 так же поддерживает
Ещё хотелось бы упомянуть, что Internet Explorer поддерживает ещё одну схему, через которую, вероятно, можно получить доступ к ресурсам