Tech Racho エンジニアの「?」を「!」に。
  • 開発

ベースファイルからの相対パスを、ドットの連続「..」を含めて解決する

HTMLなどで、リンクは自身のパスからの相対パスで記述することが多いです。

たとえばEPUBリーダーを作っているなら、「OPS/xhtml/0001.xhtml」に含まれる画像やリンクは

<img src="../image/cover.jpg">
→ OPS/image/cover.jpg

<a href="./0002.xhtml">
→ OPS/xhtml/0002.xhtml

のように解釈する必要があります。
もちろん、EPUBファイルはファイルシステムに展開せず、ZIPのまま扱いたいですね。

こんな処理を自前で書いても良いことはないので、なるべく標準ライブラリに近いものを使わせて頂きましょう。

Ruby

真っ先に思いつくのはFile.expand_pathですが、これはあくまで実ファイル用です。「0002.xhtml」という文字列を渡したら、カレントディレクトリからの相対パスとして解釈されてしまい、ベースファイルパスを指定できません。

URI.joinは近いことができますが、scheme指定が必要なので使いづらいです。

この場合、Pathname.cleanpathを使いましょう。

require 'pathname'
base = Pathname.new('a/b/c/../d/./../e/f.txt')

base.cleanpath.to_s
=> "a/b/e/f.txt"

Pathname.new(base.parent + "../g.html").cleanpath.to_s
=> "a/b/g.html"

Pathname.new(base.parent + "/root.html").cleanpath.to_s
=> "/root.html"

シンプルで使いやすいですね。

ちなみに、Pathname.realpathは、cleanpathの動作に加えてシンボリックリンクの解決や実ファイルの存在チェックをします。

Java

AndroidでJavaを使って処理する場合はURIUtilsが便利です。

public static String resolvePath(String base, String reference) 
    throws URISyntaxException {
    URI context = new URI(base);
    return URIUtils.resolve(context, reference).toString();
}
// 挙動はJUnitで言うとこうなります
assertEquals("/", StringUtil.resolvePath("/", ""));

assertEquals("index.html", StringUtil.resolvePath("", "index.html"));
assertEquals("index.html", StringUtil.resolvePath("", "./index.html"));
assertEquals("index.html", StringUtil.resolvePath("", "../index.html"));
assertEquals("index.html", StringUtil.resolvePath("hoge.xml", "index.html"));
assertEquals("index.html", StringUtil.resolvePath("abc/def/hoge.xml", "../../index.html"));

assertEquals("/index.html", StringUtil.resolvePath("/", "index.html"));
assertEquals("/index.html", StringUtil.resolvePath("/hoge.xml", "index.html"));
assertEquals("/index.html", StringUtil.resolvePath("/abc/def/hoge.xml", "/index.html"));

assertEquals("abc/def/index.html", StringUtil.resolvePath("", "abc/def/index.html"));
assertEquals("abc/def/index.html", StringUtil.resolvePath("abc/def/ghi/hoge.xml", "../index.html"));
assertEquals("abc/def/index.html", StringUtil.resolvePath("abc/def/", "index.html"));

// ちょっと注意
assertEquals("index.html", StringUtil.resolvePath("/", "../index.html"));
assertEquals("index.html", StringUtil.resolvePath("/hoge.xml", "../index.html"));

C

libxml2のxmlBuildURIが便利です。

#include <stdio.h>
#include <libxml/uri.h>

int main() {
    xmlChar *base = (xmlChar *)"abc/def/index.xml";
    printf("%s\n", xmlBuildURI((xmlChar *)("../hoge.html"), base));
}

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。