Xml 排序和 XMLRoot 属性中的序列化问题

Xml Serialization Issues in Ordering and XMLRoot attributes

我正在编写 class 来序列化 KiCAD BOM/Schematic 文件。 class 工作正常并正确序列化和反序列化原始文件,但我有两个小问题无法修复。 这是 class 代码:

[XmlRoot(ElementName = "export")]
public class SchematicExport
{
    [XmlAttribute]
    public const string version = "D";

    public Design design { get; set; }

    [XmlArrayItem(typeof(Component), ElementName = "comp")]
    public List<Component> components;

    [XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
    public List<LibPart> libparts;

    [XmlArrayItem(typeof(Library), ElementName = "library")]
    public List<Library> libraries;

    [XmlArrayItem(typeof(Net), ElementName = "net")]
    public List<Net> nets;

    public class Design
    {
        public string source { get; set; }

        public string date
        {
            get => _date.ToString("dd/MM/yyyy HH:mm:ss");
            set => _date = DateTime.Parse(value);
        }

        private DateTime _date;

        public string tool { get; set; }

        [XmlElement("sheet")]
        public List<Sheet> sheets { get; }

        public class Sheet
        {
            [XmlAttribute]
            public int number { get; set; }

            [XmlAttribute]
            public string name { get; set; }

            [XmlAttribute]
            public string tstamps { get; set; }

            public TitleBlock title_block { get; set; }

            public class TitleBlock
            {
                public string title { get; set; }

                public string company { get; set; }

                public string rev { get; set; }

                public string date
                {
                    get => _date.ToString("yyyy-MM-dd");
                    set => _date = DateTime.Parse(value);
                }

                private DateTime _date;

                public string source { get; set; }

                [XmlElement("comment")]
                public List<Comment> comments;

                public class Comment
                {
                    [XmlAttribute]
                    public int number;

                    [XmlAttribute]
                    public string value;

                    public Comment()
                    {
                    }

                    public Comment(int number, string value) : this()
                    {
                        this.number = number;
                        this.value = value;
                    }
                }

                public TitleBlock()
                {
                    comments = new List<Comment>();
                    _date = DateTime.Now;
                }

                public TitleBlock(string title, string company, string rev, string date, string source)
                {
                    this.title = title;
                    this.company = company;
                    this.rev = rev;
                    this.date = date;
                    this.source = source;
                }
            }

            public Sheet()
            {
                title_block = new TitleBlock();
                tstamps = DateTime.Now.ToFileTime().ToString("X8");
            }

            public Sheet(int number, string name) : this()
            {
                this.number = number;
                this.name = name;
            }
        }

        public Design()
        {
            sheets = new List<Sheet>();
            _date = DateTime.Now;
        }

        public Design(string source, string date, string tool) : this()
        {
            this.source = source;
            this.date = date;
            this.tool = tool;
        }
    }

    public class Component
    {
        [XmlAttribute("ref")]
        public string reference { get; set; }
        public string value { get; set; }
        public string footprint { get; set; }
        public string datasheet { get; set; }

        [XmlArrayItem(typeof(Field), ElementName = "field")]
        public List<Field> fields;

        public LibSource libsource { get; set; }
        public SheetPath sheetpath { get; set; }
        public string tstamp { get; set; }

        public class LibSource
        {
            [XmlAttribute]
            public string lib { get; set; }
            [XmlAttribute]
            public string part { get; set; }
            [XmlAttribute]
            public string description { get; set; }
        }

        public class SheetPath
        {
            [XmlAttribute]
            public string names { get; set; }
            [XmlAttribute]
            public string tstamps { get; set; }
        }

        public Component()
        {
            fields = new List<Field>();
            libsource = new LibSource();
            sheetpath = new SheetPath();

            tstamp = DateTime.Now.ToFileTime().ToString("X8");
        }

        public Component(string reference, string value, string footprint) : this()
        {
            this.reference = reference;
            this.value = value;
            this.footprint = footprint;
        }
    }

    public class LibPart
    {
        [XmlAttribute]
        public string lib { get; set; }

        [XmlAttribute]
        public string part { get; set; }

        [XmlArrayItem(typeof(Field), ElementName = "field")]
        public List<Field> fields;

        [XmlArrayItem(typeof(string), ElementName = "fp")]
        public List<string> footprints;

        [XmlArrayItem(typeof(Pin), ElementName = "pin")]
        public List<Pin> pins;

        public class Pin
        {
            [XmlAttribute]
            public string num { get; set; }

            [XmlAttribute]
            public string name { get; set; }

            [XmlAttribute]
            public string type { get; set; }

            public Pin()
            {
            }

            public Pin(string num, string name, string type) : this()
            {
                this.num = num;
                this.name = name;
                this.type = type;
            }
        }

        public LibPart()
        {
            fields = new List<Field>();
            footprints = new List<string>();
            pins = new List<Pin>();
        }

        public LibPart(string lib, string part) : this()
        {
            this.lib = lib;
            this.part = part;
        }
    }

    public class Library
    {
        [XmlAttribute]
        public string logical { get; set; }

        public string uri { get; set; }

        public Library() { }

        public Library(string logical, string uri) : this()
        {
            this.logical = logical;
            this.uri = uri;
        }
    }

    public class Net
    {
        [XmlAttribute]
        public string code { get; set; }

        [XmlAttribute]
        public string name { get; set; }

        [XmlElement("node")]
        public List<Node> nodes;

        public class Node
        {
            [XmlAttribute("ref")]
            public string reference { get; set; }

            [XmlAttribute]
            public string pin { get; set; }

            public Node() { }

            public Node(string reference, string pin) : this()
            {
                this.reference = reference;
                this.pin = pin;
            }
        }

        public Net()
        {
            nodes = new List<Node>();
        }

        public Net(string code, string name) : this()
        {
            this.code = code;
            this.name = name;
        }
    }

    public class Field
    {
        [XmlAttribute]
        public string name { get; set; }

        [XmlText]
        public string value { get; set; }

        public Field()
        {
        }

        public Field(string name, string value) : this()
        {
            this.name = name;
            this.value = value;
        }
    }

    public SchematicExport()
    {
        design = new Design();
        components = new List<Component>();
        libparts = new List<LibPart>();
        libraries = new List<Library>();
        nets = new List<Net>();
    }

    public void Serialze(string filename)
    {
        XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
        TextWriter _textWriter = new StreamWriter(filename);
        _xmlSerializer.Serialize(_textWriter, this);
    }

    public static SchematicExport Create(string filename)
    {
        if (!System.IO.File.Exists(filename))
            throw new Exception("File not existing");

        SchematicExport _schematicExport = null;

        XmlSerializer _serializer = new XmlSerializer(typeof(SchematicExport));

        StreamReader _reader = new StreamReader(filename);
        _schematicExport = (SchematicExport)_serializer.Deserialize(_reader);

        return _schematicExport;
    }
}

第一个问题是关于 SchematicExportXmlRoot 元素。我希望 version 属性出现在根 XML 标记内,但它没有:

<export xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

第二个问题是元素的顺序(好的,这不是一个大问题,因为标签不依赖于顺序)我尝试使用 XmlElement(Order=nn) 属性来强制它,但我只得到了一些 Reflection 异常。

我刚刚通过在序列化程序中稍作修改来删除 xmlns:xsixmlns:xsd

public void Serialze(string filename)
{
    XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport)); 
    XmlSerializerNamespaces _serializerNamespaces = new XmlSerializerNamespaces();

    _serializerNamespaces.Add("", "");

    using (TextWriter _textWriter = new StreamWriter(filename))
    {
        _xmlSerializer.Serialize(_textWriter, this, _serializerNamespaces);
    }
}

