programing tip

XML 용 텍스트 데이터를 인코딩하는 가장 좋은 방법

itbloger 2020. 11. 10. 07:53
반응형

XML 용 텍스트 데이터를 인코딩하는 가장 좋은 방법


Xml 요소 또는 특성에 사용할 문자열을 인코딩하기 위해 .Net에서 일반 메서드를 찾고 있었는데 즉시 찾지 못했을 때 놀랐습니다. 그래서, 더 나아 가기 전에 내장 기능을 놓칠 수 있을까요?

잠시 동안 그것이 실제로 존재하지 않는다고 가정하고, 나는 내 자신의 일반적인 EncodeForXml(string data)방법 을 모으고 있으며 이것을 수행하는 가장 좋은 방법에 대해 생각하고 있습니다.

내가 사용하고있는 데이터에는 &, <, "등과 같은 잘못된 문자가 포함될 수 있습니다. 때때로 적절하게 이스케이프 된 엔티티 인 & amp ;, & lt; 및 & quot;를 포함 할 수 있습니다. CDATA 섹션은 최선의 아이디어가 아닐 수 있습니다. 어쨌든 다소 엉망인 것 같습니다 .xml에서 직접 사용할 수있는 멋진 문자열 값으로 끝나는 것이 좋습니다.

나는 과거에 정규식을 사용하여 잘못된 앰퍼샌드를 잡았으며,이 경우 첫 번째 단계뿐만 아니라이를 잡기 위해 정규식을 사용하고 다른 문자에 대한 간단한 교체를 수행 할 생각입니다.

그래서, 이것을 너무 복잡하게 만들지 않고 더 최적화 할 수 있습니까? 그리고 내가 놓친 것이 있습니까? :

