Windowsファイルパスを標準添付ライブラリで生成/パースする時の言語ごとの違い

RubyC#Go の標準添付ライブラリで file:///c:\temp\doc.txt のようなパスを生成・パースした時の違いを示します。Ruby だけは標準ライブラリの仕様上少し手を加えてあげないと動きませんでした。そのあたり少しフェアじゃないかもしれません。

参考サイト

dobon.net

pkg.go.dev

docs.ruby-lang.org

docs.ruby-lang.org

C#

using System;

namespace ConsoleApp22
{
    class Program
    {
        static void GenUrl(string winPath)
        {
            Uri u = new Uri(winPath);
            Console.WriteLine(u.AbsoluteUri);
        }

        static void GenPath(string urlPath)
        {
            Uri u = new Uri(urlPath);
            string winPath = u.LocalPath + Uri.UnescapeDataString(u.Fragment);
            Console.WriteLine(winPath);
        }

        static void Main(string[] args)
        {
            GenUrl(@"c:\temp\テスト doc#1.txt");
            // file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt

            GenPath(@"file:///c:\temp\テスト doc#1.txt");
            // c:\temp\テスト doc#1.txt
        }
    }
}

Go

package main

import (
    "fmt"
    "net/url"
)

func GenUrl(winPath string) {
    u := &url.URL{}
    u.Scheme = "file"
    u.Path = winPath
    fmt.Println(u.String())
}

func GenPath(urlPath string) {
    u, _ := url.Parse(urlPath)
    fmt.Println(u.Path + "#" + u.Fragment)
}

func main() {
    GenUrl("c:\\temp\\テスト doc#1.txt")
    // file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
    GenPath("file:///c:\\temp\\テスト doc#1.txt")
    // /c:\temp\テスト doc#1.txt
}

Ruby

require 'URI'

def escape_path(path)
  path.gsub(/\\/, "\/").split("/").map { |elem|
    URI.encode_www_form_component(elem)
  }.join("/")
end

def gen_url(win_path)
  uri = URI::Generic.build({
    scheme: "file",
    path: escape_path("/#{win_path}")
  })
  puts uri.to_s
end

def gen_path(url_path)
  uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, "")))
  puts uri.path
end

def decode_gen_path(url_path)
  uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, "")))
  puts URI.decode_www_form_component(uri.path)
end

gen_url(%q|c:\temp\テスト doc#1.txt|)
# file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt

gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt

decode_gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# c:/temp/テスト doc#1.txt

結果

言語 生成されたパス
C# file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
Go file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
Ruby file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt
言語 パースされたパス
C# c:\temp\テスト doc#1.txt
Go /c:\temp\テスト doc#1.txt
Ruby(デコードなし) c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt
Ruby(デコードあり) c:/temp/テスト doc#1.txt

やはり C# はすごいきれいに作れる印象があります。

Go は C# 同様に平易に生成・パースができました。ただ、パースした際に頭に / がついてくるので実際にファイルパスとして使用するときは一工夫が必要そうです。

RubyWindowsはあまり得意ではないプラットフォームであることから自然には生成・パースはできませんでした。生成・パースには工夫が必要でした。ただ、Rubyaddressable という gem によって簡単に生成・パースできるようになるため、そこまで問題ではないと思いました。

おまけ

addressable を使用した Ruby の例を以下に示します。

require 'addressable/uri'

def gen_url(win_path)
  uri = Addressable::URI.convert_path(win_path)
  puts uri.to_s
end

def gen_path(url_path)
  uri = Addressable::URI.parse(url_path)
  puts uri.path
end

gen_url(%q|c:\temp\テスト doc#1.txt|)
# file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc#1.txt

gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# /c:\temp\テスト doc