Jak serializować ciąg jako CDATA przy użyciu XmlSerializer?

91

Czy jest możliwe poprzez jakiś atrybut serializacji łańcucha jako CDATA przy użyciu .Net XmlSerializer?

jamesaharvey
źródło
2
Jedną rzeczą wartą odnotowania w przypadku tych dwóch odpowiedzi jest to, że nie potrzebujesz, CDataContentjeśli czytasz tylko XML. XmlSerializer.Deserializeautomatycznie zamieni go na tekst.
Chris S

Odpowiedzi:

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
źródło
9
Nie wydaje mi się to najbardziej eleganckim rozwiązaniem. Czy to jedyny możliwy sposób na zrobienie tego?
jamesaharvey
1
Myślę, że to jedyny sposób, aby to osiągnąć, widziałem ten temat gdzie indziej i zawsze ta sama odpowiedź. Przykład Philipa jest trochę czystszy, ale ta sama koncepcja. Jedynym innym sposobem, jaki znam, jest zaimplementowanie własnego <a href=" msdn.microsoft.com/en-us/library/…> w klasie reprezentującej zawartość CDATA.
csharptest.net
Chciałem zrobić to samo, ponieważ wygląda na to, że przechowywanie ciągów znaków wydaje się oznaczać krótszy czas przetwarzania CDATA, ponieważ dzięki niemu możemy „tylko” odczytywać / zapisywać ciągi „tak jak jest”. Ile kosztuje korzystanie z wystąpień XmlDocument / XmlCDataSection?
tishma
I cała rzecz Attributes jest dostępna, dzięki czemu możemy utrzymać klasy modelu domeny w czystości od szczegółów logiki serializacji. To takie smutne, jeśli brudna droga jest jedyną drogą.
tishma
2
Rozwiązanie Philipa nieco dalej w dół strony jest bardziej uporządkowane.
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;
        }
    }
}

Stosowanie:

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

Wynik:

<?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
źródło
To właśnie uratowało mi dzień. Dziękuję Ci.
Robert,
4
// W przypadku, gdy potrzebujesz pustego CDATA, możesz ustawić wartość domyślną, jeśli wartość źródłowa jest pusta, aby uniknąć wyjątku. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r czy to również umożliwia deserializację do tego samego obiektu? Mam z tym problem
Martin
Jak utworzyć CDATA jako wartość tekstową (a nie jako element), na przykład <MyClass> <! [CDATA [<test> Hello World </test>]]> </MyClass>?
mko
1
Tylko musi być w stanie obsługiwać wartości pusty / null niż wyprowadzanie <emptyfield> </ emptyfield> <! [CDATA []]!>
bluee
91

Oprócz sposobu opublikowanego przez Johna Saundersa, możesz użyć XmlCDataSection bezpośrednio jako typu, chociaż sprowadza się to do prawie tego samego:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
źródło
1
@Philip, czy to działa w przypadku deserializacji? Widziałem notatki mówiące, że ustawiacz otrzyma wartość XmlText.
John Saunders
1
@John Saunders - W rzeczywistości otrzymuje wartość XmlCharacterData w seterze podczas deserializacji, do której służy wywołanie .Value w seterze (pierwotnie miałem to jako ToString () z pamięci, ale to było niepoprawne.)
Philip Rieck
1
@PhilipRieck A co, jeśli musimy zawinąć niestandardowy obiekt wokół sekcji CDataSection. Create CDataSection akceptuje ciąg.
zeppelin
Dziękuję Ci! Najłatwiejsze rozwiązanie. U mnie działa dobrze.
Antonio Rodríguez
43

W klasie do serializacji:

public CData Content { get; set; }

I klasa 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
źródło
Działa jak marzenie. Dziękuję Ci.
Leonel Sanches da Silva
3
Ta odpowiedź zasługuje na większe uznanie. Chociaż dostosowany typ CData nie ma już tych wygodnych wbudowanych metod, które lubi typ System.String.
Lionet Chen
miła, a potem pierwsza odpowiedź
Hsin-Yu Chen
Odpowiedź działa świetnie. Szkoda, że ​​XmlElement nie działa na polu string, wtedy można by po prostu dodać typ cdata, ale nieważne ...
jjxtra
Idealny! Dzięki!
Roy
5

Miałem podobną potrzebę, ale wymagałem innego formatu wyjściowego - chciałem mieć atrybut w węźle, który zawiera CDATA. Zainspirowałem się powyższymi rozwiązaniami, aby stworzyć własne. Może pomoże to komuś w przyszłości ...

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

    }
}

W obiekcie nadrzędnym do serializacji mam następującą właściwość:

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

Otrzymuję następujący wynik:

<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 Hej
źródło
1
Musiałem to zrobić dokładnie. Dziękuję Ci!!
Lews Therin
4

W moim przypadku używam pól mieszanych, niektóre CDATA niektóre nie, przynajmniej dla mnie działa następujące rozwiązanie ....

Czytając zawsze pole Wartość, otrzymuję zawartość, niezależnie od tego, czy jest to CDATA, czy zwykły tekst.

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

    [XmlText]
    public string Value;

Lepiej późno niż wcale.

Twoje zdrowie

Coderookie
źródło
Fantastycznie - mam wrażenie, że ta odpowiedź pozwoliła mi zaoszczędzić sporo czasu! Aby uzyskać informacje, użyłem atrybutu [XmlIgnore] na wartość
d219
Czym różni się to operacyjnie od odpowiedzi pr0gg3r ?
ruffin
2

Ta implementacja ma możliwość przetwarzania zagnieżdżonych CDATA w kodowanym ciągu (na podstawie oryginalnej odpowiedzi Johna Saundersa).

Na przykład załóżmy, że chcesz zakodować następujący ciąg literału do CDATA:

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

Chciałbyś, aby wynikowe wyjście wyglądało mniej więcej tak:

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

Poniższa pętla realizacja będzie nad ciągiem, podzielone wystąpień ...]]>...w ...]]i >...i tworzyć oddzielne sekcje CDATA dla każdego.

[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
źródło