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转换:向元素添加可选属性

问题的一个解决方案是在元素名称和所需目标属性名称之间使用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>