Ucieczka nieprawidłowych znaków XML w C #

84

Mam ciąg zawierający nieprawidłowe znaki XML. Jak mogę zmienić znaczenie (lub usunąć) nieprawidłowe znaki XML przed przeanalizowaniem ciągu?

Alireza Noori
źródło
2
Czy mógłbyś podać więcej kontekstu? Przykładowe dane wejściowe i przykładowe oczekiwane dane wyjściowe. Również co zamierzasz zrobić z wynikiem.
Darin Dimitrov
5
Piszesz XML? A może próbujesz odczytać XML, który w rzeczywistości nie jest XMLem?
Marc Gravell
3
Użyj XmlWriter, pozwoli ci uniknąć nieprawidłowych znaków
Thomas Levesque
2
@alireza, uzyskasz bardziej przydatne odpowiedzi, jeśli odpowiesz na pytania, które zadają Ci ludzie (aby uzyskać więcej informacji) tutaj w komentarzach ...
Marc Gravell
Przepraszam. Nie było mnie kilka godzin. Przeczytaj pytanie, które doprowadziło do tego: stackoverflow.com/questions/8330619/… Otrzymasz tam wszystkie potrzebne informacje
Alireza Noori

Odpowiedzi:

113

Jako sposób na usunięcie nieprawidłowych znaków XML sugeruję użycie metody XmlConvert.IsXmlChar . Został dodany od .NET Framework 4 i jest również prezentowany w Silverlight. Oto mała próbka:

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) {
    var 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;
    }
}

A jako sposób na uniknięcie nieprawidłowych znaków XML sugeruję użycie metody XmlConvert.EncodeName . Oto mała próbka:

void Main() {
    const string content = "\v\f\0";
    Console.WriteLine(IsValidXmlString(content)); // False

    string encoded = XmlConvert.EncodeName(content);
    Console.WriteLine(IsValidXmlString(encoded)); // True

    string decoded = XmlConvert.DecodeName(encoded);
    Console.WriteLine(content == decoded); // True
}

static bool IsValidXmlString(string text) {
    try {
        XmlConvert.VerifyXmlChars(text);
        return true;
    } catch {
        return false;
    }
}

Aktualizacja: Należy wspomnieć, że operacja kodowania tworzy ciąg o długości większej lub równej długości łańcucha źródłowego. Może to być ważne, gdy przechowujesz zakodowany ciąg w bazie danych w kolumnie ciągów z ograniczeniem długości i sprawdzasz poprawność długości ciągu źródłowego w aplikacji w celu dopasowania do ograniczenia kolumny danych.

Igor Kustov
źródło
XmlConvert.VerifyXmlCharsnie zgłasza wyjątku, jeśli argument zawiera nieprawidłowe znaki, zwraca ciąg pusty (i zwraca argument, jeśli wszystkie zawarte znaki są prawidłowe). Spróbuj po prostu return XmlConvert.VerifyXmlChars (text) != null.
Matt Enright,
3
@IgorKustov My bad! Dokumentacja dotycząca wartości zwrotu wydaje się temu zaprzeczać, dzięki za złapanie mnie.
Matt Enright,
3
Uważaj, aby nie używać XmlConvert.EncodeName, jeśli ciąg jest przeznaczony dla wartości XML. Ograniczenia dotyczące nazw XML są bardziej rygorystyczne niż ograniczenia wartości XML, a kodowanie nazwy doprowadzi do niepotrzebnych nieoczekiwanych znaków ucieczki.
David Burg
1
@arik mój kod służy tylko do celów demonstracyjnych, aby pokazać stan ciągu XML przed i po transformacji. Oczywiście w swoim kodzie nie musisz go sprawdzać.
Igor Kustov
67

Użyj SecurityElement.Escape

using System;
using System.Security;

class Sample {
  static void Main() {
    string text = "Escape characters : < > & \" \'";
    string xmlText = SecurityElement.Escape(text);
//output:
//Escape characters : &lt; &gt; &amp; &quot; &apos;
    Console.WriteLine(xmlText);
  }
}
BLUEPIXY
źródło
11
Nie powoduje to zmiany znaków sterujących (takich jak char 30).
zimdanen
19

Jeśli piszesz XML, po prostu użyj klas dostarczonych przez framework, aby utworzyć XML. Nie będziesz musiał zawracać sobie głowy ucieczką ani niczym.

Console.Write(new XElement("Data", "< > &"));

Wyjdzie

<Data>&lt; &gt; &amp;</Data>

Jeśli chcesz odczytać plik XML, który jest zniekształcony, nie używaj wyrażeń regularnych. Zamiast tego użyj pakietu Html Agility Pack .

Pierre-Alain Vigeant
źródło
Miły. Czy masz równoważną metodę dla kogoś używającego XmlElement?
djdanlib
3
Aktualizacja: ustawienie właściwości InnerText XmlElement wydaje się prawidłowo zmieniać znaczenie. Odpowiedziałem na moje pytanie, huzzah!
djdanlib
Więc twój xml jest źle utworzony? jak <Data>&</Data>?
Pierre-Alain Vigeant
2
Tak, w tym właśnie tkwi problem.
Alireza Noori
2
Nadal możesz mieć problemy, jeśli zawartość twoich elementów zawiera nieprawidłowe znaki, takie jak backspace (0x08), wiele innych znaków kontrolnych lub zastępczych punktów kodowych.
jakubiszon
6

