对XML+XSLT的转换应用特定的过程以改变生成的HTML代码
在使用 XslCompiledTransform 对 XML 应用 XSLT 样式表时,转换的结果是写入 XmlWriter,而在平时应用时,可能会遇到对特定的非 HTML 标签转换为 HTML 标签以及附加 javascript 脚本的情况,而且无法直接通过 XSLT 来实现转换;此外,使用 XmlTextWriter 进行写入时对于 <div></div> 这样的标签会被写入为 <div /> 导致浏览器可能不能正常解析,而且因为要考虑 XML 的情况,写入效率较低。因此我们在这里从 XmlWriter 派生一个类,来实现我们需要的功能。
我们将这个类叫做 XhtmlWriter,实现的原理很简单,在调用类的写入方法时,并不将数据写入基础 TextWriter,而是将数据写入树状的数据结构中,在调用 Flush 方法时,对树状数据结构的每个结点应用处理过程,然后将每个结点按照 XHTML 格式写入基础 TextWriter。
首先定义树状数据结构的结点类——XmlElementEntity:
public sealed class XmlElementEntity
{
public bool Content { get; set; }
// 结点是否作为文本内容而非子结点
public string Element { get; set; }
// 结点名(HTML 标签名)或者是文本内容
public Dictionary<string, string> Attributes { get; set; }
// 属性集合
public XmlElementEntity Parent { get; set; }
// 父结点
public List<XmlElementEntity> Children { get; set; }
// 子结点集合
public XmlElementEntity()
{
Content = false;
Attributes = new Dictionary<string, string>();
Children = new List<XmlElementEntity>();
}
}
接下来是结点处理过程的接口定义——IXhtmlExtendWriter:
{
public bool Content { get; set; }
// 结点是否作为文本内容而非子结点
public string Element { get; set; }
// 结点名(HTML 标签名)或者是文本内容
public Dictionary<string, string> Attributes { get; set; }
// 属性集合
public XmlElementEntity Parent { get; set; }
// 父结点
public List<XmlElementEntity> Children { get; set; }
// 子结点集合
public XmlElementEntity()
{
Content = false;
Attributes = new Dictionary<string, string>();
Children = new List<XmlElementEntity>();
}
}
public interface IXhtmlExtendWriter
{
void Flush(XhtmlWriter writer, XmlElementEntity entity);
}
最后就是 XhtmlWriter 类了,它继承 XmlWriter,对于 XmlWriter 的大多数抽象方法我们都是用不到的,我们用到的只有 Close、Flush、WriteEndAttribute、WriteEndElement、WriteStartAttribute、WriteStartElement、WriteString 以及 get_WriteState,其他的直接 throw new NotSupportedException() 就可以了。其中的属性和变量:
{
void Flush(XhtmlWriter writer, XmlElementEntity entity);
}
public TextWriter ScriptWriter { get; private set; }
// 用于写入 javascript 的 TextWriter
public XmlElementEntity RootEntity { get; private set; }
// 根结点
public XmlElementEntity CurrentEntity { get; private set; }
// 当前正在写入的结点
public override WriteState WriteState
{
get
{
return writeState;
}
}
// 写入状态属性
private TextWriter writer;
// 用于写入 HTML 的基础 TextWriter
private WriteState writeState;
// 写入状态
private IXhtmlExtendWriter[] extends;
// 结点处理过程集合
private string attribute;
// 当前整写入的属性名
// 用于写入 javascript 的 TextWriter
public XmlElementEntity RootEntity { get; private set; }
// 根结点
public XmlElementEntity CurrentEntity { get; private set; }
// 当前正在写入的结点
public override WriteState WriteState
{
get
{
return writeState;
}
}
// 写入状态属性
private TextWriter writer;
// 用于写入 HTML 的基础 TextWriter
private WriteState writeState;
// 写入状态
private IXhtmlExtendWriter[] extends;
// 结点处理过程集合
private string attribute;
// 当前整写入的属性名
接下来是构造函数和已实现的抽象方法:
public XhtmlWriter(TextWriter scriptWriter, TextWriter writer)
{
ScriptWriter = scriptWriter;
this.writer = writer;
extends = new IXhtmlExtendWriter[] { new NavigateExtendWriter(),
new ValidateCodeExtendWriter(),
new ValidationExtendWriter() };
// 结点处理过程,也可以应用工厂模式来创建
CurrentEntity = RootEntity = new XmlElementEntity();
// 初始化根结点,并将当前结点设置为根结点
}
public override void Close()
{
CurrentEntity = RootEntity = null;
ScriptWriter.Close();
writer.Close();
// 结束写入过程,关闭所有 TextWriter
}
public override void Flush()
{
foreach (XmlElementEntity child in RootEntity.Children)
Flush(child);
// 遍历根结点的子结点
ScriptWriter.Flush();
writer.Flush();
// 写入所有 TextWriter 缓冲
CurrentEntity = RootEntity = new XmlElementEntity();
}
private void Flush(XmlElementEntity entity)
{
// 写入每个结点
foreach (IXhtmlExtendWriter extend in extends)
extend.Flush(this, entity);
// 对每个结点应用每个处理过程
if (entity.Content)
{
writer.Write(entity.Element);
return;
}
// 如果为文本内容则直接写入文本并退出
writer.Write('<');
writer.Write(entity.Element);
// 写入起始标签
foreach (KeyValuePair<string, string> kv in entity.Attributes)
{
writer.Write(' ');
writer.Write(kv.Key);
writer.Write("=\"");
writer.Write(kv.Value);
writer.Write("\"");
}
// 遍历写入所有属性
switch (entity.Element.ToLower())
{
case "input":
case "img":
case "br":
writer.Write("/>");
return;
}
// 根据标签名写入不同的结束标记
writer.Write(">");
foreach (XmlElementEntity child in entity.Children)
Flush(child);
// 写入子结点
writer.Write("</");
writer.Write(entity.Element);
writer.Write(">");
// 写入结束标签
}
public override void WriteEndAttribute()
{
writeState = WriteState.Element;
// 更改状态为元素
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
writeState = WriteState.Attribute;
// 更改状态为属性
CurrentEntity.Attributes[attribute = localName] = "";
// 添加属性,并保存属性名
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
writeState = WriteState.Element;
// 更改状态为元素
XmlElementEntity entity = new XmlElementEntity() {
Parent = CurrentEntity,
Element = localName };
CurrentEntity.Children.Add(entity);
CurrentEntity = entity;
// 添加子结点并将当前结点设置为子结点
}
public override void WriteString(string text)
{
if (writeState == WriteState.Attribute)
CurrentEntity.Attributes[attribute] = text;
// 如果状态为属性则设置当前属性值
else
CurrentEntity.Children.Add(new XmlElementEntity() {
Content = true,
Element = text,
Parent = CurrentEntity });
// 元素状态添加文本内容子结点
writeState = WriteState.Content;
// 更改状态为内容
}
在结点处理过程中,可以根据标签名、属性名设置新的标签名和属性,并写入脚本。
{
ScriptWriter = scriptWriter;
this.writer = writer;
extends = new IXhtmlExtendWriter[] { new NavigateExtendWriter(),
new ValidateCodeExtendWriter(),
new ValidationExtendWriter() };
// 结点处理过程,也可以应用工厂模式来创建
CurrentEntity = RootEntity = new XmlElementEntity();
// 初始化根结点,并将当前结点设置为根结点
}
public override void Close()
{
CurrentEntity = RootEntity = null;
ScriptWriter.Close();
writer.Close();
// 结束写入过程,关闭所有 TextWriter
}
public override void Flush()
{
foreach (XmlElementEntity child in RootEntity.Children)
Flush(child);
// 遍历根结点的子结点
ScriptWriter.Flush();
writer.Flush();
// 写入所有 TextWriter 缓冲
CurrentEntity = RootEntity = new XmlElementEntity();
}
private void Flush(XmlElementEntity entity)
{
// 写入每个结点
foreach (IXhtmlExtendWriter extend in extends)
extend.Flush(this, entity);
// 对每个结点应用每个处理过程
if (entity.Content)
{
writer.Write(entity.Element);
return;
}
// 如果为文本内容则直接写入文本并退出
writer.Write('<');
writer.Write(entity.Element);
// 写入起始标签
foreach (KeyValuePair<string, string> kv in entity.Attributes)
{
writer.Write(' ');
writer.Write(kv.Key);
writer.Write("=\"");
writer.Write(kv.Value);
writer.Write("\"");
}
// 遍历写入所有属性
switch (entity.Element.ToLower())
{
case "input":
case "img":
case "br":
writer.Write("/>");
return;
}
// 根据标签名写入不同的结束标记
writer.Write(">");
foreach (XmlElementEntity child in entity.Children)
Flush(child);
// 写入子结点
writer.Write("</");
writer.Write(entity.Element);
writer.Write(">");
// 写入结束标签
}
public override void WriteEndAttribute()
{
writeState = WriteState.Element;
// 更改状态为元素
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
writeState = WriteState.Attribute;
// 更改状态为属性
CurrentEntity.Attributes[attribute = localName] = "";
// 添加属性,并保存属性名
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
writeState = WriteState.Element;
// 更改状态为元素
XmlElementEntity entity = new XmlElementEntity() {
Parent = CurrentEntity,
Element = localName };
CurrentEntity.Children.Add(entity);
CurrentEntity = entity;
// 添加子结点并将当前结点设置为子结点
}
public override void WriteString(string text)
{
if (writeState == WriteState.Attribute)
CurrentEntity.Attributes[attribute] = text;
// 如果状态为属性则设置当前属性值
else
CurrentEntity.Children.Add(new XmlElementEntity() {
Content = true,
Element = text,
Parent = CurrentEntity });
// 元素状态添加文本内容子结点
writeState = WriteState.Content;
// 更改状态为内容
}