Wie serialisiert man eine Zeichenfolge mit XmlSerializer als CDATA?

90

Ist es über ein Attribut möglich, eine Zeichenfolge mit dem .Net XmlSerializer als CDATA zu serialisieren?

Jamesaharvey
quelle
2
Eine erwähnenswerte Sache bei den beiden Antworten ist, dass Sie sie nicht benötigen, CDataContentwenn Sie nur XML lesen. XmlSerializer.Deserializeverwandelt es automatisch in Text für Sie.
Chris S

Antworten:

62
[XmlRoot("root")]
public class Sample1Xml
{
    internal Sample1Xml()
    {
    }

    [XmlElement("node")]
    public NodeType Node { get; set; }

    #region Nested type: NodeType

    public class NodeType
    {
        [XmlAttribute("attr1")]
        public string Attr1 { get; set; }

        [XmlAttribute("attr2")]
        public string Attr2 { get; set; }

        [XmlIgnore]
        public string Content { get; set; }

        [XmlText]
        public XmlNode[] CDataContent
        {
            get
            {
                var dummy = new XmlDocument();
                return new XmlNode[] {dummy.CreateCDataSection(Content)};
            }
            set
            {
                if (value == null)
                {
                    Content = null;
                    return;
                }

                if (value.Length != 1)
                {
                    throw new InvalidOperationException(
                        String.Format(
                            "Invalid array length {0}", value.Length));
                }

                Content = value[0].Value;
            }
        }
    }

    #endregion
}
John Saunders
quelle
8
Für mich scheint dies nicht die eleganteste Lösung zu sein. Ist dies der einzig mögliche Weg, dies zu tun?
Jamesaharvey
1
Ich denke, dies ist der einzige Weg, dies zu erreichen. Ich habe dieses Thema an anderer Stelle gesehen und immer die gleiche Antwort. Das Beispiel von Philip ist etwas sauberer, aber das gleiche Konzept. Die einzige andere Möglichkeit, die ich kenne, besteht darin, Ihre eigene <a href=" msdn.microsoft.com/en-us/library/…> in einer Klasse zu implementieren , die den CDATA-Inhalt darstellt.
csharptest.net
Ich wollte das Gleiche tun, weil es so aussieht, als würde man Strings speichern, da CDATA weniger Verarbeitungszeit zu bedeuten scheint, da wir damit "nur" String lesen / schreiben können ", wie er ist. Wie teuer ist die Beteiligung von XmlDocument / XmlCDataSection-Instanzen?
Tishma
Und die ganze Sache mit den Attributen ist da, damit wir die Domänenmodellklassen frei von Details der Serialisierungslogik halten können. Es ist sooo traurig, wenn der schmutzige Weg der einzige ist.
Tishma
2
Die Lösung von Philip etwas weiter unten auf der Seite ist ordentlicher.
Karl
99
[Serializable]
public class MyClass
{
    public MyClass() { }

    [XmlIgnore]
    public string MyString { get; set; }
    [XmlElement("MyString")]
    public System.Xml.XmlCDataSection MyStringCDATA
    {
        get
        {
            return new System.Xml.XmlDocument().CreateCDataSection(MyString);
        }
        set
        {
            MyString = value.Value;
        }
    }
}

Verwendung:

MyClass mc = new MyClass();
mc.MyString = "<test>Hello World</test>";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
StringWriter writer = new StringWriter();
serializer.Serialize(writer, mc);
Console.WriteLine(writer.ToString());

Ausgabe:

