c#示例-xml序列化和xml树

序列化

由于指针和引用类型的存在,在运行中的程序中,数据不一定是整块的。
可能东一块西一块散落在内存的各个地方。

序列,是指连续且有序的一个整体。序列化就是把数据变为连续有序整体的过程。
经过这样处理后的数据就可以方便的进行传输和储存了。

xml序列化

xml格式

xml是一种文本数据格式。用节点树的形式表示数据的名字和数据的内容。
在c#中,时间,数字,字符串及其他的基本类型内置了直接和字符串进行转化的方式。
而复杂类型会通过反射拆解他的成员,一直拆解直到只有基本类型为止。

public class Weapon//自带的序列化api要求类是public的。
{
    
    
	public (int, int) Attack {
    
     get; set; }
	public float Speed {
    
     get; set; }
	public int Level {
    
     get; set; }
}
<?xml version="1.0" encoding="utf-16"?>
<Weapon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Attack>
    <Item1>10</Item1>
    <Item2>20</Item2>
  </Attack>
  <Speed>1.5</Speed>
  <Level>3</Level>
</Weapon>

序列化api

标准库下只提供了System.Xml.Serialization命名空间给的序列化api。
这个api不能直接序列化为string类型。只能写入到流里面。
但是我们可以使用StringWriterStringRead,这两个把字符串伪装成流的东西让他写入。

Weapon weapon = new Weapon() {
    
     Attack = (10, 20), Speed = 1.5f, Level = 3 };

// 创建一个XmlSerializer对象,传入Person类型
XmlSerializer xs = new XmlSerializer(typeof(Weapon));

// 创建一个StringWriter对象,用于保存XML字符串
StringWriter sw = new StringWriter();

// 调用XmlSerializer的Serialize方法,把Person对象序列化为XML字符串,并写入StringWriter对象
xs.Serialize(sw, weapon);

// 从StringWriter对象中获取XML字符串
string xml = sw.ToString();

// 输出XML字符串
Console.WriteLine(xml);

为了简化这个过程,可以制作扩展方法。

public static class Extension
{
    
    
	/// <summary>
	/// 将对象xml序列化为字符串
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="value"></param>
	/// <returns></returns> 
	public static string XmlSerialize<T>(this T value)
	{
    
    
		XmlSerializer xml = new XmlSerializer(typeof(T));
		StringWriter sw = new StringWriter();
		xml.Serialize(sw, value);
		return sw.ToString();
	}

	/// <summary>
	/// 将xml字符串反序列化
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="value"></param>
	/// <returns></returns>
	public static T XmlDeSerialize<T>(this string value)
	{
    
    
		XmlSerializer xml = new XmlSerializer(typeof(T));
		StringReader sr = new StringReader(value);
		return (T)xml.Deserialize(sr);
	}
}

特性控制序列化规则

这个序列化api

  • 要求目标类必须是具有public修饰的。
  • 他只会对public的成员进行序列化,包括字段和属性。
  • 他要求目标类型有一个公共无参构造器
  • 通过反射进行赋值,如果目标属性没有set访问器,或者自己没有同名元素,目标属性会保持默认值。

一些特性可以控制他的序列化规则。

元素

XmlElement特性可以指定元素名字。
在对数组或集合使用时,他会被平铺成元素。

对数组使用前

<Person>
  <Hobbies>
    <string>读书</string>
    <string>写作</string>
    <string>编程</string>
  </Hobbies>
</Person>

对数组使用后

<Person>
  <Hobbies>读书</Hobbies>
  <Hobbies>写作</Hobbies>
  <Hobbies>编程</Hobbies>
</Person>

属性

XmlAttribute特性可以让一个成员以xml属性来进行序列化。
这要求他不能是复合类型,必须像int,bool,string这样可以不拆分直接用字符串表示的类型。

对成员使用前

<Person>
  <Age>20</Age>
</Person>

对成员使用后

<Person Age="20" />

