ISO 9660のファイル名変換

一遍に実装しようとして混乱してきたので、とりあえずファイル名の置換部分だけ書いてみました。
作業記録として、コードを貼っときます。

#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <locale>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

// boost::lexical_cast<std::string>(int)の代わり (適当)
std::string unsigned_to_string(unsigned n)
{
    std::ostringstream os;
    os.imbue(std::locale::classic());
    os << n;
    return os.str();
}

// d文字かどうかの判定をするファンクタ
struct is_iso_d_char : std::unary_function<char,bool>
{
    bool operator()(char c) const
    {
        return
            (c == '_') ||
            ((c >= 'A') && (c <= 'Z')) ||
            ((c >= '0') && (c <= '9')) ;
    }
};

// ISO 9660の正当なファイルIDか?
bool is_valid_iso_id(const std::string& s)
{
    const std::size_t size = s.size();

    if ((size < 2) || (size > (8+1+3)))
        return false;

    // ISO 9660では"."が必須
    std::size_t delim = s.find('.');
    if (delim == std::string::npos)
        return false;

    typedef std::string::const_iterator iter_type;

    iter_type di = s.begin() + delim;
    if (std::find_if(s.begin(), di, std::not1(is_iso_d_char())) != di)
        return false;

    if (di != s.end())
    {
        iter_type ei = di + 1;
        if (std::find_if(ei, s.end(), std::not1(is_iso_d_char())) != s.end())
            return false;
    }

    return true;
}

// ISO 9660で使用できない文字を適当に置き換える
std::string make_iso_alt_name(const std::string& s)
{
    std::string tmp;
    for (std::size_t i = 0, size = s.size(); i != size; ++i)
    {
        char c = s[i];
        if (!is_iso_d_char()(c))
        {
            if ((c >= 'a') && (c <= 'z'))
                c -= ('a' - 'A');
            else
                c = '_';
        }
        tmp += c;
    }
    return tmp;
}

// ファイルID文字列sをrenに含まれない正当な文字列に変換する
std::string make_iso_alt_id(
    const std::vector<std::string>& ren, const std::string& s)
{
    const std::size_t size = s.size();

    std::size_t delim = s.rfind('.');
    if (delim == std::string::npos)
        delim = size;

    // ベース名
    std::string base(s, 0, (std::min)(delim, 8u));

    // 拡張子
    std::string ext;
    if (base.empty())
        base = s.substr(0, 8); // "."始まりのファイルは全体をベース名にする
    else if (delim != size)
        ext = s.substr(delim+1, 3);

    base = ::make_iso_alt_name(base);
    ext = ::make_iso_alt_name(ext);

    std::string new_name;
    new_name.reserve(8+1+3);
    new_name = base;
    new_name += '.';
    new_name += ext;

    unsigned n = 0; // ダブったときに使う連番

    while (std::find(ren.begin(), ren.end(), new_name) != ren.end())
    {
        // ダブったので連番をベース名に追加
        const std::string& num = unsigned_to_string(n++);
        if (num.size() > 8u)
            throw std::runtime_error("cannot generate alternative file ID");

        new_name.assign(base, 0, (std::min)(8u-num.size(), base.size()));
        new_name += num;
        new_name += '.';
        new_name += ext;
    }

    return new_name;
}

// org内のファイルID文字列をISO 9660 validな文字列に変換してrenにセットする
void iso_rename(
    std::vector<std::string>& ren, const std::vector<std::string>& org)
{
    // 使ったかどうかフラグ (適当)
    std::vector<bool> used(org.size());

    std::vector<std::string> tmp;
    tmp.reserve(org.size());

    // validなものはそのまま
    for (std::size_t i = 0; i < org.size(); ++i)
    {
        if (::is_valid_iso_id(org[i]))
        {
            tmp.push_back(org[i]);
            used[i] = true;
        }
    }

    // invalidなものだけ変換
    for (std::size_t i = 0; i < org.size(); ++i)
    {
        if (!used[i])
            tmp.push_back(::make_iso_alt_id(tmp, org[i]));
    }

    ren.swap(tmp);
}

int main()
{
    std::vector<std::string> org;
    org.push_back("abc.txt");
    org.push_back("ABC.TXT");
    org.push_back("MAKEFILE");
    org.push_back("Makefile");
    org.push_back("LONGLONG.TXT");
    org.push_back("LONGLONG1.TXT");
    org.push_back("LONGLONG2.TXT");
    org.push_back("LONGLONG3.TXT");
    org.push_back("LONGLONG4.TXT");
    org.push_back("LONGLONG5.TXT");
    org.push_back("LONGLONG6.TXT");
    org.push_back("LONGLONG7.TXT");
    org.push_back("LONGLONG8.TXT");
    org.push_back("LONGLONG9.TXT");
    org.push_back("LONGLONGA.TXT");
    org.push_back("LONGLONGB.TXT");
    org.push_back("あいうえ.TXT");
    org.push_back("あいうえお.TXT");
    org.push_back("A-B.TXT");
    org.push_back(".hoge");
    org.push_back("a.b.c");

    std::vector<std::string> ren;
    iso_rename(ren, org);

    std::copy(
        ren.begin(), ren.end(),
        (std::ostream_iterator<std::string>(std::cout, "\n"))
    );
}

実行結果

ABC.TXT
LONGLONG.TXT
ABC0.TXT
MAKEFILE.
MAKEFIL0.
LONGLON0.TXT
LONGLON1.TXT
LONGLON2.TXT
LONGLON3.TXT
LONGLON4.TXT
LONGLON5.TXT
LONGLON6.TXT
LONGLON7.TXT
LONGLON8.TXT
LONGLON9.TXT
LONGLO10.TXT
________.TXT
_______0.TXT
A_B.TXT
_HOGE.
A_B.C

クラッチコードなので、

  • ISO 9660 Level-1(8.3ファイル名)前提
  • ASCII前提
  • size_tがunsigned int前提
  • ディレクトリ名("."が付かない)に未対応

などなど適当なところがたくさんありますが、アルゴリズムはこれでOKっぽいです。
vectorだとダブり検索に時間がかかるので、setに置き換えたほうがよいですかね。