XSL / XSLT / XPath合并2个节点

时间:2019-01-14 14:52:02

标签: xslt xpath

我有一些XML,其中包含记录FilePathFileName的节点。我需要一个XSLT才能将它们转换为FilePathAndName的单个节点。 FilePath值可以或可以不以'\'结尾。任何值都可以为空。如果FilePathAndName值只是一个文件夹名称,则必须以'\'结尾。

将从C#应用程序调用转换,因此我认为我可以使用任何版本的XSLT。

我已经走了一段路,但是正在努力寻求完整的解决方案,包括考虑输入中可选的'\'值。

以下是一些示例输入XML:

<Task>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="folder1\sub-folder1" />   <!-- FilePath doesn't end in '\' -->
          <Parameter ParameterName="FileName" ParameterValue="file1" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="folder2\sub-folder2\" />  <!-- FilePath ends in '\' -->
          <Parameter ParameterName="FileName" ParameterValue="file2" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="" />  <!-- Empty FilePath -->
          <Parameter ParameterName="FileName" ParameterValue="file3" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="folder4\sub-folder4" />   <!-- Empty FileName -->
          <Parameter ParameterName="FileName" ParameterValue="" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="" />  <!-- Empty FilePath and FileName -->
          <Parameter ParameterName="FileName" ParameterValue="" />
    </Action>
    <Action ActionName="GetRateData">
          <!-- No FilePath Node -->
          <Parameter ParameterName="FileName" ParameterValue="file5" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePath" ParameterValue="folder6\sub-folder6" />
          <!-- No FileName Node -->
    </Action>
    <Action ActionName="GetRateData">
          <!-- No FilePath or FileName Node -->
    </Action>
</Task>

应转换为以下内容:

<Task>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="folder1\sub-folder1\file1" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="folder2\sub-folder2\file2" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="file3" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="folder4\sub-folder4\" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="" />
    </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="file5" />
    </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName" ParameterValue="folder6\sub-folder6\"/>
   </Action>
    <Action ActionName="GetRateData">
          <Parameter ParameterName="FilePathAndName" ParameterValue="" />
    </Action>
</Task>

这是我尝试的解决方案:

<xsl:template match="Task">
    <Task>

      <xsl:for-each select="Action">
        <Action>
          <xsl:copy-of select="@ActionName"/>

          <xsl:for-each select="Parameter">
            <Parameter>

              <xsl:choose>

                <xsl:when test="@ParameterName = 'FilePath'">
                  <xsl:attribute name="ParameterName">
                    <xsl:text>FilePathAndName</xsl:text>
                  </xsl:attribute>
                  <xsl:attribute name="ParameterValue">
                    <xsl:value-of select="@ParameterValue" />\<xsl:value-of select="(../Parameter[@ParameterName='FileName'])[1]/@ParameterValue" /></xsl:attribute>

                    <!-- TODO: Don't include '\' if FilePath is empty. -->
                    <!-- TODO: What if FilePath is missing? -->
                </xsl:when>

                <xsl:when test="@ParameterName = 'FileName'">
                    <!-- FileName will be consumed above. -->
                </xsl:when>

                <xsl:otherwise>
                  <xsl:copy-of select="@ParameterName"/>
                  <xsl:copy-of select="@ParameterValue"/>
                </xsl:otherwise>
              </xsl:choose>
            </Parameter>
          </xsl:for-each>

        </Action>
      </xsl:for-each>

    </Task>
  </xsl:template>
</xsl:stylesheet>

产生以下内容(有关其余问题,请参见下面的XML注释):

<Task
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName"
                 ParameterValue="folder1\sub-folder1\file1"/>       <!-- Correct -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName"
                 ParameterValue="folder2\sub-folder2\\file2"/>      <!-- Wrong: Should not have \\ -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName" ParameterValue="\file3"/>      <!-- Wrong: FilePathAndName should not start with \ -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName" ParameterValue="folder4\sub-folder4\"/>        <!-- Correct -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName" ParameterValue="\"/>       <!-- Wrong: FilePathAndName should not start with \ -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">        <!-- Wrong: FilePathAndName is missing -->
      <Parameter/>
   </Action>
   <Action ActionName="GetRateData">
      <Parameter ParameterName="FilePathAndName" ParameterValue="folder6\sub-folder6\"/>        <!-- Correct -->
   </Action>
   <Action ActionName="GetRateData"/>           <!-- Correct -->
</Task>

1 个答案:

答案 0 :(得分:3)

如果您可以使用XSLT 2.0,则可以这样做

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>  

  <xsl:template match="Action">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <Parameter FileNameAndPath="{replace(Parameter[@ParameterName='FilePath']/@ParameterValue, '([^\\])$', '$1\\')}{Parameter[@ParameterName='FileName']/@ParameterValue}" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

如果最后一个字符还不是反斜杠(并且必须具有最后一个字符,否则它不会添加一个),它使用replace将反斜杠附加到“ FilePath”。

请注意,我在这里使用属性值模板来进一步减少代码的大小,而不是使用xsl:attribute

在XSLT 1.0中,表达添加反斜杠的逻辑变得更加冗长

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>  

  <xsl:template match="Action">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <Parameter>
        <xsl:attribute name="FileNameAndPath">
          <xsl:variable name="FilePath" select="Parameter[@ParameterName='FilePath']/@ParameterValue" />
          <xsl:value-of select="$FilePath" />
          <xsl:if test="$FilePath != '' and substring($FilePath, string-length($FilePath)) != '\'">\</xsl:if>
          <xsl:value-of select="Parameter[@ParameterName='FileName']/@ParameterValue" />
        </xsl:attribute>
      </Parameter>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>