在构造使用数据的 XmlReader 或 XPathDocument 之前,如何从基于 XML 的数据源中删除无效的十六进制字符?
-
09-06-2019 - |
题
在 XmlReader 中使用基于 XML 的数据源之前,是否有任何简单/通用的方法来清理它,以便我可以优雅地使用不符合 XML 上的十六进制字符限制的 XML 数据?
笔记:
- 该解决方案需要处理使用UTF-8以外的字符编码的XML数据源,例如通过在XML文档声明处指定编码的字符。在剥离无效的十六进制角色时,没有弄脏源的字符编码,这是一个主要的症状。
- 删除无效的十六进制字符应该只删除十六进制编码值,因为您经常可以在数据中找到恰好包含与十六进制字符匹配的字符串的 href 值。
背景:
我需要使用符合特定格式(例如 Atom 或 RSS 提要)的基于 XML 的数据源,但希望能够使用已发布的包含按照 XML 规范无效的十六进制字符的数据源。
在 .NET 中,如果您有一个表示 XML 数据源的 Stream,然后尝试使用 XmlReader 和/或 XPathDocument 对其进行解析,则会由于 XML 数据中包含无效的十六进制字符而引发异常。我当前解决此问题的尝试是将 Stream 解析为字符串并使用正则表达式来删除和/或替换无效的十六进制字符,但我正在寻找更高效的解决方案。
解决方案
它 可能并不完美 (由于人们错过了此免责声明,因此添加了重点),但我在这种情况下所做的事情如下。您可以调整以与流一起使用。
/// <summary>
/// Removes control characters and other non-UTF-8 characters
/// </summary>
/// <param name="inString">The string to process</param>
/// <returns>A string with no control characters or entities above 0x00FD</returns>
public static string RemoveTroublesomeCharacters(string inString)
{
if (inString == null) return null;
StringBuilder newString = new StringBuilder();
char ch;
for (int i = 0; i < inString.Length; i++)
{
ch = inString[i];
// remove any characters outside the valid UTF-8 range as well as all control characters
// except tabs and new lines
//if ((ch < 0x00FD && ch > 0x001F) || ch == '\t' || ch == '\n' || ch == '\r')
//if using .NET version prior to 4, use above logic
if (XmlConvert.IsXmlChar(ch)) //this method is new in .NET 4
{
newString.Append(ch);
}
}
return newString.ToString();
}
其他提示
我喜欢尤金的白名单概念。我需要做与原始海报类似的事情,但我需要支持所有 Unicode 字符,而不仅仅是 0x00FD。XML 规范是:
char =#x9 | #xa | #xd | [#x20-#xd7ff] | [#xe000-#xfffd] | [#x10000-#x10ffff
在.NET中,Unicode字符的内部表示只有16位,因此我们不能明确“允许”0x10000-0x10FFFF。XML 规范明确 不允许 代理代码点从 0xD800 开始出现。然而,如果我们在白名单中允许这些代理代码点,那么只要从 utf-16 字符的代理对中生成正确的 utf-8 编码,我们的字符串最终可能会生成有效的 utf-8 编码。 .NET 字符串。不过我还没有探索过这一点,所以我选择了更安全的选择,并且不允许代理出现在我的白名单中。
尤金解决方案中的评论具有误导性,问题是我们排除的字符在 XML ...它们是完全有效的 Unicode 代码点。我们不会删除“非 utf-8 字符”。我们正在删除可能不会出现在格式良好的 XML 文档中的 utf-8 字符。
public static string XmlCharacterWhitelist( string in_string ) {
if( in_string == null ) return null;
StringBuilder sbOutput = new StringBuilder();
char ch;
for( int i = 0; i < in_string.Length; i++ ) {
ch = in_string[i];
if( ( ch >= 0x0020 && ch <= 0xD7FF ) ||
( ch >= 0xE000 && ch <= 0xFFFD ) ||
ch == 0x0009 ||
ch == 0x000A ||
ch == 0x000D ) {
sbOutput.Append( ch );
}
}
return sbOutput.ToString();
}
作为删除无效 XML 字符的方法,我建议您使用 XmlConvert.IsXmlChar 方法。它是从 .NET Framework 4 开始添加的,并且也出现在 Silverlight 中。这是小样本:
void Main() {
string content = "\v\f\0";
Console.WriteLine(IsValidXmlString(content)); // False
content = RemoveInvalidXmlChars(content);
Console.WriteLine(IsValidXmlString(content)); // True
}
static string RemoveInvalidXmlChars(string text) {
char[] validXmlChars = text.Where(ch => XmlConvert.IsXmlChar(ch)).ToArray();
return new string(validXmlChars);
}
static bool IsValidXmlString(string text) {
try {
XmlConvert.VerifyXmlChars(text);
return true;
} catch {
return false;
}
}
DRY 实施 这个答案的解决方案(使用不同的构造函数 - 请随意使用您的应用程序中需要的构造函数):
public class InvalidXmlCharacterReplacingStreamReader : StreamReader
{
private readonly char _replacementCharacter;
public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter) : base(fileName)
{
this._replacementCharacter = replacementCharacter;
}
public override int Peek()
{
int ch = base.Peek();
if (ch != -1 && IsInvalidChar(ch))
{
return this._replacementCharacter;
}
return ch;
}
public override int Read()
{
int ch = base.Read();
if (ch != -1 && IsInvalidChar(ch))
{
return this._replacementCharacter;
}
return ch;
}
public override int Read(char[] buffer, int index, int count)
{
int readCount = base.Read(buffer, index, count);
for (int i = index; i < readCount + index; i++)
{
char ch = buffer[i];
if (IsInvalidChar(ch))
{
buffer[i] = this._replacementCharacter;
}
}
return readCount;
}
private static bool IsInvalidChar(int ch)
{
return (ch < 0x0020 || ch > 0xD7FF) &&
(ch < 0xE000 || ch > 0xFFFD) &&
ch != 0x0009 &&
ch != 0x000A &&
ch != 0x000D;
}
}
现代化 德纽科姆的 答案,你可以采取稍微简单的方法
public static string RemoveInvalidXmlChars(string input)
{
var isValid = new Predicate<char>(value =>
(value >= 0x0020 && value <= 0xD7FF) ||
(value >= 0xE000 && value <= 0xFFFD) ||
value == 0x0009 ||
value == 0x000A ||
value == 0x000D);
return new string(Array.FindAll(input.ToCharArray(), isValid));
}
或者,使用 Linq
public static string RemoveInvalidXmlChars(string input)
{
return new string(input.Where(value =>
(value >= 0x0020 && value <= 0xD7FF) ||
(value >= 0xE000 && value <= 0xFFFD) ||
value == 0x0009 ||
value == 0x000A ||
value == 0x000D).ToArray());
}
我很想知道这些方法的性能如何比较,以及它们与使用黑名单方法的比较如何 Buffer.BlockCopy
.
这是 德纽康姆自定义 StreamReader 中的答案。它只是包装一个真正的流读取器并在读取时替换字符。
我只实施了一些方法来节省自己的时间。我将其与 XDocument.Load 和文件流结合使用,并且仅调用 Read(char[] buffer, int index, int count) 方法,因此它的工作方式如下。您可能需要实现其他方法才能使其适用于您的应用程序。我使用这种方法是因为它似乎比其他答案更有效。我也只实现了一个构造函数,您显然可以实现您需要的任何 StreamReader 构造函数,因为它只是一个传递。
我选择替换字符而不是删除它们,因为它大大简化了解决方案。通过这种方式,文本的长度保持不变,因此无需跟踪单独的索引。
public class InvalidXmlCharacterReplacingStreamReader : TextReader
{
private StreamReader implementingStreamReader;
private char replacementCharacter;
public InvalidXmlCharacterReplacingStreamReader(Stream stream, char replacementCharacter)
{
implementingStreamReader = new StreamReader(stream);
this.replacementCharacter = replacementCharacter;
}
public override void Close()
{
implementingStreamReader.Close();
}
public override ObjRef CreateObjRef(Type requestedType)
{
return implementingStreamReader.CreateObjRef(requestedType);
}
public void Dispose()
{
implementingStreamReader.Dispose();
}
public override bool Equals(object obj)
{
return implementingStreamReader.Equals(obj);
}
public override int GetHashCode()
{
return implementingStreamReader.GetHashCode();
}
public override object InitializeLifetimeService()
{
return implementingStreamReader.InitializeLifetimeService();
}
public override int Peek()
{
int ch = implementingStreamReader.Peek();
if (ch != -1)
{
if (
(ch < 0x0020 || ch > 0xD7FF) &&
(ch < 0xE000 || ch > 0xFFFD) &&
ch != 0x0009 &&
ch != 0x000A &&
ch != 0x000D
)
{
return replacementCharacter;
}
}
return ch;
}
public override int Read()
{
int ch = implementingStreamReader.Read();
if (ch != -1)
{
if (
(ch < 0x0020 || ch > 0xD7FF) &&
(ch < 0xE000 || ch > 0xFFFD) &&
ch != 0x0009 &&
ch != 0x000A &&
ch != 0x000D
)
{
return replacementCharacter;
}
}
return ch;
}
public override int Read(char[] buffer, int index, int count)
{
int readCount = implementingStreamReader.Read(buffer, index, count);
for (int i = index; i < readCount+index; i++)
{
char ch = buffer[i];
if (
(ch < 0x0020 || ch > 0xD7FF) &&
(ch < 0xE000 || ch > 0xFFFD) &&
ch != 0x0009 &&
ch != 0x000A &&
ch != 0x000D
)
{
buffer[i] = replacementCharacter;
}
}
return readCount;
}
public override Task<int> ReadAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
public override int ReadBlock(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
public override Task<int> ReadBlockAsync(char[] buffer, int index, int count)
{
throw new NotImplementedException();
}
public override string ReadLine()
{
throw new NotImplementedException();
}
public override Task<string> ReadLineAsync()
{
throw new NotImplementedException();
}
public override string ReadToEnd()
{
throw new NotImplementedException();
}
public override Task<string> ReadToEndAsync()
{
throw new NotImplementedException();
}
public override string ToString()
{
return implementingStreamReader.ToString();
}
}
基于正则表达式的方法
public static string StripInvalidXmlCharacters(string str)
{
var invalidXmlCharactersRegex = new Regex("[^\u0009\u000a\u000d\u0020-\ud7ff\ue000-\ufffd]|([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])");
return invalidXmlCharactersRegex.Replace(str, "");
}
看我的 博客文章 更多细节
上述解决方案似乎是为了在转换为 XML 之前删除无效字符。
使用此代码从 XML 字符串中删除无效的 XML 字符。例如。&x1A;
public static string CleanInvalidXmlChars( string Xml, string XMLVersion )
{
string pattern = String.Empty;
switch( XMLVersion )
{
case "1.0":
pattern = @"&#x((10?|[2-F])FFF[EF]|FDD[0-9A-F]|7F|8[0-46-9A-F]9[0-9A-F]);";
break;
case "1.1":
pattern = @"&#x((10?|[2-F])FFF[EF]|FDD[0-9A-F]|[19][0-9A-F]|7F|8[0-46-9A-F]|0?[1-8BCEF]);";
break;
default:
throw new Exception( "Error: Invalid XML Version!" );
}
Regex regex = new Regex( pattern, RegexOptions.IgnoreCase );
if( regex.IsMatch( Xml ) )
Xml = regex.Replace( Xml, String.Empty );
return Xml;
}
http://balajiramesh.wordpress.com/2008/05/30/strip-illegal-xml-characters-based-on-w3c-standard/
修改后的答案或原始答案 上面的新石器.
变化:如果传递了 \0 字符,则完成删除,而不是替换。另外,使用了 XmlConvert.IsXmlChar(char) 方法
/// <summary>
/// Replaces invalid Xml characters from input file, NOTE: if replacement character is \0, then invalid Xml character is removed, instead of 1-for-1 replacement
/// </summary>
public class InvalidXmlCharacterReplacingStreamReader : StreamReader
{
private readonly char _replacementCharacter;
public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter)
: base(fileName)
{
_replacementCharacter = replacementCharacter;
}
public override int Peek()
{
int ch = base.Peek();
if (ch != -1 && IsInvalidChar(ch))
{
if ('\0' == _replacementCharacter)
return Peek(); // peek at the next one
return _replacementCharacter;
}
return ch;
}
public override int Read()
{
int ch = base.Read();
if (ch != -1 && IsInvalidChar(ch))
{
if ('\0' == _replacementCharacter)
return Read(); // read next one
return _replacementCharacter;
}
return ch;
}
public override int Read(char[] buffer, int index, int count)
{
int readCount= 0, ch;
for (int i = 0; i < count && (ch = Read()) != -1; i++)
{
readCount++;
buffer[index + i] = (char)ch;
}
return readCount;
}
private static bool IsInvalidChar(int ch)
{
return !XmlConvert.IsXmlChar((char)ch);
}
}
使用此函数删除无效的 xml 字符。
public static string CleanInvalidXmlChars(string text)
{
string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]";
return Regex.Replace(text, re, "");
}
我创建 略有更新的版本 的 @Neolisk 的回答, ,它支持 *Async
功能并使用.Net 4.0 XmlConvert.IsXmlChar
功能。
public class InvalidXmlCharacterReplacingStreamReader : StreamReader
{
private readonly char _replacementCharacter;
public InvalidXmlCharacterReplacingStreamReader(string fileName, char replacementCharacter) : base(fileName)
{
_replacementCharacter = replacementCharacter;
}
public InvalidXmlCharacterReplacingStreamReader(Stream stream, char replacementCharacter) : base(stream)
{
_replacementCharacter = replacementCharacter;
}
public override int Peek()
{
var ch = base.Peek();
if (ch != -1 && IsInvalidChar(ch))
{
return _replacementCharacter;
}
return ch;
}
public override int Read()
{
var ch = base.Read();
if (ch != -1 && IsInvalidChar(ch))
{
return _replacementCharacter;
}
return ch;
}
public override int Read(char[] buffer, int index, int count)
{
var readCount = base.Read(buffer, index, count);
ReplaceInBuffer(buffer, index, readCount);
return readCount;
}
public override async Task<int> ReadAsync(char[] buffer, int index, int count)
{
var readCount = await base.ReadAsync(buffer, index, count).ConfigureAwait(false);
ReplaceInBuffer(buffer, index, readCount);
return readCount;
}
private void ReplaceInBuffer(char[] buffer, int index, int readCount)
{
for (var i = index; i < readCount + index; i++)
{
var ch = buffer[i];
if (IsInvalidChar(ch))
{
buffer[i] = _replacementCharacter;
}
}
}
private static bool IsInvalidChar(int ch)
{
return IsInvalidChar((char)ch);
}
private static bool IsInvalidChar(char ch)
{
return !XmlConvert.IsXmlChar(ch);
}
}
private static String removeNonUtf8CompliantCharacters( final String inString ) {
if (null == inString ) return null;
byte[] byteArr = inString.getBytes();
for ( int i=0; i < byteArr.length; i++ ) {
byte ch= byteArr[i];
// remove any characters outside the valid UTF-8 range as well as all control characters
// except tabs and new lines
if ( !( (ch > 31 && ch < 253 ) || ch == '\t' || ch == '\n' || ch == '\r') ) {
byteArr[i]=' ';
}
}
return new String( byteArr );
}
您可以使用以下命令传递非 UTF 字符:
string sFinalString = "";
string hex = "";
foreach (char ch in UTFCHAR)
{
int tmp = ch;
if ((ch < 0x00FD && ch > 0x001F) || ch == '\t' || ch == '\n' || ch == '\r')
{
sFinalString += ch;
}
else
{
sFinalString += "&#" + tmp+";";
}
}
尝试一下 PHP!
$goodUTF8 = iconv("utf-8", "utf-8//IGNORE", $badUTF8);