


























通过替换事先定义的模板来生成XML文档的方法叫做“XML模板系统”,这在PHP的开发中有广泛的应用,如PHP Smarty,在.Net上有NVelocity。作为完整的模板系统,他们的功能更加强大和全面。但是上述模板系统因为插入占位标记,破坏了XML文档的结构,模板变成了普通文本,并且在模板中引入了几乎与编程语言相等循环控制,相对复杂。本文给出的XmlTemplate引入了规范XML模板、变量域、集合属性自循环和访问嵌套属性的方法,可以实现模板系统大多数的功能,但是却非常简单,根本无需记忆,你还可以通过扩展XmlTemplate来控制生成文本的格式。通过XmlTemplate,你可以更简单更灵活的生成XML文档,这在生成RSS, Atom等文档,以及开发WebAPI的client的时候都是非常方便的。
前阵子在写LINQ2Douban的时候碰到关于XML序列化的场景。通过Douban api添加和更新数据的时候都需要Post一个xml entry,如:
添加活动(xml中用%%括起来的部分是需要填写的部分,为了精简删除了xmlns)
<?xml version="1.0" encoding="UTF-8"?> <entry> <title>%title%</title> <category scheme="http://www.douban.com/2007#kind" term="http://www.douban.com/2007#%Category%"/> <content>%Content%</content> <db:attribute name="invite_only">%IsInviteOnly%</db:attribute> <db:attribute name="can_invite">%CanInvite%</db:attribute> <gd:when endTime="%Duration.End%" startTime="%Duration.Start%"/> <gd:where valueString="%Where%"/> </entry>
一下子能想到的方法有两个——通过XmlSerializer或在entity class上实现IXmlSerializable接口来实现,但很快发现有几个问题没法解决:
想来想去,最好的方法是通过XML模板+反射(简称XMl模板替换)来生成了,就像上面的xml etnry里面%%括起来的部分,替换掉就可以了,这样可以解决上述的四个问题。除了提供和XMLSerializer功能相同的序列化之外,XML模板替换还要满足下面这些要求:
下面给出一个修改过的RSS的XML模板,这次Code4Fun的目的是在最后实现这个模板的替换,并且完成一个能够实现上述功能的Helper class。
特别的地方:
<channel>
<title>%Title%</title>
<link>%Link%</link>
<category domain="%Category.Domain%">%Category.Name%</category>
<noRelacement>不需要替换</noRelacement>
<skipHours>
<hour>%SkinHours%</hour>
</skipHours>
<as:scope xmlns:as="http://xml.allsharing.com/" name="Items" type="AllSharing.Xml.Rss.RssItem">
<item>
<title>%Title%</title>
<link>%Link%</link>
<description>%Description%</description>
<channelCategory>%./Category.Name%</channelCategory>
</item>
</as:scope>
</channel>
相关class定义:
public class RssChannel
{
public string Title { get; set; }
public string Link { get; set; }
public RssCategory Category { get; set; }
public IList<int> SkinHours { get; set; }
public IList<RssItem> Items { get; set; }
}
public class RssItem
{
public string Title { get; set; }
public string Link { get; set; }
}
public class RssCategory
{
public string Domain { get; set; }
public string Name { get; set; }
}
下面将一步步讨论如何实现XML模板的替换,会给出部分代码或伪代码,完整的代码在文章最后会给出下载。
XML模板替换最终要是要回归到用文本替换,替换掉所有的%%,但由于我们要处理的模板包括变量域、子属性访问、循环的信息,所以这不是仅仅的Regex.Replace或String.Replace就可以搞定的。分析XML模板,就是要遍历XML模板生成一个Scope树,上面的XML模板可以生成下面的Scope树:
XML模板中包含了的三种我们需要处理的元素
1、包含%%的XML Attribute
2、包含%%的XML Element以及Element的子节点
3、Scope节点
从上面的Scope树可以看出,像<noReplace>这样不需要替换的XML element或XML attribute被当作常量,没有包括在Scope树中。
在Helper Class中分析Scope树的方法:
var scope = XmlTemplateScope.Compile(xmlPath, entityType)
xmlPath是模板文件的路径,entityType是Scope树用于分析的对象类型
这里用了LINQ2XML,首先用XDocument.Load(xmlPath)的到XML模板文件的XDocument,然后根据Scope树对XDocument上的节点进行属性值替换、节点Value替换、增加节点(Repeat的节点)。幸运的是XDocument比XmlDocument方便太多了,实现起来非常快。
在Helper Class中生成XML的方法:
var template = new XmlTemplate(xmlPath, scope); Console.WriteLine(template.Translate(entityObj));
template是线程安全的,根据需要你可以Cache起来,不用每次都生成Scope树,这样或许会减少部分性能消耗(未测试)
完整的代码如下(包含在Demo中):
var channel = new RssChannel();
channel.Title = "this is channel title";
channel.Link = "http://chwkai.cnblogs.com/";
channel.Description = "this is channel description";
channel.Category = new RssCategory();
channel.Category.Domain = "http://chwkai.cnblogs.com/";
channel.Category.Name = "this is channel category";
channel.SkipHours.Add(1);
channel.SkipHours.Add(2);
channel.SkipHours.Add(3);
channel.Items.Add(new RssItem { Title="Item1", Link="Link1", Description="Des1" });
channel.Items.Add(new RssItem { Title = "Item2", Link = "Link2", Description = "Des2" });
channel.Items.Add(new RssItem { Title = "Item3", Link = "Link3", Description = "Des3" });
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rss.xml");
var template = new XmlTemplate(
path, XmlTemplateScope.Compile(path, typeof(RssChannel)));
template.Translate(channel).Dump();
生成XML如下:
<channel>
<title>this is channel title</title>
<link>http://chwkai.cnblogs.com/</link>
<category domain="http://chwkai.cnblogs.com/">this is channel category</cate
gory>
<noRelacement>不需要替换</noRelacement>
<skipHours>
<hour>1</hour>
<hour>2</hour>
<hour>3</hour>
</skipHours>
<item>
<title>Item1</title>
<link>Link1</link>
<description>Des1</description>
<channelCategory>this is channel category</channelCategory>
</item>
<item>
<title>Item2</title>
<link>Link2</link>
<description>Des2</description>
<channelCategory>this is channel category</channelCategory>
</item>
<item>
<title>Item3</title>
<link>Link3</link>
<description>Des3</description>
<channelCategory>this is channel category</channelCategory>
</item>
</channel>
Source Code for XmlTemplate: https://files.cnblogs.com/chwkai/Template.rar
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。