新博客第一篇,字符串 Unicode 转义

在博客园开了博客,也算是把自己的东西分享一下,希望自己能坚持下去吧。

第一篇其实是个小东西,就是字符串的 Unicode 转义。有时候在处理一些网络信息的时候,难免要对中文进行 Unicode 转义,就是变成 \u4E2D\u6587 的样子,以便于进行网络传输。为了方便编码和解码,就写了两个函数来做这件事。

首先是十六进制字符的判断,我把它放到了单独的类中以方便重用。实现的功能很简单,就是判断字符是否在 [0-9A-Fa-f] 的范围内。这里有一个很小的技巧,就是按照二叉排序树的顺序把判断的顺序稍微微调一下,能够略微提高一些速度。

using System.Text;

namespace Cyjb {
	/// <summary>
	/// 提供 <see cref="System.Char"/> 类的扩展方法。
	/// </summary>
	public static class CharExt {

		#region IsHex

		/// <summary>
		/// 指示指定的 Unicode 字符是否属于十六进制数字类别。
		/// </summary>
		/// <param name="ch">要计算的 Unicode 字符。</param>
		/// <returns>如果 <paramref name="ch"/> 是十进制数字,则为 <c>true</c>;
		/// 否则,为 <c>false</c>。</returns>
		public static bool IsHex(this char ch) {
			if (ch <= 'f') {
				if (ch >= 'A') {
					return ch <= 'F' || ch >= 'a';
				} else {
					return ch >= '0' && ch <= '9';
				}
			}
			return false;
		}
		/// <summary>
		/// 指示指定字符串中位于指定位置处的字符是否属于十六进制数字类别。
		/// </summary>
		/// <param name="str">一个字符串。</param>
		/// <param name="index">要计算的字符在 <paramref name="str"/> 中的位置。</param>
		/// <returns>如果 <paramref name="str"/> 中位于 <paramref name="index"/> 处的字符是十进制数字,
		/// 则为 <c>true</c>;否则,为 <c>false</c>。</returns>
		/// <exception cref="System.IndexOutOfRangeException"><paramref name="index"/> 大于等于字符串的长度或小于零。</exception>
		public static bool IsHex(string str, int index) {
			return IsHex(str[index]);
		}

		#endregion // IsHex

	}
}

然后就是对字符串进行 Unicode 编码和解码的方法了。

解码方法支持 \x,\u 和 \U 转义,其中 \x 之后可跟 1~4 个十六进制字符,\u 后面是 4 个十六进制字符,\U 后面则是 8 个,而且由于 .Net 只支持小于 0x10FFFF 的 Unicode,所以使用 \U 转义时,超出的部分会被舍弃。如果不满足上面的情况,则不会进行转义,也不会报错。

编码会将可显示字符(0x20~0x7E,就是从空格到~)以外的所有字符使用 \u 转义表示。

using System.Text;

namespace Cyjb {
	/// <summary>
	/// 提供 <see cref="System.String"/> 类的扩展方法。
	/// </summary>
	public static class StringExt {

		#region Unicode 操作