我发现了另一个(更恶意的)问题,即使我可以反序列化这样的 Xml:

?xml version="1.0" encoding="UTF-8"?>
<export version="D">
  <design>
    <source>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119\V1-10A119.sch</source>
    <date>07/03/2019 10:33:01</date>
    <tool>Eeschema (5.0.2)-1</tool>
    <sheet number="1" name="/" tstamps="/">
      <title_block>
        <title>V1-10A119</title>
        <company>ROB.INT. s.r.l.</company>
        <rev>01</rev>
        <date>2019-02-25</date>
        <source>V1-10A119.sch</source>
        <comment number="1" value="Michele Santucci"/>
        <comment number="2" value=""/>
        <comment number="3" value=""/>
        <comment number="4" value=""/>
      </title_block>
    </sheet>
  </design>
  <components>
    <comp ref="U5">
      <value>ADM3101E</value>
      <footprint>Analog:LFCSP-12_EP_3x3_Pitch0.5mm</footprint>
      <datasheet>https://www.analog.com/media/en/technical-documentation/data-sheets/adm3101e.pdf</datasheet>
      <fields>
        <field name="Code">820-9369 </field>
        <field name="P/N">ADM3101EACPZ-250R7 </field>
        <field name="Productor">Analog</field>
        <field name="Provider">RS</field>
        <field name="manf#">ADM3101EACPZ-250R7 </field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="ADM3101E-analog" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C83E717</tstamp>
    </comp>
    <comp ref="C35">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AA07</tstamp>
    </comp>
    <comp ref="C37">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB0B</tstamp>
    </comp>
    <comp ref="C34">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB65</tstamp>
    </comp>
    <comp ref="C38">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AC22</tstamp>
    </comp>
    <comp ref="C36">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84D1BA</tstamp>
    </comp>
  </components>
  <libparts>
    <libpart lib="conn" part="CONN_2">
      <description>Symbole general de connecteur</description>
      <fields>
        <field name="Reference">P</field>
        <field name="Value">CONN_2</field>
      </fields>
      <pins>
        <pin num="1" name="P1" type="passive"/>
        <pin num="2" name="PM" type="passive"/>
      </pins>
    </libpart>
  </libparts>
  <libraries>
    <library logical="V1-10A119-rescue">
      <uri>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119/V1-10A119-rescue.lib</uri>
    </library>
    <library logical="conn">
      <uri>C:/Users/Public/Documents/Kicad/library/conn.lib</uri>
    </library>
  </libraries>
  <nets>
    <net code="200" name="VBUS">
      <node ref="P9" pin="2"/>
      <node ref="U3" pin="3"/>
      <node ref="C12" pin="2"/>
      <node ref="C30" pin="2"/>
      <node ref="U4" pin="3"/>
    </net>
    <net code="201" name="Net-(LD13-Pad1)">
      <node ref="LD13" pin="1"/>
      <node ref="R37" pin="1"/>
    </net>
  </nets>
