Я пытаюсь показать веб-сайт пользователю, загрузив его с помощью php. Это сценарий, который я использую:
<?php $url = 'http://stackoverflow.com/pagecalledjohn.php'; //Download page $site = file_get_contents($url); //Fix relative URLs $site = str_replace('src="','src="' . $url,$site); $site = str_replace('url(','url(' . $url,$site); //Display to user echo $site; ?>
Пока этот сценарий работает, за исключением нескольких основных проблем с функцией str_replace. Проблема связана с относительными URL-адресами. Если мы используем изображение на нашем созданном pagecalledjohn.php кота (что-то вроде этого: ). Это png, и, как я вижу, его можно разместить на странице, используя 6 разных URL-адресов:
1. src="//www.stackoverflow.com/cat.png" 2. src="http://www.stackoverflow.com/cat.png" 3. src="https://www.stackoverflow.com/cat.png" 4. src="somedirectory/cat.png"
4 в этом случае неприменим, но добавлен в любом случае!
5. src="/cat.png" 6. src="cat.png"
Есть ли способ, используя php, я могу выполнить поиск src = "и заменить его URL-адресом (удаленным именем файла) загружаемой страницы, но без ввода URL-адреса там, если это варианты 1,2 или 3, и немного изменить процедуру для 4,5 и 6?
Вместо того, чтобы пытаться изменить каждую ссылку на путь в исходном коде, почему бы вам просто не добавить <base>
в свой заголовок, чтобы конкретно указать базовый URL-адрес, по которому должны вычисляться все относительные URL-адреса?
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
Это может быть достигнуто с использованием выбранного вами инструмента для манипуляций с DOM. В приведенном ниже примере показано, как это сделать, используя DOMDocument и связанные с ним классы.
$target_domain = 'http://stackoverflow.com/'; $url = $target_domain . 'pagecalledjohn.php'; //Download page $site = file_get_contents($url); $dom = DOMDocument::loadHTML($site); if($dom instanceof DOMDocument === false) { // something went wrong in loading HTML to DOM Document // provide error messaging and exit } // find <head> tag $head_tag_list = $dom->getElementsByTagName('head'); // there should only be one <head> tag if($head_tag_list->length !== 1) { throw new Exception('Wow! The HTML is malformed without single head tag.'); } $head_tag = $head_tag_list->item(0); // find first child of head tag to later use in insertion $head_has_children = $head_tag->hasChildNodes(); if($head_has_children) { $head_tag_first_child = $head_tag->firstChild; } // create new <base> tag $base_element = $dom->createElement('base'); $base_element->setAttribute('href', $target_domain); // insert new base tag as first child to head tag if($head_has_children) { $base_node = $head_tag->insertBefore($base_element, $head_tag_first_child); } else { $base_node = $head_tag->appendChild($base_element); } echo $dom->saveHTML();
По крайней мере, вы действительно хотите изменить все ссылки на пути в исходном коде, я бы настоятельно рекомендовал это делать с инструментами DOM-манипуляции (DOMDOcument, DOMXPath и т. Д.), А не с регулярным выражением. Я думаю, вы найдете это гораздо более стабильным решением.
Я не знаю, правильно ли я понял ваш вопрос, если вы хотите иметь дело со всеми текстовыми последовательностями, заключенными в src="
and "
, следующий шаблон мог бы сделать это:
~(\ssrc=")([^"]+)(")~
В нем есть три группы захвата, второй из которых содержит интересующие вас данные. Первые и последние полезны для изменения всего матча.
Теперь вы можете заменить все экземпляры функцией обратного вызова, которая меняет места. Я создал простую строку со всеми 6 случаями, которые у вас есть:
$site = <<<BUFFER 1. src="//www.stackoverflow.com/cat.png" 2. src="http://img.ruphp.com/relative-url/cat.png" 3. src="http://img.ruphp.com/relative-url/cat.png" 4. src="somedirectory/cat.png" 5. src="/cat.png" 6. src="cat.png" BUFFER;
Давайте на мгновение проигнорируем, что нет окружающих тегов HTML, вы не разбираетесь в HTML в любом случае, я уверен, поскольку вы не просили парсер HTML, а для регулярного выражения. В следующем примере совпадение в середине (URL) будет вложено так, чтобы было ясно, что оно соответствует:
Итак, теперь, чтобы заменить каждую из ссылок, давайте начнем легко, просто выделив их в строке.
$pattern = '~(\ssrc=")([^"]+)(")~'; echo preg_replace_callback($pattern, function ($matches) { return $matches[1] . ">>>" . $matches[2] . "<<<" . $matches[3]; }, $site);
Результат для приведенного ниже примера:
1. src=">>>//www.stackoverflow.com/cat.png<<<" 2. src=">>>http://img.ruphp.com/relative-url/cat.png<<<" 3. src=">>>http://img.ruphp.com/relative-url/cat.png<<<" 4. src=">>>somedirectory/cat.png<<<" 5. src=">>>/cat.png<<<" 6. src=">>>cat.png<<<"
Поскольку способ замены строки должен быть изменен, ее можно извлечь, поэтому ее легче изменить:
$callback = function($method) { return function ($matches) use ($method) { return $matches[1] . $method($matches[2]) . $matches[3]; }; };
Эта функция создает обратный вызов замены, основанный на методе замены вашего пароля как параметра.
Такая функция замены может быть:
$highlight = function($string) { return ">>>$string<<<"; };
И это называется следующим:
$pattern = '~(\ssrc=")([^"]+)(")~'; echo preg_replace_callback($pattern, $callback($highlight), $site);
Выходные данные остаются теми же, это просто для иллюстрации того, как работает отработка:
1. src=">>>//www.stackoverflow.com/cat.png<<<" 2. src=">>>http://img.ruphp.com/relative-url/cat.png<<<" 3. src=">>>http://img.ruphp.com/relative-url/cat.png<<<" 4. src=">>>somedirectory/cat.png<<<" 5. src=">>>/cat.png<<<" 6. src=">>>cat.png<<<"
Преимущество этого заключается в том, что для функции замещения вам нужно иметь дело только с совпадением URL как с одной строкой, а не с регулярным выражением, сопоставляющим массив для разных групп.
Теперь на вторую половину вашего вопроса: как заменить это на обработку URL-адресов, например удаление имени файла. Это можно сделать, проанализировав сам URL и удалив имя файла (basename) из компонента пути. Благодаря извлечению вы можете сделать это простой функцией:
$removeFilename = function ($url) { $url = new Net_URL2($url); $base = basename($path = $url->getPath()); $url->setPath(substr($path, 0, -strlen($base))); return $url; };
В этом коде используется компонент URL-адреса Pear's Net_URL2 (также доступный через Packagist и Github, также могут иметь свои пакеты ОС). Он может легко анализировать и изменять URL-адреса, поэтому хорошо иметь работу.
Итак, теперь замена выполняется с новой функцией замены имени файла URL:
$pattern = '~(\ssrc=")([^"]+)(")~'; echo preg_replace_callback($pattern, $callback($removeFilename), $site);
И тогда результат:
1. src="//www.stackoverflow.com/" 2. src="http://www.stackoverflow.com/" 3. src="https://www.stackoverflow.com/" 4. src="somedirectory/" 5. src="/" 6. src=""
Обратите внимание, что это пример. Он показывает, как вы можете это делать с регулярными выражениями. Тем не менее, вы можете также использовать HTML-парсер. Давайте сделаем это фактическим фрагментом HTML:
1. <img src="//www.stackoverflow.com/cat.png"/> 2. <img src="http://img.ruphp.com/relative-url/cat.png"/> 3. <img src="http://img.ruphp.com/relative-url/cat.png"/> 4. <img src="somedirectory/cat.png"/> 5. <img src="/cat.png"/> 6. <img src="cat.png"/>
Затем обработайте все атрибуты <img>
" src
" с помощью созданной функции сменного фильтра:
$doc = new DOMDocument(); $saved = libxml_use_internal_errors(true); $doc->loadHTML($site, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); libxml_use_internal_errors($saved); $srcs = (new DOMXPath($doc))->query('//img/@hsrc') ?: []; foreach ($srcs as $src) { $src->nodeValue = $removeFilename($src->nodeValue); } echo $doc->saveHTML();
Результат опять же:
1. <img src="//www.stackoverflow.com/cat.png"> 2. <img src="http://img.ruphp.com/relative-url/cat.png"> 3. <img src="http://img.ruphp.com/relative-url/cat.png"> 4. <img src="somedirectory/cat.png"> 5. <img src="/cat.png"> 6. <img src="cat.png">
Использовался другой способ разбора – замена по-прежнему остается прежней. Просто предложить два разных способа, которые также частично совпадают.
Я предлагаю сделать это в несколько шагов.
Чтобы не усложнять решение, допустим, что любое значение src всегда является изображением (это может быть и другое, например скрипт). Кроме того, предположим, что нет пробелов между знаками равенства и кавычками (это можно легко устранить, если они есть). Наконец, давайте предположим, что имя файла не содержит каких-либо экранированных кавычек (если это было сделано, regexp будет более сложным). Поэтому вы можете использовать следующее regexp для поиска всех ссылок на изображения: src="([^"]*)"
. (Кроме того, это не распространяется на случай, когда src заключен в одинарные кавычки, но его легко создать аналогичное регулярное выражение для этого.)
Однако логику обработки можно выполнить с помощью функции preg_replace_callback , а не str_replace
. Вы можете обеспечить обратный вызов этой функции, где каждый URL-адрес может обрабатываться на основе его содержимого.
Так что вы могли бы сделать что-то вроде этого (не тестировалось!):
$site = preg_replace_callback( 'src="([^"]*)"', function ($src) { $url = $src[1]; $ret = ""; if (preg_match("^//", $url)) { // case 1. $ret = "src='" . $url . '"'; } else if (preg_match("^https?://", $url)) { // case 2. and 3. $ret = "src='" . $url . '"'; } else { // case 4., 5., 6. $ret = "src='http://your.site.com.com/" . $url . '"'; } return $ret; }, $site );