65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 2.0 下加速您的 XML 开发

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (11投票s)

2007年3月8日

4分钟阅读

viewsIcon

39642

关于以懒惰方式使用 XML 的一篇文章。

引言

XML 标准如今已得到广泛应用。然而,仍有许多人使用 XML API 来处理 XML。这非常繁琐且容易出错。本文将展示如何在新建项目或迁移旧项目时平滑地集成 XML 功能。

第一部分:读写 XML

.NET 引入了一个名为 XmlSerializer 的类,它可以将对象序列化为 XML,反之亦然。让我们来了解一下 XML 文档。它必须包含一个根元素,这对应于 .NET 中的 XmlRoot 属性。XML 文档可能包含元素、属性、文本等。反过来,它们对应于 .NET 框架中的 XmlElement、XmlAttribute、XmlText 类。
请看下图,我们将用它来进行演示

首先,我们来实现 Corp 类的序列化。我们需要创建一个 XmlSerializer 实例,然后使用 XmlTextWriter 来对其进行序列化。

我们在 datadef.cs 中定义了结构,内容如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace XMLDemo
{
    public class Person
    {
        private String _FirstName;
        private String _LastName;
        private String _Sex;
        private String _SecuritySN;
        private String _Address;
        private String _Email;

        public String FirstName
        {
            get { return this._FirstName; }
            set { this._FirstName = value; }
        }

        public String LastName
        {
            get { return this._LastName; }
            set { this._LastName = value; }
        }

        public String Sex
        {
            get { return this._Sex; }
            set { this._Sex = value; }
        }

        public String SecuritySN
        {
           get { return this._SecuritySN; }
            set { this._SecuritySN = value; }
        }

        public String Address
        {
            get { return this._Address; }
            set { this._Address = value; }
        }

        public String Email
        {
            get { return this._Email; }
            set { this._Email = value; }
        }
    };

    public class Department
    {
        private String _Name;
        private int _DepartmentID;
        private Person[] _DeptPersons;

        [XmlAttribute]
        public String Name
        {
            get { return this._Name; }
            set { this._Name = value; }
        }

        [XmlAttribute]
        public int DepartmentID
        {
            get { return this._DepartmentID; }
            set { this._DepartmentID = value; }
        }

        [XmlElement(ElementName = "Person")]
        public Person[] DeptPersons
        {
            get { return this._DeptPersons; }
            set { this._DeptPersons = value; }
        }

        public Person this[int i]
        {
            get { return DeptPersons[i]; }
            set { DeptPersons[i] = value; }
        }

    };

    public class Corp
    {
        private String _Name;
        private Department _Dept;

        [XmlAttribute]
        public String Name
        {
            get { return this._Name; }
            set { this._Name = value; }
        }

        [XmlElement(ElementName = "Department")]
        public Department Dept
        {
            get { return this._Dept; }
            set { this._Dept = value; }
        }
    };

}

当 .NET 处理序列化时,它只会处理公共访问的成员,字段或属性都可以。如果您不希望某个成员被序列化,请添加 XmlIgnore .NET 属性。默认情况下,序列化会将成员视为元素,因此如果您想添加一个属性,应该添加 XmlAttribute .NET 属性。同样,序列化会以成员名作为元素名或属性名,因此如果您想更改名称,应该在 .NET 属性声明中更改名称,如上面的演示。这也意味着,如果您添加或删除某些元素,您无需更改任何代码,这是与传统方法相比的优势。最有趣的是(请看红色下划线代码),您还可以直接将数组声明为元素。

这是序列化 Corp 类的演示代码:

        public static void RunDemo1()
        {
            Corp c = new Corp();
            c.Name = "ChinaCars";
            c.Dept = new Department();
            c.Dept.Name = "Product";
            c.Dept.DepartmentID = 1;

            c.Dept.DeptPersons = new Person[2];

            Person p1=new Person();
            p1.FirstName = "Andy";
            p1.LastName = "Smith";
            p1.Sex = "M";
            p1.SecuritySN = "3101121103110110";
            p1.Address = "Beijing,China";
            p1.Email = "pro@chinacars.com";

            Person p2 = new Person();
            p2.FirstName = "Kate";
            p2.LastName = "Allian";
            p2.Sex = "F";
            p2.SecuritySN = "3101110302011211";
            p2.Address = "Beijing,China";
            p2.Email = "pro@chinacars.com";

            c.Dept[0] = p1;
            c.Dept[1] = p2;

            XmlSerializer xs = new XmlSerializer(typeof(Corp));
            XmlTextWriter writer = null;
            try
            {
                writer = new XmlTextWriter(@"C:\Demo.XML", System.Text.Encoding.GetEncoding(0));
                writer.Formatting = System.Xml.Formatting.Indented;
                xs.Serialize(writer, c);
            }
            finally
            {
                if (writer != null) writer.Close();
            }

            
        }

