我做了一个符合 W3 规范的简单实现。在这里,我只是简单地持有不同的合法字符集(合法的起始字符与后面的字符不同)并使用 string.Contains。但是合法字符的集合非常大(无论如何对我来说),并且只在候选字符串的时间检查一个字符变得有点昂贵。
目前这不是一个真正的问题,因为我需要在每次执行批处理(需要几秒钟、几分钟甚至几小时)时验证几个字符串一次(需要几毫秒),但我很想知道其他人会建议什么.
这是我的简单实现:
using System;
using System.Text;
using Project.Common; // Guard
namespace Project.Common.XmlUtilities
{
static public class XmlUtil
{
static public bool IsLegalElementName(string localName)
{
Guard.ArgumentNotNull(localName, "localName");
if (localName == "")
return false;
if (NameStartChars.IndexOf(localName[0]) == -1)
return false;
for (int i = 1; i < localName.Length; i++)
if (NameChars.IndexOf(localName[i]) == -1)
return false;
return true;
}
// See W3 spec at http://www.w3.org/TR/REC-xml/#NT-NameStartChar.
static public readonly string NameStartChars = AZ.ToLower() + AZ + ":_" + GetStringFromCharRanges(0xC0, 0xD6, 0xD8, 0xF6, 0xF8, 0x2FF, 0x370, 0x37D, 0x37F, 0x1FFF, 0x200C, 0x200D, 0x2070, 0x218F, 0x2C00, 0x2FEF, 0x3001, 0xD7FF, 0xF900, 0xFDCF, 0xFDF0, 0xFFFD, 0x10000, 0xEFFFF);
// See W3 spec at http://www.w3.org/TR/REC-xml/#NT-NameChar.
static public readonly string NameChars = NameStartChars + "-.0123456789" + char.ConvertFromUtf32(0xB7) + GetStringFromCharRanges(0x0300, 0x036F, 0x203F, 0x2040);
public const string AZ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Hacky but convenient: alternating low-high unicode points specifies multiple ranges, e.g. 0-5 and 10-12 would be 0, 5, 10, 12.
static string GetStringFromCharRanges(params int[] lowHigh)
{
var sb = new StringBuilder();
for (int i = 0; i < lowHigh.Length; i += 2)
{
int low = lowHigh[i];
int high = lowHigh[i + 1];
for (int ci=low; ci < high; ci++)
sb.Append(char.ConvertFromUtf32(ci));
}
return sb.ToString();
}
}
}
虽然我没有费心构建它,但我认为在类型初始化程序中创建一个排序列表,然后对列表进行二进制搜索(而不是使用 string.Contains 进行线性搜索)以检查每个字符会在空间、时间上取得良好的平衡和复杂性。但也许你有其他(更好!)的想法?