サロゲートペア対応の文字列置換処理

entitiesをツイートに埋め込む際、entitiesに含まれるindicesを利用して文字列を置換する必要があるが、indicesには見た目の文字数が設定されている為、サロゲートペアの文字を含むtweetを扱うにあたってはJavaScriptやC#の文字列methodをそのまま使用すると問題が発生する。

仕方ないので、文字数で指定した部分の文字列を置換するmethodを自前で実装する事にする。

JavaScriptの場合:

function replace(text, start, end, part){
    var offset = 0;
    var delta = start < end ? end - start : start - end;

    for(; offset < text.length; ++offset)
    {
        if(!start)
            break;
        var ch = text.charCodeAt(offset);
        if(ch >= 0xd800 && ch < 0xdc00)
            ++offset;
        --start;
    }
    start = offset;

    for(; offset < text.length; ++offset)
    {
        if(!delta)
            break;
        var ch = text.charCodeAt(offset);
        if(ch >= 0xd800 && ch < 0xdc00)
            ++offset;
        --delta;
    }
    end = offset;

    var left = text.substring(0, start);
    var right = text.substring(end);
    return left + part + right;
}

C#の場合:

/// <summary>
/// サロゲートペア対応文字列置換
/// </summary>
/// <param name="text">置換対象の文字列を含む文字列</param>
/// <param name="start">置換対象の文字列の開始位置</param>
/// <param name="end">置換対象の文字列の終了位置</param>
/// <param name="part">新しく埋め込む文字列</param>
private string Replace(string text, int start, int end, string part)
{
    int offset = 0;
    int delta = start < end ? end - start : start - end;

    for (; offset < text.Length; ++offset)
    {
        if (start == 0)
            break;
        if (Char.IsHighSurrogate(text[offset]))
            ++offset;
        --start;
    }
    start = offset;

    for (; offset < text.Length; ++offset)
    {
        if (delta == 0)
            break;
        if (Char.IsHighSurrogate(text[offset]))
            ++offset;
        --delta;
    }
    delta = offset - start;

    return text.Remove(start, delta).Insert(start, part);
}

基本的に行う事は一緒で、対象文字列を一文字ずつ文字コードを確認して、サロゲートペアの1文字目が見つかったら2文字目を読み飛ばしつつ文字数をカウントする。 あとは算出したoffsetを基に、文字列を切り貼りする。