简体   繁体   English

将类序列化为 XML 并包含 CDATA 部分时出现问题

[英]Problems serializing a class to XML and including a CDATA section

I have a class that is serialized into XML for consumption by a web service.我有一个序列化为 XML 以供 Web 服务使用的类。 In this classes instance the XML must include a CDATA section for the web service to read it but I am at a loss on how to implement this.在这个类实例中,XML 必须包含一个 CDATA 部分供 Web 服务读取它,但我不知道如何实现它。

The XML needs to look like: XML 需要如下所示:

<UpdateOrderStatus> 
    <Action>2</Action> 
        <Value> 
            <![CDATA[ 
                <Shipment> 
                    <Header> 
                        <SellerID>
                            ...
             ]]>
         </Value>
 </UpdateOrderStatus>

I am able to generate the appropriate XML, except for the CDATA part.我能够生成适当的 XML,CDATA 部分除外。

My class structure looks like:我的班级结构如下:

public class UpdateOrderStatus
{
    public int Action { get; set; }


    public ValueInfo Value { get; set; }

    public UpdateOrderStatus()
    {
        Value = new ValueInfo();
    }


    public class ValueInfo
    {
        public ShipmentInfo Shipment { get; set; }

        public ValueInfo()
        {
            Shipment = new ShipmentInfo();
        }

        public class ShipmentInfo
        {
            public PackageListInfo PackageList { get; set; }
            public HeaderInfo Header { get; set; }
            public ShipmentInfo()
            {
                PackageList = new PackageListInfo();
                Header = new HeaderInfo();
            }

         ....

I have seen some suggestions on using:我看到了一些关于使用的建议:

[XmlElement("node", typeof(XmlCDataSection))]

but that causes an exception但这会导致异常

I have also tried我也试过

 [XmlElement("Value" + "<![CDATA[")]

but the resulting XML is incorrect showing但生成的 XML 显示不正确

 <Value_x003C__x0021__x005B_CDATA_x005B_>
 ....
 </Value_x003C__x0021__x005B_CDATA_x005B_>

Can anyone show me what I am doing wrong, or where I need to go with this?任何人都可以告诉我我做错了什么,或者我需要去哪里吗?

--Edit-- - 编辑 -

making shipmentInfo serializable per carlosfigueira works for the most part, however I get extra?使每个 carlosfigueira 的 shipmentInfo 可序列化在大多数情况下都有效,但是我得到额外的? characters in the resulting XML ( see post Writing an XML fragment using XmlWriterSettings and XmlSerializer is giving an extra character for details )生成的 XML 中的字符(有关详细信息,请参阅使用 XmlWriterSettings 和 XmlSerializer 编写 XML 片段正在提供额外字符一文

As such I changed the Write XML method to:因此,我将 Write XML 方法更改为:

public void WriteXml(XmlWriter writer)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("", "");

                XmlWriterSettings settings = new XmlWriterSettings();

                settings.OmitXmlDeclaration = true;
                settings.Encoding = new UnicodeEncoding(bigEndian: false, byteOrderMark: false);
                settings.Indent = true;

                using (XmlWriter innerWriter = XmlWriter.Create(ms, settings))
                {
                    shipmentInfoSerializer.Serialize(innerWriter, this.Shipment,ns);
                    innerWriter.Flush();
                    writer.WriteCData(Encoding.UTF8.GetString(ms.ToArray()));
                }
            }
        }

However I am not getting an exception:但是我没有例外:

System.InvalidOperationException: There was an error generating the XML document. ---> System.ArgumentException: '.', hexadecimal
value 0x00, is an invalid character.

--Edit -- - 编辑 -

The exception was caused by the inclusion of my previous serializeToString method.异常是由于包含我以前的 serializeToString 方法引起的。 Since removing that the CDATA output is correct, except for a spacing issue, but I am also getting a namespace and xml declaration that should be removed by the XML settings specified.由于删除 CDATA 输出是正确的,除了间距问题,但我也得到了一个命名空间和 xml 声明,它们应该由指定的 XML 设置删除。 Output is:输出是:

<?xml version="1.0"?>
<UpdateOrderStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Action>1</Action>
  <Value><![CDATA[< S h i p m e n t I n f o >
     < P a c k a g e L i s t >
         < P a c k a g e >
             < S h i p D a t e > 2 0 1 2 - 0 7 - 1 3 T 1 1 : 5 8 : 5 1 . 0 9 2 5 6 1 5 - 0 4 : 0 0 < / S h i p D a t e >
             < I t e m L i s t >
                 < I t e m >
                     < S h i p p e d Q t y > 0 < / S h i p p e d Q t y >
                 < / I t e m >
             < / I t e m L i s t >
         < / P a c k a g e >
     < / P a c k a g e L i s t >
     < H e a d e r >
         < S e l l e r I d > S h i p m e n t   h e a d e r < / S e l l e r I d >
         < S O N u m b e r > 0 < / S O N u m b e r >
     < / H e a d e r >
 < / S h i p m e n t I n f o > ]]></Value>
</UpdateOrderStatus>

Any ideas of avoiding the BOM using the new class?使用新类避免 BOM 的任何想法?

--Edit 3 -- SUCCESS! --编辑 3 --成功!

I have implemented changes suggested below and now have the following writer class and test methods:我已经实施了下面建议的更改,现在有以下编写器类和测试方法:

