操作XML
千呼万唤始出来,BB了这么多东西,咱们终于可以写点东西了,前方内容很多,楼主正在考虑是否可以分开写.
因为啥东西多呢?前面说了那么多关于XML的东西,什么转换啊,DTD啊,手都说了,我要是不给你演示一遍你说我骗你咋办?
首先记住一点:操作XML文档时,操作的单元是节点,首先要知道XML文档有哪些节点类型.在.NET中,有关XML的类型位于System.Xml.*命名空间下,System.Xml.XmlNodeType枚举列出了XML的节点类型.
namespace System.Xml
{
//
// 摘要:
// 指定节点的类型。
public enum XmlNodeType
{
//
// 摘要:
// 如果未调用 Read 方法,则由 System.Xml.XmlReader 返回。
None = 0,
//
// 摘要:
// 元素(例如,<item>)。
Element = 1,
//
// 摘要:
// 特性(例如,id='123')。
Attribute = 2,
//
// 摘要:
// 节点的文本内容。
Text = 3,
//
// 摘要:
// CDATA 节(例如,<![CDATA[my escaped text]]>)。
CDATA = 4,
//
// 摘要:
// 实体引用(例如,#)。
EntityReference = 5,
//
// 摘要:
// 实体声明(例如,<!ENTITY...>)。
Entity = 6,
//
// 摘要:
// 处理指令(例如,<?pi test?>)。
ProcessingInstruction = 7,
//
// 摘要:
// 注释(例如,<!-- my comment -->)。
Comment = 8,
//
// 摘要:
// 作为文档树的根的文档对象提供对整个 XML 文档的访问。
Document = 9,
//
// 摘要:
// 由以下标记指示的文档类型声明(例如,<!DOCTYPE...>)。
DocumentType = 10,
//
// 摘要:
// 文档片段。
DocumentFragment = 11,
//
// 摘要:
// 文档类型声明中的表示法(例如,<!NOTATION...>)。
Notation = 12,
//
// 摘要:
// 标记间的空白。
Whitespace = 13,
//
// 摘要:
// 混合内容模型中标记间的空白或 xml:space="preserve" 范围内的空白。
SignificantWhitespace = 14,
//
// 摘要:
// 末尾元素标记(例如,</item>)。
EndElement = 15,
//
// 摘要:
// 由于调用 System.Xml.XmlReader.ResolveEntity 而使 XmlReader 到达实体替换的末尾时返回。
EndEntity = 16,
//
// 摘要:
// XML 声明(例如,<?xml version='1.0'?>)。
XmlDeclaration = 17
}
}
上面代码不是我写的,是微软写的XML的节点类型.
对于节点类型,有与之相应的.NET类型.比如,对于注释节点,有XmlComment类型;对于空格节点,有XmlWhitespace类型;对于元素节点,有XmlElement类型.这些类型均直接或间接继承共同的基类XmlNode.就像所有的类都是继承自object一样.
使用XmlReader和XmlWriter
1.使用XmlReader读取XML文档
先创建一个完整的XML文件movieList.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE movieList [<!ENTITY Bruce "布鲁斯.威利斯"><!ENTITY Jai "杰.卡特尼">]>
<movieList>
<!--新近上映的电影-->
<movie id="1" title="魔境仙踪" director="山姆.雷米" release="2012-3-6">
<starring>詹姆斯.弗兰克/米拉.克丽丝</starring>
<genreList>
<genre>冒险</genre>
<genre>科幻</genre>
<genre>爱情</genre>
</genreList>
</movie>
<movie id="2" title="龙胆虎威" director="约翰.摩尔" release="2016-9-6">
<starring>&Bruce;/&Jai;</starring>
<genreList>
<genre>搞笑</genre>
<genre>恐怖</genre>
<genre>惊悚</genre>
</genreList>
</movie>
</movieList>
接下来使用XmlReader类型对它进行读取,XmlReader的使用和StreamReader类似,只不过StreamReader是以”行”为单位进行读取,而XmlReader是以”节点”为单位进行读取.节点在.NET中由System.Xml.XmlNode类型表示,对于节点来说,最重要的属性是名称和值,分别对应XmlNode类型的Name和Value属性.
XmlReader是一个抽象类,可以通过调用它的静态方法Create()来创建对象.
建一个控制台项目.下面的代码展示了读取所有的节点,并输出了节点的类型,名称和值.对于元素节点,如果存在属性,还输出了属性的名称和值.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace 操作XML
{
class Program
{
static void Main(string[] args)
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;//默认为Prohibit
settings.IgnoreWhitespace = true;
XmlReader reader = XmlReader.Create("movieList.xml", settings);
while (reader.Read())
{
string indent = new string(' ',reader.Depth*3);
string line = string.Format("{0}{1} | {2} - {3}",indent,reader.NodeType,reader.Name,reader.Value);
Console.WriteLine(line);
line = "";
if (reader.NodeType==XmlNodeType.Element&&reader.HasAttributes)
{
for (int i = 0; i <reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
line += string.Format("{0}[{1}, {2}]\r\n",indent,reader.Name,reader.Value);
}
Console.WriteLine(line);
}
}
reader.Close();
}
}
}
注意一点,movieList.xml文件需要放在debug目录下.
while(reader.Read())循环中的代码部分相对简单,有下面几点需要注意:
(1).不是所有的节点都有名称或者值,对于Comment节点来说,只有值没有名称.对于Element接地拿来说,只有名称没有值.
(2)尽管XmlReader是以只读的,前进的方式进行读取,但是对于元素的属性,可以根据所以访问任意属性.
(3)XmlReader具有一个属性ValueType.
XmlReader还有两组强类型方法(注意这里说的是组,说明还有asInt,asFloat,asLong,asObject.),例如:
ReadContentAsBoolean();
ReadElementContentAsBoolean();
它们用于简化操作,省却一些枯燥的类型转换的代码.这两组方法最主要的区别是:ReadContentAsXXX()方法应在Text节点上调用,ReadElementContentAsXXX()方法应在Element节点上调用.这两组方法都会使XmlReader的指针前进到下一节点,效果上相当于调用了Read()方法.
2.使用XmlWriter生成XML文档.
使用XmlWriter生成XML文档也是比较容易的.注意到它具有只写,单向前进的特点,因此使用XmlWriter不能直接修改现有的XML文档,只能用于创建全新的XML文档.如果要使用XmlWriter修改XML,比如修改某个元素的属性值,那么可以配合XmlReader来间接完成.
下面的代码使用XmlWriter生成了和之前创建的movieList.xml完全一样的XML文档.
#region XmlWriter
string filePath = "movieList2.xml";
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = System.Text.Encoding.UTF8;
settings.Indent = true;
settings.IndentChars = "\t";
settings.OmitXmlDeclaration = false;
XmlWriter writer = XmlWriter.Create(filePath, settings);
writer.WriteStartDocument();//xml声明
writer.WriteDocType("movieList", null, null, "<!ENTITY Brace \"布鲁斯.威利斯\"><!ENTITY Jai \"杰.科特尼\">");
writer.WriteStartElement("movieList");
writer.WriteComment("新近上映电影信息");
writer.WriteStartElement("movie");
writer.WriteAttributeString("id", "1");
writer.WriteAttributeString("title", "魔境仙踪");
writer.WriteAttributeString("director", "山姆.雷米");
writer.WriteAttributeString("release", "2012-9-6");
writer.WriteElementString("starring", "詹姆斯.弗兰克/米拉.克丽丝");
writer.WriteStartElement("genreList");
writer.WriteElementString("genre", "科幻");
writer.WriteElementString("genre", "冒险");
writer.WriteElementString("genre", "动作");
writer.WriteEndElement();//genreList
writer.WriteEndElement();//movie
//writer.WriteStartElement("syx","movie","");
writer.WriteStartElement("movie");
writer.WriteAttributeString("id", "2");
writer.WriteAttributeString("title", "龙胆虎威");
writer.WriteAttributeString("director", "约翰.摩尔");
writer.WriteAttributeString("release", "2013-6-9");
//writer.WriteStartElement("syx","starring",null);
writer.WriteStartElement("starring");
writer.WriteEntityRef("Brace");
writer.WriteString("/");
writer.WriteEntityRef("Jai");
writer.WriteEndElement();//starring
writer.WriteStartElement("genreList");
writer.WriteElementString("genre", "喜剧");
writer.WriteElementString("genre", "恐怖");
writer.WriteElementString("genre", "搞笑");
writer.WriteEndElement();//genreList
writer.WriteEndElement();//movie
writer.WriteEndElement();//movieList
writer.WriteEndDocument();
writer.Flush();
writer.Close();
#endregion
这段无脑代码楼主写了接近一个小时,真是醉了!!!
与XmlReader类似,在创建XmlWriter时也可以传入一个XmlWriterSettings类型的对象,在本例中,设置了缩进的格式,生成文档的字符编码信息.XmlWriter类型提供的方法相对简单,通过方法名称和注释就可以了解很多了.
注释掉的两行内容演示了如何为元素添加命名空间和命名空间的前缀:
//添加movie元素,添加命名空间和命名空间前缀
//writer.WriteStartElement("syx","movie","");
//添加命名空间前缀
//writer.WriteStartElement("syx","starring",null);
使用XmlWriter生成XML文档很繁琐,由于XML不过是纯文本文件,元素名称和文档结构通常是固定的,只是元素标记的文本值和属性值不同,因此可以直接写入原始文本,比如这样:
string element =
@" <movie> id=""{0}"" title=""{1}"" director=""{2}"" release=""{3}"">
<starring>{4}/{5}</starring>
<genreList>
<genre>{6}</genre>
<genre>{7}</genre>
<genre>{8}</genre>
</genreList>
</movie>";
element = string.Format(element, "2", "龙胆虎威", "约翰.摩尔", "2012-9-9", "&Bruce;", "/&Jai;", "动作", "爱情", "喜剧");
writer.WriteString("\r\n");
writer.WriteRaw(element);
writer.WriteString("\r\n");
WriteRaw()方法和WriteString()方法的区别是:WtiteString()方法会对字符串进行转义,例如将”\r\n”变为换行,将”<”变为”<”;而WriteRaw则会将”\r\n”以文本的形式写入到XML文档中.
使用XmlDocument和XPath
使用XmlReader遍历文档是很方便的,使用XmlWriter生成一个新的XML文档也很容易.但是对现有的XML进行修改,例如添加一个元素或修改一个属性值,就比较麻烦了.此时,可以使用XmlDocument对象,通过调用DOM方法对文档进行修改,然后再保存.由于DOM已经进行了标准化,很多语言都对他进行了支持,比如JS,因此这里的很多方法与JS中都是一致的,比如GetElementByID(),GetElementByTagName(),AppendChild(),InsertAfter()等.
下面的代码演示了在movieList.xml文档中添加一组新的元素:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace 使用XmlDocument和XPath
{
class Program
{
static void Main(string[] args)
{
string filePath = "movieList.xml";
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlElement movie = doc.CreateElement("movie");
XmlAttribute attr = doc.CreateAttribute("id");
attr.Value = "3";
movie.Attributes.Append(attr);
attr = doc.CreateAttribute("title");
attr.Value="速度与激情";
movie.Attributes.Append(attr);
attr = doc.CreateAttribute("direactor");
attr.Value = "范.迪塞尔";
movie.Attributes.Append(attr);
attr = doc.CreateAttribute("release");
attr.Value = "2016-9-9";
movie.Attributes.Append(attr);
XmlElement starring = doc.CreateElement("starring");
starring.InnerText = "奥巴马";
movie.AppendChild(starring);
XmlElement genreList = doc.CreateElement("genreList");
XmlElement genre = doc.CreateElement("genre");
genre.InnerText = "刺激";
genreList.AppendChild(genre);
genre = doc.CreateElement("genre");
genre.InnerText = "冒险";
genreList.AppendChild(genre);
movie.AppendChild(genreList);
XmlElement root = doc.DocumentElement;
root.AppendChild(movie);
doc.Save("movieList3.xml");
}
}
}
上面的代码可以说是平铺样式的,看方法的名称就知道啥意思.需要留意的有几点:
(1).Element类型节点的Value属性是没有值的.因此如果要设置元素的两个标记中间的文本(<tag>文本</tag>),使用InnerText或InnerXml属性.
(2).注意区别上面的XmlDocument类型的doc和XmlElement类型root.doc表示的是整个XML文档,包含了XML声明,文档类型声明,注释等所有内容;root则代表文档的根节点,即movieList元素.
接下来看一个例子,对id属性为2的movie元素做如下修改
(1).修改title属性
(2).修改starring元素,添加一个主演
(3).修改genreList元素的子元素,删除一个子元素
要想修改某一元素或者属性,首先需要选择元素.选择元素的方法很多,比方说简单易用的GetElementById(),这里为大家演示如何使用XPATH来获取元素.下面是代码:
string filePath = "movieList3.xml";
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlElement root = doc.DocumentElement;
XmlNode movie = root.SelectSingleNode("movie[@id=2]");
XmlNode titleAttr = movie.SelectSingleNode("@title");
titleAttr.Value = "A Good Day to Die Hard";
XmlNode starring = movie.SelectSingleNode("starring");
starring.InnerText = starring.InnerText + " / 梅西";
XmlNode genre = movie.SelectSingleNode("genreList/genre[3]");
XmlNode genreList = movie.SelectSingleNode("genreList");
genreList.RemoveChild(genre);
doc.Save("movieList4.xml");
这段代码主要调用的方法是SelectSingleNode(),它用于接受一个XPATH字符串,然后选中符合条件的第一个节点.如果想获得XPATH的全部节点,可以使用SelectNodes()方法.
接下来的一个实例是如何对XmlDocument进行遍历,便利的方式和数据结构中”树的遍历”是一样的,因为XmlDocument本身就是一个树形结构.
当然,如果要遍历XML文档,那么应该优先考虑xmlReader而不是XmlDocument.
测试代码:
string filePath = "movieList4.xml";
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlNode root = doc.DocumentElement;
showNode(root);
private static void showNode(XmlNode root)
{
if (root.NodeType==XmlNodeType.Text)
{
Console.WriteLine(root.Value);
}
if (root.NodeType==XmlNodeType.Element)
{
Console.WriteLine(root.Name);
}
if (root.Attributes!=null&&root.Attributes.Count>0)
{
foreach (XmlAttribute attr in root.Attributes)
{
Console.Write("{0}={1} ",attr.Name,attr.Value);
}
Console.WriteLine();
}
XmlNodeList chiledList = root.ChildNodes;
foreach (XmlNode child in chiledList)
{
showNode(child);
}
}
使用XSD验证XML
如果要验证movieList.xml的有效性,那么首先需要一个XSD模式定义文件.XSD和XML一样是纯文本文件,其本身也是一个XML文件,使用文本编辑器就可以创建.VS中也提供了创建XSD文件的模板,在项目中选择”添加”和”新建项”,然后选择”XML架构”,单机添加,就在项目下新建了一个空的XSD文件.如果想从现用的XML文档生成模式,可以打开”VS命令提示”,然后使用xsd.exe实用工具来创建,可以用下面的语句生成movieList.xml.
>xsd movieList.xml //楼主亲测不行!!!
为啥呢?
因为你需要赞数取消掉实体引用的部分,即&Bruce;和&Jai;!!!
对于movieList.xml生成的XSD文件如下:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="movieList" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="movieList" msdata:IsDataSet="true" msdata:Locale="en-US">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="movie">
<xs:complexType>
<xs:sequence>
<xs:element name="starring" type="xs:string" minOccurs="0" msdata:Ordinal="0" />
<xs:element name="genreList" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="genre" nillable="true" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:simpleContent msdata:ColumnName="genre_Text" msdata:Ordinal="0">
<xs:extension base="xs:string">
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:string" />
<xs:attribute name="title" type="xs:string" />
<xs:attribute name="director" type="xs:string" />
<xs:attribute name="release" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
使用XSD.exe实用工具生成的XSD文件,其中的元素文本,属性值的类型军默认是字符串,这一点可以从type=”xs:string”语句可以看出.除了字符串以外,XML模式还定义了其他的类型,比如unsignedInt,boolean等.
创建好XSD文件以后,就可以对XML文档的有效性进行验证,代码如下:
测试代码:
string filePath = "movieList.xml";
string xsdPath = "movieList.xsd";
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add(null,"movieList.xsd");
settings.ValidationEventHandler += new ValidationEventHandler(settings_ValidationEventHandler); ;
XmlReader reader = XmlReader.Create(filePath,settings);
while (reader.Read())
{
}
Console.WriteLine("Complete!");
函数代码:
private static void settings_ValidationEventHandler(object sender, ValidationEventArgs e)
{
Console.WriteLine("line: {0},column: {1},Error: {2}",e.Exception.LineNumber,e.Exception.LinePosition,e.Message);
}
上面的代码与前面使用XmlReader读取XML文档时类似的,区别是在XmlReaderSettings对象上设置了Schemas属性,ValidationType属性和ValidationEventHandler事件.ValidationEventHandler在验证文档出错时进行了方法回调,XmlReader在出错后会继续验证下一节点.在settings_ValidationEventHandler()回调方法中,打印了出错的信息.由于这个movieList.xsd模式文件本身就是从movieList.xml直接生成的,因此不会出错,只会在控制台出现”Complete”.作为测试,我们可以修改一下movieList.xsd,将title的类型修改为unsignedInt,让它接受无符号类型:
<xs:attribute name="title" type="xs:unsignedInt" />
此时就会这样:
line: 5,column: 17,Error: “title”特性无效 - 根据数据类型“http://www.w3.org/2001/XMLSchema:unsignedInt”,值“魔境仙踪”无效 - 字符串“魔境仙踪”不是有效的 UInt32 值。
line: 13,column: 17,Error: “title”特性无效 - 根据数据类型“http://www.w3.org/2001/XMLSchema:unsignedInt”,值“龙胆虎威”无效 - 字符串“龙胆虎威”不是有效的 UInt32 值。
Complete!
使用XSLT对XML进行转换
使用XSLT最常见的一个场景就是将XML转换为XHTML,以便在浏览器中对XML进行显示.要进行XML格式转换,首先需要创建一个XSLT文件,这也是一个XML文件.下面的movieList.xslt文件将movieList.xml转换为了一个HTML表格:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<table border="1">
<tr>
<th>标题</th>
<th>导演</th>
<th>上映日期</th>
<th>主演</th>
<th>类型</th>
</tr>
<xsl:for-each select="movieList/movie">
<tr>
<td>
<xsl:value-of select="@director"/>
</td>
<td>
<xsl:value-of select="@release"/>
</td>
<td>
<xsl:value-of select="@starring"/>
</td>
<td>
<xsl:for-each select="genreList/genre">
<xsl:value-of select="."/>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
上面代码只应用了XSLT中最少和最基本的内容:
(1).xsl:for-each,用于循环选出选中的节点
(2).xsl:value-of,用于输出选中的节点的值
这两个元素都有一个属性”select”,这个属性接收XPATH字符串,用于选择XML中的节点.修改movieList.xml,使用处理指令movieList.xslt文件引用进来:
<?xml version="1.0" encoding="utf-8" ?>
<!--引用xslt-->
<?xml-stylesheet type="text/xsl" href="movieList.xslt"?>
<!DOCTYPE movieList [<!ENTITY Bruce "布鲁斯.威利斯"><!ENTITY Jai "杰.卡特尼">]>
<movieList>
<!--新近上映的电影-->
<movie id="1" title="魔境仙踪" director="山姆.雷米" release="2012-3-6">
<starring>詹姆斯.弗兰克/米拉.克丽丝</starring>
<genreList>
<genre>冒险</genre>
<genre>科幻</genre>
<genre>爱情</genre>
</genreList>
</movie>
<movie id="2" title="龙胆虎威" director="约翰.摩尔" release="2016-9-6">
<starring>&Bruce;/&Jai;</starring>
<genreList>
<genre>搞笑</genre>
<genre>恐怖</genre>
<genre>惊悚</genre>
</genreList>
</movie>
</movieList>
如果使用兼容XSLT的浏览器打开会看到一个表格.
如果浏览器不支持XSLT时,就需要XSLT的转换结果----HTML,此时可以使用.NET中的XslCompiledTransform类型来完成:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
XmlReader reader = XmlReader.Create("movieList.xml",settings);
XmlWriterSettings settings2 = new XmlWriterSettings();
settings2.OmitXmlDeclaration = true;
XmlWriter writer = XmlWriter.Create("movieList.html",settings2);
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load("movieList.xslt");
transform.Transform(reader,writer);
上面的代码创建乐一个movieList.heml文件,它只包含了一个HTML表格.Transform(0方法接受两个参数,分别为XmlReader和XmlWriter类型.在XmlWriterSettings的设置中,将OmitXmlDeclaration属性设置为true,标书忽略XML声明,否则在生成的html文件中会出现”<?xml version=”1.0” encoding=”UTF-8”?>”
这样的话,关于XML的东西就算是说完了,不知道你记住了多少东西,前面是对XML以及相关概念进行了描述,后面是对XML操作进行了演示.