Serialize Your Deck with Positron [XML Serialization, XSD, C#]
Written by Allen Lee
0. Table of Content
1. Positron S
在《Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]》中,我们体验了 XLinq 是如何简化我们的 XML 处理工作,但现阶段就把使用 XLinq 的程序部署到用户的电脑未免有点为时过早。这次,我们来看看采用业已成熟的 XML Serialization 技术的 Positron,为了标识使用不同技术的 Positron,我在其后加上一个标识字母,目前 Positron 有两个版本:
注意:本文将沿用《Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]》中的 sample.xml 作为原始数据,而某些设计决策也将基于该文的部分分析,如果你没有读过该文,我强烈建议你先浏览一遍。
2. xsd.exe
xsd.exe 是一个神奇的转换工具,它提供了
等一系列的转换功能。当你用它来生成代码文件时,如果你没有明确指示使用何种语言,它将默认生成 .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 属性将这两者重新关联起来。重排后的版本如下:
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 作为例子:
首先,我定义一个命名枚举类型:
这里需要注意的有两点:
然后,把 category 属性的 type 设置为 MonsterCardCategory:
接着,我们可以用同样的方法处理其它枚举类型的属性。
3.5 <xs:simpleContent> vs. <xs:complexContent>
重读 sample.xsd,你会发现,无论是 MonsterCard、SpellCard 或者 TrapCard,都有着三个功能相同的成员:img、name 和 body text。为了减少重复,我决定对它们进行泛化,提取公共部分。
首先,我定义一个 Card 类型:
注意:我将 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 来示范如何实现继承:
虽然这三种卡都有 category 属性,但因为该属性实际上具有不同的含义,并且类型也不同,所以不被纳入它们的共性。
3.6 cards.xsd
至此,我们已经完成了整个 XML Schema 的制作了:
评论