Code4Fun: 通过XML模板系统实现对象的灵活序列化
通过替换事先定义的模板来生成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模板系统实现对象的灵活序列化相关推荐
- Django模板系统(非常详细)
翻译www.djangobook.com之第四章:Django模板系统 The Django Book:第4章 Django模板系统 revised by xin_wang 前面的章节我们看到如何在视 ...
- 翻译www.djangobook.com之第四章:Django模板系统
[color=red][b]The Django Book:第4章 Django模板系统[/b][/color] revised by [url=http://xin-wang.iteye.com/] ...
- Django模板系统(十分 非常详细)
转载:http://www.czug.org/python/django/04.html 翻译www.djangobook.com之第四章:Django模板系统 The Django Book:第4章 ...
- 04 Django之模板系统
一.语法 关于模板渲染只需要记住两种特殊符号(语法): {{ }} 和 {% %} (变量相关用{{ }} 逻辑相关用{% %}) 二.变量 在Django的模板语言中按照{{ 变量名 }}来使用 ...
- xstream xml模板_XStream – XStreamely使用Java中的XML数据的简便方法
xstream xml模板 有时候,我们不得不处理XML数据. 而且大多数时候,这不是我们一生中最快乐的一天. 甚至有一个术语" XML地狱"描述了程序员必须处理许多难以理解的XM ...
- django模板系统(上)
filters 过滤 default 替代作用 filesizeformat 格式化为人类可读 add 给变量加参数 lower 小写 upper 大写 title 标题 ljust 左对齐 rjus ...
- XML模板解析————Dom4j解析xml案例分析
引言 目前项目中包含大量的xml模板文件,现就xml模板的数据解析.提取.及部分常用方法做简单的应用和总结. 一.XML文件转为Document对象 通过SAXReader对象的read方法,读取Do ...
- flask 模板 php,Flask 模板系统
模板 基本数据类型 可以执行python语法,如:dict.get(), list['xx'] 比django 更加亲近于 python 传入函数 - django,自动执行 - flask,不自动执 ...
- 9.动态生成实体类,根据XML模板使用Emit生成动态类绑定到DataGrid
在实际项目中,我们可能会遇到用户自定义XML模板字段,根据这个模板上的字段来显示相应的字段的值到DataGrid.在这种情况下,需要使用 XmlReader解析获取这个用户自定义的XML模板上有哪些字 ...
最新文章
- win10+anaconda3在 安装后‘conda‘ 不是内部或外部命令,也不是可运行的程序
- bootstrap学习(三)表单
- Bean的依赖注入方式
- python初学者代码示例_python实现手势识别的示例(入门)
- 数据结构杂谈(五)——栈
- unique去除重复的向量_R语言向量与因子
- 2025年的呼叫中心是什么样的?
- 如何退订语音包_怎么关闭语音助手 - 卡饭网
- android 读取俄文csv乱码,android导出CSV,中文乱码问题
- php用哪个稳定版本linux系统,PHP的版本选择
- Windows自带md5校验工具使用说明
- 机械制图与计算机绘图实训报告前言,《机械制图与计算机绘图》的课程标准.doc...
- 查找重复姓名的SQL语句
- 平衡二叉树(C++实现)
- 企业私有云规划资源设计
- C语言-简单的Simon游戏
- 天猫淘宝卡券包演进史
- html打印页眉页脚_HTML5基本元素:页眉,导航和页脚
- Three.js + React + Echart(折线图 光线流动效果,柱状图数据动态更新动画) + Svga-Web应用之数据大屏(适配1920*1080 2560*1440 3840*2160)
- java乱码base64_JavaScript BASE64算法实现(完美解决中文乱码)