</export>

生成的序列化不完整:

<?xml version="1.0" encoding="UTF-8"?>
<export version="D">
  <design>
    <source>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119\V1-10A119.sch</source>
    <date>07/03/2019 10:33:01</date>
    <tool>Eeschema (5.0.2)-1</tool>
    <sheet number="1" name="/" tstamps="/">
      <title_block>
        <title>V1-10A119</title>
        <company>ROB.INT. s.r.l.</company>
        <rev>01</rev>
        <date>2019-02-25</date>
        <source>V1-10A119.sch</source>
        <comment number="1" value="Michele Santucci"/>
        <comment number="2" value=""/>
        <comment number="3" value=""/>
        <comment number="4" value=""/>
      </title_block>
    </sheet>
  </design>
  <components>
    <comp ref="U5">
      <value>ADM3101E</value>
      <footprint>Analog:LFCSP-12_EP_3x3_Pitch0.5mm</footprint>
      <fields>
        <field name="Code">820-9369 </field>
        <field name="P/N">ADM3101EACPZ-250R7 </field>
        <field name="Productor">Analog</field>
        <field name="Provider">RS</field>
        <field name="manf#">ADM3101EACPZ-250R7 </field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="ADM3101E-analog" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C83E717</tstamp>
    </comp>
    <comp ref="C35">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AA07</tstamp>
    </comp>
    <comp ref="C37">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB0B</tstamp>
    </comp>
    <comp ref="C34">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB65</tstamp>
    </comp>
    <comp ref="C38">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AC22</tstamp>
    </comp>
    <comp ref="C36">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84D1BA</tstamp>
    </comp>
  </components>
  <libparts>
    <libpart lib="conn" part="CONN_2">
      <fields>
        <field name="Reference">P</field>
        <field name="Value">CONN_2</field>
      </fields>
      <pins>
        <pin num="1" name="P1" type="passive"/>
        <pin num="2" name="PM" type="passive"/>
      </pins>
    </libpart>
  </libparts>
  <libraries>
    <library logical="V1-10A119-rescue">
      <uri>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119/V1-10A119-rescue.lib</uri>
    </library>
    <library logical="conn">
      <uri>C:/Users/Public/Documents/Kicad/library/conn.lib</uri>
    </library>
  </libraries>
  <nets>
    <net code="200" name="VBUS">
      <node ref="P9" pin="2"/>
      <node ref="U3" pin="3"/>
      <node ref="C12" pin="2"/>
      <node ref="C30" pin="2"/>
      <node ref="U4" pin="3"/>
    </net>
    <net code="201" name="Net-(LD13-Pad1)">
      <node ref="LD13" pin="1"/>
      <node ref="R37" pin="1"/>
    </net>
  </nets>