 UpdateOrderStatus obj = new UpdateOrderStatus();

        obj.Action = 1;
        obj.Value = new UpdateOrderStatus.ValueInfo();
        obj.Value.Shipment = new UpdateOrderStatus.ValueInfo.ShipmentInfo();
        obj.Value.Shipment.Header.SellerId = "Shipment header";
        obj.Value.Shipment.PackageList = new UpdateOrderStatus.ValueInfo.ShipmentInfo.PackageListInfo();
        obj.Value.Shipment.PackageList.Package = new UpdateOrderStatus.ValueInfo.ShipmentInfo.PackageListInfo.PackageInfo();
        obj.Value.Shipment.PackageList.Package.ShipDate = DateTime.Now;



        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", "");
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.OmitXmlDeclaration = true;
        settings.Encoding = new UTF8Encoding(false);
        settings.Indent = true;
        XmlSerializer xs = new XmlSerializer(typeof(UpdateOrderStatus));
        MemoryStream ms = new MemoryStream();


        XmlWriter writer = XmlWriter.Create(ms, settings);
        xs.Serialize(writer, obj, ns);
        Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
    }


public void WriteXml(XmlWriter writer)
        {

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");

            XmlWriterSettings settings = new XmlWriterSettings();

            settings.OmitXmlDeclaration = true;
            settings.Indent = true;

            StringBuilder sb = new StringBuilder();
            using (XmlWriter innerWriter = XmlWriter.Create(sb, settings))
            {
                shipmentInfoSerializer.Serialize(innerWriter, this.Shipment, ns);
                innerWriter.Flush();
                writer.WriteCData(sb.ToString());
            }   
        }

This produces the following XML:这会生成以下 XML:

<UpdateOrderStatus>
  <Action>1</Action>
  <Value><![CDATA[<ShipmentInfo>
  <PackageList>
    <Package>
      <ShipDate>2012-07-13T14:05:36.6170802-04:00</ShipDate>
      <ItemList>
        <Item>
          <ShippedQty>0</ShippedQty>
        </Item>
      </ItemList>
    </Package>
  </PackageList>
  <Header>
    <SellerId>Shipment header</SellerId>
    <SONumber>0</SONumber>
  </Header>
</ShipmentInfo>]]></Value>
</UpdateOrderStatus>

In response to the 'spaces' you are seeing after your edit, it is because of the encoding you are using (Unicode, 2 bytes per character).针对您在编辑后看到的“空格”,这是因为您使用的编码(Unicode,每个字符 2 个字节)。

Try:尝试:

settings.Encoding = new Utf8Encoding(false);

EDIT:编辑:

Also, note that format of the MemoryStream is not necessarily a valid UTF-8 encoded string!另外,请注意MemoryStream的格式不一定是有效的 UTF-8 编码字符串! You can use a StringBuilder instead of MemoryStream to create your inner writer.您可以使用StringBuilder而不是MemoryStream来创建您的内部编写器。