		/// <summary>
		/// 将字符串中的 \u,\U 和 \x 转义转换为对应的字符。
		/// </summary>
		/// <param name="str">要转换的字符串。</param>
		/// <returns>转换后的字符串。</returns>
		public static string DecodeUnicode(this string str) {
			if (string.IsNullOrEmpty(str)) {
				return str;
			}
			int idx = str.IndexOf('\\');
			if (idx < 0) {
				return str;
			}
			int len = str.Length, start = 0;
			StringBuilder builder = new StringBuilder(len);
			while (idx >= 0) {
				// 添加当前 '\' 之前的字符串。
				if (idx > start) {
					builder.Append(str, start, idx - start);
					start = idx;
				}
				// 跳过 '\' 字符。
				idx++;
				// '\' 字符后的字符数小于 2,不可能是转义字符,直接返回。
				if (idx + 1 >= len) {
					break;
				}
				// 十六进制字符的长度。
				int hexLen = 0;
				// 处理 Unicode 转义。
				switch (str[idx]) {
					case 'x':
						// \x 后面可以是 1 至 4 位。
						hexLen = GetHexLength(str, idx + 1, 4);
						break;
					case 'u':
						// \u 后面必须是 4 位。
						if (idx + 4 < len && GetHexLength(str, idx + 1, 4) == 4) {
							hexLen = 4;
						}
						else {
							hexLen = 0;
						}
						break;
					case 'U':
						// \U 后面必须是 8 位。
						if (idx + 8 < len && GetHexLength(str, idx + 1, 8) == 8) {
							hexLen = 8;
						}
						else {
							hexLen = 0;
						}
						break;
				}
				if (hexLen > 0) {
					idx++;
					int charNum = int.Parse(str.Substring(idx, hexLen), NumberStyles.HexNumber,
						CultureInfo.InvariantCulture);
					if (charNum < 0xFFFF) {
						// 单个字符。
						builder.Append((char)charNum);
					} else {
						// 代理项对的字符。
						builder.Append(char.ConvertFromUtf32(charNum & 0x1FFFFF));
					}
					idx = start = idx + hexLen;
				}
				idx = str.IndexOf('\\', idx);
			}
			// 添加剩余的字符串。
			if (start < len) {
				builder.Append(str.Substring(start));
			}
			return builder.ToString();
		}
		/// <summary>
		/// 返回字符串指定索引位置之后的十六进制字符的个数。
		/// </summary>
		/// <param name="str">要获取十六进制字符个数的字符串。</param>
		/// <param name="index">要开始计算十六进制字符个数的其实索引。</param>
		/// <param name="maxLength">需要的最长的十六进制字符个数。</param>
		/// <returns>实际的十六进制字符的个数。</returns>
		internal static int GetHexLength(string str, int index, int maxLength) {
			if (index + maxLength > str.Length) {
				maxLength = str.Length - index;
			}
			for (int i = 0; i < maxLength; i++, index++) {
				if (!CharExt.IsHex(str, index)) {
					return i;
				}
			}
			return maxLength;
		}
		/// <summary>
		/// 将字符串中不可显示字符(0x00~0x1F,0x7F之后)转义为 \\u 形式,其中十六进制以大写字母形式输出。
		/// </summary>
		/// <param name="str">要转换的字符串。</param>
		/// <returns>转换后的字符串。</returns>
		public static string EncodeUnicode(this string str) {
			return EncodeUnicode(str, true);
		}
		/// <summary>
		/// 将字符串中不可显示字符(0x00~0x1F,0x7F之后)转义为 \\u 形式。
		/// </summary>
		/// <param name="str">要转换的字符串。</param>
		/// <param name="upperCase">是否以大写字母形式输出十六进制。如果为 <c>true</c> 则是以大写字母形式输出十六进制,
		/// 否则以小写字母形式输出。</param>
		/// <returns>转换后的字符串。</returns>
		public static string EncodeUnicode(this string str, bool upperCase) {
			if (string.IsNullOrEmpty(str)) {
				return str;
			}
			string format = upperCase ? "X4" : "x4";
			StringBuilder builder = new StringBuilder(str.Length * 2);
			for (int i = 0; i < str.Length; i++) {
				char c = str[i];
				if (c >= ' ' && c <= '~') {
					// 可显示字符。
					builder.Append(c);
				}
				else {
					builder.Append("\\u");
					builder.Append(((int)c).ToString(format, CultureInfo.InvariantCulture));
				}
			}
			return builder.ToString();
		}

		#endregion

	}
}

代码可见 https://github.com/CYJB/Cyjb/tree/master/Cyjb 这里的 CharExt 和 StringExt 类。

原文地址:https://www.cnblogs.com/cyjb/p/StringDecodeUnicode.html