I'm using XSLT to transform from one format of XML into another but I also need to do some value s开发者_JS百科ubstitutions at the same time if possible. Can someone provide a solution to change a large number of values; e.g. "AppName" should be changed to "1", "AppNameTwo" to "2" and I'd ideally like to do this via some type of look-up lists within the XSLT:
<Application>
<oldvalue="AppName" replacewith="1">
<oldvalue="AppNameTwo" replacewith="2">
</Application>
<ResponseOne>
<oldvalue="True" replacewith="Okay">
<oldvalue="False" replacewith="Error">
</ResponseOne>
The only way I can currently think of doing this is instead via a number of many nested replaces?
Input
<Message>
<Header>
<Application>AppName</Application>
<ResponseOne>True</ResponseOne>
...
</Header>
</Message>
XSLT so far
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0">
<xsl:template match="/">
<n1:Message>
<Header>
<Application><xsl:value-of select="//Message/Organisation/Application/Name"/> </Application>
<Response><xsl:value-of select="//Message/Organisation/Application/ResponseOne"/> </Response>
...
</Header>
</n1:Message>
Required Output
<?xml version="1.0" encoding="utf-8"?>
<n1:Message>
<Header>
<Application>1</Application>
<Response>Error</Response>
...
</Header>
</n1:Message>
Intending to run this XSLT within Visual Studio 2010.
This simple transformation (only a single template overriding the identity rule and no need for extension functions), allows the use of huge number of replacement rules, without the need to change the code at all. Another alternative is to specify the value of the global parameter $pReps
outside of the transformation -- then this code can be even slightly simplified:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pReps">
<elem name="Application">
<replace>
<this>AppName</this>
<with>1</with>
</replace>
<replace>
<this>AppNameTwo</this>
<with>2</with>
</replace>
</elem>
<elem name="ResponseOne">
<replace>
<this>True</this>
<with>Okay</with>
</replace>
<replace>
<this>False</this>
<with>Error</with>
</replace>
</elem>
</xsl:param>
<xsl:variable name="vReps" select=
"document('')/*/xsl:param[@name='pReps']"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:variable name="vNewVal" select=
"$vReps/elem
[@name=name(current()/..)]
/replace[this = current()]
/with/text()
"/>
<xsl:copy-of select=
"$vNewVal | self::text()[not($vNewVal)]"/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<Message>
<Header>
<Application>AppName</Application>
<ResponseOne>True</ResponseOne>
...
</Header>
</Message>
the wanted, correct result is produced:
<Message>
<Header>
<Application>1</Application>
<ResponseOne>Okay</ResponseOne>
...
</Header>
</Message>
Explanation:
The identity rule (template) copies every node "as-is".
The replacement rules are coded as a sequence of
elem
elements that are children of the global paramertepReps
. The structure and meaning of everyelem
element should be self-explanatory.There is a single template overriding the identity rule, that matches any text node. Within this template a possible new value is calculated as defined by the variable
$vNewVal
. This is either the empty node-set (in case the parent name of the current node and the string value of the current node are not matced by anyreplace/this
value from the$pReps
. Or, if matched, this is thewith
sibling of the matchingreplace/this
value from the$pReps
. Finally, either$vNewVal
(if not empty) or the current node are copied.
Here's a tested example using exslt:node-set()
, which should be available in the MS XML processor:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0">
<xsl:variable name="tbl">
<Application>
<oldvalue val="AppName" replacewith="1"/>
<oldvalue val="AppNameTwo" replacewith="2"/>
</Application>
<ResponseOne>
<oldvalue val="True" replacewith="Okay"/>
<oldvalue val="False" replacewith="Error"/>
</ResponseOne>
</xsl:variable>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Application">
<xsl:variable name="old" select="./text()"/>
<xsl:copy><xsl:value-of select="exslt:node-set($tbl)/Application/oldvalue[@val=$old]/@replacewith"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
This uses a variable containing the lookup table (I fixed your XML for the table), along with an identity transform that copies the input to the output. Then there's a template for the Application
nodes that does the conversion using the lookup table. You need the exslt:node-set()
function to convert the result tree fragment to a node-set that can be searched with XPath.
I've left the conversion of the <ResponseOne>
tags for you to do. Hint: just make another template like the one for Application
.
Here's an approach using keys. The template is simple without complicated expressions and the replacement rules are kept seperate from the code. The rules can be extended without any code modification.
lookup.xml
<?xml version="1.0"?>
<replacements>
<Application>
<replace oldvalue="AppName" replacewith="1"/>
<replace oldvalue="AppNameTwo" replacewith="2"/>
</Application>
<ResponseOne>
<replace oldvalue="True" replacewith="Okay"/>
<replace oldvalue="False" replacewith="Error"/>
</ResponseOne>
</replacements>
input.xml
<?xml version="1.0"?>
<Message>
<Header>
<Application>AppName</Application>
<ResponseOne>True</ResponseOne>
</Header>
<Header>
<Application>AppNameTwo</Application>
<ResponseOne>False</ResponseOne>
</Header>
</Message>
transform.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="lookup" select="document('lookup.xml')"/>
<xsl:key name="MasterKey" match="/replacements/*/replace" use="concat(local-name(..), ':', @oldvalue)"/>
<xsl:template match="/Message">
<Message>
<xsl:apply-templates select="$lookup"/>
<xsl:apply-templates/>
</Message>
</xsl:template>
<xsl:template match="replacements"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Header/*">
<xsl:variable name="ThisKey" select="concat(local-name(), ':', text())"/>
<xsl:variable name="nodename" select="local-name()"/>
<xsl:choose>
<xsl:when test="$lookup/replacements/*[name() =$nodename]">
<xsl:element name="{$nodename}">
<xsl:for-each select="$lookup/replacements[1]">
<xsl:value-of select="key('MasterKey', $ThisKey)/@replacewith"/>
</xsl:for-each>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-16"?>
<Message>
<Header>
<Application>1</Application>
<ResponseOne>Okay</ResponseOne>
</Header>
<Header>
<Application>2</Application>
<ResponseOne>Error</ResponseOne>
</Header>
</Message>
精彩评论