1. XML
1.1. XML标准
可扩展标记语言(英语:Extensible Markup Language,简称:XML),是一种标记语言。
标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。
XML在.NET Framework中有重要作用。不仅允许在应用程序中使用XML,.NET Framework本身也在配置文件和源代码文档中使用XML。
对XML处理的支持由.NET中System.Xml名称空间的类提供。
它和超文本标记语言(HTML)语法区别:
超文本标记语言的标记不是所有的都需要成对出现,它则要求所有的标记必须成对出现;
HTML标记不区分大小写,它则大小敏感,即区分大小写。
XML 被设计用来结构化、存储以及传输信息。
1.1.1. 语法规则
声明
通过第一行xml的声明,定义xml版本和所使用的编码,如下:
<?xml version="1.0" encoding="utf-8"?>
注释
同html的注释,如下:
<!-- 注释 -->
所有元素都须有关闭标签
省略关闭标签是非法的:
<!-- 错误的,p标签未关闭 -->
<p>This is a paragraph
<p>This is another paragraph
<!-- 合法的表示 -->
<p>This is a paragraph</p>
<p>This is another paragraph</p>
标签对大小写敏感
标签的大小写是严格限定的,标签 <Letter>
与标签 <letter>
是不同的。
<Message>这是错误的。</message>
<message>这是正确的。</message>
必须正确地嵌套
所有元素必须正确的按顺序关闭,如下:
<!-- 错误的表示,因为name在stu内部打开,关闭时应先关闭name再关闭stu标签 -->
<Students>
<stu>
<name></stu>
</name>
</Students>
<!-- 正确的关闭 -->
<Students>
<stu>
<name></name>
</stu>
</Students>
文档必须有根元素
必须有一个元素是所有其他元素的父元素。该元素称为根元素。
<root>
<child>
<subchild>.....</subchild>
</child>
</root>
属性值须加引号
属性值是以key-value形式进行存储,属性值上需要加引号
<!-- 错误 -->
<stu name=zhangsan>
<!-- 规范 -->
<stu name="zhangsan">
实体引用
如果,某元素内容包含特殊字符<
,对于这样属性的解析会发生错误,
因为<
是标签符号,会造成解析错误,我们通过实体定义可以解决这样的问题。
<!-- 解析失败 -->
<stu name="zhangsan"> 1<9 </stu>
<!-- < 替换 < -->
<stu name="zhangsan">1 < 9</stu>
实体定义 | 字符 | 说明 |
---|---|---|
< |
< | 小于 |
> |
> | 大于 |
& |
& | 与 |
' |
' | 单引号 |
" |
" | 引号 |
ps:在 XML 中,只有字符 "<" 和 "&" 确实是非法的。大于号是合法的,但是用实体引用来代替它是一个好习惯。
1.1.2. 格式
树结构
bookstore.xml基本格式如下:
<?xml version="1.0" encoding="utf-8" ?>
<!-- bookstore 根节点 -->
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
属性和元素表示
//定义一个英雄类
public class Hero {
public string Name { get; set; }
public string Gender { get; set; }
}
//实例化了三个对象
Hero superMan = new Hero() { Name = "超人", Gender = "Male" }
Hero superWomen = new Hero() { Name = "女超人", Gender = "Female" }
Hero spiderMan = new Hero() { Name = "蜘蛛侠", Gender = "Male" }
针对上面CS代码中三个对象,使用属性(名称-值)方式表示如下:
<?xml version="1.0" encoding="utf-8" ?>
<heros>
<hero name="超人" gender="Male"/>
<hero name="女超人" gender="Female"/>
<hero name="蜘蛛侠" gender="Male"/>
</heros>
针对上面CS代码中三个对象,使用元素方式表示如下:
<?xml version="1.0" encoding="utf-8" ?>
<heros>
<hero>
<name>超人</name>
<gender>Male</gender>
</hero>
<hero>
<name>女超人</name>
<gender>Female</gender>
</hero>
<hero>
<name>蜘蛛侠</name>
<gender>Male</gender>
</hero>
</heros>
1.2. 读写XML
1.2.1. 基于流的分析XmlReader/XmlReaderSettings
XmlReader 类是一个提供对 XML 数据的非缓存、只进只读访问的抽象基类,该类不可以实例化,使用XmlReader.Create()创建实例。
XmlReader 类支持从流或文件读取 XML 数据。
读取上面示例中bookstore.xml,读取内容(包含属性和文本):
string xmlPath = @"E:\Attachment\xml\bookstore.xml";
using (XmlReader reader = XmlReader.Create(xmlPath))
{
while (reader.Read())
{
switch (reader.NodeType)
{
//节点元素的处理
case XmlNodeType.Element:
Console.Write("标签开始: <{0}", reader.Name);
//判断该元素是否有属性
if (reader.HasAttributes)
{
//移动到下一个属性,遍历属性
while (reader.MoveToNextAttribute())
{
Console.Write(" {0}=\"{1}\"", reader.Name, reader.Value);
}
}
Console.WriteLine(">");
break;
//节点的文本内容
case XmlNodeType.Text:
Console.WriteLine("元素内容:{0}", reader.Value);
break;
//关闭标签名称
case XmlNodeType.EndElement:
Console.WriteLine("元素关闭:</{0}>", reader.Name);
break;
//其他类型请自行研究
default:
Console.WriteLine("其他节点类型:{0},该节点值为:{1}", reader.NodeType, reader.Value);
break;
}
}
}
[MSDN-XmlReader 类]https://msdn.microsoft.com/zh-cn/library/system.xml.xmlreader(v=vs.110).aspx
1.2.2. 基于流的创建XmlWriter/XmlWriterSettings
XmlWriter 类是一个抽象基类,提供只进、只写、非缓存的方式来生成 XML 流。 使用静态方法XmlWrite.Create()创建实例。
在当前目录创建一个books.xml文件,创建和关闭标记一定要按照正确顺序,如下:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";
using (XmlWriter writer = XmlWriter.Create("books.xml", settings))
{
//创建开始标记,根节点
writer.WriteStartElement("bookstore");
//创建book标记
writer.WriteStartElement("book");
writer.WriteAttributeString("category", "COOKING");
//创建title标记
writer.WriteStartElement("title");
writer.WriteString("category");
writer.WriteEndElement();
//关闭标记
writer.WriteEndElement();
//关闭标记
writer.WriteEndElement();
//将XML文档写入磁盘(冲刷缓冲区)
writer.Flush();
}
得到books.xml内容如下,在上述代码中有很多重复的创建标记,代码很冗余,我们可以重构提取一个方法出来
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book category="COOKING">
<title>category</title>
</book>
</bookstore>
重构提取方法,简单对页面节点做了个提取,方便了叶子标记的创建。
static void Main()
{
XmlWriterSettings settings = new XmlWriterSettings();
//指定是否缩进元素,默认为false,默认缩进字符是两个空格
settings.Indent = true;
//指定缩进为制表符
settings.IndentChars = "\t";
using (XmlWriter writer = XmlWriter.Create("books.xml", settings))
{
//创建开始标记,根节点
writer.WriteStartElement("bookstore");
//创建book标记
writer.WriteStartElement("book");
writer.WriteAttributeString("category", "COOKING");
WriteLeafNode(writer, "title", "Everyday Italian", new List<TagPropInfo>() { new TagPropInfo("lang", "en") });
WriteLeafNode(writer, "author", "Giada De Laurentiis", null);
WriteLeafNode(writer, "year", "2005", null);
WriteLeafNode(writer, "price", "30.00", null);
writer.WriteEndElement();
//关闭标记
writer.WriteEndElement();
//将XML文档写入磁盘(冲刷缓冲区)
writer.Flush();
}
}
/// <summary>
/// 构造一个标记属性类,方便进行传递
/// </summary>
public class TagPropInfo
{
public TagPropInfo(string name, string propValue)
{
Name = name;
PropValue = propValue;
}
public string Name { get; set; }
public string PropValue { get; set; }
}
/// <summary>
/// 创建叶子节点,简单封装
/// </summary>
/// <param name="xw">xmlwrite对象</param>
/// <param name="tagName">标记的名称</param>
/// <param name="text">元素的内容文本</param>
/// <param name="lstProps">元素属性集合</param>
static void WriteLeafNode(XmlWriter xw, string tagName, string text, List<TagPropInfo> lstProps)
{
xw.WriteStartElement(tagName);
if (lstProps != null)
{
foreach (TagPropInfo item in lstProps)
{
xw.WriteAttributeString(item.Name, item.PropValue);
}
}
if (!string.IsNullOrEmpty(text))
{
xw.WriteString(text);
}
xw.WriteEndElement();
}
1.2.3. 内存中的处理XmlDocument
XML 文档对象模型 (DOM) 将 XML 数据作为一组标准的对象对待,用于处理内存中的 XML 数据。
常用的XML操作类如下:
- XmlNode:表示文档中的一个节点
- XmlDocument:扩张了XmlNode类,用于加载和保存磁盘或其他地方的数据
- XmlElement:表示Xml文档中的一个元素
- XmlAttribute:表示一个属性
- XmlText:表示开标记和闭标记之间的文本
- XmlComment:表示一种特殊类型的节点,为读者提供各部分的信息
- XmlNodeList:表示节点的集合
将xml文档加载到内存中:
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
常用的XmlElement类包含的属性和方法:
- FirstChild:返回根节点后第一个节点
- LastChild:返回当前节点的最后一个子节点
- ParentNode:返回当前节点的父节点
- NextSibling:返回紧跟着的兄弟节点
- HasChildNodes:判断当前节点是否有子节点
查询
使用DOM方式进行遍历节点:
XmlDocument doc = new XmlDocument();
doc.Load(@"E:\Attachment\xml\bookstore.xml");
//以文本方式打印所有内容
Console.WriteLine(doc.OuterXml);
//可以按照路径寻找节点,从根节点开始寻找,遍历所有的book节点
XmlNodeList nodeList = doc.SelectNodes("/bookstore/book");
foreach (XmlNode node in nodeList)
{
/*
node.InnerText 读取节点内容文本
node.Attributes["category"].Value 节点对应的category属性的值
省略了很多判断,需要注意捕获异常
*/
string cate = node.Attributes["category"].Value;
string title = node.SelectSingleNode("title").InnerText;
string year = node.SelectSingleNode("year").InnerText;
string price = node.SelectSingleNode("price").InnerText;
Console.WriteLine("category:{0},title:{1},year:{2},price:{3}", cate, title, year, price);
}
修改
使用DOM方式进行节点的修改:
string xmlPath = @"E:\Attachment\xml\bookstore.xml";
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
//取第一个匹配的book标记
XmlNode node = doc.SelectSingleNode("/bookstore/book");
//修改book标记的属性
node.Attributes["category"].Value = "ART";
//修改后一定要保存
doc.Save(xmlPath);
创建
同样,我们以前面所说的bookstore为例,使用XmlDocument添加文档:
XmlDocument doc = new XmlDocument();
//创建Xml声明部分,并添加到文档,即<?xml version="1.0" encoding="utf-8" ?>
XmlDeclaration xmlDecl = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(xmlDecl);
//创建根元素,并添加到文档
XmlNode rootNode = doc.CreateElement("bookstore");
doc.AppendChild(rootNode);
//创建 book 元素,添加到 根元素
XmlNode book = doc.CreateElement("book");
XmlAttribute attr = doc.CreateAttribute("category");
attr.Value = "COOKING";
book.Attributes.Append(attr);
rootNode.AppendChild(book);
//创建 title 元素,添加到 book 元素
XmlNode title = doc.CreateElement("title");
attr = doc.CreateAttribute("lang");
attr.Value = "en";
title.Attributes.Append(attr);
title.InnerText = "Everyday Italian";
book.AppendChild(title);
//创建 author 元素,添加到book元素
XmlNode author = doc.CreateElement("author");
author.InnerText = "Giada De Laurentiis";
book.AppendChild(author);
//创建 year 元素,添加到book元素
XmlNode year = doc.CreateElement("year");
year.InnerText = "2005";
book.AppendChild(year);
//创建 price 元素,添加到book元素
XmlNode price = doc.CreateElement("price");
price.InnerText = "30.00";
book.AppendChild(price);
//最后进行保存
doc.Save(@".\booksNew.xml");
删除
使用XmlDocument进行修改、删除xml节点,以上面bookstore为例,删除其中category为WEB的元素
string xmlPath = @"..\..\bookstore.xml";
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
//所有的book元素
XmlNodeList nodeList = doc.SelectNodes("/bookstore/book");
//book的父元素,删除category="WEB"的book元素需要使用父级元素
XmlNode bookstore = doc.SelectSingleNode("/bookstore");
foreach (XmlNode node in nodeList)
{
if (node.Attributes["category"].Value == "WEB")
{
//从父级元素的角度进行删除该book元素及其子元素
bookstore.RemoveChild(node);
}
}
doc.Save(xmlPath);
1.2.4. XmlDocument和XmlReader
- Dom模型
使用DOM的好处在于它允许编辑和更新XML文档,可以随机访问文档中的数据,可以使用XPath查询。
但是,DOM的缺点在于它需要一次性的加载整个文档到内存中,对于大型的文档,这会造成资源问题。
- 流模型
流模型很好的解决了这个问题,因为它对XML文件的访问采用的是流的概念,
也就是说,任何时候在内存中只有当前节点,但它也有它的不足,它是只读的,仅向前的,不能在文档中执行向后导航操作。
1.2.5. XDocument(方便、快捷)
XDocument是.net 3.5为Linq to XML准备的轻量级Document对象
和XmlDocument相同的是会把所有的XML所有的节点加载到内存中。
创建xml
使用XDocument进行创建xml文件,和上面XmlDocument创建xml文档一致,但是代码要精简很多,如下所示:
//XDocument 需要 using System.Xml.Linq;
XDocument xdoc = new XDocument(new XDeclaration("1.0", "utf-8", null));
XElement root = new XElement("bookstore");
xdoc.Add(root);
/*
new XElement("标记名称","元素内容",new XAttribute(),子元素)
*/
XElement book1 = new XElement("book", new XAttribute("category", "COOKING")
, new XElement("title", "Everyday Italian", new XAttribute("lang", "en"))
, new XElement("author", "Giada De Laurentiis")
, new XElement("year", "2005")
, new XElement("price", "30.00"));
//记得要进行保存
root.Add(book1);
XElement book2 = new XElement("book", new XAttribute("category", "CHILDREN")
, new XElement("title", "Harry Potter", new XAttribute("lang", "en"))
, new XElement("author", "J K. Rowling")
, new XElement("year", "2005")
, new XElement("price", "29.99"));
//记得要进行保存
root.Add(book2);
xdoc.Save("xbooks.xml");
读取所有
查找节点经常用到的方法有Descendants()和Elements()。
两者的区别是:Descendants()方法会一直向下遍历查找直到没有子节点,
Elements()只会查找当前节点的子节点,不会向下递归查找。
XElement books = XElement.Load("books.xml");
// 查找当前节点所有子节点
var xes1 = books.Elements();
Console.WriteLine(xes1.Count());
foreach (XElement el in xes1)
{
Console.WriteLine(el);
}
// 查找所有节点,递归
var xes2 = books.Descendants();
// 查找当前节点所有book子节点
var xes3 = books.Elements("book");
// 查找所有title节点
var xes4 = books.Descendants("title");
// 查找book节点下直接title子节点
var xes5 = books.Elements("book").Elements("title");
// 查找所有title节点,匹配属性lang为en的节点
var xes6 = books.Descendants("title").Where(t => t.Attribute("lang").Value == "en");
修改和删除
XElement books = XElement.Load("books.xml");
// FirstOrDefault查找category属性为"COOKING"的第一个节点
var cookBook = books.Descendants("book")
.FirstOrDefault(t => t.Attribute("category").Value == "COOKING");
// 如果有,则修改该属性值
if (null != cookBook)
{
cookBook.Attribute("category").Value = "eating";
}
// 修改后需要进行保存
books.Save("edit_books.xml");
// 查找所有出版年份为2005年的book节点,并删除
var bookYear2005 = books.Descendants("book").Where(t => t.Element("year").Value == "2005");
bookYear2005.Remove();
books.Save("remove_books.xml");
1.3. 配置文件的读写
配置文件在很多情况下都使用到, 配置文件分为两种,一种是应用程序的配置文件, 一种是web的配置文件。
两种配置文件最大的区别是web的配置文件更新之后会实时更新,应用程序的配置文件不会实时更新.
<configuration>
<appSettings>
<add key="name" value="我是远程服务器"/>
</appSettings>
</configuration>
读写需要使用【ConfigurationManager】类,则必须在工程添加【System.Configuration.dll】程序集的引用
读取:
// 需要 using System.Configuration;
string str = ConfigurationManager.AppSettings["Name"];
新建和修改(慎用):
Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
cfg.AppSettings.Settings.Add("Version", "1.0");
cfg.AppSettings.Settings["Version"].Value = "2.0";
//修改后要进行保存,并且此处添加修改的是/bin/debug中同程序名的config配置
cfg.Save();
//需要进行RefreshSection刷新节,否则无法更新到内存中!
ConfigurationManager.RefreshSection("appSettings");
Console.WriteLine(ConfigurationManager.AppSettings["Version"]);
拓展参考: