HTMLに埋め込まれた相対パスを絶対パスに変換する

スクレイピングをした時に、imgタグなどで埋め込まれている画像が相対パスで指定されている場合に絶対パスに変換する関数です。

もっとスマートなやり方がありそうですが。。。ほかの人が改善してくれることを期待して公開してみます。

相対パスのパターン

相対パスのパターンは以下の7通りを想定しています。抜けているパターンがあれば教えてください。

絶対パス

http://example.jp/sample.jpg

・http(s)の省略

//example.jp/sample.jpg

ディレクトリ以下を指定

/images/sample.jpg

・同一ディレクトリ内

./sample.jpg

・同一ディレクトリ内

sample.jpg

・親ディレクトリ内

../../sample.jpg

・並列するディレクトリ内

../../images/sample.jpg

相対パスから絶対パスに変換するPHP関数

ベースURLと変換する相対パスを引数に指定します。

ベースURLを元に、相対パス絶対パスに変換します。

/**
 * スクレイピングなどで画像URLを取得する時に使うために 
 * ベースURLを元に相対パスから絶対パスに変換する関数
 * 
 * @param string $target_path 変換する相対パス
 * @param string $base ベースとなるパス
 * @return $uri string 絶対パスに変換済みのパス
 */
function convert_to_uri($target_path, $base) {
  $component = parse_url($base);

  $directory = preg_replace('!/[^/]*$!', '/', $component["path"]);

  switch (true) {

    // [0] 絶対パスのケース(簡易版)
    case preg_match("/^http/", $target_path):
      $uri =  $target_path;
      break;

    // [1]「//exmaple.jp/aa.jpg」のようなケース
    case preg_match("/^\/\/.+/", $target_path):
      $uri =  $component["scheme"].":".$target_path;
      break;

    // [2]「/aaa/aa.jpg」のようなケース
    case preg_match("/^\/[^\/].+/", $target_path):
      $uri =  $component["scheme"]."://".$component["host"].$target_path;
      break;

    // [3]「./aa.jpg」のようなケース
    case preg_match("/^\.\/(.+)/", $target_path,$maches):
      $uri =  $component["scheme"]."://".$component["host"].$directory.$maches[1];
      break;

    // [4]「aa.jpg」のようなケース([3]と同じ)
    case preg_match("/^([^\.\/]+)(.*)/", $target_path,$maches):
      $uri =  $component["scheme"]."://".$component["host"].$directory.$maches[1].$maches[2];
      break;

    // [5]「../aa.jpg」のようなケース
    case preg_match("/^\.\.\/.+/", $target_path):
      //「../」をカウント
      preg_match_all("!\.\./!", $target_path, $matches);
      $nest =  count($matches[0]);

      //ベースURLのディレクトリを分解してカウント
      $dir = preg_replace('!/[^/]*$!', '/', $component["path"])."\n";
      $dir_array = explode("/",$dir);
      array_shift($dir_array);
      array_pop($dir_array);
      $dir_count = count($dir_array);

      $count = $dir_count - $nest;

      $pathto="";
      $i = 0;
      while ( $i < $count) {
        $pathto .= "/".$dir_array[$i];
        $i++;
      }
      $file = str_replace("../","",$target_path);
      $uri =  $component["scheme"]."://".$component["host"].$pathto."/".$file;

      break;
  }

  return $uri;
}

関数の実行結果

上記の関数を相対パス7パターンで実行してみます。

$base = "http://example.jp/posts/2015/06/convert_to_uri.html";
$pathes = array(
  "http://example.jp/sample.jpg",
  "//example.jp/sample.jpg",
  "/images/sample.jpg",
  "./sample.jpg",
  "sample.jpg",
  "../../sample.jpg",
  "../../images/sample.jpg"
);

foreach ($pathes as $key => $path) {
  echo convert_to_uri($path,$base)."\n";
}

実行結果は以下の通りです。なんとか上手くいった気がします。

http://example.jp/sample.jpg
http://example.jp/sample.jpg
http://example.jp/images/sample.jpg
http://example.jp/posts/2015/06/sample.jpg
http://example.jp/posts/2015/06/sample.jpg
http://example.jp/posts/sample.jpg
http://example.jp/posts/images/sample.jpg

フィードバックなどあればコメントなどにお願いします!

[amazonjs asin=“B00BCQ5Q96” locale=“JP” title=“Programming PHP”]

[amazonjs asin=“B00P0UDWQY” locale=“JP” title=“パーフェクトPHP”]