使用 XSLT 将 XML 转换为带有嵌套 AND/OR 的“布尔”英文句子
Posted
技术标签:
【中文标题】使用 XSLT 将 XML 转换为带有嵌套 AND/OR 的“布尔”英文句子【英文标题】:Using XSLT to transform XML into "Boolean" English sentence with nested AND/OR 【发布时间】:2013-09-08 13:43:22 【问题描述】:我需要将 XML 转换成类似于英文句子的东西。 例如以下 XML:
<event>
<criteria>
<and>A</and>
<and>B</and>
<and>
<or>
<and>C</and>
<and>D</and>
</or>
<or>E</or>
</and>
</criteria>
</event>
必须变成类似:
To meet the criteria event must have A and B and either C and D or E.
这是一个例子,但“and”和“or”条件可以进一步嵌套。
规则似乎是:
如果一个元素没有后续兄弟姐妹或子元素,则不会输出任何内容,您就完成了。 如果“and”或“or”有一个没有子代的后续兄弟,则输出后续兄弟的类型(“and”或“or”)。(例如,A 和 B;C 和 D;D或 E) 如果“and”后面有一个带有“or”子代的“and”兄弟,则输出“and either”(例如,and either C)。 不输出没有文本的元素。我尝试了几种方法来生成此输出,但都没有成功。一个问题是没有得到正确的递归。我见过很多嵌套一个元素的 xslt 处理示例(例如,Item 可以由由其他 Item 组成的其他 Item 组成,等等),但是没有像“and”和“or”这样的两个元素的示例" 可以是兄弟姐妹和/或相互嵌套。 我尝试使用 xsl:template match= "and | or" 然后测试 "and" 或 "or",但我要么没有降到叶级别,要么以错误的顺序出现。
我想知道是否有人可以指出我处理此类结构的正确方向,和/或是否有人可以建议更好的结构来表示“布尔”句子。由于 XML 尚未最终确定,如果使处理更容易,可以对其进行修改。
注意:我使用的是 Saxon 9,可以使用 xslt 2.0 解决方案。
更多信息:
再次感谢@g-ken-holman。我喜欢建议的自上而下的方法,但我遇到了一些问题。我不确定为什么在肯的例子中和/或序列被更改为或/和。和/或顺序似乎是正确的。无论如何,我运行了这个例子并且它有效。但是,我总共收到了 5 个案例。它适用于前两个简单的情况,即所有的和或或,以及情况 5,即上面的情况。但是案例 3 和 4 没有用。这是 XML 和结果。
<event>
<example>3</example>
<criteria>
<or>
<op>A</op>
<op>B</op>
</or>
<and>
<op>C</op>
</and>
</criteria>
</event>
Result: To meet the criteria, event must have either A or B C
Expected: To meet the criteria, event must have either A or B and C
示例 4:
<event>
<example>4</example>
<criteria>
<and>
<op>A</op>
<op>B</op>
</and>
<and>
<or>
<op>C</op>
<op>D</op>
<op>E</op>
</or>
</and>
</criteria>
</event>
结果:要满足条件,事件必须有 A 和 B C 或 D 或 E 预期:要满足条件,事件必须具有 A 和 B 以及 C 或 D 或 E
我认为原因是 and/or or 仅在有多个 (position()>1) 测试时才输出。但这不会涵盖所有情况。也许如果节点数的 position()>1 = 1?
可以添加一个“either”元素,如果这样会更容易的话。
答案说明:
这对于 cmets 部分来说太长了,所以我在这里添加它。我相信@Ken 已经提供了答案,并且他建议的第二种方法是最好的。
如果我了解处理。我们正在匹配文档中的所有节点。我们匹配“事件”并首先执行,因为它嵌套在其他节点之外。然后,如果遇到“and”节点,我们会在“and”上得到一个匹配,然后我们迭代(for-each)该级别的所有“and”兄弟节点。我们不会为第一个节点输出单词“and”,因为测试“position() > 1”失败。我们总是使用 xls:text 输出一个空格。接下来我们从当前(上下文)节点()应用模板。这开始让我们沿着树向下走,因为我们现在只匹配“and”的子节点。如果我们接下来匹配一个“and”,我们将重复我们到目前为止所做的事情。如果我们接下来匹配一个“或”,我们会执行 match="or" 模板,它与“and”几乎相同,只是它输出单词“or”。但是,有两个可能的模板匹配“或”和 1]" priority="1">。priority="1" 将该匹配项的优先级设置为高于另一个“或”匹配项,因为除非指定了优先级, 匹配的默认优先级为 0.5. 因此如果当前 "or" 节点有 2 个子节点 (or[count(*) > 1]), 我们输出 "either" 然后调用这将允许较低优先级 "or"匹配运行。
我认为这是正确的,但我有一个问题。 操作数的文本如何输出?
【问题讨论】:
现在您将and
和or
混合为兄弟姐妹,这正是我所避免的。 “任何一个”对你来说有多重要?我补充说,当or
有两个操作数时......你其他时候想要吗?
请参阅下面的我在示例 3 和 4 中的 cmets 的替代答案(只是为了我的样式表的期望而写的不正确。
谢谢@Ken。我认为“and either”对于可读性很重要。我认为在 or 有 2 个或更多操作数的情况下需要它。会在测试中更改为 or[count(*) > 1] 吗?我正在研究我对处理的理解,并将发布它的描述和一些问题。
【参考方案1】:
此替代答案具有相同的样式表逻辑(唯一的变化是示例编号的说明),但发布是为了解决示例 3 和 4 的已编辑问题。
你在哪里:
<event>
<example>3</example>
<criteria>
<or>
<op>A</op>
<op>B</op>
</or>
<and>
<op>C</op>
</and>
</criteria>
</event>
我会写与以下相同的内容,使用我的原始逻辑为您提供您想要的结果:
t:\ftemp>type boolean3.xml
<event>
<example>3</example>
<criteria>
<and>
<or>
<op>A</op>
<op>B</op>
</or>
<op>C</op>
</and>
</criteria>
</event>
t:\ftemp>xslt2 boolean3.xml boolean2.xsl
3 To meet the criteria, event must have either A or B and C
类似的例子 4,你有:
<event>
<example>4</example>
<criteria>
<and>
<op>A</op>
<op>B</op>
</and>
<and>
<or>
<op>C</op>
<op>D</op>
<op>E</op>
</or>
</and>
</criteria>
</event>
我会这样写:
t:\ftemp>type boolean4.xml
<event>
<example>4</example>
<criteria>
<and>
<op>A</op>
<op>B</op>
<or>
<op>C</op>
<op>D</op>
<op>E</op>
</or>
</and>
</criteria>
</event>
t:\ftemp>xslt2 boolean4.xml boolean2.xsl
4 To meet the criteria, event must have A and B and C or D or E
在我的代码中,当有两个 or
操作数时,我只使用了“either”这个词......我想当有两个以上的操作数时它也可以工作,所以你可以将它添加到 or
处理逻辑中.
这是修改后的样式表以容纳示例编号:
t:\ftemp>type boolean2.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<!--eat white-space-->
<xsl:template match="text()[not(normalize-space())]"/>
<!--start result-->
<xsl:template match="event">
<xsl:value-of select="example"/>
<xsl:text> To meet the criteria, event must have</xsl:text>
<xsl:apply-templates select="criteria"/>
</xsl:template>
<!--handle conjunction-->
<xsl:template match="and">
<xsl:for-each select="*">
<xsl:if test="position()>1"> and</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--handle alternation-->
<xsl:template match="or">
<xsl:for-each select="*">
<xsl:if test="position()>1"> or</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--special grammar case for alternation between 2 operands-->
<xsl:template match="or[count(*) = 2]" priority="1">
<xsl:text> either</xsl:text>
<xsl:next-match/>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>
所以,这完全取决于您如何编写 XML。检查我是如何重写你所做的操作数如何工作的,并询问你是否需要更多说明。
【讨论】:
【参考方案2】:我建议您始终“自上而下”地处理数据,而不是尝试处理同级数据。
以下是一个解决方案:
t:\ftemp>type boolean1.xml
<event>
<criteria>
<and>A</and>
<and>B</and>
<and>
<or>
<and>C</and>
<and>D</and>
</or>
<or>E</or>
</and>
</criteria>
</event>
t:\ftemp>call xslt2 boolean1.xml boolean1.xsl
To meet the criteria, event must have A and B and either C and D or E
t:\ftemp>type boolean1.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<!--eat white-space-->
<xsl:template match="text()[not(normalize-space())]"/>
<!--start result-->
<xsl:template match="event">
To meet the criteria, event must have<xsl:apply-templates/>
</xsl:template>
<!--handle conjunction-->
<xsl:template match="*[child::and]">
<xsl:for-each select="child::and">
<xsl:if test="position()>1"> and</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--handle alternation-->
<xsl:template match="*[child::or]">
<xsl:for-each select="child::or">
<xsl:if test="position()>1"> or</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--special grammar case for alternation between 2 operands-->
<xsl:template match="*[count(child::or) = 2]" priority="1">
<xsl:text> either</xsl:text>
<xsl:next-match/>
</xsl:template>
<!--don't allow a mixture-->
<xsl:template match="*[child::and and child::or]" priority="2">
<xsl:message terminate="yes">
<xsl:text>A mixture of ands and ors is not allowed.</xsl:text>
</xsl:message>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
关于更改 XML 的建议,我建议使用不允许意外组合的结构,例如“当 ands 和 ors 都是兄弟时该怎么办”。考虑以下几点:
t:\ftemp>type boolean2.xml
<event>
<criteria>
<and>
<op>A</op>
<op>B</op>
<or>
<and>
<op>C</op>
<op>D</op>
</and>
<op>E</op>
</or>
</and>
</criteria>
</event>
t:\ftemp>call xslt2 boolean2.xml boolean2.xsl
To meet the criteria, event must have A and B and either C and D or E
t:\ftemp>type boolean2.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<!--eat white-space-->
<xsl:template match="text()[not(normalize-space())]"/>
<!--start result-->
<xsl:template match="event">
To meet the criteria, event must have<xsl:apply-templates/>
</xsl:template>
<!--handle conjunction-->
<xsl:template match="and">
<xsl:for-each select="*">
<xsl:if test="position()>1"> and</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--handle alternation-->
<xsl:template match="or">
<xsl:for-each select="*">
<xsl:if test="position()>1"> or</xsl:if>
<xsl:text> </xsl:text>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<!--special grammar case for alternation between 2 operands-->
<xsl:template match="or[count(*) = 2]" priority="1">
<xsl:text> either</xsl:text>
<xsl:next-match/>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
在第二种方法中,“动作”由元素触发,而不是由子操作数元素触发。我觉得这样会更直接。
请注意,对于英语读者而言,在没有标点符号的情况下深度嵌套 ands 和 ors 时可能会遇到一些语法挑战。
【讨论】:
哇,感谢您的快速响应。我将不得不消化这个,因为我直到深夜才得到它。我同意关于没有标点符号的深度嵌套时的挑战的评论。我不认为这对于这个特定的应用程序来说是一个太大的问题,但我同意你的观点。 再次感谢@g-ken-holman。我喜欢自上而下的方法,但我遇到了一些问题。我不确定为什么 and/or 序列在您的示例中更改为 or/and 。和/或顺序似乎是正确的。无论如何,我运行了这个例子,它适用于这种情况。但是,我总共有 5 个样本案例。它适用于前两个简单的情况,所有情况都是和或或,以及情况 5,即上面的情况。但是案例 3 和 4 没有用。这是 XML 和结果。以上是关于使用 XSLT 将 XML 转换为带有嵌套 AND/OR 的“布尔”英文句子的主要内容,如果未能解决你的问题,请参考以下文章