文本

XmlText特性可以让一个成员成为文本节点进行序列化。
因为文本节点没法进行区分,所以一个类下最多只能有一个成员具有这个特性。
对成员使用前

<Person>
  <Age>20</Age>
</Person>

对成员使用后

<Person>
  20
</Person>

忽略

带有XmlIgnore特性的成员在序列化和反序列化中会被无视。

排序

属性和元素的特性,可以对属性或元素命名。
此外,元素具有可选的Order属性,这个属性可以控制序列化的顺序。
但是要么全都没有这个属性,要么全部显式声明这个属性。

public class Person
{
    
    
	[XmlElement("姓名",Order =1)]
	public string Name {
    
     get; set; }
	[XmlAttribute("年龄")]
	public int Age {
    
     get; set; }
	[XmlElement(Order =0)]
	public string[] Hobbies {
    
     get; set; }
}

多态

xml可以表示更多的信息,以至于多态都可以保存。
对数组或集合使用XmlArrayItem指定类型,可以在反序列化的时候识别出类型
(序列化的时候有没有都会保存类型)。
不过你需要提前预测可能出现的所有类型并一个一个进行指定。

public class Data
{
    
    
	[XmlArrayItem("字符串", typeof(string))]
	[XmlArrayItem("数字", typeof(int))]
	public object[] Datas;
}

更多

请参阅使用属性控制 XML 序列化

xml树

对于既存的xml字符串,可以使用System.Xml.Linq命名空间下的XElement.Parse进行解析。
对于文件,流之类的东西,可以使用XElement.Load进行读取加载(参数是流或路径)。

一个XElement实例可以使用ToString查看他的xml字符串,
可以使用Save保存为文件或写入到流中。

xml节点

xml树的内容非常多,按照继承链有以下类型。

  • XObject
    • XAttribute
    • XNode
      • XComment
      • XDocumentType
      • XProcessingInstruction
      • XText
        • XCData
      • XContainer
        • XDocument
        • XElement
<?xml version="1.0" encoding="utf-8"?>
<!-- 这是一个处理指令(XProcessingInstruction),用于声明 XML 文档的版本、编码等信息 -->
<!-- This is a comment -->
<!-- 这是一个注释(XComment),用于添加一些说明性的文本 -->
<!DOCTYPE Root [
	<!-- 这是一个文档类型声明(XDocumentType),用于定义 XML 文档的结构和约束 -->
	<!ELEMENT Root (Child1, Child2)>
	<!-- 这是一个元素类型声明,用于指定 Root 元素的内容模型 -->
	<!ATTLIST Root id ID #REQUIRED>
	<!-- 这是一个属性列表声明,用于指定 Root 元素的属性 -->
]>
<Root id="R1">
	<!-- 这是一个元素(XElement),表示 XML 文档的根元素,它有一个属性(XAttribute) id,值为 R1 -->
	<Child1>Some text</Child1>
	<!-- 这是一个元素(XElement),表示 Root 元素的第一个子元素,它有一些文本内容(XText) -->
	<Child2 att="A1"/>
	<!-- 这是一个元素(XElement),表示 Root 元素的第二个子元素,它有一个属性(XAttribute) att,值为 A1 -->
	<Child3><![CDATA[在这里可以输入<>,xml,!!]]></Child3>
	<!--这是一个元素(XElement),他里面有一个CData,表示不会被转义的文本。-->
</Root>

属性

属性是在元素上面,以键值对形式的东西。
属性只能保存纯文本信息,不能表示有层级关系的内容。

XElement xel = new XElement("ele");
Console.WriteLine(xel);
var xat = new XAttribute("name", "张三");
xel.Add(xat);
Console.WriteLine(xel);
<ele />
<ele name="张三" />

从XElement实例上可以调用Attribute方法来查询指定名字的特性。
可以从获取到的Attribute上修改他,也可以从XElement直接Set指定名字的属性。

