我想转换这个xml:
<Root>
<Result>
<Message>
<Header>
<!-- Hundreds of child nodes -->
</Header>
<Body>
<Node1>
<!-- Hundreds of child nodes -->
<Node2>
<!-- Hundreds of child nodes -->
<Node3>
<!-- Hundreds of child nodes -->
<NodeX>value 1 to be changed</NodeX>
<!-- Hundreds of child nodes -->
<Node4>
<!-- Hundreds of child nodes -->
<NodeY>value 2 to be changed</NodeY>
</Node4>
</Node3>
</Node2>
</Node1>
</Body>
<RealValuesRoot>
<!-- These two nodes -->
<Value ID="1">this value must replace the value of Node X</Value>
<Value ID="2">this value must replace the value of Node Y</Value>
</RealValuesRoot>
</Message>
</Result>
<!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>
进入这个xml:
<Root>
<Result>
<Message>
<Header>
<!-- Hundreds of child nodes -->
</Header>
<Body>
<Node1>
<!-- Hundreds of child nodes -->
<Node2>
<!-- Hundreds of child nodes -->
<Node3>
<!-- Hundreds of child nodes -->
<NodeX>this value must replace the value of Node X</NodeX>
<!-- Hundreds of child nodes -->
<Node4>
<!-- Hundreds of child nodes -->
<NodeY>this value must replace the value of Node Y</NodeY>
</Node4>
</Node3>
</Node2>
</Node1>
</Body>
</Message>
</Result>
<!-- Hundreds to thousands of similar MessageRoot nodes -->
</Root>
输出几乎与输入相同,但以下更改除外:
“值”节点具有唯一ID,表示消息正文中的唯一x路径,例如, ID 1刷新到xpath / Message / Body / Node1 / Node2 / Node3 / NodeX。
我必须使用微软的xslt版本1.0 !!
我已经有了一个可以正常运行的xslt并且做了我想要的一切,但我对性能不满意!
我的xslt的工作原理如下:
我创建了一个全局字符串变量,其作用类似于键值对,类似于:1:xpath1_2:xpath2_ ... _N:xpathN。此变量将“Value”节点的ID与消息正文中需要替换的节点相关联。
xslt从根节点开始递归迭代输入xml。
我计算当前节点的xpath,然后执行以下操作之一:
如上所述,我的xslt工作正常,但我想尽可能提高性能,请随时提出一个全新的xslt逻辑!欢迎您的想法和建议!
答案 0 :(得分:2)
我计算当前节点的xpath,然后执行以下操作之一......
这可能是您的低效率 - 如果您每次可能正在查看O(N 2 )算法时重新计算回到根的路径。在没有看到你的XSLT的情况下,这是相当推测的,但你可以通过使用参数将当前路径传递给递归来调整这一点 - 如果你的主算法基于标准身份模板
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
</xsl:template>
然后将其更改为
<xsl:template match="@*|node()">
<xsl:param name="curPath" />
<xsl:copy>
<xsl:apply-templates select="@*|node()">
<xsl:with-param name="curPath" select="concat($curPath, '/', name())" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
或用于构建所需路径的逻辑。现在,在您要按摩的节点的特定模板中,您已经拥有了其父节点的路径,并且您不必每次都一直走到根目录。
您可能需要添加
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
所以你不会在$curPath
xpath在xslt中作为全局变量
中的字符串进行硬编码
如果您在XML结构中表示此映射而不是字符串,那么您可以使用 key 机制来加速查找:
<xsl:variable name="rtfLookupTable">
<lookuptable>
<val xpath="/first/xpath/expression" id="1" />
<val xpath="/second/xpath/expression" id="2" />
<!-- ... -->
</lookuptable>
</xsl:variable>
<xsl:variable name="lookupTable" select="msxsl:node-set($rtfLookupTable)" />
<xsl:key name="valByXpath" match="val" use="@xpath" />
(将xmlns:msxsl="urn:schemas-microsoft-com:xslt"
添加到您的xsl:stylesheet
)。或者,如果您想避免使用node-set
扩展功能,则可以使用替代定义
<xsl:variable name="lookupTable" select="document('')//xsl:variable[name='rtfLookupTable']" />
通过将样式表本身视为纯XML文档来工作。
多个文档中的键在XSLT 1.0中有点繁琐但可以完成,基本上你必须在调用键函数之前将当前上下文切换到指向$lookupTable
,所以你需要保存当前变量中的上下文让你稍后引用它:
<xsl:template match="text()">
<xsl:param name="curPath" />
<xsl:variable name="dot" select="." />
<xsl:variable name="slash" select="/" />
<xsl:for-each select="$lookupTable">
<xsl:variable name="valId" select="key('valByXpath', $curPath)/@id" />
<xsl:choose>
<xsl:when test="$valId">
<xsl:value-of select="$slash//Value[@id = $valId]" />
<!-- or however you extract the right Value -->
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$dot" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
或者实际上,为什么不让XSLT引擎为您完成艰苦的工作。而不是将您的映射表示为字符串
/path/to/node1_1:/path/to/node2_2
直接将其表示为模板
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- copy everything as-is apart from exceptions below -->
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
</xsl:template>
<!-- delete the RealValuesRoot -->
<xsl:template match="RealValuesRoot" />
<xsl:template match="/path/to/node1">
<xsl:copy><xsl:value-of select="//Value[id='1']" /></xsl:copy>
</xsl:template>
<xsl:template match="/path/to/node2">
<xsl:copy><xsl:value-of select="//Value[id='2']" /></xsl:copy>
</xsl:template>
</xsl:stylesheet>
我相信您可以看到如何使用某种模板机制(甚至可能是另一种XSLT)从现有映射中轻松自动生成特定模板。
答案 1 :(得分:1)
最有效的方法是使用XSL密钥。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- a key that indexes real values by their IDs -->
<xsl:key name="kRealVal" match="RealValuesRoot/Value" use="@ID" />
<!-- the identity template to copy everything -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<!-- ...except elements named <NodeX> -->
<xsl:template match="*[starts-with(name(), 'Node')]">
<xsl:variable name="myID" select="substring-after(name(), 'Node')" />
<xsl:variable name="myRealVal" select="key('kRealVal', $myID)" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:choose>
<xsl:when test="$myRealVal">
<xsl:value-of select="$myRealVal" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="node()" />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<!-- the <RealValuesRoot> element can be trashed -->
<xsl:template match="RealValuesRoot" />
</xsl:stylesheet>
以下是此解决方案的实时预览:http://www.xmlplayground.com/R78v0n
这是一个概念验证解决方案,它使用Microsoft脚本扩展来完成繁重的任务:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:script="http://tempuri.org/script"
>
<msxsl:script language="JScript" implements-prefix="script">
var index = {};
function getNode(context, xpath) {
var theContext = context[0],
theXpath = xpath[0].text,
result;
try {
result = theContext.selectSingleNode(theXpath)
} catch (ex) {
// xpath is invalid. we could also just throw here
// but lets return the empty node set.
result = theContext.selectSingleNode("*[false()]");
}
return result;
}
function buildIndex(id, node) {
var theNode = node[0];
if (id) index[id] = theNode;
return "";
}
function getValue(id) {
return (id in index) ? index[id] : '';
}
</msxsl:script>
<!-- this is the boilerplate to evaluate all the XPaths -->
<xsl:variable name="temp">
<xsl:for-each select="/root/source/map">
<xsl:value-of select="script:buildIndex(generate-id(script:getNode(/, @xpath)), .)" />
</xsl:for-each>
</xsl:variable>
<!-- the identity template to get things rolling -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<!-- actually evaluate $temp once, so the variable is being calculated -->
<xsl:value-of select="$temp" />
<xsl:apply-templates select="node() | @*" />
</xsl:template>
<!-- all <value> nodes do either have a related "actual value" or they are copied as they are -->
<xsl:template match="value">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:variable name="newValue" select="script:getValue(generate-id())" />
<xsl:choose>
<xsl:when test="$newValue">
<xsl:value-of select="$newValue" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="node() | @*" />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<!-- the <source> element can be dropped -->
<xsl:template match="source" />
</xsl:stylesheet>
它转换
<root>
<value id="foo">this is to be replaced</value>
<source>
<map xpath="/root/value[@id = 'foo']">this is the new value</map>
</source>
</root>
到
<root>
<value id="foo">this is the new value</value>
</root>
也许你可以在你的设置中走这条路。
思路是:
.selectSingleNode()
评估它们。generate-id()
从节点获取ID。使用msxsl.exe成功测试。
当然,这假设输入具有那些<map xpath="...">
元素,但该部分并非真正必要且易于适应您的实际情况。例如,您可以从JavaScript中index
的长字符串中构建split()
对象。