天天看點

使用序列化快速讀寫XML檔案

InfoPath是微軟提供的一個非常好用的XML編輯工具,可以用InfoPath編輯好各種各樣表單的模闆以及資料輸入界面,然後其他人可以使用這個表單模闆輸入表單資料,送出到比如說ERP系統當中去—因為它儲存的資料是XML格式的,而且你可以在InfoPath裡面設定XML驗證表單的方式,我會在另一篇文章裡面介紹如何使用InfoPath建立一個表單模闆并且使用這個模闆。

例如下面是一個InfoPath生成的XML檔案:

<?xml version="1.0" encoding="UTF-8"?>

<? color: blue; font-family: 'Courier New';"> solutionVersion="1.0.0.15" productVersion="12.0.0" PIVersion="1.0.0.0"

                       href="file:///C:"Documents%20and%20Settings"xyz"My%20Documents"Paper.xsn"

                       name="urn:schemas-microsoft-com:office:infopath:Paper:-myXSD-2005-10-21T21-12-27" ?>

<? color: blue; font-family: 'Courier New';"> progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>

<my:Paper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xhtml="http://www.w3.org/1999/xhtml"

          xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27"

          xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"xml:lang="zh-cn">

 <my:Candidate>張三</my:Candidate>

 <my:PositionApplies>STE</my:PositionApplies>

 <my:ApplicationDate xsi:nil="true"></my:ApplicationDate>

 <my:FillInOffice>false</my:FillInOffice>

 <my:Questions>

    <my:Question>

      <my:QuestionText>asdfasdf</my:QuestionText>

      <my:Answers my:SelectedAnswer="false">

        <my:AnswerDescription>asfasfdsa</my:AnswerDescription>

      </my:Answers>

      <my:Answers my:SelectedAnswer="true">

        <my:AnswerDescription>afsafd</my:AnswerDescription>

        <my:AnswerDescription>afasfdf</my:AnswerDescription>

        <my:AnswerDescription>asfadsfsafd</my:AnswerDescription>

      <my:Id>1</my:Id>

    </my:Question>

 </my:Questions>

 <my:Skill>C#</my:Skill>

</my:Paper>

如果将上面的XML檔案封裝的話,最直覺的封裝方法就是這樣的了:

    public class Answers : List<Answer>

    {

    }

    public class Questions : List<Question>

    public class Answer

        public bool SelectedAnswer { get; set; }

        public string AnswerDescription { get; set; }

    public class Question

        public Question()

        {

            Answers = new Answers();

        }

        public string QuestionText { get; set; }

        public Answers Answers { get; set; }

        public int Id { get; set; }

    public class Paper

        public Paper()

            Questions = new Questions();

        public string Candidate { get; set; }

        public string PositionApplies { get; set; }

        public DateTime ApplicationDate { get; set; }

        public bool FillInOffice { get; set; }

        public Questions Questions { get; set; }

        public string Skill { get; set; }

至今為止,看起來還是一片完美,類型和屬性都是直接映射到XML對應的節點和屬性上去了,接着我們想用下面的代碼來嘗試将自己生成的自定義的對象序列化成一個XML檔案,并且對比兩個檔案的記憶體是否相似:

// 省去了執行個體化自定義對象的一些代碼

