简体   繁体   English

C#XML反序列化设置默认值

[英]C# XML deserialization set default values

I've an XML in my application, like below XML 我的应用程序中有一个XML ,就像下面的XML

<Default>       
     <Port>7252</Port>
     <FileLocation>D:/test</FileLocation>
</Default>
<Files>
    <File>
        <Type>Send</Type>
            <FileName>xyz</FileName>            
            <Port>7252</Port>
            <FileLocation>c:/test</FileLocation>
    </File>
    <File>
        <Type>Send</Type>
            <FileName>abc</FileName>            
            <Port></Port>
            <FileLocation></FileLocation>
    </File>
</Files>

while deserialization I want to make sure if File element doesn't have any value, it will get picked from Default element. 反序列化时,我想确保File元素没有任何值,它将从Default元素中选取。 Is there already any classes/ way to do so, or do I need to write a custom logic for this in my program? 是否已经有任何类/方法可以这样做,或者我需要在程序中为此编写自定义逻辑?

PS: I'm a bit flexible to change names/design in the XML as long as it's in the same proportion. PS:我可以灵活地更改XML中的名称/设计,只要它的比例相同即可。

Try this : 尝试这个 :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);
            XElement _default = doc.Descendants("Default").FirstOrDefault();

            int defaultPort = (int)_default.Element("Port");
            string defaultFileLocation = (string)_default.Element("FileLocation");

            var files = doc.Descendants("File").Select(x => new {
                type = (string)x.Element("Type"),
                fileName = (string)x.Element("FileName"),
                port = (string)x.Element("Port") == "" ? defaultPort : (int)x.Element("Port"),
                fileLocation = (string)x.Element("FileLocation") == "" ? defaultFileLocation : (string)x.Element("FileLocation"),
            }).ToList();
        }
    }
}

There isn't magic in the XmlSerializer or the supporting attributes around it to achieve what you want. XmlSerializer或它周围的支持属性没有魔力来实现您想要的。 You could venture into creating your own overload of an XmlReader that stores the Default node when read and the rewrite the values for any File node on reading. 您可以冒险创建自己的XmlReader重载,该重载在读取时存储Default节点,并在读取时重写任何File节点的值。 If your file is larger I would look in to that solution. 如果您的文件较大,我将寻求该解决方案。 However, you said the file is small so I guess it is small enough to be transformed first, before being read by the XmlSerializer. 但是,您说的是文件很小,因此我想它足够小,可以首先转换,然后再由XmlSerializer读取。

The following XSLT sheet achieves that for your given xml (when wrapped in a Root node). 以下XSLT工作表为给定的xml(当包装在Root节点中时)实现了该功能。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   <!-- copy template -->
   <xsl:template match="@*|node()">
     <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
     </xsl:copy>
   </xsl:template>
   <!-- handle nodes that should have defaults -->
   <xsl:template match="File/*">
      <xsl:choose>
        <!-- there is a value -->
        <xsl:when test="string-length(.)>0">
           <xsl:copy-of select="."/>
        </xsl:when>
        <xsl:otherwise>
           <!-- no value, look up an default -->
           <xsl:variable name="def" select="name()"/>
           <xsl:copy-of select="/Root/Default/*[name(.) = $def]/."/>
        </xsl:otherwise>
      </xsl:choose>
   </xsl:template>

 </xsl:stylesheet>

The following code applies the above style over the input XML with the XslCompiledTransform and the hands its result to the XmlSerializer: 以下代码使用XslCompiledTransform将上述样式应用于输入XML,并将其结果传递给XmlSerializer:

   // create the Xsl Transformation
   var xct = new XslCompiledTransform();
   // use any another Stream if needed
   // xsl holds the XSLT stylesheet
   xct.Load(XmlReader.Create(new StringReader(xsl)));
   Root result;
   // we stream the result in memory
   using(var ms = new MemoryStream())
   {
      // write it
      using(var xw = XmlWriter.Create(ms))
      {
         // transform input XML  
         // the string xml holds the test XML input, replace with a stream
         xct.Transform(XmlReader.Create(new StringReader(xml)), xw);
      }
      Encoding.UTF8.GetString(ms.ToArray()).Dump(); // linqpad testing
      // now we're ready to deserialize
      var xs = new XmlSerializer(typeof(Root));
      ms.Position = 0;
      result= (Root) xs.Deserialize(ms);
      result.Dump(); // Linqpad testing
   }

What basically happens is that instead of your input XML the following XML is fed into the serializer: 基本上发生的是,以下XML代替了输入XML,而被馈送到序列化器中:

<?xml version="1.0" encoding="utf-8"?>
<Root>
  <Default>       
     <Port>7252</Port>
     <FileLocation>D:/test</FileLocation>
  </Default>
  <Files>
    <File>
        <Type>Send</Type>
        <FileName>xyz</FileName>            
        <Port>7252</Port>
        <FileLocation>c:/test</FileLocation>
    </File>
    <File>
        <Type>Send</Type>
        <FileName>abc</FileName>            
        <Port>7252</Port>
        <FileLocation>D:/test</FileLocation>
    </File>
  </Files>
</Root>

For completeness here are the serialization types: 为了完整起见,以下是序列化类型:

public class Root
{
    public File Default;
    public List<File> Files;
}

public class File
{
   public int Port;
   public string FileName;
   public string FileLocation;
}

I can suggest the following approach. 我可以建议以下方法。

Create xml schema, which will set default values for certain elements. 创建xml模式,该模式将为某些元素设置默认值。

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Files">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="File">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Type"         type="xs:string" />
              <xs:element name="FileName"     type="xs:string" />
              <xs:element name="Port"         type="xs:int"    default="7252" />
              <xs:element name="FileLocation" type="xs:string" default="D:/test" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Your xml data file should look like this: 您的xml数据文件应如下所示:

<?xml version="1.0" encoding="utf-8"?>
<Files>
  <File>
    <Type>Send</Type>
    <FileName>xyz</FileName>
    <Port>7252</Port>
    <FileLocation>c:/test</FileLocation>
  </File>
  <File>
    <Type>Send</Type>
    <FileName>abc</FileName>
    <Port></Port>
    <FileLocation></FileLocation>
  </File>
</Files>

So are your classes for deserialization: 反序列化的类也是如此:

public class Files
{
    [XmlElement("File")]
    public File[] File { get; set; }
}

public class File
{
    public string Type { get; set; }
    public string FileName { get; set; }
    public int Port { get; set; }
    public string FileLocation { get; set; }
}

Now, it is enough to add a xml schema during deserialization to retrieve default values. 现在,在反序列化过程中添加xml架构以检索默认值就足够了。

Files files;

var settings = new XmlReaderSettings();
settings.Schemas.Add("", "test.xsd");
settings.ValidationType = ValidationType.Schema;

var xs = new XmlSerializer(typeof(Files));
using (var reader = XmlReader.Create("test.xml", settings))
{
    files = (Files)xs.Deserialize(reader);
}

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

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