데이터를 사용하는 XmlReader 또는 XPathDocument를 생성하기 전에 XML 기반 데이터 원본에서 잘못된 16 진수 문자를 어떻게 제거합니까?
XML에 적용된 16 진수 문자 제한을 준수하지 않는 XML 데이터를 정상적으로 사용할 수 있도록 XmlReader에서 사용하기 전에 XML 기반 데이터 원본을 정리하는 쉽고 일반적인 방법이 있습니까?
노트 :
- 솔루션은 예를 들어 XML 문서 선언에서 문자 인코딩을 지정하여 UTF-8 이외의 문자 인코딩을 사용하는 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();
}
나는 Eugene의 화이트리스트 개념을 좋아합니다. 원본 포스터와 비슷한 작업을해야했지만 0x00FD까지가 아닌 모든 유니 코드 문자를 지원해야했습니다. XML 사양은 다음과 같습니다.
문자 = # x9 | #xA | #xD | [# x20- # xD7FF] | [# xE000- # xFFFD] | [# x10000- # x10FFFF]
.NET에서 유니 코드 문자의 내부 표현은 16 비트에 불과하므로 0x10000-0x10FFFF를 명시 적으로 '허용'할 수 없습니다. XML 사양 은 0xD800에서 시작하는 대리 코드 포인트가 나타나지 않도록 명시 적으로 허용하지 않습니다 . 그러나 화이트리스트에서 이러한 대리 코드 포인트를 허용하면 문자열의 utf-16 문자 대리 쌍에서 적절한 utf-8 인코딩이 생성되는 한 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());
}
이 방법의 성능이 어떻게 비교되는지, 그리고 .NET을 사용하는 블랙리스트 접근 방식과 어떻게 비교되는지 알고 싶습니다 Buffer.BlockCopy
.
다음은 사용자 정의 StreamReader의 dnewcome 의 답변입니다. 단순히 실제 스트림 리더를 래핑하고 읽을 때 문자를 대체합니다.
시간을 절약하기 위해 몇 가지 방법 만 구현했습니다. 나는 이것을 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/
위의 Neolisk에 의해 수정 된 답변 또는 원래 답변 .
변경 사항 : \ 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, "");
}
기능 을 지원 하고 .Net 4.0 기능을 사용하는 @Neolisk의 답변 의 약간 업데이트 된 버전 을 만들었습니다 .*Async
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);
'Development Tip' 카테고리의 다른 글
Git : 수동으로 삭제 한 파일을 커밋하는 방법? (0) | 2020.10.20 |
---|---|
string_agg ()에서 결과를 정렬하는 방법 (0) | 2020.10.20 |
동적으로 할당 된 어레이의 이상적인 성장률은 얼마입니까? (0) | 2020.10.20 |
Java에서 Duration을 어떻게 "예쁜 인쇄"할 수 있습니까? (0) | 2020.10.20 |
HTML에서 href 대신 JavaScript 함수를 호출하는 방법 (0) | 2020.10.20 |