让我们来看看结果:

<? xml version="1.0" encoding="gb2312" > <Corp xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="ChinaCars"> <Department Name="Product" DepartmentID="1"> <Person> <FirstName>Andy</FirstName> <LastName>Smith</LastName> <Sex>M</Sex> <SecuritySN>3101121103110110</SecuritySN> <Address>Beijing,China</Address> <Email>pro@chinacars.com</Email> </Person> <Person> <FirstName>Kate</FirstName> <LastName>Allian</LastName> <Sex>F</Sex> <SecuritySN>3101110302011211</SecuritySN> <Address>Beijing,China</Address> <Email>pro@chinacars.com</Email> </Person> </Department> </Corp>

生成的 XML 带有默认命名空间,有些人偏好 null 空间,因此您可以像下面这样更改代码。同样,如果您想更改 XML 的命名空间,只需更改命名空间参数即可。.NET XML 属性构造函数有许多参数,方便您更改为相应的 XML 属性。

        public static int WriteXML(String xmlFileName, Object oData,bool useNullNameSpace)
        {

            XmlSerializer xs = new XmlSerializer(oData.GetType());
            XmlTextWriter writer = null;
            try
            {
                writer = new XmlTextWriter(xmlFileName, System.Text.Encoding.GetEncoding(0));
                writer.Formatting = System.Xml.Formatting.Indented;
                if (useNullNameSpace)
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    xs.Serialize(writer, oData,ns);
                }
                else
                {
                    xs.Serialize(writer, oData);
                }
                writer.Close();
            }
            finally
            {
                if (writer != null) { writer.Close(); }
            }
            return 0;
        }

现在,我们来反序列化它:

        static void RunDemo2()
        {
            Corp c = null;
            XmlSerializer xs = new XmlSerializer(typeof(Corp));
            XmlTextReader reader = null;
            try
            {
                reader = new XmlTextReader(@"C:\Demo.XML");
                c = (Corp)  xs.Deserialize(reader);
            }
            finally
            {
                if (reader != null) { reader.Close(); }
            }

        }

同样,这些代码可以重用,有些人可能偏好泛型而不是强制类型转换,请看下面的代码:

        public static T ReadXML<T>(String xmlFilename)
        {
            return (T)ReadXML(xmlFilename, typeof(T));
        }


        public static Object ReadXML(String xmlFilename, Type dataType)
        {
            Object objXML = null;
            XmlSerializer xs = new XmlSerializer(dataType);
            XmlTextReader reader = null;
            try
            {
                reader = new XmlTextReader(xmlFilename);
                objXML = xs.Deserialize(reader);
            }
            finally
            {
                if (reader != null) { reader.Close(); }
            }

            return objXML;
        }

第二部分:结合 XSLT 或转储部分 XML

有时人们可能想使用 xml-schema。XML-XSL 模式有很多优点。要做到这一点,您需要使用 XmlTextWriter 类的 WriteRaw 方法。

            public static String GetXML( Object oData,String xslURL,bool useNullNameSpace)
        {

            XmlSerializer xs = new XmlSerializer(oData.GetType());
            XmlTextWriter writer = null;
            System.IO.MemoryStream msStream = null;
            String strResult = null;
            try
            {
                msStream = new System.IO.MemoryStream();
              //Use System's default encoding
                System.Text.Encoding defaultEnc = System.Text.Encoding.GetEncoding(0);
                writer = new XmlTextWriter(msStream, defaultEnc);
                writer.Formatting = System.Xml.Formatting.Indented;
             //Need the xslt file for display
                if (xslURL != null)
                {
                    writer.WriteRaw("\r\n");
                    writer.WriteRaw(String.Format("{0}\r\n", xslURL));
                }

              //nullspace processing
                if (useNullNameSpace)
                {
                    XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                    ns.Add("", "");
                    xs.Serialize(writer, oData, ns);
                }
                else
                {
                    xs.Serialize(writer, oData);
                }
                writer.Close();
                strResult = System.Text.Encoding.Default.GetString(msStream.ToArray());
            }
            finally
            {
                if (msStream != null) { msStream.Close(); }
                if (writer != null) { writer.Close(); }
            }
            return strResult;
        }
