function CITTextConvert() {

    // -*- coding: utf-8 -*-
    // Utility functions for strings.
    //
    // Copyright (C) 2007 Satoru Takabayashi <satoru 0xcc.net>
    // All rights reserved.  This is free software with ABSOLUTELY NO WARRANTY.
    // You can redistribute it and/or modify it under the terms of
    // the GNU General Public License version 2.

    // NOTES:
    //
    // Surrogate pairs:
    //
    //   1st 0xD800 - 0xDBFF (high surrogate)
    //   2nd 0xDC00 - 0xDFFF (low surrogate)
    //
    // UTF-8 sequences:
    //
    //   0xxxxxxx
    //   110xxxxx 10xxxxxx
    //   1110xxxx 10xxxxxx 10xxxxxx
    //   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

    //#---------------------------------------------------------
    //# 20081118 Christian Fisker - Columbus IT Partner A/S
    //# Adapted for use from http://0xcc.net/jsescape/
    //#---------------------------------------------------------

    // Base10: "ab" => "&#12354;&#12356;"
    // Base16: "ab" => "&#x3042;&#x3044;"
    this.escapeToNumRef = function(str, base) {
        var unicode_codes = this.convertStringToUnicodeCodePoints(str);
        var escaped = ''
        var prefix = base == 10 ? ''  : 'x';
        for (var i = 0; i < unicode_codes.length; ++i) {
            var code = unicode_codes[i].toString(base).toUpperCase();
            var num_ref = "&#" + prefix + code + ";"
            escaped += num_ref;
        }
        return escaped;
    }
    
    // Base10: "&#12354;&#12356;" => "ab"
    // Base16: "&#x3042;&#x3044;" => "ab"
    this.unescapeFromNumRef = function(str, base) {
        var unicode_codes = this.convertNumRefToUnicodeCodePoints(str, base);
        return this.convertUnicodeCodePointsToString(unicode_codes);
    }
    
    // "ab" => [ 0x3042,  0x3044 ]
    this.convertStringToUnicodeCodePoints = function(str) {
        var surrogate_1st = 0;
        var unicode_codes = [];
        for (var i = 0; i < str.length; ++i) {
            var utf16_code = str.charCodeAt(i);
            if (surrogate_1st != 0) {
                if (utf16_code >= 0xDC00 && utf16_code <= 0xDFFF) {
                    var surrogate_2nd = utf16_code;
                    var unicode_code = (surrogate_1st - 0xD800) * (1 << 10) + (1 << 16) +
                                       (surrogate_2nd - 0xDC00);
                    unicode_codes.push(unicode_code);
                }
                else {
                    // Malformed surrogate pair ignored.
                }
                surrogate_1st = 0;
            }
            else if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
                surrogate_1st = utf16_code;
            }
            else {
                unicode_codes.push(utf16_code);
            }
        }
        return unicode_codes;
    }
    
    // "&amp;#12354;&amp;#12356;" => [ 0x3042, 0x3044 ]
    // "&amp;#x3042;&amp;#x3044;" => [ 0x3042, 0x3044 ]
    this.convertNumRefToUnicodeCodePoints = function(str, base) {
        var num_refs = str.split(";");
        num_refs.pop();  // Trim the last element.
        var unicode_codes = [];
        for (var i = 0; i < num_refs.length; ++i) {
            var decimal_str = num_refs[i].replace(/^&#x?/, "");
            var unicode_code = parseInt(decimal_str, base);
            unicode_codes.push(unicode_code);
        }
        return unicode_codes;
    }

    // [ 0x3042, 0x3044 ] => "ab"
    this.convertUnicodeCodePointsToString = function(unicode_codes) {
        var utf16_codes = this.convertUnicodeCodePointsToUtf16Codes(unicode_codes);
        return this.convertUtf16CodesToString(utf16_codes);
    }
    
    // [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ]
    // [ 0xD840, 0xDC0B ] => [ 0x2000B ]  // A surrogate pair.
    this.convertUnicodeCodePointsToUtf16Codes = function(unicode_codes) {
        var utf16_codes = [];
        for (var i = 0; i < unicode_codes.length; ++i) {
            var unicode_code = unicode_codes[i];
            if (unicode_code < (1 << 16)) {
                utf16_codes.push(unicode_code);
            }
            else {
                var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800;
                var second = (unicode_code % (1 << 10)) + 0xDC00;
                utf16_codes.push(first)
                utf16_codes.push(second)
            }
        }
        return utf16_codes;
    }

    // [ 0x3042, 0x3044 ] => "ab"
    this.convertUtf16CodesToString = function(utf16_codes) {
        var unescaped = '';
        for (var i = 0; i < utf16_codes.length; ++i) {
            unescaped += String.fromCharCode(utf16_codes[i]);
        }
        return unescaped;
    }

}