<?xml version="1.0" encoding="utf-16"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <MyString><![CDATA[<test>Hello World</test>]]></MyString>
</MyClass>
pr0gg3r
quelle
Das hat mir gerade den Tag gerettet. Danke dir.
Robert
4
// Falls Sie leere CDATA benötigen, können Sie den Standardwert festlegen, wenn der Quellwert null ist, um Ausnahmen zu vermeiden. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r erlaubt dies auch das Deserialisieren auf dasselbe Objekt? Ich habe Probleme damit
Martin
Wie erstelle ich CDATA als Textwert (und nicht als Element) wie <MyClass> <! [CDATA [<test> Hallo Welt </ Test>]]> </ MyClass>?
mko
1
Muss nur in der Lage sein, leere / Null-Werte zu verarbeiten, als <leeres Feld> <! [CDATA [] </ leeres Feld>
auszugeben
91

Zusätzlich zu der von John Saunders veröffentlichten Methode können Sie einen XmlCDataSection direkt als Typ verwenden, obwohl es sich fast um dasselbe handelt:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
quelle
1
@Philip, funktioniert das für die Deserialisierung? Ich habe Notizen gesehen, die besagen, dass der Setter einen XmlText-Wert erhält.
John Saunders
1
@ John Saunders - Während der Deserialisierung erhält es tatsächlich einen XmlCharacterData-Wert im Setter. Dafür ist der Aufruf von .Value im Setter vorgesehen (ich hatte ihn ursprünglich als ToString () aus dem Speicher, aber das war falsch.)
Philip Rieck
1
@PhilipRieck Was ist, wenn wir ein benutzerdefiniertes Objekt um einen CDataSection wickeln müssen? CDataSection erstellen akzeptiert Zeichenfolge.
Zeppelin
Danke dir! Einfachste Lösung. Funktioniert gut für mich.
Antonio Rodríguez
42

In der zu serialisierenden Klasse:

public CData Content { get; set; }

Und die CData-Klasse:

public class CData : IXmlSerializable
{
    private string _value;

    /// <summary>
    /// Allow direct assignment from string:
    /// CData cdata = "abc";
    /// </summary>
    /// <param name="value">The string being cast to CData.</param>
    /// <returns>A CData object</returns>
    public static implicit operator CData(string value)
    {
        return new CData(value);
    }

    /// <summary>
    /// Allow direct assignment to string:
    /// string str = cdata;
    /// </summary>
    /// <param name="cdata">The CData being cast to a string</param>
    /// <returns>A string representation of the CData object</returns>
    public static implicit operator string(CData cdata)
    {
        return cdata._value;
    }

    public CData() : this(string.Empty)
    {
    }

    public CData(string value)
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value;
    }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        _value = reader.ReadElementString();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteCData(_value);
    }
}
Sagis
quelle
Klappt wunderbar. Danke dir.
Leonel Sanches da Silva
3
Diese Antwort verdient mehr Anerkennung. Der angepasste CData-Typ verfügt jedoch nicht mehr über die praktischen integrierten Methoden, die der System.String-Typ bietet.
Lionet Chen
schön dann erste Antwort
Hsin-Yu Chen
Antwort funktioniert super. Es ist eine Schande, dass XmlElement nicht auf Zeichenfolgenfeld funktioniert, dann könnten Sie einfach einen CD-Datentyp hinzufügen, aber was auch immer ...
jjxtra
Perfekt! Vielen Dank!
Roy
4

In meinem Fall verwende ich gemischte Felder, einige CDATA, andere nicht, zumindest für mich funktioniert die folgende Lösung ....

Wenn ich immer das Feld Wert lese, erhalte ich den Inhalt, unabhängig davon, ob es sich um CDATA oder nur um einfachen Text handelt.

    [XmlElement("")]
    public XmlCDataSection CDataValue {
        get {
            return new XmlDocument().CreateCDataSection(this.Value);
        }
        set {
            this.Value = value.Value;
        }
    }

    [XmlText]
    public string Value;

Besser spät als nie.

Prost

Coderookie
quelle
Fantastisch - ich habe das Gefühl, diese Antwort hat mir ein bisschen Zeit gespart! Für Informationen habe ich das Attribut [XmlIgnore] für Value
d219
Wie unterscheidet sich dies operativ von der Antwort von pr0gg3r ?
Ruffin
4

