데이터를 사용하는 XmlReader 또는 XPathDocument를 구성하기 전에 XML 기반 데이터 소스에서 잘못된 16진수 문자를 어떻게 제거합니까?
-
09-06-2019 - |
문제
XML에 적용된 16진수 문자 제한을 따르지 않는 XML 데이터를 정상적으로 사용할 수 있도록 XmlReader에서 사용하기 전에 XML 기반 데이터 소스를 정리하는 쉽고 일반적인 방법이 있습니까?
메모:
- 솔루션은 UTF-8 이외의 문자 인코딩을 사용하는 XML 데이터 소스를 처리해야합니다.XML 문서 선언에서 인코딩을 지정함으로써.유효하지 않은 16 진 문자를 벗기면서 소스의 캐릭터 인코딩을 망가지 않는 것이 주요 고집 지점이었습니다.
- 잘못된 16진수 문자를 제거하면 16진수로 인코딩된 값만 제거되어야 합니다. 데이터에서 16진수 문자와 일치하는 문자열이 포함된 href 값을 자주 찾을 수 있기 때문입니다.
배경:
특정 형식(Atom 또는 RSS 피드 등)을 준수하는 XML 기반 데이터 소스를 사용해야 하지만 XML 사양에 따라 잘못된 16진수 문자가 포함된 게시된 데이터 소스를 사용할 수 있기를 원합니다.
.NET에서 XML 데이터 소스를 나타내는 Stream이 있고 XmlReader 및/또는 XPathDocument를 사용하여 이를 구문 분석하려고 하면 XML 데이터에 잘못된 16진수 문자가 포함되어 예외가 발생합니다.이 문제를 해결하기 위한 현재 시도는 Stream을 문자열로 구문 분석하고 정규식을 사용하여 잘못된 16진수 문자를 제거 및/또는 바꾸는 것이지만 더 성능이 좋은 솔루션을 찾고 있습니다.
해결책
그것 완벽하지 않을 수도 있다 (사람들이 이 면책 조항을 놓쳤기 때문에 강조가 추가되었습니다.) 그러나 이 경우 제가 한 일은 아래와 같습니다.스트림과 함께 사용하도록 조정할 수 있습니다.
/// <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();
}
다른 팁
나는 유진의 화이트리스트 개념을 좋아한다.원본 포스터와 비슷한 작업을 수행해야 했지만 최대 0x00FD뿐만 아니라 모든 유니코드 문자를 지원해야 했습니다.XML 사양은 다음과 같습니다
char = #x9 | #xa | #xd | [#x20-#xd7ff] | [#xe000-#xfffd] | [#x10000-#x10ffff
.NET에서 유니코드 문자의 내부 표현은 16비트에 불과하므로 0x10000-0x10FFFF를 명시적으로 '허용'할 수 없습니다.명시적으로 XML 사양 불허하다 0xD800에서 시작하는 대리 코드 포인트가 표시되지 않습니다.그러나 화이트리스트에서 이러한 대리 코드 포인트를 허용하면 문자열의 utf-8 인코딩이 utf-16 문자의 대리 쌍에서 적절한 utf-8 인코딩이 생성되는 한 결국 유효한 XML을 생성할 수 있습니다. .NET 문자열.하지만 나는 이것을 탐구하지 않았기 때문에 더 안전한 내기를 선택했고 내 화이트리스트에 대리자를 허용하지 않았습니다.
Eugene 솔루션의 설명은 오해의 소지가 있지만 문제는 우리가 제외하는 문자가 유효하지 않다는 것입니다. XML ...이는 완벽하게 유효한 유니코드 코드 포인트입니다.우리는 `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;
}
}
현대화 dnewcombe의 대답하자면, 좀 더 간단한 접근 방식을 취할 수 있습니다
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);