Metoda RemoveInvalidXmlChars dostarczona przez Irishman nie obsługuje znaków zastępczych. Aby to przetestować, użyj następującego przykładu:

static void Main()
{
    const string content = "\v\U00010330";

    string newContent = RemoveInvalidXmlChars(content);

    Console.WriteLine(newContent);
}

To zwraca pusty ciąg, ale nie powinno! Powinien zwrócić „\ U00010330”, ponieważ znak U + 10330 jest prawidłowym znakiem XML.

Aby wspierać znaki zastępcze, sugeruję użycie następującej metody:

public static string RemoveInvalidXmlChars(string text)
{
    if (string.IsNullOrEmpty(text))
        return text;

    int length = text.Length;
    StringBuilder stringBuilder = new StringBuilder(length);

    for (int i = 0; i < length; ++i)
    {
        if (XmlConvert.IsXmlChar(text[i]))
        {
            stringBuilder.Append(text[i]);
        }
        else if (i + 1 < length && XmlConvert.IsXmlSurrogatePair(text[i + 1], text[i]))
        {
            stringBuilder.Append(text[i]);
            stringBuilder.Append(text[i + 1]);
            ++i;
        }
    }

    return stringBuilder.ToString();
}
Francois C
źródło
4

Oto zoptymalizowana wersja powyższej metody RemoveInvalidXmlChars, która nie tworzy nowej tablicy przy każdym wywołaniu, niepotrzebnie obciążając w ten sposób GC:

public static string RemoveInvalidXmlChars(string text)
{
    if (text == null)
        return text;
    if (text.Length == 0)
        return text;

    // a bit complicated, but avoids memory usage if not necessary
    StringBuilder result = null;
    for (int i = 0; i < text.Length; i++)
    {
        var ch = text[i];
        if (XmlConvert.IsXmlChar(ch))
        {
            result?.Append(ch);
        }
        else if (result == null)
        {
            result = new StringBuilder();
            result.Append(text.Substring(0, i));
        }
    }

    if (result == null)
        return text; // no invalid xml chars detected - return original text
    else
        return result.ToString();

}
Urs Meili
źródło
Jaka jest ta ?.składnia? w kolejce result?.Append(ch);?
JB. Z Monicą.
1
?.jest Null-Conditional Operator. docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Pure.Krome
1
// Replace invalid characters with empty strings.
   Regex.Replace(inputString, @"[^\w\.@-]", ""); 

Wzorzec wyrażenia regularnego [^ \ w. @ -] dopasowuje dowolny znak, który nie jest znakiem słowa, kropką, symbolem @ ani łącznikiem. Znak słowa to dowolna litera, cyfra dziesiętna lub łącznik interpunkcyjny, taki jak podkreślenie. Każdy znak, który pasuje do tego wzorca, jest zastępowany przez String.Empty, czyli ciąg zdefiniowany przez wzorzec zastępujący. Aby umożliwić wprowadzanie dodatkowych znaków przez użytkownika, dodaj te znaki do klasy znaków we wzorcu wyrażenia regularnego. Na przykład wzorzec wyrażenia regularnego [^ \ w. @ - \%] dopuszcza również symbol procentu i ukośnik odwrotny w ciągu wejściowym.

Regex.Replace(inputString, @"[!@#$%_]", "");

Odnieś się też do tego:

Usuwanie nieprawidłowych znaków z tagu nazwy XML - RegEx C #

Oto funkcja służąca do usuwania znaków z określonego ciągu XML:

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace XMLUtils
{
    class Standards
    {
        /// <summary>
        /// Strips non-printable ascii characters 
        /// Refer to http://www.w3.org/TR/xml11/#charsets for XML 1.1
        /// Refer to http://www.w3.org/TR/2006/REC-xml-20060816/#charsets for XML 1.0
        /// </summary>
        /// <param name="content">contents</param>
        /// <param name="XMLVersion">XML Specification to use. Can be 1.0 or 1.1</param>
        private void StripIllegalXMLChars(string tmpContents, 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(tmpContents))
            {
                tmpContents = regex.Replace(tmpContents, String.Empty);
            }
            tmpContents = string.Empty;
        }
    }
}
Siva Charan
źródło
0
string XMLWriteStringWithoutIllegalCharacters(string UnfilteredString)
{
    if (UnfilteredString == null)
        return string.Empty;

    return XmlConvert.EncodeName(UnfilteredString);
}

string XMLReadStringWithoutIllegalCharacters(string FilteredString)
{
    if (UnfilteredString == null)
        return string.Empty;

    return XmlConvert.DecodeName(UnfilteredString);
}

Ta prosta metoda zastępuje nieprawidłowe znaki tą samą wartością, ale akceptowaną w kontekście XML.


Aby napisać ciąg, użyj XMLWriteStringWithoutIllegalCharacters (string UnfilteredString).
Aby odczytać ciąg, użyj XMLReadStringWithoutIllegalCharacters (string FilteredString).

Marco Concas
źródło