我想使用XSLT根据不同XML文件中指定的键值映射从一个XML文件创建XML模式的多个实例,以供参考,参见https://en.wikipedia.org/wiki/Substitution_%28logic%29。 应将XSL应用于键值映射(替换),其中包含对模式的引用。 我猜这可以用一般的(也可能是优雅的)解决方案来完成,但我不知道如何继续。
以下是一个例子:
模式:(pattern.xml)
library(dplyr)
as.winner <- test_data %>% group_by(winner) %>% summarise(winner_sum = sum(winner_points))
as.loser <- test_data %>% group_by(loser) %>% summarise(loser_sum = sum(loser_points))
names(as.winner)[1] <- 'player'
names(as.loser)[1] <- 'player'
totals <- merge(as.winner, as.loser, by = 'player', all.x = T, all.y = T)
totals[is.na(totals)] <- 0
totals <- transform(totals, total_points = winner_sum + loser_sum)
totals
键值映射:(substitution.xml)(更新:从input.xml重命名)
更新:替换应包含对模式的引用,并在常用浏览器中工作,我理解这需要使用XSLT 1.0。
<?xml version="1.0" encoding="UTF-8"?>
<pattern>
<key1>a</key1>
<element2>key2</element2>
<element3 attribute="key2">key1</element3>
</pattern>
将地图“应用”到模式的结果应该是:
<?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>
...创建模式的实例,其中所有元素名称,属性名称和值(匹配整个值,例如,不是子字符串)按键值映射的指定放置。
我的用例是模式是一个非常复杂的XML结构(不是我设计的),我想创建(可能非常大的一组)实例 - 保持模式的结构和大部分内容原样,但改变一些节点,这些节点可能出现在模式XML的任何位置,即元素名称,属性名称和值(匹配完整值)。模式实例的总和在模式XML语言中形成完整且合理的数据集。将模式视为一种由input.xml中的每个<?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>
调用或实例化的宏。
到目前为止,我的解决方案只复制模式而不替换值:
<copy>
更新
以下是一个开始(但是,包含@Eiríkr的回答所指出的错误,并使用对pattern.xml的硬编码引用)。它替换元素,属性名称和值以及文本节点---并递归应用。
[removed since irrelevant due to updates. New version below]
答案 0 :(得分:1)
你问,“是否有可能避免在任何地方传递$ map?”
如果您要以这种方式交叉应用不同的XML结构,那么您可能需要传入参数。
除此之外,我注意到以下内容:
在答案的XSL代码中,您有两个文件:input.xml
和pattern.xml
。这些名称具有误导性 - 您需要将input.xml
中的模式应用于pattern.xml
中的XML结构。为了获得更流畅的流程和更自然的XSL-y处理方式,我们希望引用input.xml
,并实际输入pattern.xml
。这样做也可以使您的XSL样式表更加灵活 - 您可以将其传递给任何文件而不是pattern.xml
,而无需编辑XSL本身来更改硬编码路径。
简单地包含您在XSL本身的外部input.xml
文件中定义的所有键值替换可能更有意义。
作为进一步考虑,如果您使用match
模板与<xsl:apply-templates/>
一起使用,而不是使用name
d模板,则XSL本质上是递归的。
扩展<pattern>
XML结构时,代码的输出不符合您声明的规范。例如,按如下方式扩展XML:
<pattern>
<key1>a</key1>
<element2>key2</element2>
<element3 attribute="key2">key1</element3>
<element4>
<key3>something <key1>and something else with <another key1="key1">key2</another> content.</key1></key3>
</element4>
</pattern>
...并且应用您的代码会产生太多输出:
<root>
<pattern>
<VALUE1>a<VALUE3>something <VALUE1>and something else with <another VALUE1="VALUE1">VALUE2</another>
</VALUE1>
</VALUE3>
</VALUE1>
<element2>VALUE2<VALUE3>something <VALUE1>and something else with <another VALUE1="VALUE1">VALUE2</another>
</VALUE1>
</VALUE3>
</element2>
<element3 attribute="VALUE2">VALUE1<VALUE3>something <VALUE1>and something else with <another VALUE1="VALUE1">VALUE2</another>
</VALUE1>
</VALUE3>
</element3>
<element4>
<VALUE3>something <VALUE1>and something else with <another VALUE1="VALUE1">VALUE2</another>
</VALUE1>
</VALUE3>
</element4>
</pattern>
<pattern>
<VALUE3>a<VALUE5>something <VALUE3>and something else with <another VALUE3="VALUE3">VALUE4</another>
</VALUE3>
</VALUE5>
</VALUE3>
<element2>VALUE4<VALUE5>something <VALUE3>and something else with <another VALUE3="VALUE3">VALUE4</another>
</VALUE3>
</VALUE5>
</element2>
<element3 attribute="VALUE4">VALUE3<VALUE5>something <VALUE3>and something else with <another VALUE3="VALUE3">VALUE4</another>
</VALUE3>
</VALUE5>
</element3>
<element4>
<VALUE5>something <VALUE3>and something else with <another VALUE3="VALUE3">VALUE4</another>
</VALUE3>
</VALUE5>
</element4>
</pattern>
</root>
如果我已正确理解您的要求,那么您需要的是:
<root>
<pattern>
<VALUE1>a</VALUE1>
<element2>VALUE2</element2>
<element3 attribute="VALUE2">VALUE1</element3>
<element4>
<VALUE3>something <VALUE1>and something else with <another VALUE1="VALUE1">VALUE2</another> content.</VALUE1>
</VALUE3>
</element4>
</pattern>
<pattern>
<VALUE3>a</VALUE3>
<element2>VALUE4</element2>
<element3 attribute="VALUE4">VALUE3</element3>
<element4>
<VALUE5>something <VALUE3>and something else with <another VALUE3="VALUE3">VALUE4</another> content.</VALUE3>
</VALUE5>
</element4>
</pattern>
</root>
在咀嚼这一段时间以确保我理解你要做的事情之后,我意识到我最初在理解你的代码时遇到的一部分就是你开始使用input.xml
文件并处理该上下文,然后您更改为处理pattern.xml
文件并处理该上下文。
当您调用模板并希望该模板处理某些内容(而不仅仅是返回静态值或XML结构)时,您必须明确传入该内容,这会使上下文混乱。以下是使用<xsl:apply-templates>
代替<xsl:call-template name="...">
解决同一问题的另一种方法。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- We need to create output based on processing the
`pattern.xml` file, so we use that as our input. -->
<!-- Despite its name, the `input.xml` file actually contains
the pattern we wish to apply to the incoming XML file.
We'll store this pattern in a variable here. -->
<xsl:variable name="replacements" select="document('input.xml')">
<!-- Rather than relying on external files, we could leave out the
`select` attribute and just put all the replacement information
right here in the XSL file. --><!--
<root>
<copy>
<key1>VALUE1</key1>
<key2>VALUE2</key2>
<key3>VALUE3</key3>
</copy>
<copy>
<key1>VALUE3</key1>
<key2>VALUE4</key2>
<key3>VALUE5</key3>
</copy>
</root>-->
</xsl:variable>
<!-- We assume that we don't know the names of any of the elements
or attributes in the file we're processing.
If you _do_ know any of those names, specify them: literal
XPaths tend to be faster. -->
<xsl:template match="/">
<!-- We put the top-level XML structure into a variable so
we can refer to it from within the for-each. If we
don't do this, we can't get at it: within the for-each,
both the `.` operator and the `current()` function
refer to the context item for the `for-each` call,
and _not_ to the context item for the `template match`
statement. The `child::` axis in the `select` statement
is optional; we could just use `select="*"` instead.
I added the `child::` to make it clear for humans. -->
<xsl:variable name="top" select="child::*"/>
<!-- At the root level, we want to create a copy of the
topmost element from the $replacements variable. -->
<xsl:element name="{name($replacements/*[1])}">
<!-- We want to apply the key-value pairs to the topmost
XML structure from the input file, once for each
instance of <copy> in the $replacements variable. -->
<xsl:for-each select="$replacements/*/copy">
<!-- Now we apply our templates to the input
file structure, which we stored in $top. -->
<xsl:apply-templates select="$top">
<!-- We do need to have some way of knowing which
`copy` we want to reference, so we pass this
along as a parameter. -->
<xsl:with-param name="this" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- This matches all elements. -->
<xsl:template match="*">
<xsl:param name="this"/>
<!-- We compare the current element name against all the
element names in $this, and use that value if found,
or the current element name if not found. -->
<xsl:element name="{if (name(.) = $this//*/name())
then ($this//*[name() = name(current())])
else (name(.))}">
<xsl:apply-templates select="*|@*|text()">
<xsl:with-param name="this" select="$this"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<!-- This matches all atributes. We evaluate both attribute
names and attribute values, allowing us to replace either
or both, if they match the key-value pairs in $this.-->
<xsl:template match="@*">
<xsl:param name="this"/>
<xsl:variable name="attname" select="if (name(.) = $this//*/name())
then ($this//*[name() = name(current())])
else (name(.))"/>
<xsl:variable name="attval" select="if (. = $this//*/name())
then ($this//*[name() = current()])
else (.)"/>
<xsl:attribute name="{$attname}" select="$attval"/>
</xsl:template>
<!-- This matches all text. -->
<xsl:template match="text()">
<xsl:param name="this"/>
<xsl:value-of select="if (. = $this//*/name())
then ($this//*[name() = current()])
else (.)"/>
</xsl:template>
</xsl:stylesheet>
从您的帖子来看,您是否仅限于XSL 1.0并不完全清楚。我使用上面的XSL 2.0,XPath表达式中的if () then () else ()
逻辑仅适用于XSL 2.0+。在XSL 1.0中也不支持在XPath表达式的末尾使用*/name()
。这两个问题都可以根据需要进行编码。
这个XSL通过添加<element4>
从扩展输入XML生成(我相信预期?)更短的XML输出。
以下内容大致相当于上面的XSL,但重新设计为与XSL 1.0兼容。主要区别:
$replacements
变量中引用的文件名更改为substitution.xml
。if () then () else ()
逻辑。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- Filename changed to match the new requirements. -->
<xsl:variable name="replacements" select="document('substitution.xml')"/>
<xsl:template match="/">
<xsl:variable name="top" select="*"/>
<xsl:element name="{name($replacements/*[1])}">
<xsl:for-each select="$replacements/*/copy">
<xsl:apply-templates select="$top">
<xsl:with-param name="this" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:element>
</xsl:template>
<!-- This matches all elements. -->
<xsl:template match="*">
<xsl:param name="this"/>
<xsl:variable name="elem_name">
<!-- XSL 1.0 does not support `if / then / else` logic in
XPath statements, so we use this variable instead to
call a named template containing the logic, and then
to store the resulting value. -->
<xsl:call-template name="get_sub_value">
<xsl:with-param name="this" select="$this"/>
<xsl:with-param name="val" select="name()"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$elem_name}">
<xsl:apply-templates select="*|@*|text()">
<xsl:with-param name="this" select="$this"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<!-- This matches all atributes. -->
<xsl:template match="@*">
<xsl:param name="this"/>
<xsl:variable name="attname">
<xsl:call-template name="get_sub_value">
<xsl:with-param name="this" select="$this"/>
<xsl:with-param name="val" select="name()"/>
</xsl:call-template>
</xsl:variable>
<xsl:attribute name="{$attname}">
<!-- For attribute alues, we just call `get_sub_value` directly. -->
<xsl:call-template name="get_sub_value">
<xsl:with-param name="this" select="$this"/>
<xsl:with-param name="val" select="."/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<!-- This matches all text. -->
<xsl:template match="text()">
<xsl:param name="this"/>
<!-- For straight text, we just call `get_sub_value` directly. -->
<xsl:call-template name="get_sub_value">
<xsl:with-param name="this" select="$this"/>
<xsl:with-param name="val" select="."/>
</xsl:call-template>
</xsl:template>
<!-- Named template to handle the logic of looking for key-value pairs
in the relevant `<copy>` element from the `substitution.xml` file. -->
<xsl:template name="get_sub_value">
<xsl:param name="this"/>
<xsl:param name="val"/>
<xsl:choose>
<xsl:when test="$this//*[name() = $val]">
<xsl:value-of select="$this//*[name() = $val]"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$val"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
我对您在浏览器中执行此操作的用例感到有点困惑,但您知道自己需要什么。 :)
使用Xalan,Saxon 6.5和Saxon 9.6.0.7处理器在Oxygen XML v17.1中测试。唯一的区别是缩进。