【问题标题】:Problems serializing a class to XML and including a CDATA section将类序列化为 XML 并包含 CDATA 部分的问题
【发布时间】:2012-07-13 13:42:04
【问题描述】:

我有一个类被序列化为 XML 以供 Web 服务使用。在此类实例中,XML 必须包含一个 CDATA 部分,以便 Web 服务读取它,但我不知道如何实现它。

XML 需要如下所示:

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

我能够生成适当的 XML,除了 CDATA 部分。

我的班级结构如下:

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

         ....

我看到了一些使用建议:

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

但这会导致异常

我也试过

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

但生成的 XML 显示不正确

 <Value_x003C__x0021__x005B_CDATA_x005B_>
 ....
 </Value_x003C__x0021__x005B_CDATA_x005B_>

谁能告诉我我做错了什么,或者我需要去哪里?

--编辑--

使 shippingInfo 每个 carlosfigueira 可序列化在大多数情况下都有效,但是我得到了额外的?生成的 XML 中的字符(有关详细信息,请参阅帖子 Writing an XML fragment using XmlWriterSettings and XmlSerializer is giving an extra character

因此,我将 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()));
                }
            }
        }

但是我没有遇到异常:

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

--编辑--

异常是由于包含了我之前的 serializeToString 方法引起的。由于删除 CDATA 输出是正确的,除了间距问题,但我还得到了应由指定的 XML 设置删除的命名空间和 xml 声明。输出是:

<?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>

有没有使用新类避免 BOM 的想法?

--编辑 3 -- 成功!

我已经实现了以下建议的更改,现在具有以下编写器类和测试方法:

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

这会产生以下 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>

【问题讨论】:

  • 请注意,CDATA 部分是转义 xml 以使其更具可读性的一种方式。 CDATA 的内容不是 Xml。 ]]> 等价于 <test />如果您确定内容始终是有效的 Xml 文档,您应该能够预处理文档以删除 CDATA 并取消转义 CDATA 部分的内容。请注意,如果内容无效 Xml 这样做会使整个文档无效。另一种选择是实现 IXmlSerializable,如下所述,但是一旦开始,它就会增长并且很难维护。

标签: c# xml xml-serialization


【解决方案1】:

您在编辑后看到“空格”,这是因为您使用的编码(Unicode,每个字符 2 个字节)。

试试:

settings.Encoding = new Utf8Encoding(false);

编辑:

另外,请注意MemoryStream 的格式不是一定是有效的 UTF-8 编码字符串!您可以使用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());   
        }   
    }

【讨论】:

  • 好的,在阅读您链接到的文档时,我的行“XmlWriter writer = XmlWriter.Create(ms, settings);”应该保留设置中列出的编码,因为我没有在其他任何地方指定编码,但是当我执行时出现异常:“System.InvalidOperationException:生成 XML 文档时出错。---> System.ArgumentException:' .',十六进制值 0x00,是无效字符。"
  • @RobertH 我认为这是因为 MemoryStream 内容不一定是有效的 UTF8 编码字符串。我已经更新了我的答案。
【解决方案2】:

这有什么帮助吗: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);

另外,你在使用属性的时候遇到了什么异常?

-- 编辑--

您可以尝试添加自定义类,然后执行以下操作:

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

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

【讨论】:

  • 我得到异常:System.InvalidOperationException:无法生成临时类(结果=1)。
  • 试着把属性像这样[XmlElement("CDataElement")]
  • 当我这样做时,它会将 Value 重命名为 CDataElement,这将导致 Web 服务验证失败
  • 我也试过:UpdateString.Replace(@"", @"", @"]]>");没有运气
  • 查看编辑 :-) 添加您自己的 CDATA 类型并在您自己的类中使用类似上述类的属性
【解决方案3】:

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

【讨论】:

  • 所以我做了一些更改,因为您的代码几乎可以满足我的需要 - 我需要删除一些额外的东西吗?字符(见我的另一篇文章),但它抛出异常。请参阅编辑了解更多信息。
  • 要移除 BOM,请将 XmlWriterSettings 的 Encoding 属性设置为没有 BOM 的实例:new XmlWriterSettings { OmitXmlDeclaration = true, Encoding = new UTF8Encoding(false) }
【解决方案4】:

以下示例仅适用于定义了架构的结构并且您无法选择更改架构的情况。

当您反序列化/序列化 [xmltext] 值时,很难将文本保存在 CDATA[] 括。您可以使用 compiletransform 原样获取 xml 中的 CDATA 值,但是一旦您在 C# 中反序列化并加载到内存中,CDATA 就会丢失。

这是最简单的方法之一

  1. 反序列化/序列化
  2. 一旦导出最终的 xml 输出。最终的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()

【讨论】:

    【解决方案5】:

    我认为 carlosfigueira 只是给出了一个优雅的答案,但乍一看可能有点难以理解。这里有一个替代方案供您考虑,您可以分别对UpdateOrderStatusShipmentInfo 进行序列化/反序列化。

    将您的业务对象类定义为:

        [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; }
        }
    

    请注意,您应该将XmlCDataSection 用于Value 字段。 这是测试/帮助函数:

        // 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);
                }
            }
    
            ...
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-04
      • 1970-01-01
      • 2018-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多