    public void WriteXml(XmlWriter writer)   
    {   
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();   
        ns.Add("", "");   

        XmlWriterSettings settings = new XmlWriterSettings();   

        settings.OmitXmlDeclaration = true;   
        settings.Indent = true;   

        StringBuilder sb = new StringBuilder();
        using (XmlWriter innerWriter = XmlWriter.Create(sb, settings))   
        {   
            shipmentInfoSerializer.Serialize(innerWriter, this.Shipment,ns);   
            innerWriter.Flush();   
            writer.WriteCData(sb.ToString());   
        }   
    }

Could this be of any help: http://msdn.microsoft.com/en-us/library/system.xml.xmldocument.createcdatasection.aspx这有什么帮助吗:http: //msdn.microsoft.com/en-us/library/system.xml.xmldocument.createcdatasection.aspx

//Create a CData section.
XmlCDataSection CData;
CData = doc.CreateCDataSection("All Jane Austen novels 25% off starting 3/23!");    

//Add the new node to the document.
XmlElement root = doc.DocumentElement;
root.AppendChild(CData);  

Console.WriteLine("Display the modified XML...");        
doc.Save(Console.Out);

Also, what Exception did you get when using the attribute?另外,您在使用该属性时得到了什么异常?

-- edit -- - 编辑 -

You could try adding a custom class, and do something like this:您可以尝试添加自定义类,然后执行以下操作:

some xml serializable class,
 {
    .......

    [XmlElement("PayLoad", Type=typeof(CDATA))]
    public CDATA PayLoad
    {
       get { return _payLoad; }
       set { _payLoad = value; }
    }
 }


 public class CDATA : IXmlSerializable
 {
    private string text;
    public CDATA()
    {}

    public CDATA(string text)
    {
       this.text = text;
    }

    public string Text
    {
       get { return text; }
    }

    /// <summary>
    /// Interface implementation not used here.
    /// </summary>
    XmlSchema IXmlSerializable.GetSchema()
    {
       return null;
    }

    /// <summary>
    /// Interface implementation, which reads the content of the CDATA tag
    /// </summary>
    void IXmlSerializable.ReadXml(XmlReader reader)
    {
       this.text = reader.ReadElementString();
    }

    /// <summary>
    /// Interface implementation, which writes the CDATA tag to the xml
    /// </summary>
    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
       writer.WriteCData(this.text);
    }
 }

As found here http://bytes.com/topic/net/answers/530724-cdata-xmltextattribute在这里找到http://bytes.com/topic/net/answers/530724-cdata-xmltextattribute

Implementing ShipmentInfo as an IXmlSerializable type will get close to what you need - see example below.将 ShipmentInfo 实现为IXmlSerializable类型将接近您的需要 - 请参见下面的示例。

public class StackOverflow_11471676
{
    public class UpdateOrderStatus
    {
        public int Action { get; set; }
        public ValueInfo Value { get; set; }
    }
    [XmlType(TypeName = "Shipment")]
    public class ShipmentInfo
    {
        public string Header { get; set; }
        public string Body { get; set; }
    }
    public class ValueInfo : IXmlSerializable
    {
        public ShipmentInfo Shipment { get; set; }
        private XmlSerializer shipmentInfoSerializer = new XmlSerializer(typeof(ShipmentInfo));

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

        public void ReadXml(XmlReader reader)
        {
            using (MemoryStream ms = new MemoryStream(
                Encoding.UTF8.GetBytes(
                    reader.ReadContentAsString())))
            {
                Shipment = (ShipmentInfo)this.shipmentInfoSerializer.Deserialize(ms);
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (XmlWriter innerWriter = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }))
                {
                    shipmentInfoSerializer.Serialize(innerWriter, this.Shipment);
                    innerWriter.Flush();
                    writer.WriteCData(Encoding.UTF8.GetString(ms.ToArray()));
                }
            }
        }
    }
    public static void Test()
    {
        UpdateOrderStatus obj = new UpdateOrderStatus
        {
            Action = 1,
            Value = new ValueInfo
            {
                Shipment = new ShipmentInfo
                {
                    Header = "Shipment header",
                    Body = "Shipment body"
                }
            }
        };

        XmlSerializer xs = new XmlSerializer(typeof(UpdateOrderStatus));
        MemoryStream ms = new MemoryStream();
        xs.Serialize(ms, obj);
        Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
    }
}

The below example is only when the structure of the schema is defined and you have no choice of altering the schema.以下示例仅在定义架构的结构并且您无法选择更改架构时使用。

