XSL转换:向元素添加可选属性
本文关键字:属性 添加 元素 转换 XSL | 更新日期: 2023-09-27 18:26:35
只有在值不为空的情况下,我才会尝试向某个元素添加属性。我知道我可以使用模板和选择器来提供元素的静态版本,因此不需要xsl:if,但我有10+个元素,不想创建所有可能的排列。
使用模板仍然是可能的,如果可能的话,这将是理想的。如果没有,我可以使用xsl:If。
源Xml:
<Test>
<Attribute1>A</Attribute1>
<Attribute3>B</Attribute3>
</Test>
使用以下XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="Test">
<xsl:element name="MyElement">
<xsl:attribute name="FirstAttribute">
<xsl:value-of select="Attribute1"/>
</xsl:attribute>
<xsl:attribute name="SecondAttribute">
<xsl:value-of select="Attribute2"/>
</xsl:attribute>
<xsl:attribute name="ThirdAttribute">
<xsl:value-of select="Attribute3"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
我得到的输出如下:
<MyElement FirstAttribute="A" SecondAttribute="" ThirdAttribute="B" />
但我想要这个:
<MyElement FirstAttribute="A" ThirdAttribute="B" />
我最初想使用这样一种模板:
<xsl:element name="MyElement">
<xsl:if test="Attribute1 != ''">
<xsl:attribute name="FirstAttribute">
<xsl:value-of select="Attribute1"/>
</xsl:attribute>
</xsl:if>
<xsl:if test="Attribute2 != ''">
<xsl:attribute name="SecondAttribute">
<xsl:value-of select="Attribute2"/>
</xsl:attribute>
</xsl:if>
<xsl:if test="Attribute3 != ''">
<xsl:attribute name="ThirdAttribute">
<xsl:value-of select="Attribute3"/>
</xsl:attribute>
</xsl:if>
</xsl:element>
不幸的是,这导致了以下错误:
XslTransformException was unhandled by user code
Attribute and namespace nodes cannot be added to the parent element after a text, comment, pi, or sub-element node has already been added.
当我使用这个C#代码来处理它时:
namespace XslTest
{
using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
class Program
{
private const string xmlSrcPath = "testXml.xml";
private const string xslPath = "testXsl.xsl";
static void Main(string[] args)
{
var result = Serialize(xmlSrcPath, xslPath);
Console.WriteLine(result);
Console.ReadLine();
}
private static string Serialize(string xmlFile, string xslTemplate)
{
var xmlMemoryStream = new MemoryStream();
var xmlFileStream = new FileStream(xmlFile, FileMode.Open);
xmlFileStream.CopyTo(xmlMemoryStream);
xmlMemoryStream.Flush();
xmlMemoryStream.Seek(0, SeekOrigin.Begin);
var xPathDoc = new XPathDocument(xmlMemoryStream);
var xslCompiledTransform = new XslCompiledTransform();
var transformedMemoryStream = new MemoryStream();
xslCompiledTransform.Load(xslTemplate);
var transformedWriter = new XmlTextWriter(transformedMemoryStream, null);
xslCompiledTransform.Transform(xPathDoc, transformedWriter);
transformedMemoryStream.Seek(0, SeekOrigin.Begin);
var transformedReader = new StreamReader(transformedMemoryStream);
return transformedReader.ReadToEnd();
}
}
}
有什么办法做这种手术吗?
问题的一个解决方案是在元素名称和所需目标属性名称之间使用xsl:key
映射。在本例中,我在数据文件中包含了attr
映射,但它可以外包到一个新的XML文档中,然后作为数据源包含。我根据这个示例的需要适当地修改了XSLT。下面是带有映射的XML:
<Root>
<attr elemName="Attribute1" attribName="FirstAttribute" />
<attr elemName="Attribute2" attribName="SecondAttribute" />
<attr elemName="Attribute3" attribName="ThirdAttribute" />
<Test>
<Attribute1>A</Attribute1>
<Attribute3>B</Attribute3>
</Test>
</Root>
xsl:element
中的xsl:for-each
对所有元素进行迭代,并将它们的名称与具有密钥映射的attr
节点elemName
属性中定义的名称进行比较。表达式周围的大括号用于计算表达式,而不是将其解释为文字字符串。样式表中的xsl:key
使用来自XML的attr
节点。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="AllAttributes" match="attr" use="@elemName"/>
<xsl:template match="/Root">
<xsl:for-each select="Test">
<xsl:element name="MyElement">
<xsl:for-each select="*">
<xsl:attribute name="{key('AllAttributes',local-name())/@attribName}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>