XElement xel2 = XElement.Parse(@"<ele name=""张三"" />"); 
var xat2=xel2.Attribute("name");
xat2.Value = "999";//Value只能是string类型
Console.WriteLine(xel2);

xel2.SetAttributeValue("name",true);//这个可以是任意类型,如果是null则会删除这个属性。
Console.WriteLine(xel2);

Console.WriteLine(xat2);
<ele name="999" />
<ele name="true" />
name="true"

属性也可以通过强转转为字符串,数字,时间等基础类型。

XAttribute xat3 = new XAttribute("name", "16");
float? f = (float?)xat3;
Console.WriteLine(f);

基础类型是指在xml格式中定义了的类型。是xml的基础类型而不是c#的基础类型。

注释

在xml中,使用<!---->包围的部分是注释。注释内不会要求格式。

XComment comment = new XComment("这个是注释");
Console.WriteLine(comment);
Console.WriteLine("================");
XElement xel = new XElement("root",comment); 
Console.WriteLine(xel);
<!--这个是注释-->
================
<root>
  <!--这个是注释-->
</root>

注释不保存数据信息,所以不能像属性那样使用强转来解析数据。

文档类型

文档类型是提供文档节点验证的说明。
通常会引用外部文件来要求xml符合格式。
如果内联进xml树则如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

他要求note元素必须包含to,from,heading,body元素。
然后to,from,heading,body都必须是可解析的文字(不能包含嵌套的元素)。

获取文档约束需要通过文档进行解析来获取。

XDocument xDoc = XDocument.Parse(xml);//xml替换为上述的字符串

var xdt = xDoc.DocumentType; 
Console.WriteLine(xdt.InternalSubset);

验证有专门的方法,不会出现在解析的时候出现异常。
不过如何验证我没查到。

处理命令

处理命令是<??>之间的内容。
紧跟随左边的<?的文本会被解析为目标(Target)。
然后一个空格之后的所有内容(即便仍然有空格拆分多块)全部为数据(Data)。