When you Deserialize/Serialize a [xmltext] value it is very difficult to hold the text in the CDATA[] enclose.当您反序列化/序列化[xmltext]值时,很难将文本包含在CDATA[]中。 you can use compiletransform to get the CDATA value in the xml as it is but the CDATA is lost as soon as you deserialize in C# and load to memory stuff.您可以使用 compiletransform 来获取xml中的CDATA值,但是一旦您在 C# 中反序列化并加载到内存中, CDATA就会丢失。

This is one of the easiest way to do这是最简单的方法之一

  1. deserialize/Serialize反序列化/序列化
  2. once the final xml output is derived.一旦导出最终的 xml 输出。 The final xml can be converted to string and parsed as shown below and return it as string will embed CDATA to the test1 value.最终的 xml 可以转换为字符串并如下所示进行解析,并将其作为字符串返回,将CDATA嵌入到 test1 值中。
string xml ="<test><test1>@#@!#!#!@#!@#%$%@#$%#$%</test1></test>";
XNamespace ns = @"";
XDocument doc = XDocument.Parse(xml);
string xmlString = string.Empty; 
var coll = from query in doc.Descendants(ns + "test1")
                select query;

foreach (var value in coll){
    value.ReplaceNodes(new XCData(value .Value));
}

doc.save("test.xml");// convert doc.tostring()

I think carlosfigueira just gave an elegant answer, but perhaps a little hard to understand at first sight.我认为carlosfigueira只是给出了一个优雅的答案,但乍一看可能有点难以理解。 Here is an alternative for your consideration, you can Serialize/Deserialize UpdateOrderStatus and ShipmentInfo separately.这是供您考虑的替代方案,您可以分别序列化/反序列化UpdateOrderStatusShipmentInfo

Define your business object class as:将您的业务对象类定义为:

    [XmlRoot("UpdateOrderStatus")]
    public class UpdateOrderStatus
    {
        [XmlElement("Action")]
        public int Action { get; set; }

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

        [XmlIgnore]
        public ShipmentInfo Shipment
        {
            get;
            set;
        }
    }

    [XmlRoot("ShipmentInfo")]
    public class ShipmentInfo
    {
        [XmlElement("Package")]
        public String Package { get; set; }
        [XmlElement("Header")]
        public String Header { get; set; }
    }

Note that you should use XmlCDataSection for the Value field.请注意,您应该为Value字段使用XmlCDataSection Here is the test/helper functions:这是测试/帮助函数:

    // Test function
    const string t = @"<UpdateOrderStatus> 
        <Action>2</Action> 
            <Value> 
                <![CDATA[<ShipmentInfo>
      <Package>packageInfo</Package>
      <Header>headerInfo</Header>
    </ShipmentInfo>]]>
             </Value>
    </UpdateOrderStatus>";

    static void Test1()
    {
        UpdateOrderStatus os = Deserialize(t);

        String t2 = XmlUtil.Serialize(os);
    }

    // Helper functions
    static UpdateOrderStatus Deserialize(String str)
    {
        UpdateOrderStatus os = XmlUtil.DeserializeString<UpdateOrderStatus>(str);
        os.Shipment = XmlUtil.DeserializeString<ShipmentInfo>(os.Value.InnerText);
        return os;
    }

    public static class XmlUtil
    {
        public static String Serialize<T>(T o)
        {
            XmlSerializer s = new XmlSerializer(typeof(T)); //, overrides);
            //StringBuilder builder = new StringBuilder();

            MemoryStream ms = new MemoryStream();
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = Encoding.UTF8;
            settings.Indent = true;
            using (XmlWriter xmlWriter = XmlWriter.Create(ms, settings))
            {
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add(String.Empty, String.Empty);
                s.Serialize(xmlWriter, o, ns);
            }
            Byte[] bytes = ms.ToArray();
            // discard the BOM part
            Int32 idx = settings.Encoding.GetPreamble().Length;
            return Encoding.UTF8.GetString(bytes, idx, bytes.Length - idx);
        }

        public static T DeserializeString<T>(String content)
        {
            using (TextReader reader = new StringReader(content))
            {
                XmlSerializer s = new XmlSerializer(typeof(T));
                return (T)s.Deserialize(reader);
            }
        }

        ...
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM