C#实现Punycode编码/解码

更新于 2024-01-11

punycode

Punycode编码/解码的实现。

用法

Console.WriteLine(PunyCode.Encode("用法")); //nwwn1p
Console.WriteLine(PunyCode.Decode("nwwn1p")); //用法

Console.WriteLine(PunyCode.IDN2Punycode("用法.中国")); //xn--nwwn1p.xn--fiqs8s
Console.WriteLine(PunyCode.Punycode2IDN("xn--nwwn1p.xn--fiqs8s")); //用法.中国

源代码

public class PunyCode
{
    private static readonly string[] spliter = ["."];
    public static string IDN2Punycode(string input)
    {
        string[] inputArray = input.Split(spliter, StringSplitOptions.RemoveEmptyEntries);
        string retstr = "";
        for (int i = 0; i < inputArray.Length; i++)
        {
            Regex myreg = new Regex("^[0-9a-zA-Z\\-]+$");
            if (myreg.IsMatch(inputArray[i]))
                retstr += inputArray[i] + ".";
            else
                retstr += "xn--" + Encode(inputArray[i]) + ".";
        }

        return retstr.TrimEnd('.');
    }
    public static string Punycode2IDN(string input)
    {
        string[] inputArray = input.ToLower().Split(spliter, StringSplitOptions.RemoveEmptyEntries);
        string retstr = "";
        for (int i = 0; i < inputArray.Length; i++)
        {
            string tmp = inputArray[i];
            if (tmp.StartsWith("xn--"))
                retstr += Decode(tmp.Substring(4)) + ".";
            else
                retstr += tmp + ".";
        }

        return retstr.TrimEnd('.');
    }
    public static string Encode(string input)
    {
        int n = 0x80;
        int delta = 0;
        int bias = 72;
        StringBuilder output = new StringBuilder();
        int b = 0;
        for (int i = 0; i < input.Length; i++)
        {
            char c = input[i];
            if (c < 0x80)
            {
                output.Append(c);
                b++;
            }
        }

        if (b > 0) output.Append('-');

        int h = b;
        while (h < input.Length)
        {
            int m = int.MaxValue;

            for (int i = 0; i < input.Length; i++)
            {
                int c = input[i];
                if (c >= n && c < m) m = c;
            }

            if (m - n > (int.MaxValue - delta) / (h + 1)) throw new Exception();

            delta += (m - n) * (h + 1);
            n = m;

            for (int j = 0; j < input.Length; j++)
            {
                int c = input[j];
                if (c < n)
                {
                    delta++;
                    if (0 == delta) throw new Exception();
                }
                if (c == n)
                {
                    int q = delta;

                    for (int k = 36; ; k += 36)
                    {
                        int t;
                        if (k <= bias) t = 1;
                        else if (k >= bias + 26) t = 26;
                        else t = k - bias;

                        if (q < t) break;

                        output.Append((char)Digit2Codepoint(t + (q - t) % (36 - t)));
                        q = (q - t) / (36 - t);
                    }

                    output.Append((char)Digit2Codepoint(q));
                    bias = Adapt(delta, h + 1, h == b);
                    delta = 0;
                    h++;
                }
            }

            delta++;
            n++;
        }

        return output.ToString();
    }

    public static string Decode(string input)
    {
        int n = 0x80;
        int i = 0;
        int bias = 72;
        StringBuilder output = new StringBuilder();

        int d = input.LastIndexOf('-');
        if (d > 0)
        {
            for (int j = 0; j < d; j++)
            {
                char c = input[j];
                if (c >= 0x80) throw new Exception();
                output.Append(c);
            }
            d++;
        }
        else d = 0;

        while (d < input.Length)
        {
            int oldi = i;
            int w = 1;

            for (int k = 36; ; k += 36)
            {
                if (d == input.Length) throw new Exception();

                int c = input[d++];
                int digit = Codepoint2Digit(c);
                if (digit > (int.MaxValue - i) / w) throw new Exception();

                i += digit * w;

                int t;
                if (k <= bias) t = 1;
                else if (k >= bias + 26) t = 26;
                else t = k - bias;

                if (digit < t) break;

                w *= 36 - t;
            }

            bias = Adapt(i - oldi, output.Length + 1, oldi == 0);

            if (i / (output.Length + 1) > int.MaxValue - n) throw new Exception();

            n += i / (output.Length + 1);
            i %= output.Length + 1;
            output.Insert(i, (char)n);
            i++;
        }
        return output.ToString();
    }

    private static int Adapt(int delta, int numpoints, bool first)
    {
        delta /= first ? 700 : 2;

        delta += delta / numpoints;

        int k = 0;
        while (delta > 455)
        {
            delta /= 35;
            k += 36;
        }

        return k + (36 * delta) / (delta + 38);
    }

    private static int Digit2Codepoint(int d)
    {

        if (d < 26) return d + 97;

        if (d < 36) return d + 22;

        throw new Exception();

    }

    private static int Codepoint2Digit(int c)
    {

        if (c < 58) return c - 22;

        if (c < 123) return c - 97;

        throw new Exception();
    }
}