同样,您可以使用 WriteRaw 方法来编写任何您想写的内容。

我们可以都使用 xml 来转储,而不是为每个类编写 toString 方法,这对于调试特别有用。我们也可以将类的成员转储为 xml,我们所需要做的就是使用 XmlRootAttribute,这里是代码:

        public static String GetShortXML(Object oData, String strType)
        {
            XmlRootAttribute xrAttr=new XmlRootAttribute(strType);           
            XmlSerializer xs = new XmlSerializer(oData.GetType(),xrAttr);
            XmlTextWriter writer = null;
            System.IO.MemoryStream msStream = null;
            String strResult = null;
            try
            {
                msStream = new System.IO.MemoryStream();
                writer = new XmlTextWriter(msStream, System.Text.Encoding.GetEncoding(0));
                writer.Formatting = System.Xml.Formatting.None;
                writer.WriteRaw(null);
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add("", "");
                xs.Serialize(writer, oData, ns);
                writer.Close();
                strResult = System.Text.Encoding.Default.GetString(msStream.ToArray());
            }
            finally
            {
                if (msStream != null) { msStream.Close(); }
                if (writer != null) { writer.Close(); }
            }
            return strResult;
        }

第三部分:编辑 XML 文件

有时人们可能想编辑一个 XML 文件。首先,我们需要一个临时对象用于编辑,如果更改被接受,则将成员设置到临时对象,否则只需丢弃临时对象。问题是,如何克隆一个对象?逐字段复制是很好的。即使使用对象的 memberwiseClone 方法,我们仍然有很多事情要做。幸运的是,.NET 框架提供了一个通用的 SerializeClone 方法,可以进行通用的成员复制,它就像 Java 的序列化一样。我们所需要做的就是为每个类添加一个 .NET [Serializable] 属性,就像下面这个:

    [Serializable]
    public class Corp
    {
        ……
    };

之后,您可以使用以下通用的 SerializeClone 方法来克隆一个副本:

        public static T SerializeClone<T>(T srcObject)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bfFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            System.IO.MemoryStream msStream = new System.IO.MemoryStream();
            T result = default(T);
            try
            {
                bfFormatter.Serialize(msStream, srcObject);
                msStream.Seek(0, System.IO.SeekOrigin.Begin);
                result=(T)bfFormatter.Deserialize(msStream);
            }
            finally
            {
                if (msStream != null) msStream.Close();
            }
            return result;
        }

例如,我们可以通过这种方式复制一个 Corp 实例:

    Corp t=SerializeClone(typeof(Corp))(destCorp);

让我们再来考虑编辑,我们所需要做的就是 AUD,即添加、更新和删除。在这里,添加意味着向数组添加新成员,删除意味着从数组中删除成员。借助反射和泛型,这个目标可以轻松实现。

        public static T[] ArrayAppend<T>(T[] array, T newObject)
        {
            T[] newArray = ExtentArray<T>(array, 1);
            newArray[newArray.Length - 1] = newObject;
            return newArray;
        }

        public static T[] ArrayRemove<T>(T[] array, T destObject)
        {
            T[] newArray = ExtentArray<T>(array, -1);
            int nPos = Array.IndexOf(array, destObject);
            if (nPos >= 0)
            {
                for (int i = nPos; i < newArray.Length; i++)
                {
                    newArray[i] = array[i + 1];
                }
            }
            return newArray;            
        }

        public static T[] ExtentArray<T>(T[] array, int nShrinkSize)
        {
            T[] result = null;
            if (array == null)
            {
                if (nShrinkSize >= 0)
                {
                    result = new T[nShrinkSize];
                    for (int i = 0; i < nShrinkSize; i++)
                    {
                        result[i] =(T) typeof(T).GetConstructor(new Type[0]).Invoke(null);
                    }
                }
            }
            else
            {
                result = new T[array.Length + nShrinkSize];
                for (int i = 0; i < array.Length && i < result.Length; i++)
                {
                    result[i] = array[i];
                }
                for (int i = array.Length; i < result.Length; i++)
                {
                    result[i] = (T)typeof(T).GetConstructor(new Type[0]).Invoke(null);
                }
            }
            return result;
        }
 

