2015-04-07 2 views
3

Я пытаюсь отобразить веб-сайт для пользователя, скачав его с помощью PHP Это сценарий, я использую:file_get_contents (- Фикс Относительные URL

<?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 кота (что-то вроде этого: Cat). Это png, и, как я вижу, это могут быть размещены на странице с использованием 6 разных адресов:

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?

ответ

7

Вместо того, чтобы пытаться изменить каждую ссылку на путь в исходном коде, почему бы вам просто не добавить тег <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 и т.д.), а не регулярное выражение. Я думаю, вы найдете это гораздо более стабильным решением.

+0

Спасибо - Есть ли способ исправить ссылки, используя ваше решение? – JBithell

+0

@JBithell Он будет работать для всех URL-адресов на странице - a.href, img.src, script.src, link.src и т. Д. –

+0

wow большое спасибо за это хорошо работает ... потрясающе! – sputn1k

1

Я предлагаю делать это в несколько стадий.

для того, чтобы не усложнять решение, давайте предположим, что любое значение SRC всегда является изображение (это также может быть что-то другое, например скрипт). Кроме того, предположим, что нет пробелов между знаками равенства и кво tes (это можно легко устранить, если есть). Наконец, давайте предположим, что имя файла не содержит каких-либо экранированных кавычек (если это было сделано, regexp будет более сложным). Чтобы найти все ссылки на изображения, используйте следующее регулярное выражение: 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 
); 
2

Я не знаю, если я получаю ваш вопрос совершенно правильно, если вы хотите иметь дело со всеми текстовыми последовательностями, заключенных в 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 и удалив имя файла (basename) из компонента пути. Благодаря добыче, вы можете поместить это в простую функцию:

$removeFilename = function ($url) { 
    $url = new Net_URL2($url); 
    $base = basename($path = $url->getPath()); 
    $url->setPath(substr($path, 0, -strlen($base))); 
    return $url; 
}; 

Этот код использует Pear's Net_URL2 URL component (также доступно через 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"> 

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

Смежные вопросы