使用 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"匹配运行。

我认为这是正确的,但我有一个问题。 操作数的文本如何输出?

【问题讨论】:

现在您将andor 混合为兄弟姐妹,这正是我所避免的。 “任何一个”对你来说有多重要?我补充说,当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 的“布尔”英文句子的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别