通过替换事先定义的模板来生成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接口来实现,但很快发现有几个问题没法解决:

  • 1、XmlSerializer不支持泛型集合的序列化,而我在定义entity class时用了不少IList和IDictionary,如db:attribute我就定义成IDictionary
  • 2、XmlSerializer只能生成完整的element和attribute,像上面那段xml里<category>节点term属性里变化的只有后面%Category%部分,这就没法生成了
  • 3、存在添加和更新的post内容不一样的情况,这就意味着同一个entity class,存在多种序列化方案
  • 4、douban的xml entry格式可能会更改,而我不希望因此而更改代码

想来想去,最好的方法是通过XML模板+反射(简称XMl模板替换)来生成了,就像上面的xml etnry里面%%括起来的部分,替换掉就可以了,这样可以解决上述的四个问题。除了提供和XMLSerializer功能相同的序列化之外,XML模板替换还要满足下面这些要求:

  • 1、可以序列化实现IEnumerable的集合,这是最常用的集合,当然大多数的泛型集合也是应用了IEnumerable的
  • 2、提供更灵活的替换。XmlSerializer实现的序列化顺序是”A(B(c)B)A”,对于子对象的序列化只能是嵌套的模式,而XML模板替换可以实现任何层次的替换。
  • 3、为每种类型的对象提供通用的序列化方式,不需要任何Attribute定义,不需要修改对象的定义。对于给定的object和XML模板,通过发射获取属性值进行XML替换后生成XML内容;对于相同的object,提供不同的XML模板就能生成不同的XML。
  • 4、通过修改XML模板即可修改序列化结果

下面给出一个修改过的RSS的XML模板,这次Code4Fun的目的是在最后实现这个模板的替换,并且完成一个能够实现上述功能的Helper class。

特别的地方:

  • 1、<category>节点:通过”.”可访问子对象的属性,如果你希望获取Domain的长度可以写成”%Category.Domain.Length%”
  • 2、<noReplacement>节点:该节点不包含任何替换信息,当进行替换处理时应当忽略
  • 3、<skipHours>节点:SkipHours是一个List<int>集合,我们希望能够根据SkipHours的值,展开多个<hour>节点
  • 4、<as:scope>节点:<scope>是模板定义,声明<scope>节点内包含的子节点在Channel.Items对象的作用域中,所有%%(不包括%./Category.Name%)的属性都是对Items对象的属性访问。由于此处Items对象是List<RssItem>集合,所以将循环生成多个<item>。Scope的含义类似于程序域,支持多个scope的嵌套,Scope定义不会出现在最后生成的xml中。
  • 5、<channelCategory>节点:<channelCategory>节点在Items的作用域中,但我们可以通过”./”访问外部scope的属性,类似dos文件路径,如果要访问上上级scope,则是”././”。%./Category.Name%表示访问Channel对象的Category属性的Name属性。
<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模板

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树用于分析的对象类型

对给定的object,生成XML

这里用了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:  http://files.cnblogs.com/chwkai/Template.rar

Code4Fun: 通过XML模板系统实现对象的灵活序列化相关推荐

  1. Django模板系统(非常详细)

    翻译www.djangobook.com之第四章:Django模板系统 The Django Book:第4章 Django模板系统 revised by xin_wang 前面的章节我们看到如何在视 ...

  2. 翻译www.djangobook.com之第四章:Django模板系统

    [color=red][b]The Django Book:第4章 Django模板系统[/b][/color] revised by [url=http://xin-wang.iteye.com/] ...

  3. Django模板系统(十分 非常详细)

    转载:http://www.czug.org/python/django/04.html 翻译www.djangobook.com之第四章:Django模板系统 The Django Book:第4章 ...

  4. 04 Django之模板系统

    一.语法 关于模板渲染只需要记住两种特殊符号(语法): {{ }} 和 {% %}  (变量相关用{{ }}  逻辑相关用{% %}) 二.变量 在Django的模板语言中按照{{ 变量名 }}来使用 ...

  5. xstream xml模板_XStream – XStreamely使用Java中的XML数据的简便方法

    xstream xml模板 有时候,我们不得不处理XML数据. 而且大多数时候,这不是我们一生中最快乐的一天. 甚至有一个术语" XML地狱"描述了程序员必须处理许多难以理解的XM ...

  6. django模板系统(上)

    filters 过滤 default 替代作用 filesizeformat 格式化为人类可读 add 给变量加参数 lower 小写 upper 大写 title 标题 ljust 左对齐 rjus ...

  7. XML模板解析————Dom4j解析xml案例分析

    引言 目前项目中包含大量的xml模板文件,现就xml模板的数据解析.提取.及部分常用方法做简单的应用和总结. 一.XML文件转为Document对象 通过SAXReader对象的read方法,读取Do ...

  8. flask 模板 php,Flask 模板系统

    模板 基本数据类型 可以执行python语法,如:dict.get(), list['xx'] 比django 更加亲近于 python 传入函数 - django,自动执行 - flask,不自动执 ...

  9. 9.动态生成实体类,根据XML模板使用Emit生成动态类绑定到DataGrid

    在实际项目中,我们可能会遇到用户自定义XML模板字段,根据这个模板上的字段来显示相应的字段的值到DataGrid.在这种情况下,需要使用 XmlReader解析获取这个用户自定义的XML模板上有哪些字 ...

最新文章

  1. win10+anaconda3在 安装后‘conda‘ 不是内部或外部命令,也不是可运行的程序
  2. bootstrap学习(三)表单
  3. Bean的依赖注入方式
  4. python初学者代码示例_python实现手势识别的示例(入门)
  5. 数据结构杂谈(五)——栈
  6. unique去除重复的向量_R语言向量与因子
  7. 2025年的呼叫中心是什么样的?
  8. 如何退订语音包_怎么关闭语音助手 - 卡饭网
  9. android 读取俄文csv乱码,android导出CSV,中文乱码问题
  10. php用哪个稳定版本linux系统,PHP的版本选择
  11. Windows自带md5校验工具使用说明
  12. 机械制图与计算机绘图实训报告前言,《机械制图与计算机绘图》的课程标准.doc...
  13. 查找重复姓名的SQL语句
  14. 平衡二叉树(C++实现)
  15. 企业私有云规划资源设计
  16. C语言-简单的Simon游戏
  17. 天猫淘宝卡券包演进史
  18. html打印页眉页脚_HTML5基本元素:页眉,导航和页脚
  19. Three.js + React + Echart(折线图 光线流动效果,柱状图数据动态更新动画) + Svga-Web应用之数据大屏(适配1920*1080 2560*1440 3840*2160)
  20. java乱码base64_JavaScript BASE64算法实现(完美解决中文乱码)

热门文章

  1. 20160319中艺收盘总结
  2. JSon数据操作示列
  3. 自己动手开发编译器(四)利用DFA转换表建立扫描器
  4. 同样当程序员,产值是比别人多出5倍以上,拿的工资是别人的2/3左右,你是领导你会怎么样妥善处理?...
  5. CCNP精粹系列之四----OSPF(open short path first)
  6. php 百科源码,php源码是什么意思
  7. 为何需要商业智能BI软件
  8. 大数据可视化的三大误区
  9. 大数据可视化技术挑战和措施
  10. 玩转CSS盒子之 三角形盒子