LoginSignup
5
7

More than 5 years have passed since last update.

codePointAt を使って基本的な文字列メソッドを定義する

Last updated at Posted at 2015-01-17

EcmaScript 6 で String.prototype.codePointAt() が導入され、サロゲートペアを考慮した文字列関数を定義しやすくなった。es6-shim が配布されているので、古いブラウザーでも利用可能である。io.js では codePointAt だけでなく for..of を使うことができる。使い方はこちらの記事を参照。

2015年9月4月追記

文字列の長さ、部分文字列を求めるメソッドを利用できる npm のパッケージを公開しました。チュートリアルとはメソッドの名前は異なります。

文字数を求める

文字数に関しては、正規表現を使ったほうがコードがコンパクトになる。文字列の length プロパティからサロゲートペアの組を引けばよい。名前は ICU の UnicodeString#countChar32 から借りた。

if (!String.prototype.countChar32) {
  String.prototype.countChar32 = function() {
    return this.length - (this.match(/[\uD800-\uDBFF][\uDC00-\uDFFFF]/g)||[]).length;
  };
}

var str = '\uD842\uDFB7野家';
console.log(3 === str.countChar32());

codePointAt から定義すると次のようになる。

if (!String.prototype.countChar32) {
    String.prototype.countChar32 = function() {

        var pos = 0;
        var length = this.length;
        var cp = 0;
        var count = 0;

        while (pos < length) {
            cp = this.codePointAt(pos);
            pos += cp < 0x10000 ? 1 : 2;
            ++count;
        }

        return count;
    };
}

var str = '\uD842\uDFB7野家';
console.log(3 === str.countChar32());

指定したインデックスに対応する1文字取り出す

ICU の UnicodeString#char32At から名前を借りた。

if (!String.prototype.char32At) {
    String.prototype.char32At = function(offset) {

        var pos = 0;
        var length = this.length;
        var cp = 0;
        var size = 0;
        var index = 0;

        while (pos < length) {
            cp = this.codePointAt(pos);
            size = cp < 0x10000 ? 1 : 2;

            if (index === offset) {
                return this.substr(pos, size);
            }

            pos += size;
            ++index;
        }

        return '';
    };
}

var str = '\uD842\uDFB7野家';
console.log('\uD842\uDFB7' === str.char32At(0));

文字列を逆順に並べ替える

if (!String.prototype.reverse) {
    String.prototype.reverse = function() {

        var pos = 0;
        var length = this.length;
        var cp = 0;
        var size = 0;
        var ret = '';

        while (pos < length) {
            cp = this.codePointAt(pos);
            size = cp < 0x10000 ? 1 : 2;
            ret  = this.substr(pos, size) + ret;
            pos += size;
        }

        return ret;
    };
}

var str = '\uD842\uDFB7野家';
console.log('家野\uD842\uDFB7' === str.reverse());

部分文字列を取り出す

先ほど定義した countChar32char32At を組み合わせる。

if (!String.prototype.substr32) {
    String.prototype.substr32 = function(pos, length) {

        var count = this.countChar32();
        var ret = '';
        var last = pos + length - 1;

        for (var i = 0; i < count; ++i) {

            if (i >= pos && last >= i) {
                ret += this.char32At(i);
            }

            if (i === last) {
                return ret;
            }
        }

        return ret;
    };
}

var str = '\uD842\uDFB7野家';
console.log('' === str.substr32(1, 1));

codePointAt から定義するのであれば、少しコードが長くなる。

if (!String.prototype.substr32) {
    String.prototype.substr32 = function(pos, length) {

        var offset = 0;
        var count = this.length;
        var cp = 0;
        var size = 0;
        var start = 0;
        var index = 0;
        var last = pos + length - 1;
        var retSize = 0;

        while (offset < count) {

            cp = this.codePointAt(offset);
            size = cp < 0x10000 ? 1 : 2;

            if (start === 0 && index === pos) {
                start = offset;
            }

            if (index >= pos && last >= index) {
                retSize += size;
            }

            if (index === last) {
                 return this.substr(start, retSize); 
            }

            offset += size;
            ++index;
        }

        return String(this);
    };
}

var str = '\uD842\uDFB7野家';
console.log(str.substr32(1, 1));

1文字ずつコールバックを適用する

名前は Ruby の String#eachChar から借りた。

if (!String.prototype.eachChar) {
    String.prototype.eachChar = function(callback) {

        var pos = 0;
        var length = this.length;
        var cp = 0;
        var size = 0;

        while (pos < length) {
            cp = this.codePointAt(pos);
            size = cp < 0x10000 ? 1 : 2;
            callback(this.substr(pos, size));
            pos += size;
        }

    };
}

var str = '\uD842\uDFB7野家';
str.eachChar(function(c) {
    console.log(c);
});

1つずつコードポイントを取り出してコールバックを適用する

上述のメソッドを少し修正すればよい。名前は Ruby の String#each_codepoint から借りた。

if (!String.prototype.eachCodePoint) {
    String.prototype.eachCodePoint = function(callback) {

        var pos = 0;
        var length = this.length;
        var cp = 0;
        var size = 0;

        while (pos < length) {
            cp = this.codePointAt(pos);
            size = cp < 0x10000 ? 1 : 2;
            callback(cp);
            pos += size;
        }

    };
}

var str = '\uD842\uDFB7野家';
str.eachCodePoint(function(cp) {
    console.log('U+' + cp.toString(16).toUpperCase());
});
5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7