XSL转换:向元素添加可选属性

时间:2015-12-14 00:23:34

标签: c# xml xslt

我试图仅在值不为空时才向某个元素添加属性。我知道我可以使用模板和选择器来提供元素的静态版本,因此不需要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();
        }
    }
}

有没有办法做这种操作?

1 个答案:

答案 0 :(得分:0)

您的问题的一个解决方案是在元素名称和所需的目标属性名称之间使用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:for-each中的xsl:element遍历所有元素,并使用键映射将其名称与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>