实现原理:
首先,用替换元素批注树中的元素。
然后,循环访问整个树,创建一个新树,并用其批注替换树中的每个元素。 本示例在名为 XForm 的函数中实现迭代和创建新树。
具体地说,此方法包括:
执行一个或多个 LINQ to XML 查询,用这些查询返回要从一种形状转换为另一种形状的元素集。 对于查询中的每个元素,添加一个新 XElement 对象作为该元素的批注。 在转换的新树中会用此新元素替换批注的元素。 这是示例中所示的唯一需要编写的代码。
作为批注添加的新元素可以包含新的子节点,它可以形成一个具有任意形状的子树。
有一条特殊规则:如果新元素的子节点位于不同的命名空间,即专门为此建立的命名空间(在本示例中,此命名空间为 http://www.microsoft.com/LinqToXmlTransform/2007),则不会将该子元素复制到新树。 而如果命名空间是上面提到的特殊命名空间,并且元素的本地名称为 ApplyTransforms,则会迭代源树中该元素的子节点并将其复制到新树(但批注的子元素本身例外,它们将根据这些规则进行转换)。
这有些类似于 XSL 中的转换规范。 用于选择一组节点的查询类似于用于模板的 XPath 表达式。 用于创建以批注形式保存的新 XElement 的代码类似于 XSL 中的序列构造函数,ApplyTransforms 元素的功能类似于 XSL 中的 xsl:apply-templates 元素。
采用此方法的优势之一是在用公式表述查询时,您始终是对未修改的源树编写查询。 您不必担心对树所做的修改如何影响要编写的查询。
实现实例:
示例1:转换一个树
下面的第一个示例将所有 Paragraph 节点重命名为 para。
XNamespace xf = "http://www.microsoft.com/LinqToXmlTransform/2007";
XName at = xf + "ApplyTransforms";
//原始树
XElement root = XElement.Parse(@"
<Root>
<Paragraph>This is a sentence with <b>bold</b> and <i>italic</i> text.</Paragraph>
<Paragraph>More text.</Paragraph>
</Root>");
// 指定批注元素替换Paragraph为para
foreach (var el in root.Descendants("Paragraph"))
el.AddAnnotation(
new XElement("para",
// 此作用形似于xsl:apply-templates
new XElement(xf + "ApplyTransforms")
)
);
//小函数 XForm 可以从原始的、已批注的树创建新的、转换后的树。
XElement newRoot = XForm(root);
WriteLine(newRoot.ToString());
结果为:
<Root>
<para>
This is a sentence with <b>bold</b> and <i>italic</i> text.
</para>
<para>More text.</para>
</Root>
示例2:更复杂的转换
//下面的示例对树进行查询并计算 Data 元素的平均值和总和,并将它们作为新元素添加到树中。
XNamespace xf = "http://www.microsoft.com/LinqToXmlTransform/2007";
XName at = xf + "ApplyTransforms";
XElement data = new XElement("Root",
new XElement("Data", 20),
new XElement("Data", 10),
new XElement("Data", 3)
);
//当在添加批注时,可以把需要的新转换树的数据写入其中
//当批注沿有发生变化时,它将作为新转换树的数据
data.AddAnnotation(
new XElement("Root",
new XElement(xf + "ApplyTransforms"),
new XElement("Average",
String.Format("{0:F4}",
data
.Elements("Data")
.Select(z => (Decimal)z)
.Average()
)
),
new XElement("Sum",
data
.Elements("Data")
.Select(z => (int)z)
.Sum()
)
)
);
//原型数据
WriteLine(data.ToString());
//执行转换
XElement newData = XForm(data);
//转换后数据
WriteLine(newData.ToString());
结果为:
<Root>
<Data>20</Data>
<Data>10</Data>
<Data>3</Data>
</Root>
<br />
<Root>
<Data>20</Data>
<Data>10</Data>
<Data>3</Data>
<Average>11.0000</Average>
<Sum>33</Sum>
</Root>
实施转换:
小函数 XForm 可以从原始的、已批注的树创建新的、转换后的树。
该函数的伪代码非常简单:
The function takes an XElement as an argument and returns an XElement.
If an element has an XElement annotation, then
Return a new XElement
The name of the new XElement is the annotation element's name.
All attributes are copied from the annotation to the new node.
All child nodes are copied from the annotation, with the
exception that the special node xf:ApplyTransforms is
recognized, and the source element's child nodes are
iterated. If the source child node is not an XElement, it
is copied to the new tree. If the source child is an
XElement, then it is transformed by calling this function
recursively.
If an element is not annotated
Return a new XElement
The name of the new XElement is the source element's name
All attributes are copied from the source element to the
destination's element.
All child nodes are copied from the source element.
If the source child node is not an XElement, it is copied to
the new tree. If the source child is an XElement, then it
is transformed by calling this function recursively.
具体实现:
// Build a transformed XML tree per the annotations
static XElement XForm(XElement source)
{
XNamespace xf = "http://www.microsoft.com/LinqToXmlTransform/2007";
XName at = xf + "ApplyTransforms";
if (source.Annotation<XElement>() != null)
{
XElement anno = source.Annotation<XElement>();
return new XElement(anno.Name,
anno.Attributes(),
anno
.Nodes()
.Select(
(XNode n) =>
{
XElement annoEl = n as XElement;
if (annoEl != null)
{
if (annoEl.Name == at)
return (object)(
source.Nodes()
.Select(
(XNode n2) =>
{
XElement e2 = n2 as XElement;
if (e2 == null)
return n2;
else
return XForm(e2);
}
)
);
else
return n;
}
else
return n;
}
)
);
}
else
{
return new XElement(source.Name,
source.Attributes(),
source
.Nodes()
.Select(n =>
{
XElement el = n as XElement;
if (el == null)
return n;
else
return XForm(el);
}
)
);
}
}