Модифицируем HTML на лету
Привет друзья. Хочу поделиться методикой, которую иногда использую в тех случаях, когда мне нужно подменить или каким то образом модифицировать на лету готовый HTML. Причем сделать это на уровне сервера, а не через JS.
Начнем с того, что определимся, каким образом вообще используя MODX и его технику сбора конечного кода страницы из множества разных частей (шаблонов, чанков, сниппетов), можно получить доступ к уже готовой конечной версии DOM дерева.
Для этого нам на помощь приходит событие для плагина OnWebPagePrerender — оно срабатывает именно в тот момент, когда веб страница собрана и готова к выводу. Единственное, что здесь не получится учесть — это код, выполняемый при помощи JS в момент рендера страницы браузером.
Исторически сложилось, что я для подобных задач использую библиотеку PHP Simple HTML DOM Parser, которая дает возможность перемещаться по DOM дереву и осуществлять перебор и подбор нужных элементов CSS или Jquery подобным способом.
В состав компонент входит свежая версия библиотеки и плагин-болванка.
Приведу еще несколько реальных примеров использования.
Очищаем контент от лишнего мусора в атрибутах
Некоторые примеры любезно предоставил @mngatoff в свое время, за что ему большое спасибо.
Более подробно о методах библиотеки можно почитать в официальной документации
Также использую эту библиотеку для парсера страниц. Если интересно — дайте знать, расскажу и покажу как нибудь.
Начнем с того, что определимся, каким образом вообще используя MODX и его технику сбора конечного кода страницы из множества разных частей (шаблонов, чанков, сниппетов), можно получить доступ к уже готовой конечной версии DOM дерева.
Для этого нам на помощь приходит событие для плагина OnWebPagePrerender — оно срабатывает именно в тот момент, когда веб страница собрана и готова к выводу. Единственное, что здесь не получится учесть — это код, выполняемый при помощи JS в момент рендера страницы браузером.
switch ($modx->event->name) {
case 'OnWebPagePrerender':
//Получаем доступ к готовому DOM дереву
$output = &$modx->resource->_output;
//Модифицируем как хотим
$output = preg_replace('|\s+|', ' ', $output);
break;
}
Мы можем модифицировать HTML код нативными средствами PHP — для этого у нас есть preg_replace, str_replace и другие методы. Также мы можем использовать PHP Класс DOMDocument дающий более обширные возможности. Можем подключить и использовать и дополнительные библиотеки. Исторически сложилось, что я для подобных задач использую библиотеку PHP Simple HTML DOM Parser, которая дает возможность перемещаться по DOM дереву и осуществлять перебор и подбор нужных элементов CSS или Jquery подобным способом.
// Find all element which id=foo
$ret = $html->find('#foo');
// Find all element which class=foo
$ret = $html->find('.foo');
// Find all element has attribute id
$ret = $html->find('*[id]');
// Find all anchors and images
$ret = $html->find('a, img');
// Find all anchors and images with the "title" attribute
$ret = $html->find('a[title], img[title]');
Немного модифицируем плагин и получаем вот такую историюswitch ($modx->event->name) {
// Стартуем плагин
case 'OnWebPagePrerender':
// Получаю доступ к DOM дереву
$output = &$modx->resource->_output;
// Здесь доступны все поля ресурса.
// Определяю с каким шаблоном имею дело.
$template = $modx->resource->template;
//Подключаю библиотеку
include_once MODX_CORE_PATH . 'components/simple_html_dom/vendor/simple_html_dom.php';
if (class_exists('simple_html_dom')) {
$html = new simple_html_dom;
$html->load($output);
switch ($template) {
case 17:
// Внутри конкретного шаблона Ищу все изображения внутри div.content
foreach ($html->find('.content img') as $img) {
/**
* @var simple_html_dom_node $img
*/
// Хочу активировать Lazy load для этого
// Сохраняю ссылку на изображение
$src = $img->src;
if ($src) {
// Удаляю атрибут src
$img->src = null;
// Добавляю атрибут data-src
$img->{'data-src'} = $src;
// Добавляю класс lazy
$img->class = 'lazy';
// Далее картинки с нужным классом и дата атрибутом подхватит js плагин lazy load и сайт прилично ускорит свою работу
}
}
break;
}
$output = $html->save();
$html->clear();
unset($html);
$output = preg_replace('|\s+|', ' ', $output);
}
break;
}
Для удобства я упаковал всю описанную историю в компонент, который скоро будет доступен в наших репозитариях.В состав компонент входит свежая версия библиотеки и плагин-болванка.
Приведу еще несколько реальных примеров использования.
Очищаем контент от лишнего мусора в атрибутах
foreach ($html->find('.product_page table') as $table) {
$table->border = null;
$table->cellpadding = null;
$table->cellspacing = null;
$table->style = null;
$table->width = null;
$table->class = 'table table-responsive';
}
foreach ($html->find('.product_page table tr') as $tr) {
$tr->style = null;
}
Добавляем обертку noindex для всех фреймовforeach ($html->find('iframe') as $iframe) {
$iframe->outertext = '<!--noindex-->' . $iframe->outertext . '<!--/noindex-->';
}
Добавляем обертку noindex для внешних ссылокforeach ($html->find('link') as $link) {
if (is_external_url($link->href) && stripos($link->href, $site_url) !== 0) {
$link->outertext = '<!--noindex-->' . $link->outertext . '<!--/noindex-->';
}
}
Добавляем rel=nofollow и target=_blank для внешних ссылокforeach ($html->find('link') as $link) {
if (is_external_url($link->href) && stripos($link->href, $site_url) !== 0) {
$link->rel = 'nofollow';
$link->target = '_blank';
}
}
В общем как вы видите — довольно простыми манипуляциями можно как угодно модифицировать html разметку страницы без вмешательства в исходных код шаблонов и текста в текстовом редакторе. Некоторые примеры любезно предоставил @mngatoff в свое время, за что ему большое спасибо.
Более подробно о методах библиотеки можно почитать в официальной документации
Также использую эту библиотеку для парсера страниц. Если интересно — дайте знать, расскажу и покажу как нибудь.
Поблагодарить автора
Отправить деньги
Комментарии: 26
Насколько этот вариант быстрее чем DOMDocument
case 'OnWebPagePrerender':
$content = &$modx->resource->_output;
$html = $modx->resource->_output;
$dom = new DOMDocument();
$dom->encoding = "utf-8";
@$dom->loadHTML($content);
А то я делал подобное через него, но мне не понравилась производительность
Сравнительных тестов не проводил. Вряд ли быстрее чем нативный класс. Основное удобство библиотеки — более простой доступ к выборке нужных узлов дерева
Да выборка гораздо проще, чем через xpath, вообщем достаточно интересно.
Спасибо за статью
Спасибо за статью
я бы еще добавил что-то вроде:
if($modx->resource->content_type != 3) { // или 3 или 2, тут надо проверять
$output = preg_replace('|\s+|', ' ', $output);
}
Чтоб не минифицировать докуметы формата txt, по типу robots.txt.
Да все что угодно можно написать, любые условия конечно. Я лишь показал что такая библиотека есть и мы можем ее использовать
тэкс, плагиатец и копипаста :)
раз уж на то пошло, функции is_external_url в оригинале библиотеки нет, я ее сам туда дописал, вот она:
раз уж на то пошло, функции is_external_url в оригинале библиотеки нет, я ее сам туда дописал, вот она:
function is_external_url($url)
{
return (bool)preg_match('/^(http:|https:|ftp:|\/\/)/i', $url);
}
Да эти строчки я у тебя позаимствовал года два назад. Ты только сильно громко не кричи, а то еще и ребята из sourceforge прибегут — начнут возмущаться что я их документацию скопипастил.
при чем тут «не кричи». ты функцию используешь, которой нет в библиотеке, не заведется у людей
Я тоже часто пользуюсь этим событием, чтобы обработать код перед отправкой, но мне больше нравится phpQuery, это почти jQuery, только на PHP) К примеру, очистка style в тексте статьи:
require_once(MODX_CORE_PATH . 'components/phpquery/phpQuery/phpQuery.php');
$html = phpQuery::newDocumentHTML($output);
$paragraphs = $html->find('.article__txt p');
foreach($paragraphs as $el) {
$p = pq($el);
$p->attr('style','');
}
$output = $html->html();
В прошлом примере пропустил строку в самом начале:
Или вот так можно fancybox подцепить:
То есть менеджер добавляет фото к статье как обычно, и может управлять его размерами. А плагин обрезает фото до заданных размеров и оборачивает его ссылкой, кликнув на которую откроется увеличенное изображение с помощью fancybox.
$output = &$modx->resource->_output;
Или вот так можно fancybox подцепить:
$output = &$modx->resource->_output;
require_once(MODX_CORE_PATH . 'components/phpquery/phpQuery/phpQuery.php');
$html = phpQuery::newDocumentHTML($output);
$images = $html->find('.article__txt img');
foreach($images as $el) {
$img = pq($el);
$src = $img->attr('src');
$width = $img->attr('width');
$height = $img->attr('height');
$options = "w={$width}&h={$height}&zc=1";
$thumb = $modx->runSnippet('phpthumbon', [
'input' => $src,
'options' => $options
]);
$img->attr('src', $thumb);
$img->attr('data-original', $src);
$img->wrap("<a href='{$src}' data-fancybox=''></a>");
$img->removeAttr('width');
$img->removeAttr('height');
}
$output = $html->html();
То есть менеджер добавляет фото к статье как обычно, и может управлять его размерами. А плагин обрезает фото до заданных размеров и оборачивает его ссылкой, кликнув на которую откроется увеличенное изображение с помощью fancybox.
использую библиотеку PHP Simple HTML DOM ParserЭто-ж старьё, которое хз когда в последний раз обновлялось. Не слышал про DomCrawler?
в этом году обновилась
На три года вперёд?
собственно, какая разница?) работает хорошо и легкая
Да я не то, чтобы прямо часто пользуюсь подобным. Не стояла задача найти лучшую либу, иба та что есть вполне справляется с задачами. Про DomCrawler не слышал. Как нибудь на досуге изучу, спасибо.
Спасибо за статью! И, да присоединяюсь к просьбе по поводу парсера страниц. Было бы интересно.
modx.pro/development/16940
Но где то тут же я упоминал что гораздо более грамотно было бы делать это на событие сохранения ресурса, не гоже жертвовать скоростью отрисовки страницы ради служебных нужд. Возможно при одном посетителе это не заметно, но представь эти же самые операции когда одновременных посетителей 10 и более
Но где то тут же я упоминал что гораздо более грамотно было бы делать это на событие сохранения ресурса, не гоже жертвовать скоростью отрисовки страницы ради служебных нужд. Возможно при одном посетителе это не заметно, но представь эти же самые операции когда одновременных посетителей 10 и более
Но это же совершенно другой функционал, ведь у ТС помимо содержимого ресурса, плагин действует ещё и на все остальное.
Николай приветствую! А как то можно плагин использовать для amp страниц? Именно для контента, заменять img на amp-img и iframe на amp-iframe?
Конечно, чего бы нет.
amp — это у нас XML страница, генерируемая в MODX верно? А значит для нее так же можно включить данный плагин. Ну а плагину в общем то все равно что обрабатывать, HTML или XML.
amp — это у нас XML страница, генерируемая в MODX верно? А значит для нее так же можно включить данный плагин. Ну а плагину в общем то все равно что обрабатывать, HTML или XML.
Весь этот код заменяет class был он там или не был
$src = $img->src;
if ($src) {
$img->src = null;
$img->{'data-src'} = $src;
$img->class = 'lazy';
А как переписать чтобы добавлял class lazy к существующим class'ам при наличии? и при отсутствии просто добавлял class=«lazy»?
Оператор конкатенации просто добавь, должно сработать
$img->class .= ' lazy';
Если нет — то сохрани в переменную сначала содержимое атрибута class, затем приконкатенируй свой класс и вставь в атрибут.
Да я по другому решил сделать, чтобы ко всем img добавить class lazy
по сути не определяю в контенте это или нет, чтобы добавило класс.
Теперь работает так как надо. Спасибо
по сути не определяю в контенте это или нет, чтобы добавило класс.
Теперь работает так как надо. Спасибо
Эта конструкция вырезает удаляет переносы строк
Можно ли как-то ограничить действие только для типа содержимого HTML или исключить для text?
$output = $html->save();
$html->clear();
unset($html);
$output = preg_replace('|\s+|', ' ', $output);
Но как оказалось она работает в том числе и для документов со всеми типами содержимого, в том числе textМожно ли как-то ограничить действие только для типа содержимого HTML или исключить для text?
У тебя всегда доступен объект $modx->resource
Пиши условие, проверяй у него content_type или как там, не помню сейчас на лету.
Пиши условие, проверяй у него content_type или как там, не помню сейчас на лету.
Да, достаточно сделать такую проверку, чтобы срабатывало только для типа содержимого text/html
$type = $modx->resource->get('contentType');
if ($type == 'text/html') {
$output = $html->save();
$html->clear();
unset($html);
$output = preg_replace('|\s+|', ' ', $output);
}
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.