Serialize Your Deck with Positron [XML Serialization, XSD, C#]

Serialize Your Deck with Positron [XML Serialization, XSD, C#]

Written by Allen Lee

0. Table of Content

  • 1. Positron S
  • 2. xsd.exe
  • 3. From .xml to .xsd
  • 4. From .xsd to .cs
  • 5. Serialize Your Deck
  • 6. What's More...
  • R. References

1. Positron S

《Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]》中,我们体验了 XLinq 是如何简化我们的 XML 处理工作,但现阶段就把使用 XLinq 的程序部署到用户的电脑未免有点为时过早。这次,我们来看看采用业已成熟的 XML Serialization 技术的 Positron,为了标识使用不同技术的 Positron,我在其后加上一个标识字母,目前 Positron 有两个版本:

  • 1) Positron Q:Q 版 Positron 使用了 XLinq 技术,“Q”代表 Query
  • 2) Positron S:S 版 Positron 使用了 XML Serialization 技术,“S”代表 Serialization

注意:本文将沿用《Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]》中的 sample.xml 作为原始数据,而某些设计决策也将基于该文的部分分析,如果你没有读过该文,我强烈建议你先浏览一遍。

2. xsd.exe

xsd.exe 是一个神奇的转换工具,它提供了

  • 1) XDR to XSD
  • 2) XML to XSD
  • 3) XSD to DataSet
  • 4) XSD to Classes
  • 5) Classes to XSD

等一系列的转换功能。当你用它来生成代码文件时,如果你没有明确指示使用何种语言,它将默认生成 .cs 文件。你可以使用 /l 参数来显示指定使用何种语言,xsd.exe 支持 CS、VB、JS 和 VJS 等语言。在这篇文章,我将会介绍 XML to XSD 和 XSD to Classes 这两种转换。

3. From .xml to .xsd

3.1 Generate sample.xsd with xsd.exe

打开 SDK Command Prompt,去到 sample.xml 所在的目录并输入

xsd sample.xml

然后按下 [Enter],xsd.exe 将在当前目录生成一个 sample.xsd 文件。但这个自动生成版的布局不便于我们对其展开讨论,于是我对其进行等效重排。方法是将原来的匿名类型变为命名类型并从其所属元素中分离出来,然后使用 <xs:element> 的 type 属性将这两者重新关联起来。重排后的版本如下:

ContractedBlock.gif ExpandedBlockStart.gif sample.xsd
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->None.gif<?xmlversion="1.0"encoding="utf-8"?>
None.gif
<xs:schemaid="cards"xmlns=""xmlns:xs="http://www.w3.org/2001/XMLSchema"xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
None.gif
None.gif
<!--MonsterCard-->
None.gif
<xs:complexTypename="MonsterCard">
None.gif
<xs:simpleContentmsdata:ColumnName="monstercard_Text"msdata:Ordinal="8">
None.gif
<xs:extensionbase="xs:string">
None.gif
<xs:attributename="img"type="xs:string"/>
None.gif
<xs:attributename="category"type="xs:string"/>
None.gif
<xs:attributename="name"type="xs:string"/>
None.gif
<xs:attributename="attribute"type="xs:string"/>
None.gif
<xs:attributename="level"type="xs:string"/>
None.gif
<xs:attributename="type"type="xs:string"/>
None.gif
<xs:attributename="atk"type="xs:string"/>
None.gif
<xs:attributename="def"type="xs:string"/>
None.gif
</xs:extension>
None.gif
</xs:simpleContent>
None.gif
</xs:complexType>
None.gif
None.gif
<!--SpellCard-->
None.gif
<xs:complexTypename="SpellCard">
None.gif
<xs:simpleContentmsdata:ColumnName="spellcard_Text"msdata:Ordinal="3">
None.gif
<xs:extensionbase="xs:string">
None.gif
<xs:attributename="img"type="xs:string"/>
None.gif
<xs:attributename="category"type="xs:string"/>
None.gif
<xs:attributename="name"type="xs:string"/>
None.gif
</xs:extension>
None.gif
</xs:simpleContent>
None.gif
</xs:complexType>
None.gif
None.gif
<!--TrapCard-->
None.gif
<xs:complexTypename="TrapCard">
None.gif
<xs:simpleContentmsdata:ColumnName="trapcard_Text"msdata:Ordinal="3">
None.gif
<xs:extensionbase="xs:string">
None.gif
<xs:attributename="img"type="xs:string"/>
None.gif
<xs:attributename="category"type="xs:string"/>
None.gif
<xs:attributename="name"type="xs:string"/>
None.gif
</xs:extension>
None.gif
</xs:simpleContent>
None.gif
</xs:complexType>
None.gif
None.gif
<!--Cards-->
None.gif
<xs:complexTypename="Cards">
None.gif
<xs:choiceminOccurs="0"maxOccurs="unbounded">
None.gif
<xs:elementname="monstercards">
None.gif
<xs:complexType>
None.gif
<xs:sequence>
None.gif
<xs:elementname="monstercard"type="MonsterCard"nillable="true"minOccurs="0"maxOccurs="unbounded"/>
None.gif
</xs:sequence>
None.gif
</xs:complexType>
None.gif
</xs:element>
None.gif
<xs:elementname="spellcards">
None.gif
<xs:complexType>
None.gif
<xs:sequence>
None.gif
<xs:elementname="spellcard"type="SpellCard"nillable="true"minOccurs="0"maxOccurs="unbounded"/>
None.gif
</xs:sequence>
None.gif
</xs:complexType>
None.gif
</xs:element>
None.gif
<xs:elementname="trapcards">
None.gif
<xs:complexType>
None.gif
<xs:sequence>
None.gif
<xs:elementname="trapcard"type="TrapCard"nillable="true"minOccurs="0"maxOccurs="unbounded"/>
None.gif
</xs:sequence>
None.gif
</xs:complexType>
None.gif
</xs:element>
None.gif
</xs:choice>
None.gif
</xs:complexType>
None.gif
None.gif
<xs:elementname="cards"type="Cards"msdata:IsDataSet="true"msdata:UseCurrentLocale="true"/>
None.gif
None.gif
</xs:schema>

3.2 <xs:choice> vs. <xs:all>

在 Cards 的类型定义中,xsd.exe 将其子元素的排布方式设置为 <xs:choice>,这个意味着其子元素 monstercards、spellcards 和 trapcards 只有一种出现,而该种子元素的出现次数可以任意。这明显不符合原设计理念。

all 指示所有的子元素可以以任意顺序出现,且每种子元素最多只能出现一次。我假设 Positron S 的用户懂得均衡卡组,即卡组中既有怪兽卡又有魔法卡和陷阱卡,并使不同种类的卡片数目达到一个恰当的比例。于是,我将三种子元素的排布方式改为:

<xs:all>...</xs:all>

注意:如果我们没有显式为 <xs:all> 指明 minOccurs 和 maxOccurs 的值,它们都将会使用默认值1,即每种子元素都必须出现一次也只能出现一次。

3.3 xs:string vs. xs:int

在 MonsterCard 的类型定义中,xsd.exe 把 level、atk 和 def 三个属性的类型指定为 xs:string,但我们很清楚这些属性的值是整数,所以我把它们都改为 xs:int。这样做的好处不仅仅在于让人能从 sample.xsd 中了解到这三个属性的值是整数类型,更重要的是将来使用 xsd.exe 根据 sample.xsd 生成 sample.cs 时,MonsterCard 类中的 m_Level 字段以及 Level 属性能被自动映射为 Int32 类型。并且在反序列化时,让 XmlSerializer 为你进行数值的解析而不必亲自动手。

3.4 xs:string vs. xs:enumeration