using (XmlWriter writer = XmlWriter.Create(@"c:"test.xml") )

{

      XmlSerializer xs = new XmlSerializer(typeof(Paper));

 xs.Serialize(writer, paper);

}

然而最終的序列化結果卻不能被InfoPath打開,對比我們序列化生産的檔案和InfoPath生成的檔案,可以發現兩個檔案主要的差别在對于節點的處理,有些節點的内容可以為空,而我們在C#代碼裡面卻定義成值類型—值類型不能為空值,所有的節點和屬性都是在一個命名空間下面的(而我們通過序列化生成的檔案是在預設的命名空間底下的)。在下表中,我将序列化生成的檔案的差别用淺灰色标注出來,而InfoPath檔案的差别用黃色标注出來:

序列化生成的檔案

InfoPath原始檔案

<?xml version="1.0" encoding="utf-8"?>

<Paperxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 <Candidate>張三</Candidate>

 <PositionApplies>STE</PositionApplies>

 <ApplicationDate>2009-03-10T00:59:05.9912457+08:00</ApplicationDate>

 <FillInOffice>false</FillInOffice>

 <Questions>

    <Question>

      <QuestionText>This is a test question</QuestionText>

      <Answers>

        <Answer>

          <SelectedAnswer>false</SelectedAnswer>

          <AnswerDescription>Answer 1</AnswerDescription>

        </Answer>

        <Answer>

         <SelectedAnswer>true</SelectedAnswer>

          <AnswerDescription>Answer 2</AnswerDescription>

          <AnswerDescription>Answer 3</AnswerDescription>

          <AnswerDescription>Answer 4</AnswerDescription>

      </Answers>

      <Id>0</Id>

    </Question>

 </Questions>

 <Skill>C#</Skill>

</Paper>

<? background: yellow; color: blue; font-family: 'Courier New';">solutionVersion="1.0.0.15" productVersion="12.0.0" PIVersion="1.0.0.0"

                       href="file:///C:"Documents%20and%20Settings"xyz"My%20Documents"Paper.xsn"                        name="urn:schemas-microsoft-com:office:infopath:Paper:-myXSD-2005-10-21T21-12-27" ?>

<? background: yellow; color: blue; font-family: 'Courier New';">progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>

<my:Paperxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:xhtml="http://www.w3.org/1999/xhtml"          

xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27"

xml:lang="zh-cn">

      <my:Answersmy:SelectedAnswer="false">

      <my:Answersmy:SelectedAnswer="true">

其中,我們可以注意到兩者的差别有下列幾項,為了友善說明,下面的文字将InfoPath檔案簡稱I檔案,而序列化檔案簡稱S檔案:

1.         I檔案中所有的節點都在别名為my的命名空間裡面.

2.         S檔案沒有<?mso* ?>的程式處理指令節點(ProcessInstruction Node),這也就是為什麼我們用序列化生成的XML檔案在資料總管裡不能通過輕按兩下用InfoPath打開檔案.

3.         I檔案裡面有一些值類型的節點的值為空,而在C#中值類型是不能為空的.

4.         S檔案裡面将Paper.Questions.Answers數組的Answer都當作一個個獨立的節點序列化了,而I檔案當中,每一個C#的Answer對象都是用Answers節點表示.

5.         I檔案裡面,SelectedAnswer是一個Xml屬性,而S檔案當中,它卻被序列化成一個Xml節點了。

雖然有這麼多的差異,但是幸運的是,我們的.NET Framework的XML序列化功能足夠強大,強大到可以讓我們通過編寫很少的代碼将上面4個差異去掉.

1.         XmlSerializer提供了一個構造函數,允許我們在執行序列化的時候,顯示地加上需要的命名空間支援.然後你可以在類型的每一個域(Field)上面加上對應的屬性表明你要将該域(Field)序列化到哪一個命名空間裡面去.例如下面代碼

public class Question

    public Question()

        Answers = new Answers();

    [XmlElement(

"http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27")]

    public string QuestionText { get; set; }

    public Answers Answers { get; set; }

    public int Id { get; set; }

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

ns.Add("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27");

XmlSerializer xs = new XmlSerializer(typeof(Paper));

xs.Serialize(reader, paper, ns);

2.         這個差别.NET序列化沒有提供在序列化的時候寫入程式處理指令資訊的功能,因為序列化應該隻做序列化操作,其他的操作應該由其他的函數或者功能子產品完成,為什麼?想不通的話請去讀一下著名的《Code Complete》。但是我們可以通過在序列化的時候,給XmlSeriailzer提供一個XmlWriter執行個體,序列化操作執行之前,我們先顯示地寫入程式處理指令資訊,然後再讓XmlSerializer接着在XmlWriter目前的指針位置開始我們未竟的事業。

3.         這個可以通過C# 2.0裡面就提供的可空值類型(Nullable)來處理,如果你沒有使用或者聽說過可空值類型(Nullable)的話,還是趕緊去翻翻書吧,畢竟現在C# 4.0都快要釋出了……

4.         XmlSerializer所在的命名空間提供了很多屬性(Attribute)來控制序列化過程中節點寫入的方式,XmlElement屬性(Attribute)可以在序列化數組的時候,讓你有機會指定數組中每一個元素序列化到Xml檔案當中對應的節點名稱。

5.         而XmlAttribute屬性(Attribute)将XmlSerializer預設的把類型域序列化成一個Xml節點的操作修改成序列化成一個Xml屬性,在将類型域序列化Xml屬性的時候,預設的行為是不會加上為Xml屬性加上命名空間的,是以我們需要指定XmlAttribute的Form域設定成XmlSchemaForm.Qualified,這樣在序列化的時候,XmlSerializer就會自動為Xml屬性加上命名空間了。

下面是完整的序列化和反序列化的源代碼:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

using System.Xml.Serialization;

using System.Xml;

using System.Xml.Schema;

namespace TestSerialization

    public class Answers : List<Answer>

    public class Questions : List<Question>

    public class Answer

        [XmlAttribute(Form = XmlSchemaForm.Qualified)]

        public bool SelectedAnswer { get; set; }

        public string AnswerDescription { get; set; }

    public class Question

        public Question()

            Answers = new Answers();

        public string QuestionText { get; set; }

        [XmlElement]

        public Answers Answers { get; set; }

        public int Id { get; set; }

    public class Paper

        public Paper()

            Questions = new Questions();

        public string Candidate { get; set; }

        public string PositionApplies { get; set; }

        public DateTime? ApplicationDate { get; set; }

        public bool FillInOffice { get; set; }

        [XmlArray]

        public Questions Questions { get; set; }

        public string Skill { get; set; }

    class Program

        static void Main(string[] args)

            Paper paper = new Paper

            {

                ApplicationDate = DateTime.Now,

                Candidate = "Shi Yimin",

                PositionApplies = "STE I"

            };

            Question question = new Question();

            question.QuestionText = "This is a test question";

            question.Answers.Add(new Answer { AnswerDescription = "Answer 1", SelectedAnswer = false });

            question.Answers.Add(new Answer { AnswerDescription = "Answer 2", SelectedAnswer = true });

            question.Answers.Add(new Answer { AnswerDescription = "Answer 3", SelectedAnswer = false });

            question.Answers.Add(new Answer { AnswerDescription = "Answer 4", SelectedAnswer = false });

            paper.Questions.Add(question);

            paper.Skill = "C#";

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

            ns.Add("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27");

            ns.Add("xd", "http://schemas.microsoft.com/office/infopath/2003");

            using (XmlWriter writer = XmlWriter.Create(@"c:"test.xml") )

                writer.WriteProcessingInstruction("">                    "solutionVersion=""1.0.0.15"" productVersion=""12.0.0"" " +

                    "PIVersion=""1.0.0.0"" href=""file:///C:""Documents%20and%20Settings""v-yishi""My%20Documents""Paper.xsn"" " +

                    "name=""urn:schemas-microsoft-com:office:infopath:Paper:-myXSD-2005-10-21T21-12-27""");

                writer.WriteProcessingInstruction("">                    "progid=""InfoPath.Document"" versionProgid=""InfoPath.Document.2""");

                XmlSerializer xs = new XmlSerializer(typeof(Paper), "http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27");

                xs.Serialize(writer, paper, ns);

            }

            using ( FileStream fs = new FileStream(@"E:"家庭作業"InterviewForm.xml", FileMode.Open) )

                XmlReader reader = XmlReader.Create(fs);

               reader.MoveToElement();

                XmlSerializer xs =new XmlSerializer(typeof(Paper), "http://schemas.microsoft.com/office/infopath/2003/myXSD/2005-10-21T21:12:27");

                Paper paper1 = (Paper)xs.Deserialize(reader);

            }

用InfoPath打開我們序列化的Xml檔案試試?喔……

實際上,既然從Xml節點到類型的映射這麼直接,能不能有個自動化的方法生成映射的類型呢?呵呵,未完待續.

本文轉自 donjuan 部落格園部落格,原文連結: http://www.cnblogs.com/killmyday/archive/2009/03/12/1409965.html  ,如需轉載請自行聯系原作者