Reputation: 580
I have some XML containing nodes recording FilePath
and FileName
. I need an XSLT
to transform these into single nodes for FilePathAndName
. The FilePath
values may, or may not, end in a '\'. Any of the values can be blank. The FilePathAndName
values must end in a '\' if it is just a folder name.
The transform will be called from a C# application so I think I can probably use any version of XSLT.
I have got part-way but am struggling to do the complete solution, including accounting for optional '\' values in the input.
Here is some sample input 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>
which should be converted to the following:
<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>
This my attempted solution:
<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>
which produces the following (see XML comments below for the remaining problems):
<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>
Upvotes: 1
Views: 69
Reputation: 70618
If you could use XSLT 2.0, you could do this
<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>
This uses replace
to append a backslash to the "FilePath" if the last character is not already a backslash (and it must have a last character, otherwise it also won't add one).
Note I am using Attribute Value Templates here to reduce the size of the code further, rather than using xsl:attribute
In XSLT 1.0, it becomes a bit more verbose to express the logic to append the backslash
<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>
Upvotes: 3