我们知道 MonsterCard 的类型定义中的 category、attribute 和 type 其实是枚举类型,我希望将来使用 xsd.exe 生成代码文件时,它懂得把这些属性映射为 .NET 的枚举类型。为了达到这个目的,我们需要独立定制这些属性的类型,并使用 <xs:attribute> 的 type 属性进行类型关联,即我先前所说“等效重排”。下面我将以 MonsterCard 的 category 作为例子:

首先,我定义一个命名枚举类型:

None.gif < xs:simpleType name ="MonsterCardCategory" >
None.gif
< xs:restriction base ="xs:string" >
None.gif
< xs:enumeration value ="Normal" />
None.gif
< xs:enumeration value ="Effect" />
None.gif
< xs:enumeration value ="Fusion" />
None.gif
< xs:enumeration value ="Ritual" />
None.gif
</ xs:restriction >
None.gif
</ xs:simpleType >

这里需要注意的有两点:

  • 1) 枚举类型必须为命名类型,否则 xsd.exe 会忽略之并把 category 映射为 String 类型
  • 2) 枚举类型的基类型必须为 xs:string 或兼容类型,否则 xsd.exe 不会将之当作一回事

然后,把 category 属性的 type 设置为 MonsterCardCategory:

None.gif < xs:attribute name ="category" type ="MonsterCardCategory" />

接着,我们可以用同样的方法处理其它枚举类型的属性。

3.5 <xs:simpleContent> vs. <xs:complexContent>

重读 sample.xsd,你会发现,无论是 MonsterCard、SpellCard 或者 TrapCard,都有着三个功能相同的成员:img、name 和 body text。为了减少重复,我决定对它们进行泛化,提取公共部分。

首先,我定义一个 Card 类型:

None.gif < xs:complexType name ="Card" abstract ="true" >
None.gif
< xs:simpleContent msdata:ColumnName ="description" msdata:Ordinal ="2" >
None.gif
< xs:extension base ="xs:string" >
None.gif
< xs:attribute name ="img" type ="xs:string" use ="required" />
None.gif
< xs:attribute name ="name" type ="xs:string" use ="required" />
None.gif
</ xs:extension >
None.gif
</ xs:simpleContent >
None.gif
</ xs:complexType >

注意:我将 Card 的 abstract 属性设为 true,这点很重要,它保证了在将来的 XML 文档中出现的是 Card 的继承类型而不是 Card 这个类型。这一点和程序语言的抽象类在设计理念上是一致的。

然后让 MonsterCard、SpellCard 和 TrapCard 继承 Card,要做到这点,我们可以修改 <xs:extension> 的 base 属性,使其指向 Card。

然而,<xs:extension> 用在 <xs:simpleContent> 或者 <xs:complexContent> 上会对 xsd.exe 所生成的代码产生不同的影响。对于前者,xsd.exe 会把 base 属性所指定的类型映射为类的一个字段,即我们通常说的 Composition;对于后者,情形就是我们所熟悉的 Inheritance。很明显,这里我们应该选用 Inheritance,因为 Card 的 abstract 属性被设为 true,如果使用 Composition 的话,抽象类 Card 作为类的一个字段而存在,必须有(非抽象)派生类才能产生实例变量,这样我们就重新回到 Inheritance 了。

现在,我用 SpellCard 来示范如何实现继承:

None.gif < xs:complexType name ="SpellCard" >
None.gif
< xs:complexContent >
None.gif
< xs:extension base ="Card" >
None.gif
< xs:attribute name ="category" type ="SpellCardCategory" />
None.gif
</ xs:extension >
None.gif
</ xs:complexContent >
None.gif
</ xs:complexType >

虽然这三种卡都有 category 属性,但因为该属性实际上具有不同的含义,并且类型也不同,所以不被纳入它们的共性。

3.6 cards.xsd

至此,我们已经完成了整个 XML Schema 的制作了:

