Я пытаюсь отобразить веб-сайт для пользователя, загрузив его с помощью 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. Если мы используем изображение на нашей вымышленной странице catledjohn.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="
а также "
, следующий шаблон может сделать это:
~(\ssrc=")([^"]+)(")~
Он имеет три группы захвата, вторая из которых содержит интересующие вас данные. Первая и последняя полезны для изменения всего совпадения.
Теперь вы можете заменить все экземпляры функцией обратного вызова, которая меняет местами. Я создал простую строку со всеми 6 случаями:
$site = <<<BUFFER
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"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://www.stackoverflow.com/cat.png<<<"3. src=">>>https://www.stackoverflow.com/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://www.stackoverflow.com/cat.png<<<"3. src=">>>https://www.stackoverflow.com/cat.png<<<"4. src=">>>somedirectory/cat.png<<<"5. src=">>>/cat.png<<<"6. src=">>>cat.png<<<"
Преимущество этого заключается в том, что для функции замены вам нужно иметь дело только с совпадением URL-адреса в виде одной строки, а не с массивом совпадений регулярного выражения для разных групп.
Теперь ко второй части вашего вопроса: как заменить это обработкой определенного URL, например, удалением имени файла. Это можно сделать, проанализировав сам URL-адрес и удалив имя файла (базовое имя) из компонента пути. Благодаря извлечению вы можете поместить это в простую функцию:
$removeFilename = function ($url) {
$url = new Net_URL2($url);
$base = basename($path = $url->getPath());
$url->setPath(substr($path, 0, -strlen($base)));
return $url;
};
Этот код использует URL компонента Pear_ 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://www.stackoverflow.com/cat.png"/>
3. <img src="https://www.stackoverflow.com/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://www.stackoverflow.com/cat.png">
3. <img src="https://www.stackoverflow.com/cat.png">
4. <img src="somedirectory/cat.png">
5. <img src="/cat.png">
6. <img src="cat.png">
Просто был использован другой способ разбора — замена все та же. Просто предложить два разных способа, которые тоже частично одинаковы.
Я предлагаю сделать это в несколько шагов.
Чтобы не усложнять решение, давайте предположим, что любое значение src всегда является изображением (это также может быть что-то еще, например, скрипт).
Также, давайте предположим, что между знаком равенства и кавычками нет пробелов (это можно легко исправить, если они есть). Наконец, давайте предположим, что имя файла не содержит экранированных кавычек (если это так, регулярное выражение будет более сложным).
Таким образом, вы использовали бы следующее регулярное выражение, чтобы найти все ссылки на изображения:
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
);