Bagaimana Anda membuat serial string sebagai CDATA menggunakan XmlSerializer?

92

Apakah mungkin melalui atribut untuk membuat serial string sebagai CDATA menggunakan .Net XmlSerializer?

jamesaharvey
sumber
2
Satu hal yang perlu diperhatikan tentang kedua jawaban tersebut adalah Anda tidak perlu CDataContentjika Anda hanya membaca XML. XmlSerializer.Deserializeakan secara otomatis mengubahnya menjadi teks untuk Anda.
Chris S

Jawaban:

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
sumber
9
Bagi saya ini bukan solusi yang paling elegan. Apakah ini satu-satunya cara yang mungkin untuk melakukan ini?
jamesaharvey
1
Saya pikir ini adalah satu-satunya cara untuk mencapai ini, saya telah melihat topik ini di tempat lain dan selalu jawaban yang sama. Contoh dari Philip sedikit lebih bersih tetapi konsepnya sama. Satu-satunya cara lain yang saya ketahui adalah mengimplementasikan <a href=" msdn.microsoft.com/en-us/library/…> Anda sendiri pada kelas yang mewakili konten CDATA.
csharptest.net
Saya ingin melakukan hal yang sama karena sepertinya menyimpan string karena CDATA tampaknya menyiratkan waktu pemrosesan yang lebih sedikit, karena dengan itu kami 'hanya' membaca / menulis string 'sebagaimana adanya'. Seberapa mahal melibatkan instance XmlDocument / XmlCDataSection?
tishma
Dan seluruh Atribut ada di sana sehingga kami dapat menjaga kelas model domain bersih dari detail logika serialisasi. Sungguh menyedihkan jika cara kotor adalah satu-satunya cara.
tishma
2
Solusi Philip sedikit lebih jauh ke bawah halaman adalah hal yang lebih rapi untuk dilakukan.
Karl
100
[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;
        }
    }
}

Pemakaian:

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());

Keluaran:

<?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
sumber
Ini baru saja menyelamatkan hariku. Terima kasih.
Robert
4
// Jika Anda membutuhkan CDATA kosong, Anda dapat menyetel default jika nilai sumber null untuk menghindari pengecualian. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r apakah ini juga memungkinkan deserialisasi ke objek yang sama? Saya mengalami masalah dengan itu
Martin
Bagaimana cara membuat CDATA sebagai nilai teks (dan bukan sebagai elemen) seperti <MyClass> <! [CDATA [<test> Hello World </test>]]> </MyClass>?
mko
1
Hanya perlu menangani nilai kosong / nol daripada mengeluarkan <emptyfield><![CDATA[]]> </emptyfield>
bluee
91

Selain cara diposting oleh John Saunders, Anda dapat menggunakan XmlCDataSection sebagai tipe secara langsung, meskipun intinya hampir sama:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
sumber
1
@ Philip, apakah ini berfungsi untuk deserialisasi? Saya telah melihat catatan yang mengatakan bahwa penyetel akan menerima nilai XmlText.
John Saunders
1
@John Saunders - Ini benar-benar menerima nilai XmlCharacterData di penyetel selama deserialisasi, yang merupakan panggilan ke. Nilai untuk di penyetel (Saya awalnya memilikinya sebagai ToString () dari memori, tapi itu salah.)
Philip Rieck
1
@PhilipRieck Bagaimana jika kita perlu membungkus objek khusus di sekitar CDataSection. Buat CDataSection menerima string.
zeppelin
Terima kasih! Solusi termudah. Bekerja dengan baik untuk saya.
Antonio Rodríguez
43

Di kelas yang akan diserialkan:

public CData Content { get; set; }

Dan kelas CData:

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
sumber
Bekerja seperti pesona. Terima kasih.
Leonel Sanches da Silva
3
Jawaban ini membutuhkan lebih banyak pengakuan. Meskipun, tipe CData yang disesuaikan tidak lagi memiliki metode built-in yang nyaman seperti yang dinikmati oleh tipe System.String.
Lionet Chen
bagus maka jawaban pertama
Hsin-Yu Chen
Jawabannya bagus. Sayang sekali XmlElement tidak berfungsi pada bidang string, maka Anda bisa menambahkan jenis cdata, tetapi apa pun ...
jjxtra
Sempurna! Terima kasih!
Roy
5

Saya memiliki kebutuhan yang sama tetapi membutuhkan format keluaran yang berbeda - Saya menginginkan atribut pada node yang berisi CDATA. Saya mengambil beberapa inspirasi dari solusi di atas untuk membuatnya sendiri. Mungkin itu akan membantu seseorang di masa depan ...

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()
    {

    }
}

Di objek induk yang akan diserialisasi, saya memiliki properti berikut:

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

Saya mendapatkan output berikut:

<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 Hei
sumber
1
Saya harus melakukan ini. Terima kasih!!
Lews Ada
4

Dalam kasus saya, saya menggunakan bidang campuran, beberapa CDATA beberapa tidak, setidaknya bagi saya solusi berikut berfungsi ....

Dengan selalu membaca bidang Nilai, saya mendapatkan isinya, terlepas dari apakah CDATA atau hanya teks biasa.

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

    [XmlText]
    public string Value;

Lebih baik terlambat daripada tidak sama sekali.

Bersulang

Coderookie
sumber
Luar biasa - Saya merasa jawaban ini menyelamatkan saya sebagian besar waktu! Untuk info saya menggunakan atribut [XmlIgnore] pada Nilai
d219
Bagaimana ini secara operasional berbeda dengan jawaban pr0gg3r ?
ruffin
2

Penerapan ini memiliki kemampuan untuk memproses CDATA bertingkat dalam string yang Anda enkode (berdasarkan jawaban asli John Saunders).

Misalnya, Anda ingin mengenkode string literal berikut ke dalam CDATA:

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

Anda ingin keluaran yang dihasilkan terlihat seperti ini:

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

Implementasi berikut akan mengulang string, membagi instance ...]]>...menjadi ...]]dan >...dan membuat bagian CDATA terpisah untuk masing-masing.

[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
sumber