Ich hatte einen ähnlichen Bedarf, benötigte aber ein anderes Ausgabeformat - ich wollte ein Attribut auf dem Knoten, der die CDATA enthält. Ich habe mich von den oben genannten Lösungen inspirieren lassen, um meine eigenen zu erstellen. Vielleicht hilft es jemandem in der Zukunft ...

public class EmbedScript
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlText]
    public XmlNode[] Script { get; set; }

    public EmbedScript(string type, string script)
    {
        Type = type;
        Script = new XmlNode[] { new XmlDocument().CreateCDataSection(script) };
    }

    public EmbedScript()
    {

    }
}

In dem zu serialisierenden übergeordneten Objekt habe ich die folgende Eigenschaft:

    [XmlArray("embedScripts")]
    [XmlArrayItem("embedScript")]
    public List<EmbedScript> EmbedScripts { get; set; }

Ich erhalte folgende Ausgabe:

<embedScripts>
    <embedScript type="Desktop Iframe">
        <![CDATA[<div id="play_game"><iframe height="100%" src="http://www.myurl.com" width="100%"></iframe></div>]]>
    </embedScript>
    <embedScript type="JavaScript">
        <![CDATA[]]>
    </embedScript>
</embedScripts>
Adam Hey
quelle
Ich musste genau das tun. Danke dir!!
Lews Therin
2

Diese Implementierung bietet die Möglichkeit, verschachtelte CDATA innerhalb der von Ihnen codierten Zeichenfolge zu verarbeiten (basierend auf der ursprünglichen Antwort von John Saunders).

Angenommen, Sie möchten die folgende Literalzeichenfolge in CDATA codieren:

I am purposefully putting some <![CDATA[ cdata markers right ]]> in here!!

Sie möchten, dass die resultierende Ausgabe ungefähr so ​​aussieht:

<![CDATA[I am purposefully putting some <![CDATA[ cdata markers right ]]]]><![CDATA[> in here!!]]>

Die folgende Implementierung wird Schleife über die Zeichenfolge, aufgeteilt Instanzen ...]]>...in ...]]und >...und separate CDATA - Abschnitte für jeden erstellen.

[XmlRoot("root")]
public class Sample1Xml
{
    internal Sample1Xml()
    {
    }

    [XmlElement("node")]
    public NodeType Node { get; set; }

    #region Nested type: NodeType

    public class NodeType
    {
        [XmlAttribute("attr1")]
        public string Attr1 { get; set; }

        [XmlAttribute("attr2")]
        public string Attr2 { get; set; }

        [XmlIgnore]
        public string Content { get; set; }

        [XmlText]
        public XmlNode[] CDataContent
        {
            get
            {
                XmlDocument dummy = new XmlDocument();
                List<XmlNode> xmlNodes = new List<XmlNode>();
                int tokenCount = 0;
                int prevSplit = 0;
                for (int i = 0; i < Content.Length; i++)
                {
                    char c = Content[i];
                    //If the current character is > and it was preceded by ]] (i.e. the last 3 characters were ]]>)
                    if (c == '>' && tokenCount >= 2)
                    {
                        //Put everything up to this point in a new CData Section
                        string thisSection = Content.Substring(prevSplit, i - prevSplit);
                        xmlNodes.Add(dummy.CreateCDataSection(thisSection));
                        prevSplit = i;
                    }
                    if (c == ']')
                    {
                        tokenCount++;
                    }
                    else
                    {
                        tokenCount = 0;
                    }
                }
                //Put the final part of the string into a CData section
                string finalSection = Content.Substring(prevSplit, Content.Length - prevSplit);
                xmlNodes.Add(dummy.CreateCDataSection(finalSection));

                return xmlNodes.ToArray();
            }
            set
            {
                if (value == null)
                {
                    Content = null;
                    return;
                }

                if (value.Length != 1)
                {
                    throw new InvalidOperationException(
                        String.Format(
                            "Invalid array length {0}", value.Length));
                }

                Content = value[0].Value;
            }
        }
    }
Iain Fraser
quelle