ContractedBlock.gif ExpandedBlockStart.gif cards.xsd
<!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>-->None.gif<?xmlversion="1.0"encoding="utf-8"?>
None.gif
<xs:schemaid="cards"xmlns=""xmlns:xs="http://www.w3.org/2001/XMLSchema">
None.gif
None.gif
<!--enumMonsterCardCategory-->
None.gif
<xs:simpleTypename="MonsterCardCategory">
None.gif
<xs:restrictionbase="xs:string">
None.gif
<xs:enumerationvalue="Normal"/>
None.gif
<xs:enumerationvalue="Effect"/>
None.gif
<xs:enumerationvalue="Fusion"/>
None.gif
<xs:enumerationvalue="Ritual"/>
None.gif
</xs:restriction>
None.gif
</xs:simpleType>
None.gif
None.gif
<!--enumMonsterAttribute-->
None.gif
<xs:simpleTypename="MonsterAttribute">
None.gif
<xs:restrictionbase="xs:string">
None.gif
<xs:enumerationvalue="Earth"/>
None.gif
<xs:enumerationvalue="Water"/>
None.gif
<xs:enumerationvalue="Fire"/>
None.gif
<xs:enumerationvalue="Wind"/>
None.gif
<xs:enumerationvalue="Light"/>
None.gif
<xs:enumerationvalue="Dark"/>
None.gif
</xs:restriction>
None.gif
</xs:simpleType>
None.gif
None.gif
<!--enumMonsterType-->
None.gif
<xs:simpleTypename="MonsterType">
None.gif
<xs:restrictionbase="xs:string">
None.gif
<xs:enumerationvalue="Dragon"/>
None.gif
<xs:enumerationvalue="Spellcaster"/>
None.gif
<xs:enumerationvalue="Zombie"/>
None.gif
<xs:enumerationvalue="Warrior"/>
None.gif
<xs:enumerationvalue="BeastWarrior"/>
None.gif
<xs:enumerationvalue="Beast"/>
None.gif
<xs:enumerationvalue="WingedBeast"/>
None.gif
<xs:enumerationvalue="Fiend"/>
None.gif
<xs:enumerationvalue="Fairy"/>
None.gif
<xs:enumerationvalue="Insert"/>
None.gif
<xs:enumerationvalue="Dinosaur"/>
None.gif
<xs:enumerationvalue="Reptile"/>
None.gif
<xs:enumerationvalue="Fish"/>
None.gif
<xs:enumerationvalue="SeaSerpent"/>
None.gif
<xs:enumerationvalue="Machine"/>
None.gif
<xs:enumerationvalue="Thunder"/>
None.gif
<xs:enumerationvalue="Aqua"/>
None.gif
<xs:enumerationvalue="Pyro"/>
None.gif
<xs:enumerationvalue="Rock"/>
None.gif
<xs:enumerationvalue="Plant"/>
None.gif
</xs:restriction>
None.gif
</xs:simpleType>
None.gif
None.gif
<!--enumSpellCardCategory-->
None.gif
<xs:simpleTypename="SpellCardCategory">
None.gif
<xs:restrictionbase="xs:string">
None.gif
<xs:enumerationvalue="Normal"/>
None.gif
<xs:enumerationvalue="Continuous"/>
None.gif
<xs:enumerationvalue="Equip"/>
None.gif
<xs:enumerationvalue="Field"/>
None.gif
<xs:enumerationvalue="QuickPlay"/>
None.gif
<xs:enumerationvalue="Ritual"/>
None.gif
</xs:restriction>
None.gif
</xs:simpleType>
None.gif
None.gif
<!--enumTrapCardCategory-->
None.gif
<xs:simpleTypename="TrapCardCategory">
None.gif
<xs:restrictionbase="xs:string">
None.gif
<xs:enumerationvalue="Normal"/>
None.gif
<xs:enumerationvalue="Counter"/>
None.gif
<xs:enumerationvalue="Continuous"/>
None.gif
</xs:restriction>
None.gif
</xs:simpleType>
None.gif
None.gif
<!--abstractclassCard-->
None.gif
<xs:complexTypename="Card"abstract="true">
None.gif
<xs:simpleContent>
None.gif
<xs:extensionbase="xs:string">
None.gif
<xs:attributename="img"type="xs:string"use="required"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值