借助下面的 ArrayAppend 和 ArrayRemove 方法,可以轻松地向数组添加新成员或从数组中删除成员。可能会出现类似下面的代码:

        Corp r=SerializeClone<Corp>(config);
        r.CorpName=newCorpName;
        ……
        ArrayAppend(r.Dept.DeptPersons,newPerson)

使用数组来表示 XML 数组的想法很直接,但是,我们可以使用另一种方法。

XML 序列化使用公共字段或属性。如果我们使用属性声明,我们可以获得一种更简单的方法。首先声明一个泛型列表,然后声明相应的 set 和 get 方法。
        public class DepartmentU
        {
            private String _Name;
            private int _DepartmentID;
            private List<Person> _DeptPersonList=new List<Person>();

            [XmlAttribute]
            public String Name
            {
                get { return this._Name; }
                set { this._Name = value; }
            }

            [XmlAttribute]
            public int DepartmentID
            {
                get { return this._DepartmentID; }
                set { this._DepartmentID = value; }
            }

            [XmlElement(ElementName = "Person")]
            public Person[] DeptPersons
            {
                get { return this._DeptPersonList.ToArray(); }
                set {
                    ChinaCars.Util.SysUtil.LoadListFromArray<Person>(_DeptPersonList, value);
                }
            }

            public List<Person> GetDeptPersonList()
            {
                return _DeptPersonList;
            }

            public Person this[int i]
            {
                get { return DeptPersons[i]; }
                set { DeptPersons[i] = value; }
            }
        };

这是一种简单的方法,您无需为类添加 Serializable 属性。通过 GetDeptPersonList 函数,您可以轻松地操作 Person 元素。这是处理 XML 数组元素的建议方法。

使用代码

通过使用 XMLUtil 类,您可以将对象序列化到文件,或序列化到字符串对象,反之亦然。

// Serialize a object to a file,if the useNullNameSpace parameter is true,then the object will be serialized with a empty namespace.
        public static int WriteXML(String xmlFileName, Object oData,bool useNullNameSpace)
// If you want to read a xml from file and deserialize it to an object
        public static Object ReadXML(String xmlFilename, Type dataType)
        public static T ReadXML<T>(String xmlFilename)
// If you just want to get the XML text of the object,then you can call the GetXML method.
        public static String GetXML(Object oData)
// If you want to get the XML text of the object and you want a xsl description or you don't want a empty namespace
        public static String GetXML( Object oData,String xslURL,bool useNullNameSpace)
// You may want to get part of the object as xml,you can do this
        public static String GetShortXML(Object oData, String strType)    
// You may want to deserialize a xml string or a byte array to an object                
        public static Object LoadXML(String s, Type dataType)
        public static Object LoadXML(byte[] bRawData, Type dataType)
        public static Object LoadXML<T>(String s)        
    

有些人可能想手动修改 XML,然后您可以使用 ArrayUtil 类来实现此目标,或者您可以利用文章中使用的技术。

// To find the elements matching the fieldname and value:
        public static T[] FindElementList<T>(T[] array, String strKeyWord, Object value)
// To find the first element matching the fieldname and value
        public static T FindElement<T>(T[] array, String strKeyWord, Object value)
// To append an element to the array:
        public static T[] ArrayAppend<T>(T[] array, T newObject)
// To remove an element element from array
        public static T[] ArrayRemove<T>(T[] array, T destObject)
//To extend or shirink the     size of the array
        public static T[] ExtentArray<T>(T[] array, int nShrinkSize)

关注点

以一种“懒惰”的方式保存/加载/维护 XML。

源代码来自 SmartK Application Server,这是一个快速的 xslt 开发平台。

历史

2006 年 9 月 26 日 发布第一个版本

© . All rights reserved.