</export>

如您所见,缺少 <datasheet> 标签(comp 部分)以及 <description>(libpart 部分)。

我真的不明白哪里出了问题,但显然问题不在反序列化中:

第一期是关于XmlRoot元素的。我希望 version 属性出现在 XMLRoot 标签内,但它没有。

Introducing XML Serialization 中所述,即使在 public:[=37 时,XmlSerializer 也不会序列化 const 成员=]

XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information.

<snip>

XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.

解决此问题的最简单方法是为版本添加一个替代项 属性,如下所示:

public const string version = "D";

[XmlAttribute("version")]
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public string Version { get { return version; } set { /* Do nothing */ } }

属性 必须是 public,但您可以通过属性 Browsable, EditorBrowsable and DebuggerBrowsable.

降低其可见性

第二个问题是元素的顺序(好吧,这不是什么大问题,因为标签不依赖于顺序)我尝试使用 XmlElement(Order=nn) 属性来强制它,但我只是得到了一个Reflection 个异常数。

您没有提供这方面的示例,但我能够重现以下问题。如果我在 some 而不是 SchematicExport 的集合成员的 all 上设置 XmlArrayAttribute.Order,我会得到以下异常:

System.InvalidOperationException: There was an error reflecting type 'SchematicExport'. 
---> System.InvalidOperationException: Inconsistent sequencing: if used on one of the class's members, the 'Order' property is required on all particle-like members, please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member 'nets'.
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)

演示 fiddle #1 here.

解决方案是遵循异常消息中的建议,将 order 属性应用于 class 中的所有可序列化成员。

因此 SchematicExport 应该类似于:

[XmlRoot(ElementName = "export")]
public partial class SchematicExport
{
    public const string version = "D";

    [XmlAttribute("version")]
    [System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
    public string Version { get { return version; } set { /* Do nothing */ } }

    [XmlElement(Order = 1)]
    public Design design { get; set; }

    [XmlArray(Order = 2)]
    [XmlArrayItem(typeof(Component), ElementName = "comp")]
    public List<Component> components;

    [XmlArray(Order = 3)]
    [XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
    public List<LibPart> libparts;

    [XmlArray(Order = 4)]
    [XmlArrayItem(typeof(Library), ElementName = "library")]
    public List<Library> libraries;

    [XmlArray(Order = 5)]
    [XmlArrayItem(typeof(Net), ElementName = "net")]
    public List<Net> nets;
}

请注意,设置元素顺序不仅会在序列化期间对元素重新排序,而且要求它们在反序列化期间按该顺序排列。

顺便说一句,在 SchematicExport.Serialize(string) 中,您需要关闭 StreamWriter。最简单的方法是通过 using 语句:

public void Serialze(string filename)
{
    XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
    // FIXED ensure the file is closed.
    using (var _textWriter = new StreamWriter(filename))
    {
        _xmlSerializer.Serialize(_textWriter, this);
    }
}

您问题中显示的此方法的第一个版本不会执行此操作。

演示 fiddle #2 here.