XElement note = XElement.Parse(@"
<场景>
  <对话 text=""您想要什么?"">
    <回答 text=""红宝石"">
      <?属性 攻击力 1?>
    </回答>
    <回答 text=""蓝宝石"">
      <?属性 防御力 1?>
    </回答>
    <回答 text=""血瓶"">
      <?属性 生命 100?>
    </回答>
  </对话>
</场景>
");
var p1 = (XProcessingInstruction)note.Elements().First().Elements().First().FirstNode;
Console.WriteLine(p1.Target);//属性
Console.WriteLine(p1.Data);//攻击力 1

文本

在元素下的文本为文本节点。

XElement note = XElement.Parse(@"
<root>
  你好,世界
</root>
");
var t1 =(XText) note.FirstNode;

Console.WriteLine(t1.Value);

但是只要没有贴合元素的开闭标签,都会包含。
例如这里的标签和文字之间有换行。所以这里的文本内容会包含换行符。

转义文本

通常情况下,xml内容是不能包含尖括号,引号之类的东西。如果要书写则需要转义。
但是如果是文本元素,可以通过<![CDATA[]]>声明转义文本。
因为他的开启和结束符号很多。所以通常请看下,里面的东西都不会有歧义的解读。

XElement note = XElement.Parse(@"
<root>
  <![CDATA[这是一个xml的标签:<a int=""3"" />]]>
</root>
");
var t1 =(XCData) note.FirstNode;

Console.WriteLine(t1.Value);

这个节点只有可能是文本节点,所以这个类型是从XText派生的。

文档

文档和元素的共同基类XContainer是指里面可能嵌套东西的。
只是用来解析一个元素,使用文档和元素都可以。
但是文档另外可能包含xml开头的说明符。
<?xml version="1.0" encoding="UTF-8"?>这表示使用1.0语法的xml,使用utf-8编码。

XDeclaration declaration = new XDeclaration("1.0", "UTF-8", null);

元素

xml元素是xml树中最重要的东西,可以表示层级,可以包含内容,可以携带属性。
一个元素可以嵌套多个同名元素,所以不像json可以使用索引器访问内容。
以下示例展示xml元素的常用方法。

XElement letters = XElement.Parse(@"
<letters>
  <letter from=""张三"" to=""李四"" date=""2022-01-30"">
    <subject>问候</subject>
    <body>李四,你好!最近过得怎么样?</body>
  </letter>
  <letter from=""李四"" to=""张三"" date=""2022-02-01"">
    <subject>回复</subject>
    <body>张三,你好!我最近很好,谢谢你的关心。</body>
  </letter>
</letters>
");
foreach (var item in letters.Descendants().Where(x=>x.Name=="subject"))
{
    
    //Descendants方法可以递归获取所有子节点。
	Console.WriteLine(item.Value);
}

Console.WriteLine(letters.Elements("letter").First().Value);
//Elements方法为获取所有指定名字的直属子节点。可以不填名字。

letters.SetAttributeValue("count",4);
//SetAttributeValue方法可以修改属性的值,如果没有这个属性会添加。如果使用null值会移除属性。

元素可以使用Add方法,或者在构造器的名字后面加入多个值。

  • 如果是XObject家族的,会被嵌入进树里面。
  • 对于数组,可迭代类型的东西,会拆开后对里面的元素依次添加。
  • 其他类型的数据,会使用ToString转文字后加入进去。所以复杂类型需要自己先序列化,然后解析为xml节点,再添加xml节点。

XName

虽然在上面的例子里面,属性和节点的名字都是直接使用字符串类型。
但实际上构造器接受的是XName类型,他细分为命名空间和名字。

XElement xel = new XElement("{火蜥蜴战队}张三");
Console.WriteLine(xel);
//<张三 xmlns="火蜥蜴战队" />

XName xn = xel.Name;
Console.WriteLine(xn.NamespaceName); //火蜥蜴战队
Console.WriteLine(xn.LocalName);     //张三

XName类型的构造器是私有的。但是有一个从string而来的隐式转换。
在if语句中可以直接使用==进行判断,
但是在switch语句中,需要从他的属性里访问出他的名字。
因为隐式转换是一个操作过程,不属于常量,不能用于switch。

一个节点有明明空间时,他的子节点默认和他是相同的命名空间。
所以命名空间不同的都要标识,包括命名空间为""的。

XElement xel = new XElement("{火蜥蜴战队}张三"
	, new XElement("李四")
	, new XElement("{不死鸟战队}王五")
	, new XElement("{火蜥蜴战队}赵六"));
Console.WriteLine(xel);
/*
<张三 xmlns="火蜥蜴战队">
  <李四 xmlns="" />
  <王五 xmlns="不死鸟战队" />
  <赵六 />
</张三>
*/

用于属性上时,会再额外声明一个命名空间属性。
这个额外的命名空间属性是可以更改的。

XElement xel = new XElement("{火蜥蜴战队}张三"
	, new XAttribute("{2023}职务","队长")
	, new XElement("李四"
		,new XAttribute("{2023}职务","副队长")
	)
); 
Console.WriteLine(xel);
/*
<张三 p1:职务="队长" xmlns:p1="2023" xmlns="火蜥蜴战队">
  <李四 p1:职务="副队长" xmlns="" />
</张三>
*/
Console.WriteLine("===========");
xel.SetAttributeValue(XNamespace.Xmlns + "zhang", "2023");
//XNamespace.Xmlns 这个静态变量是声明命名空间的xml命名空间。和一个字符串相加以后会变成XName
Console.WriteLine(xel);
/*
<张三 zhang:职务="队长" xmlns:zhang="2023" xmlns="火蜥蜴战队">
  <李四 zhang:职务="副队长" xmlns="" />
</张三>
*/

猜你喜欢

转载自blog.csdn.net/zms9110750/article/details/131715224