Serialize Your Deck with Positron [XML Serialization, XSD, C#]
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 檔案。但這個自動生成版的佈局不便於我們對其展開討論,於是我對其進行等效重排。方法是將原來的匿名型別變為命名型別並從其所屬元素中分離出來,然後使用
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
-->xml version="1.0" encoding="utf-8"?>
<xs:schema id="cards" xmlns="" xmlns:xs="" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<!-- MonsterCard -->
<xs:complexType name="MonsterCard">
<xs:simpleContent msdata:ColumnName="monstercard_Text" msdata:Ordinal="8">
<xs:extension base="xs:string">
<xs:attribute name="img" type="xs:string" />
<xs:attribute name="category" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="attribute" type="xs:string" />
<xs:attribute name="level" type="xs:string" />
<xs:attribute name="type" type="xs:string" />
<xs:attribute name="atk" type="xs:string" />
<xs:attribute name="def" type="xs:string" />
xs:extension>
xs:simpleContent>
xs:complexType>
<!-- SpellCard -->
<xs:complexType name="SpellCard">
<xs:simpleContent msdata:ColumnName="spellcard_Text" msdata:Ordinal="3">
<xs:extension base="xs:string">
<xs:attribute name="img" type="xs:string" />
<xs:attribute name="category" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
xs:extension>
xs:simpleContent>
xs:complexType>
<!-- TrapCard -->
<xs:complexType name="TrapCard">
<xs:simpleContent msdata:ColumnName="trapcard_Text" msdata:Ordinal="3">
<xs:extension base="xs:string">
<xs:attribute name="img" type="xs:string" />
<xs:attribute name="category" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
xs:extension>
xs:simpleContent>
xs:complexType>
<!-- Cards -->
<xs:complexType name="Cards">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="monstercards">
<xs:complexType>
<xs:sequence>
<xs:element name="monstercard" type="MonsterCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
<xs:element name="spellcards">
<xs:complexType>
<xs:sequence>
<xs:element name="spellcard" type="SpellCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
<xs:element name="trapcards">
<xs:complexType>
<xs:sequence>
<xs:element name="trapcard" type="TrapCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
xs:choice>
xs:complexType>
<xs:element name="cards" type="Cards" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" />
xs:schema>
3.2
在 Cards 的型別定義中,xsd.exe 將其子元素的排布方式設定為
all 指示所有的子元素可以以任意順序出現,且每種子元素最多隻能出現一次。我假設 Positron S 的使用者懂得均衡卡組,即卡組中既有怪獸卡又有魔法卡和陷阱卡,並使不同種類的卡片數目達到一個恰當的比例。於是,我將三種子元素的排布方式改為:
注意:如果我們沒有顯式為
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:restriction base="xs:string">
<xs:enumeration value="Normal" />
<xs:enumeration value="Effect" />
<xs:enumeration value="Fusion" />
<xs:enumeration value="Ritual" />
xs:restriction>
xs:simpleType>
這裡需要注意的有兩點:
- 1) 列舉型別必須為命名型別,否則 xsd.exe 會忽略之並把 category 對映為 String 型別
- 2) 列舉型別的基型別必須為 xs:string 或相容型別,否則 xsd.exe 不會將之當作一回事
然後,把 category 屬性的 type 設定為 MonsterCardCategory:
接著,我們可以用同樣的方法處理其它列舉型別的屬性。
3.5
重讀 sample.xsd,你會發現,無論是 MonsterCard、SpellCard 或者 TrapCard,都有著三個功能相同的成員:img、name 和 body text。為了減少重複,我決定對它們進行泛化,提取公共部分。
首先,我定義一個 Card 型別:
<xs:simpleContent msdata:ColumnName="description" msdata:Ordinal="2">
<xs:extension base="xs:string">
<xs:attribute name="img" type="xs:string" use="required" />
<xs:attribute name="name" type="xs:string" use="required" />
xs:extension>
xs:simpleContent>
xs:complexType>
注意:我將 Card 的 abstract 屬性設為 true,這點很重要,它保證了在將來的 XML 文件中出現的是 Card 的繼承型別而不是 Card 這個型別。這一點和程式語言的抽象類在設計理念上是一致的。
然後讓 MonsterCard、SpellCard 和 TrapCard 繼承 Card,要做到這點,我們可以修改
然而,
現在,我用 SpellCard 來示範如何實現繼承:
<xs:complexContent>
<xs:extension base="Card">
<xs:attribute name="category" type="SpellCardCategory" />
xs:extension>
xs:complexContent>
xs:complexType>
雖然這三種卡都有 category 屬性,但因為該屬性實際上具有不同的含義,並且型別也不同,所以不被納入它們的共性。
3.6 cards.xsd
至此,我們已經完成了整個 XML Schema 的製作了:
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
-->xml version="1.0" encoding="utf-8"?>
<xs:schema id="cards" xmlns="" xmlns:xs="">
<!-- enum MonsterCardCategory -->
<xs:simpleType name="MonsterCardCategory">
<xs:restriction base="xs:string">
<xs:enumeration value="Normal" />
<xs:enumeration value="Effect" />
<xs:enumeration value="Fusion" />
<xs:enumeration value="Ritual" />
xs:restriction>
xs:simpleType>
<!-- enum MonsterAttribute -->
<xs:simpleType name="MonsterAttribute">
<xs:restriction base="xs:string">
<xs:enumeration value="Earth" />
<xs:enumeration value="Water" />
<xs:enumeration value="Fire" />
<xs:enumeration value="Wind" />
<xs:enumeration value="Light" />
<xs:enumeration value="Dark" />
xs:restriction>
xs:simpleType>
<!-- enum MonsterType -->
<xs:simpleType name="MonsterType">
<xs:restriction base="xs:string">
<xs:enumeration value="Dragon" />
<xs:enumeration value="Spellcaster" />
<xs:enumeration value="Zombie" />
<xs:enumeration value="Warrior" />
<xs:enumeration value="BeastWarrior" />
<xs:enumeration value="Beast" />
<xs:enumeration value="WingedBeast" />
<xs:enumeration value="Fiend" />
<xs:enumeration value="Fairy" />
<xs:enumeration value="Insert" />
<xs:enumeration value="Dinosaur" />
<xs:enumeration value="Reptile" />
<xs:enumeration value="Fish" />
<xs:enumeration value="SeaSerpent" />
<xs:enumeration value="Machine" />
<xs:enumeration value="Thunder" />
<xs:enumeration value="Aqua" />
<xs:enumeration value="Pyro" />
<xs:enumeration value="Rock" />
<xs:enumeration value="Plant" />
xs:restriction>
xs:simpleType>
<!-- enum SpellCardCategory -->
<xs:simpleType name="SpellCardCategory">
<xs:restriction base="xs:string">
<xs:enumeration value="Normal" />
<xs:enumeration value="Continuous" />
<xs:enumeration value="Equip" />
<xs:enumeration value="Field" />
<xs:enumeration value="QuickPlay" />
<xs:enumeration value="Ritual" />
xs:restriction>
xs:simpleType>
<!-- enum TrapCardCategory -->
<xs:simpleType name="TrapCardCategory">
<xs:restriction base="xs:string">
<xs:enumeration value="Normal" />
<xs:enumeration value="Counter" />
<xs:enumeration value="Continuous" />
xs:restriction>
xs:simpleType>
<!-- abstract class Card -->
<xs:complexType name="Card" abstract="true">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="img" type="xs:string" use="required" />
<xs:attribute name="name" type="xs:string" use="required" />
xs:extension>
xs:simpleContent>
xs:complexType>
<!-- class MonsterCard : Card-->
<xs:complexType name="MonsterCard">
<xs:complexContent>
<xs:extension base="Card">
<xs:attribute name="category" type="MonsterCardCategory" use="required" />
<xs:attribute name="attribute" type="MonsterAttribute" use="required" />
<xs:attribute name="level" type="xs:int" use="required" />
<xs:attribute name="type" type="MonsterType" use="required" />
<xs:attribute name="atk" type="xs:int" use="required" />
<xs:attribute name="def" type="xs:int" use="required" />
xs:extension>
xs:complexContent>
xs:complexType>
<!-- class SpellCard : Card -->
<xs:complexType name="SpellCard">
<xs:complexContent>
<xs:extension base="Card">
<xs:attribute name="category" type="SpellCardCategory" use="required" />
xs:extension>
xs:complexContent>
xs:complexType>
<!-- class TrapCard : Card -->
<xs:complexType name="TrapCard">
<xs:complexContent>
<xs:extension base="Card">
<xs:attribute name="category" type="TrapCardCategory" use="required" />
xs:extension>
xs:complexContent>
xs:complexType>
<!-- class Cards -->
<xs:complexType name="Cards">
<xs:all>
<xs:element name="monstercards">
<xs:complexType>
<xs:sequence>
<xs:element name="monstercard" type="MonsterCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
<xs:element name="spellcards">
<xs:complexType>
<xs:sequence>
<xs:element name="spellcard" type="SpellCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
<xs:element name="trapcards">
<xs:complexType>
<xs:sequence>
<xs:element name="trapcard" type="TrapCard" nillable="true" minOccurs="0" maxOccurs="unbounded" />
xs:sequence>
xs:complexType>
xs:element>
xs:all>
xs:complexType>
<xs:element name="cards" type="Cards" />
xs:schema>
值得注意的是,所有型別的屬性的 use 屬性值都被設為 required,這是因為實際的卡片中必定包含這些資訊,這樣做保證了資訊的完整性。另外,我去掉了那些以 msdata 為字首的屬性,因為這些東西僅在你進行 XSD to DataSet 轉換時才有用,而在這裡明顯是垃圾資料,當然,如果你認為將來有可能來一個 XSD to DataSet 的話,你也可以保留它們。
4. From .xsd to .cs
4.1 Generate cards.cs with xsd.exe
再次啟動你的 SDK Command Prompt,去到 cards.xsd 所在的目錄並輸入
xsd cards.xsd /c
然後按下 [Enter],xsd.exe 將在當前目錄生成一個 cards.cs 檔案。為了便於討論,我對程式碼進行等效整理。整理的內容包括:
- 1) 去掉所有的註釋。這些註釋是 xsd.exe 自動生成的,它們不但對於我們的討論不起作用,還妨礙了我們的視線。
- 2) 使用 Attribute 的簡寫形式。例如 [System.Xml.Serialization.XmlAttributeAttribute()] 將被改為 [XmlAttribute()]。
- 3) 重構類的私有欄位的名字。例如 monstercardsField 將被改為 m_MonsterCards。
- 4) 把類內透過 this 使用自身私有欄位改為直接使用私有欄位。例如 this.m_MonsterCards 將被改為 m_MonsterCards。
- 5) 修改一下程式碼的縮排。
整理後的版本如下:
using System;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Positron
{
[Serializable()]
[XmlRoot("cards", Namespace = "", IsNullable = false)]
public partial class Cards
{
private MonsterCard[] m_MonsterCards;
private SpellCard[] m_SpellCards;
private TrapCard[] m_TrapCards;
[XmlArray(Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("monstercard", Form = XmlSchemaForm.Unqualified)]
public MonsterCard[] monstercards
{
get { return m_MonsterCards; }
set { m_MonsterCards = value; }
}
[XmlArray(Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("spellcard", Form = XmlSchemaForm.Unqualified)]
public SpellCard[] spellcards
{
get { return m_SpellCards; }
set { m_SpellCards = value; }
}
[XmlArray(Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("trapcard", Form = XmlSchemaForm.Unqualified)]
public TrapCard[] trapcards
{
get { return m_TrapCards; }
set { m_TrapCards = value; }
}
}
[Serializable()]
public partial class MonsterCard : Card
{
private MonsterCardCategory m_Category;
private MonsterAttribute m_Attribute;
private int m_Level;
private MonsterType m_Type;
private int m_Atk;
private int m_Def;
[XmlAttribute()]
public MonsterCardCategory category
{
get { return m_Category; }
set { m_Category = value; }
}
[XmlAttribute()]
public MonsterAttribute attribute
{
get { return m_Attribute; }
set { m_Attribute = value; }
}
[XmlAttribute()]
public int level
{
get { return m_Level; }
set { m_Level = value; }
}
[XmlAttribute()]
public MonsterType type
{
get { return m_Type; }
set { m_Type = value; }
}
[XmlAttribute()]
public int atk
{
get { return m_Atk; }
set { m_Atk = value; }
}
[XmlAttribute()]
public int def
{
get { return m_Def; }
set { m_Def = value; }
}
}
[Serializable()]
public enum MonsterCardCategory
{
Normal,
Effect,
Fusion,
Ritual,
}
[Serializable()]
public enum MonsterAttribute
{
Earth,
Water,
Fire,
Wind,
Light,
Dark,
}
[Serializable()]
public enum MonsterType
{
Dragon,
Spellcaster,
Zombie,
Warrior,
BeastWarrior,
Beast,
WingedBeast,
Fiend,
Fairy,
Insert,
Dinosaur,
Reptile,
Fish,
SeaSerpent,
Machine,
Thunder,
Aqua,
Pyro,
Rock,
Plant,
}
[Serializable()]
[XmlInclude(typeof(TrapCard))]
[XmlInclude(typeof(SpellCard))]
[XmlInclude(typeof(MonsterCard))]
public abstract partial class Card
{
private string m_Img;
private string m_Name;
private string m_Description;
[XmlAttribute()]
public string img
{
get { return m_Img; }
set { m_Img = value; }
}
[XmlAttribute()]
public string name
{
get { return m_Name; }
set { m_Name = value; }
}
[XmlText()]
public string Value
{
get { return m_Description; }
set { m_Description = value; }
}
}
[Serializable()]
public partial class TrapCard : Card
{
private TrapCardCategory m_Category;
[XmlAttribute()]
public TrapCardCategory category
{
get { return m_Category; }
set { m_Category = value; }
}
}
[Serializable()]
public enum TrapCardCategory
{
Normal,
Counter,
Continuous,
}
[Serializable()]
public partial class SpellCard : Card
{
private SpellCardCategory m_Category;
[XmlAttribute()]
public SpellCardCategory category
{
get { return m_Category; }
set { m_Category = value; }
}
}
[Serializable()]
public enum SpellCardCategory
{
Normal,
Continuous,
Equip,
Field,
QuickPlay,
Ritual,
}
}
#endregion
從程式碼中,我們可以看到,xsd.exe 的確很能幹,生成的程式碼基本上都符合預期的設想了。
4.2 attribute & body text
預設情況下,XmlSerializer 會將類的公有欄位和公有屬性序列化為 XML 元素,但我們很清楚,三大卡類的資料要麼以
目前三大卡類的公有屬性的名字都是全小寫的,接下來我準備使用 Pascal 命名方式為它們重新命名。要順利完成重構,除了常規的重構手續之外,我們還需要叫XmlAttribute 通知 XmlSerializer 將來使用重構之前的名字來進行序列化。下面用 Card.name 這個公有屬性來舉例:
[XmlAttribute("name")]
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
由於每個元素有且僅有一個 body text,於是重新命名卡片描述這個公有屬性就僅需辦理常規手續了:
[XmlText()]
public string Description
{
get { return m_Description; }
set { m_Description = value; }
}
接著,我們可以用同樣的方法處理其他公有屬性。
4.3 array & array item
Cards 類中的三個公有屬性都是陣列型別,為了使得 XmlSerializer 將來能夠產生正確的輸出,你應該為這些屬性應用 XmlArray 和 XmlArrayItem。現在,我又希望使用 Pascal 命名方式對這三個公有屬性進行重新命名,那麼除了常規的重構手續,我還需要向 XmlArray 說明情況。下面以 Cards.trapcards 為例:
[XmlArray("trapcards", Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("trapcard", Form = XmlSchemaForm.Unqualified)]
public TrapCard[] TrapCards
{
get { return m_TrapCards; }
set { m_TrapCards = value; }
}
4.4 include
三大卡類繼承了抽象類 Card,為了使得將來 XmlSerializer 在進行序列化式能夠正確把 Card 的成員包含到三大卡類裡,我們需要明確指出三大卡類和 Card 的繼承關係,這可以透過 XmlInclude 來做到。熟悉 C++ 的朋友會發現這個做法很象 C++ 的 #include,不同的是,#include 是在 client 端使用,而 XmlInclude 則在 server 端使用,並且有多少個派生類就使用多少次 XmlInclude。假如我們並不知道有多少個可能的派生類,那麼我們將陷入泥潭。一個可行的解決方法就是使用 Composition 來實現複用,即上一部分(§ 3.5)所說的在
4.5 Replace Array with Collection
當我們把 XML 檔案反序列化後,通常我們並不僅僅為了檢視資料,更多時候我們會修改資料,例如新增新的資料或者刪除現有的資料。Cards 內部使用陣列來儲存反序列化後的資料明顯有著侷限性,現在我打算改用集合類來儲存資料。以 m_MonsterCards 為例:
private List<MonsterCard> m_MonsterCards;
接著,公有屬性 MonsterCards 也需要作相應的調整:
[XmlArray("monstercards", Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("monstercard", Form = XmlSchemaForm.Unqualified)]
public MonsterCard[] MonsterCards
{
get { return m_MonsterCards.ToArray(); }
set
{
m_MonsterCards.Clear();
m_MonsterCards.AddRange(value);
}
}
然後,我們應該為 m_MonsterCards 提供常用的操作介面:
public void Add(MonsterCard monsterCard)
{
m_MonsterCards.Add(item);
}
public void Remove(MonsterCard item)
{
m_MonsterCards.Remove(item);
}
為了使得 Remove 執行的更有效,你應該為 MonsterCard 類實現 System.IComparable
現在萬事俱備,只欠建構函式了,我們應該在建構函式中例項化 m_MonsterCards:
public Cards()
{
m_MonsterCards = new List<MonsterCard>();
//
}
或者有人會問:為什麼不直接讓客戶端操作集合類而要這樣大費周折呢?沒錯,公開集合類能夠簡化類的程式碼,但這樣一來,客戶端和 Cards 的耦合度就提高了,不便於我們將來修改 Cards 的實現。
5. Serialize Your Deck
5.1 Cards.Load
該方法的程式碼很簡單:
public static Cards Load(string filename)
{
using (XmlReader reader = XmlReader.Create(filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(Cards));
return (Cards)serializer.Deserialize(reader);
}
}
當我們需要進行反序列化時,只需:
Cards cards = Cards.Load(filename);
另外,你不需要自行檢查指定檔案是否存在,因為 XmlReader.Create 會在指定檔案不存在是丟擲 FileNotFoundException 的。
5.2 Cards.Save
該方法的程式碼也非常簡單:
public void Save(string filename)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
using (XmlWriter writer = XmlWriter.Create(filename, settings))
{
XmlSerializer serializer = new XmlSerializer(typeof(Cards));
serializer.Serialize(writer, this);
}
}
值得注意的是,你必須傳遞一個 XmlWriterSettings 的例項給 XmlWriter.Create,且該例項的 Indent 屬性被設為 true,否則輸出的 XML 文件將會凌亂不堪,因為沒有經過縮排。另外,如果你希望改變用於縮排的字元,你可以設定 IndentChars 屬性,預設為兩個空格,這裡我將之設為四個空格。
5.3 Pay Attention to the Default Constructor
原本三大卡類都沒有定義建構函式,於是 .NET 為你提供一個以便它們能正常工作,而 Positron 也工作良好。但有一天你心血來潮為三大卡類分別提供一個建構函式用於初始化所有欄位,你覺得這樣方便,然而,Positron 出現異常了,它抱怨你沒有提供預設建構函式。
原來,當我們為類提供建構函式後,.NET 將不再為它提供預設建構函式,而 XmlSerializer 又非要和預設建構函式一起工作不可,如果我們沒有為類顯式提供預設建構函式,XmlSerializer 將會罷工!另外,不管你提供的預設建構函式是 public、protected 還是 private,只要你提供了,XmlSerializer 就會高興了。
5.5 cards.cs
至此,整個 Positron S 的核心程式碼已經制作完畢:
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Positron
{
[XmlRoot("cards", Namespace = "", IsNullable = false)]
public class Cards
{
public Cards()
{
m_MonsterCards = new List<MonsterCard>();
m_SpellCards = new List<SpellCard>();
m_TrapCards = new List<TrapCard>();
}
private List<MonsterCard> m_MonsterCards;
private List<SpellCard> m_SpellCards;
private List<TrapCard> m_TrapCards;
[XmlArray("monstercards", Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("monstercard", Form = XmlSchemaForm.Unqualified)]
public MonsterCard[] MonsterCards
{
get { return m_MonsterCards.ToArray(); }
set
{
m_MonsterCards.Clear();
m_MonsterCards.AddRange(value);
}
}
[XmlArray("spellcards", Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("spellcard", Form = XmlSchemaForm.Unqualified)]
public SpellCard[] SpellCards
{
get { return m_SpellCards.ToArray(); }
set
{
m_SpellCards.Clear();
m_SpellCards.AddRange(value);
}
}
[XmlArray("trapcards", Form = XmlSchemaForm .Unqualified)]
[XmlArrayItem("trapcard", Form = XmlSchemaForm.Unqualified)]
public TrapCard[] TrapCards
{
get { return m_TrapCards.ToArray(); }
set
{
m_TrapCards.Clear();
m_TrapCards.AddRange(value);
}
}
public void Add(MonsterCard card)
{
m_MonsterCards.Add(card);
}
public void Add(SpellCard card)
{
m_SpellCards.Add(card);
}
public void Add(TrapCard card)
{
m_TrapCards.Add(card);
}
public void AddRange(IEnumerable<MonsterCard> cards)
{
m_MonsterCards.AddRange(cards);
}
public void AddRange(IEnumerable<SpellCard> cards)
{
m_SpellCards.AddRange(cards);
}
public void AddRange(IEnumerable<TrapCard> cards)
{
m_TrapCards.AddRange(cards);
}
public void Remove(MonsterCard card)
{
m_MonsterCards.Remove(card);
}
public void Remove(SpellCard card)
{
m_SpellCards.Remove(card);
}
public void Remove(TrapCard card)
{
m_TrapCards.Remove(card);
}
public static Cards Load(string filename)
{
using (XmlReader reader = XmlReader.Create(filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(Cards));
return (Cards)serializer.Deserialize(reader);
}
}
public void Save(string filename)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
using (XmlWriter writer = XmlWriter.Create(filename, settings))
{
XmlSerializer serializer = new XmlSerializer(typeof(Cards));
serializer.Serialize(writer, this);
}
}
}
public sealed class MonsterCard : Card
{
public MonsterCard() {}
public MonsterCard(string img, string name, string description, MonsterCardCategory category, MonsterAttribute attribute, MonsterType type, int level, int atk, int def)
{
m_Img = img;
m_Name = name;
m_Description = description;
m_Category = category;
m_Attribute = attribute;
m_Type = type;
m_Level = level;
m_Atk = atk;
m_Def = def;
}
private MonsterCardCategory m_Category;
private MonsterAttribute m_Attribute;
private int m_Level;
private MonsterType m_Type;
private int m_Atk;
private int m_Def;
[XmlAttribute("category")]
public MonsterCardCategory Category
{
get { return m_Category; }
set { m_Category = value; }
}
[XmlAttribute("attribute")]
public MonsterAttribute Attribute
{
get { return m_Attribute; }
set { m_Attribute = value; }
}
[XmlAttribute("level")]
public int Level
{
get { return m_Level; }
set { m_Level = value; }
}
[XmlAttribute("type")]
public MonsterType Type
{
get { return m_Type; }
set { m_Type = value; }
}
[XmlAttribute("atk")]
public int Atk
{
get { return m_Atk; }
set { m_Atk = value; }
}
[XmlAttribute("def")]
public int Def
{
get { return m_Def; }
set { m_Def = value; }
}
}
public enum MonsterCardCategory
{
Normal,
Effect,
Fusion,
Ritual,
}
public enum MonsterAttribute
{
Earth,
Water,
Fire,
Wind,
Light,
Dark,
}
public enum MonsterType
{
Dragon,
Spellcaster,
Zombie,
Warrior,
BeastWarrior,
Beast,
WingedBeast,
Fiend,
Fairy,
Insert,
Dinosaur,
Reptile,
Fish,
SeaSerpent,
Machine,
Thunder,
Aqua,
Pyro,
Rock,
Plant,
}
[XmlInclude(typeof(TrapCard))]
[XmlInclude(typeof(SpellCard))]
[XmlInclude(typeof(MonsterCard))]
public abstract class Card : IComparable<Card>
{
protected string m_Img;
protected string m_Name;
protected string m_Description;
[XmlAttribute("img")]
public string Img
{
get { return m_Img; }
set { m_Img = value; }
}
[XmlAttribute("name")]
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
[XmlText()]
public string Description
{
get { return m_Description; }
set { m_Description = value; }
}
IComparable Members#region IComparable
int IComparable<Card>.CompareTo(Card other)
{
return m_Name.CompareTo(other.m_Name);
}
#endregion
}
public sealed class TrapCard : Card
{
public TrapCard() {}
public TrapCard(string img, string name, string description, TrapCardCategory category)
{
m_Img = img;
m_Name = name;
m_Description = description;
m_Category = category;
}
private TrapCardCategory m_Category;
[XmlAttribute("category")]
public TrapCardCategory Category
{
get { return m_Category; }
set { m_Category = value; }
}
}
public enum TrapCardCategory
{
Normal,
Counter,
Continuous,
}
public sealed class SpellCard : Card
{
public SpellCard() {}
public SpellCard(string img, string name, string description, SpellCardCategory category)
{
m_Img = img;
m_Name = name;
m_Description = description;
m_Category = category;
}
private SpellCardCategory m_Category;
[XmlAttribute("category")]
public SpellCardCategory Category
{
get { return m_Category; }
set { m_Category = value; }
}
}
public enum SpellCardCategory
{
Normal,
Continuous,
Equip,
Field,
QuickPlay,
Ritual,
}
}
#endregion
對比之前那個版本,除了上面的一系列改動之外,我還去掉了所有型別的 SerializableAttribute,因為它與 XML Serialization 無關。當然,如果你將來希望把這些型別用於 Binary Serialization,你仍然可以保留它們。
另外,抽象類實現了 System.IComparable
6. What's More...
6.1 Is Your Data Source Valid?
我在這裡提供的 Cards.Load 很簡單,實際上,你可以在處理任何資料來源之前對其進行驗證,例如資料來源的資料的型別是否與預期設想吻合,資料有否越界,即不落在預期的範圍內,這樣可以保證在作進一步處理前資料來源是有效的(valid)。這裡的有效是指待處理的資料來源(即 XML 文件)不但良構,還要符合預先定義的 DTD 或者 schema。
6.2 Enable Linq for Positron S
如果你讀過我的《Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]》,你應該會記得我曾經在那篇文章示範如何尋找等級3或者以下並且具有效果的怪獸。現在我們來看看如何結合 Linq 和 Positron S 來尋找目標:
Cards cards = Cards.Load("sample.xml");
var wanted = from c in cards.MonsterCards
where (c.Level < 4)&&(c.Category == MonsterCardCategory.Effect)
orderby c.Atk
select c;
6.3 An O/X Mapping Solution?
第一次接觸 XML Serialization 時,我覺得它的做法很像 O/R Mapping,雖然我不知道稱它為 O/X Mapping 是否恰當,但它卻實實在在提供了 Object 和 XML Data Source 之間的相互轉換。與常規的 DOM 處理方式相比,它的確為我帶來了巨大的便利。
R. References
- Yu-Gi-Oh! Power of XLinq [C#, XLinq, XML]
- Microsoft .NET Framework SDK v2.0 Documentation
- CSharp Language Specification v1.2
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/20200170/viewspace-757418/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- XML XSD XSL區別XML
- XML Schema(XSD)詳解:定義 XML 文件結構合法性的完整指南XML
- jQuery serialize()jQuery
- C# XML解析C#XML
- C#讀取XMLC#XML
- C# 讀寫xmlC#XML
- C# 操作xml(轉)C#XML
- 用c#生成xml字串及解析xml字串C#XML字串
- eosjs 文件(Serialize介面)JS
- C# 之 Linq to XmlC#XML
- C# 讀 xml註釋C#XML
- C#:XML操作(簡單)C#XML
- C# 建立XML檔案C#XML
- C#操作XML方法集合C#XML
- 在C#中操作XML .C#XML
- C#解析XML檔案C#XML
- C#基礎系列:Linq to Xml讀寫xmlC#XML
- 如何用 Visual C#.net 中的 DTD、 XDR,或 XSD 驗證 XML 文件C#XML
- 應用SQLServer For XML 生成XML避免在C# 拼字串SQLServerXMLC#字串
- asp.net教程-C#中使用XML——編寫XMLASP.NETC#XML
- xmlbean 多個xsd 打包一個jar 生成xml的名稱空間問題XMLBeanJAR
- C#讀取Xml檔案C#XML
- C# 將HTML轉為XMLC#HTMLXML
- C# 將Excel轉為XMLC#ExcelXML
- C# 儲存Word模板 【XML】C#XML
- C# 對XML檔案控制C#XML
- C# 讀取XML文件 (轉)C#XML
- XSD 空元素概述
- 物件的序列化(Serialization)物件
- [.net 物件導向程式設計進階] (11) 序列化(Serialization)(三) 通過介面 IXmlSerializable 實現XML序列化 及 通用XML類物件程式設計XML
- 用C#讀寫XML的方法C#XML
- 使用C#讀寫xml檔案C#XML
- 【Lintcode】1728. X of a Kind in a Deck of Cards
- XSD 僅含元素概述
- XSD 簡易元素概述
- XSD 複合元素概述
- Your Prediction Gets As Good As Your DataGo
- AS3CoreLib JSON serializationS3JSON