file_get_contents( - 修复相对网址

时间:2015-04-07 15:54:49

标签: php regex relative-path relative-url

我正在尝试向用户显示网站,使用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函数的一些主要问题外,这个脚本还可以处理。问题来自相对网址。如果我们在我们制作的catcalledjohn.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(文件名已删除),但如果它是选项1,2或3并且没有粘贴url稍微改变程序4,5和6?

3 个答案:

答案 0 :(得分:8)

与其尝试更改源代码中的每个路径引用,为什么不简单地在标题中注入<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等)而不是正则表达式。我想你会发现它是一个更稳定的解决方案。

答案 1 :(得分:2)

我不知道我的问题是否完全正确,如果您想处理src=""中包含的所有文字序列,可以使用以下模式:< / p>

~(\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];
    };
};

此函数根据将pass传递为参数的方法创建替换回调。

这样的替换功能可能是:

$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>&#34; src&#34;使用创建的替换过滤器函数的属性:

$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">

只使用了不同的解析方式 - 替换仍然是相同的。只提供两种不同的方式,部分也是相同的。

答案 2 :(得分:1)

我建议更多步骤。

为了不使解决方案复杂化,让我们假设任何src值总是一个图像(它也可能是其他东西,例如脚本)。 另外,我们假设在等号和引号之间没有空格(如果存在则可以很容易地修复)。最后,让我们假设文件名不包含任何转义引号(如果有,则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
);