Function EncodeForXml(ByVal data As String) As String
    Static badAmpersand As new Regex("&(?![a-zA-Z]{2,6};|#[0-9]{2,4};)")

    data = badAmpersand.Replace(data, "&amp;")

    return data.Replace("<", "&lt;").Replace("""", "&quot;").Replace(">", "gt;")
End Function

모든 C # 전용 사용자에게 죄송합니다. 어떤 언어를 사용하는지는 신경 쓰지 않지만 Regex를 정적으로 만들고 싶었고 메서드 외부에서 선언하지 않고는 C #에서 할 수 없으므로 VB가 될 것입니다. .그물

마지막으로, 우리는 여전히 .Net 2.0을 사용하고 있지만 누군가가 최종 제품을 가져 와서 문자열 클래스의 확장 메서드로 바꿀 수 있다면 매우 멋질 것입니다.

업데이트 처음 몇 개의 응답은 .Net이 실제로이를 수행하는 기본 제공 방법을 가지고 있음을 나타냅니다. 하지만 이제 시작 했으므로 EncodeForXml () 메서드를 재미로 끝내고 싶으므로 여전히 개선 할 아이디어를 찾고 있습니다. 특히, 엔티티로 인코딩되어야하는 문자 목록 (아마도 목록 / 맵에 저장 됨)과 불변 문자열에서 직렬로 .Replace ()를 수행하는 것보다 더 나은 성능을 제공하는 것입니다.


System.XML이 인코딩을 처리하므로 이와 같은 메서드가 필요하지 않습니다.


입력에 대해 얼마나 알고 있는지에 따라 모든 유니 코드 문자가 유효한 XML 문자가 아니라는 점을 고려해야 할 수 있습니다 .

Server.HtmlEncodeSystem.Security.SecurityElement.Escape모두 잘못된 XML 문자를 무시하는 것처럼 보이지만 System.XML.XmlWriter.WriteString잘못된 문자를 발견 하면 ArgumentException을 throw합니다 (이 검사를 사용하지 않는 경우 무시하지 않는 한). 여기 에서 라이브러리 기능의 개요를 볼 수 있습니다 .

2011/8/14 편집 : 지난 몇 년 동안 적어도 몇 명의 사람들 이이 답변을 참조 한 것을 보았을 때 UTF-16을 끔찍하게 잘못 처리하는 것을 포함하여 수많은 문제가 있었던 원본 코드를 완전히 다시 작성하기로 결정했습니다 .

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

/// <summary>
/// Encodes data so that it can be safely embedded as text in XML documents.
/// </summary>
public class XmlTextEncoder : TextReader {
    public static string Encode(string s) {
        using (var stream = new StringReader(s))
        using (var encoder = new XmlTextEncoder(stream)) {
            return encoder.ReadToEnd();
        }
    }

    /// <param name="source">The data to be encoded in UTF-16 format.</param>
    /// <param name="filterIllegalChars">It is illegal to encode certain
    /// characters in XML. If true, silently omit these characters from the
    /// output; if false, throw an error when encountered.</param>
    public XmlTextEncoder(TextReader source, bool filterIllegalChars=true) {
        _source = source;
        _filterIllegalChars = filterIllegalChars;
    }

    readonly Queue<char> _buf = new Queue<char>();
    readonly bool _filterIllegalChars;
    readonly TextReader _source;

    public override int Peek() {
        PopulateBuffer();
        if (_buf.Count == 0) return -1;
        return _buf.Peek();
    }

    public override int Read() {
        PopulateBuffer();
        if (_buf.Count == 0) return -1;
        return _buf.Dequeue();
    }

    void PopulateBuffer() {
        const int endSentinel = -1;
        while (_buf.Count == 0 && _source.Peek() != endSentinel) {
            // Strings in .NET are assumed to be UTF-16 encoded [1].
            var c = (char) _source.Read();
            if (Entities.ContainsKey(c)) {
                // Encode all entities defined in the XML spec [2].
                foreach (var i in Entities[c]) _buf.Enqueue(i);
            } else if (!(0x0 <= c && c <= 0x8) &&
                       !new[] { 0xB, 0xC }.Contains(c) &&
                       !(0xE <= c && c <= 0x1F) &&
                       !(0x7F <= c && c <= 0x84) &&
                       !(0x86 <= c && c <= 0x9F) &&
                       !(0xD800 <= c && c <= 0xDFFF) &&
                       !new[] { 0xFFFE, 0xFFFF }.Contains(c)) {
                // Allow if the Unicode codepoint is legal in XML [3].
                _buf.Enqueue(c);
            } else if (char.IsHighSurrogate(c) &&
                       _source.Peek() != endSentinel &&
                       char.IsLowSurrogate((char) _source.Peek())) {
                // Allow well-formed surrogate pairs [1].
                _buf.Enqueue(c);
                _buf.Enqueue((char) _source.Read());
            } else if (!_filterIllegalChars) {
                // Note that we cannot encode illegal characters as entity
                // references due to the "Legal Character" constraint of
                // XML [4]. Nor are they allowed in CDATA sections [5].
                throw new ArgumentException(
                    String.Format("Illegal character: '{0:X}'", (int) c));
            }
        }
    }

    static readonly Dictionary<char,string> Entities =
        new Dictionary<char,string> {
            { '"', "&quot;" }, { '&', "&amp;"}, { '\'', "&apos;" },
            { '<', "&lt;" }, { '>', "&gt;" },
        };

    // References:
    // [1] http://en.wikipedia.org/wiki/UTF-16/UCS-2
    // [2] http://www.w3.org/TR/xml11/#sec-predefined-ent
    // [3] http://www.w3.org/TR/xml11/#charsets
    // [4] http://www.w3.org/TR/xml11/#sec-references
    // [5] http://www.w3.org/TR/xml11/#sec-cdata-sect
}

단위 테스트 및 전체 코드는 여기 에서 찾을 수 있습니다 .


SecurityElement.Escape

여기에 문서화


In the past I have used HttpUtility.HtmlEncode to encode text for xml. It performs the same task, really. I havent ran into any issues with it yet, but that's not to say I won't in the future. As the name implies, it was made for HTML, not XML.

You've probably already read it, but here is an article on xml encoding and decoding.

EDIT: Of course, if you use an xmlwriter or one of the new XElement classes, this encoding is done for you. In fact, you could just take the text, place it in a new XElement instance, then return the string (.tostring) version of the element. I've heard that SecurityElement.Escape will perform the same task as your utility method as well, but havent read much about it or used it.

EDIT2: Disregard my comment about XElement, since you're still on 2.0


Microsoft's AntiXss library AntiXssEncoder Class in System.Web.dll has methods for this:

AntiXss.XmlEncode(string s)
AntiXss.XmlAttributeEncode(string s)

it has HTML as well:

AntiXss.HtmlEncode(string s)
AntiXss.HtmlAttributeEncode(string s)

In .net 3.5+

new XText("I <want> to & encode this for XML").ToString();

Gives you:

I &lt;want&gt; to &amp; encode this for XML

Turns out that this method doesn't encode some things that it should (like quotes).

SecurityElement.Escape (workmad3's answer) seems to do a better job with this and it's included in earlier versions of .net.

If you don't mind 3rd party code and want to ensure no illegal characters make it into your XML, I would recommend Michael Kropat's answer.


XmlTextWriter.WriteString() does the escaping.


If this is an ASP.NET app why not use Server.HtmlEncode() ?


This might be the case where you could benefit from using the WriteCData method.

public override void WriteCData(string text)
    Member of System.Xml.XmlTextWriter

Summary:
Writes out a <![CDATA[...]]> block containing the specified text.

Parameters:
text: Text to place inside the CDATA block.

A simple example would look like the following:

writer.WriteStartElement("name");
writer.WriteCData("<unsafe characters>");
writer.WriteFullEndElement();

The result looks like:

<name><![CDATA[<unsafe characters>]]></name>

When reading the node values the XMLReader automatically strips out the CData part of the innertext so you don't have to worry about it. The only catch is that you have to store the data as an innerText value to an XML node. In other words, you can't insert CData content into an attribute value.


Brilliant! That's all I can say.

Here is a VB variant of the updated code (not in a class, just a function) that will clean up and also sanitize the xml

Function cXML(ByVal _buf As String) As String
    Dim textOut As New StringBuilder
    Dim c As Char
    If _buf.Trim Is Nothing OrElse _buf = String.Empty Then Return String.Empty
    For i As Integer = 0 To _buf.Length - 1
        c = _buf(i)
        If Entities.ContainsKey(c) Then
            textOut.Append(Entities.Item(c))
        ElseIf (AscW(c) = &H9 OrElse AscW(c) = &HA OrElse AscW(c) = &HD) OrElse ((AscW(c) >= &H20) AndAlso (AscW(c) <= &HD7FF)) _
            OrElse ((AscW(c) >= &HE000) AndAlso (AscW(c) <= &HFFFD)) OrElse ((AscW(c) >= &H10000) AndAlso (AscW(c) <= &H10FFFF)) Then
            textOut.Append(c)
        End If
    Next
    Return textOut.ToString

End Function

Shared ReadOnly Entities As New Dictionary(Of Char, String)() From {{""""c, "&quot;"}, {"&"c, "&amp;"}, {"'"c, "&apos;"}, {"<"c, "&lt;"}, {">"c, "&gt;"}}

You can use the built-in class XAttribute, which handles the encoding automatically:

using System.Xml.Linq;

XDocument doc = new XDocument();

List<XAttribute> attributes = new List<XAttribute>();
attributes.Add(new XAttribute("key1", "val1&val11"));
attributes.Add(new XAttribute("key2", "val2"));

XElement elem = new XElement("test", attributes.ToArray());

doc.Add(elem);

string xmlStr = doc.ToString();

Here is a single line solution using the XElements. I use it in a very small tool. I don't need it a second time so I keep it this way. (Its dirdy doug)

StrVal = (<x a=<%= StrVal %>>END</x>).ToString().Replace("<x a=""", "").Replace(">END</x>", "")

Oh and it only works in VB not in C#


If you're serious about handling all of the invalid characters (not just the few "html" ones), and you have access to System.Xml, here's the simplest way to do proper Xml encoding of value data:

string theTextToEscape = "Something \x1d else \x1D <script>alert('123');</script>";
var x = new XmlDocument();
x.LoadXml("<r/>"); // simple, empty root element
x.DocumentElement.InnerText = theTextToEscape; // put in raw string
string escapedText = x.DocumentElement.InnerXml; // Returns:  Something &#x1D; else &#x1D; &lt;script&gt;alert('123');&lt;/script&gt;

// Repeat the last 2 lines to escape additional strings.

It's important to know that XmlConvert.EncodeName() is not appropriate, because that's for entity/tag names, not values. Using that would be like Url-encoding when you needed to Html-encode.

참고URL : https://stackoverflow.com/questions/157646/best-way-to-encode-text-data-for-xml

반응형