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>

<!-- &lt; 替换 < -->
<stu name="zhangsan">1 &lt; 9</stu>
实体定义 字符 说明
&lt; < 小于
&gt; > 大于
&amp; &
&apos; ' 单引号
&quot; " 引号

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"]);

拓展参考:

XML技术总结之XDocument 和XmlDocument

C#读写xml文件

results matching ""

    No results matching ""