我想使用XSLT根据不同XML文件中指定的键值映射从一个XML文件创建XML模式的多个实例,以供参考,参见https://en.wikipedia.org/wiki/Substitution_%28logic%29 . 应将XSL应用于键值映射(替换),其中包含对模式的引用 . 我猜这可以用一般的(也可能是优雅的)解决方案来完成,但我不知道如何继续 .
一个例子如下:
模式:(pattern.xml)
<?xml version="1.0" encoding="UTF-8"?>
<pattern>
<key1>a</key1>
<element2>key2</element2>
<element3 attribute="key2">key1</element3>
</pattern>
键值映射:(substitution.xml)(更新:从input.xml重命名)
Update: 替换应该包含对模式的引用,并且在常见的浏览器中工作,我理解这需要使用XSLT 1.0 .
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="apply-substitution.xsl"?>
<root pattern="http://example.com/link-to-pattern.xml">
<copy>
<key1>VALUE1</key1>
<key2>VALUE2</key2>
<key3>VALUE3</key3>
</copy>
<copy>
<key1>VALUE3</key1>
<key2>VALUE4</key2>
<key3>VALUE5</key3>
</copy>
</root>
将 Map “应用”到模式的结果应该是:
<?xml version="1.0" encoding="UTF-8"?>
<some_root>
<!-- instance 1 -->
<pattern>
<VALUE1>a</VALUE1>
<element2>VALUE2</element2>
<element3 attribute="VALUE2">VALUE1</element3>
</pattern>
<!-- instance 2 -->
<pattern>
<VALUE3>a</VALUE3>
<element2>VALUE4</element2>
<element3 attribute="VALUE4">VALUE3</element3>
</pattern>
</some_root>
...创建模式的实例,其中所有元素名称,属性名称和值(匹配整个值,例如,不是子字符串)按键值映射的指定放置 .
我的用例是模式是一个非常复杂的XML结构(不是我设计的),我想创建(可能非常大的一组)实例 - 保持模式的结构和大多数内容原样,但更改一些节点,这些节点可能出现在模式XML的任何位置,即元素名称,属性名称和值(匹配完整值) . 模式实例的总和在模式XML语言中形成完整且合理的数据集 . 将模式视为一种由input.xml中的每个 <copy>
调用或实例化的宏 .
到目前为止,我的解决方案只复制模式而不替换值:
[removed since irrelevant due to updates. New version below]
Update:
以下是一个开始(但是,包含@Eiríkr的回答所指出的错误,并使用对pattern.xml的硬编码引用) . 它替换元素,属性名称和值以及文本节点---并递归应用 .
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:variable name="pattern" select="document('pattern.xml')"/>
<xsl:template match="/">
<root>
<xsl:for-each select="*/copy">
<xsl:call-template name="substitute-element">
<xsl:with-param name="element" select="$pattern"/>
<xsl:with-param name="map" select="."/>
</xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="substitute-element">
<xsl:param name="element"/>
<xsl:param name="map"/>
<!-- substitute element name -->
<xsl:for-each select="$element/*">
<xsl:variable name="elementName">
<xsl:call-template name="substitute-value">
<xsl:with-param name="value" select="name(.)"/>
<xsl:with-param name="map" select="$map"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$elementName}">
<!-- replace attributes -->
<xsl:for-each select="@*">
<xsl:variable name="attrName">
<xsl:call-template name="substitute-value">
<xsl:with-param name="value" select="name(.)"/>
<xsl:with-param name="map" select="$map"/>
</xsl:call-template>
</xsl:variable>
<xsl:attribute name="{$attrName}">
<!-- replace attribute value -->
<xsl:call-template name="substitute-value">
<xsl:with-param name="value" select="."/>
<xsl:with-param name="map" select="$map"/>
</xsl:call-template>
</xsl:attribute>
</xsl:for-each>
<!-- replace element text node -->
<xsl:call-template name="substitute-value">
<xsl:with-param name="value" select="text()"/>
<xsl:with-param name="map" select="$map"/>
</xsl:call-template>
<!-- recurse -->
<xsl:for-each select="$element/*">
<xsl:call-template name="substitute-element">
<xsl:with-param name="element" select="."/>
<xsl:with-param name="map" select="$map"/>
</xsl:call-template>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template name="substitute-value">
<xsl:param name="value"/>
<xsl:param name="map"/>
<xsl:choose>
<!-- search for matching key -->
<xsl:when test="$map/*[name(.)=$value]">
<xsl:value-of select="$map/*[name(.)=$value]/text()"/>
</xsl:when>
<!-- default to incoming value if matching key not found -->
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
1 回答
答案帖子中的XSL代码
你问,"is it possible to avoid passing $map around everywhere?"
如果您要以这种方式交叉应用不同的XML结构,那么您可能需要传入参数 .
除此之外,我注意到以下几点:
在答案中的XSL代码中,您有两个文件:
input.xml
和pattern.xml
. 这些名称具有误导性 - 您需要将input.xml
中的模式应用于pattern.xml
中的XML结构 . 为了更流畅和更自然的XSL-y处理方式,我们想要引用input.xml
,实际上输入pattern.xml
. 这样做也可以使您的XSL样式表更加灵活 - 您可以将其传递给任何文件而不是pattern.xml
,而无需编辑XSL本身来更改硬编码路径 .简单地包含您在XSL本身的外部
input.xml
文件中定义的所有键值替换可能更有意义 .作为进一步的考虑,如果您使用带有
<xsl:apply-templates/>
的match
模板而不是使用name
d模板,则XSL本质上是递归的 .扩展
<pattern>
XML结构时,代码的输出不符合您声明的规范 . 例如,扩展XML如下:...并且应用您的代码会产生太多输出:
如果我已正确理解您的要求,那么您需要的是:
另一种方法
在咀嚼了一段时间之后,为了确保我理解你要做的事情,我意识到我最初理解你的代码的部分困难在于你开始处理
input.xml
文件并处理这个上下文,然后你改为处理pattern.xml
文件并处理该上下文 .当您调用模板并希望该模板处理某些内容(而不仅仅是返回静态值或XML结构)时,您必须明确传入该内容,这会使上下文混乱 . 这是解决同一问题的另一种方法,使用
<xsl:apply-templates>
而不是<xsl:call-template name="...">
.从您的帖子来看,您是否仅限于XSL 1.0并不完全清楚 . 我使用上面的XSL 2.0,XPath表达式中的
if () then () else ()
逻辑仅适用于XSL 2.0 . 在XSL 1.0中也不支持在XPath表达式的末尾使用_2682179 . 这两个问题都可以根据需要进行编码 .这个XSL通过添加
<element4>
扩展输入XML产生(我相信预期的?)更短的XML输出 .更新 - XSL1.0版本
以下内容大致相当于上面的XSL,但重新设计为与XSL 1.0兼容 . 主要区别:
将
$replacements
变量中引用的文件名更改为substitution.xml
.创建了一个命名模板,用于替换XSL 2.0 XPath表达式中的
if () then () else ()
逻辑内联 .在模板中创建变量以调用该模板并存储该值 .
我对您在浏览器中执行此操作的用例感到有点困惑,但您知道自己需要什么 . :)
使用Xalan,Saxon 6.5和Saxon 9.6.0.7处理器在Oxygen XML v17.1中进行了测试 . 唯一的区别是缩进 .