【问题标题】:XQuery / BaseX - Limit depth of resultXQuery / BaseX - 限制结果的深度
【发布时间】:2021-01-19 04:40:35
【问题描述】:

在使用 XPath 或 XQuery 时,有没有办法限制结果的深度?

我正在使用 BaseX,它支持 XQuery 3.1 和 XSLT 2.0。

例如,给定这个输入文档:

<country name="United States">
  <state name="California">
    <county name="Alameda" >
      <city name="Alameda" />
      <city name="Oakland" />
      <city name="Piedmont" />
    </county>
    <county name="Los Angeles">
      <city name="Los Angeles" />
      <city name="Malibu" />
      <city name="Burbank" />
    </county>
    <county name="Marin">
      <city name="Fairfax" />
      <city name="Larkspur" />
      <city name="Ross" />
    </county>
    <county name="Sacramento">
      <city name="Folsom" />
      <city name="Elk Grove" />
      <city name="Sacramento" />
    </county>
  </state>
</country>

如果我执行这个查询:/country/state,我会得到以下结果:

<state name="California">
  <county name="Alameda">
    <city name="Alameda"/>
    <city name="Oakland"/>
    <city name="Piedmont"/>
  </county>
  <county name="Los Angeles">
    <city name="Los Angeles"/>
    <city name="Malibu"/>
    <city name="Burbank"/>
  </county>
  <county name="Marin">
    <city name="Fairfax"/>
    <city name="Larkspur"/>
    <city name="Ross"/>
  </county>
  <county name="Sacramento">
    <city name="Folsom"/>
    <city name="Elk Grove"/>
    <city name="Sacramento"/>
  </county>
</state>

我想限制结果的深度。理想情况下,我有办法指定深度,而不是硬编码 XPath 查询。

例如,我想将结果限制为结果节点及其子节点,但不包括孙子节点,因此结果将是:

<state name="California">
  <county name="Alameda" />
  <county name="Los Angeles" />
  <county name="Marin" />
  <county name="Sacramento" />
</state>

【问题讨论】:

    标签: xpath xquery basex


    【解决方案1】:

    @zx845 的帖子让我走上了正轨。我的最终目标是限制结果的深度,目的是获得“摘要”和必要时获得更深入结果所需的元数据。

    BaseX has a function "db:node-id" 将返回任何给定节点的内部节点 ID。 another function, "db:open-id" 返回具有给定 ID 的节点。

    假设这个给定的输入:

    <country name="United States">
      <state name="California">
        <county name="Alameda">
          <city name="Alameda"/>
          <city name="Oakland"/>
          <city name="Piedmont"/>
        </county>
        <county name="Los Angeles">
          <city name="Los Angeles"/>
          <city name="Malibu"/>
          <city name="Burbank"/>
        </county>
        <county name="Marin">
          <city name="Fairfax"/>
          <city name="Larkspur"/>
          <city name="Ross"/>
        </county>
        <county name="Sacramento">
          <city name="Folsom"/>
          <city name="Elk Grove"/>
          <city name="Sacramento"/>
        </county>
      </state>
      <state name="New York">
        <county name="Albany">
          <city name="Albany"/>
          <city name="Cohoes"/>
          <city name="Watervliet"/>
        </county>
        <county name="Erie">
          <city name="Buffalo"/>
          <city name="Lackawanna"/>
          <city name="Tonawanda"/>
        </county>
      </state>
    </country>
    

    我定义了这个函数,它可以让我控制深度,并返回每个节点的节点 ID。

    declare function local:abbreviated($input, $depth as xs:integer)
    {
      if($depth = 0) then
        element node {
          db:node-id($input)
        }
      else
        element { node-name($input) } { 
          attribute node-id {
            db:node-id($input)
          },
          $input/@*,
          $input/text(),
          for $child in $input/*
            return local:abbreviated($child, $depth - 1)
        }
    };
    

    如果我执行以下操作:

    declare variable $input := /country/state;
    for $result in $input
      return local:abbreviated($result, 1)
    

    然后我得到这个结果:

    <state node-id="3" name="California">
      <node>5</node>
      <node>13</node>
      <node>21</node>
      <node>29</node>
    </state>
    <state node-id="37" name="New York">
      <node>39</node>
      <node>47</node>
    </state>
    

    现在,当我处理结果时,如果用户想要state 元素的更多详细信息,我可以处理每个“节点”元素并执行此查询以获取节点的实际内容

    local:abbreviated(db:open-id('states', 5), 2)
    

    导致:

    <county node-id="5" name="Alameda">
      <city node-id="7" name="Alameda"/>
      <city node-id="9" name="Oakland"/>
      <city node-id="11" name="Piedmont"/>
    </county>
    

    【讨论】:

      【解决方案2】:

      实际上,您的查询结果是单个节点,即源文档中的 state 节点。然后某些软件会以某种特定格式显示查询的结果(即state 节点),但原则上结果可以以不同的方式显示而无需更改查询。例如,我知道有软件会将此查询的结果显示为

      /country[1]/state[1]
      

      所以你需要分开两个问题:查询返回哪些节点,它们是如何显示的?在某些情况下,创建一个处理管道可能是有意义的,其中第一步选择感兴趣的节点,第二步控制结果的呈现。

      就我个人而言,我总是会在 XSLT 中进行第二步,但有些人更喜欢 XQuery。任君挑选。

      【讨论】:

        【解决方案3】:

        一种简单直接的方法是使用带有空模板的 XSLT-2.0,取消 &lt;county&gt; 的所有子级。 &lt;xsl:strip-space&gt; 删除了孩子们会使用的空间。

        <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
          <xsl:strip-space elements="*" />
         
          <!-- Identity template -->
          <xsl:template match="@* | node()">
            <xsl:copy>
              <xsl:apply-templates select="@* | node()" />
            </xsl:copy>
          </xsl:template>
          
          <xsl:template match="/">
              <xsl:apply-templates select="/country/state" />
          </xsl:template>
          
          <xsl:template match="county/*" />
          
        </xsl:stylesheet>
        

        输出是:

        <?xml version="1.0" encoding="UTF-8"?>
        <state name="California">
            <county name="Alameda"/>
            <county name="Los Angeles"/>
            <county name="Marin"/>
            <county name="Sacramento"/>
        </state>
        

        使用 XQuery,解决方案可能如下所示:

        for $st in doc("b.xml")/country/state return
          element { node-name($st) } { $st/@*,
          for $ct in $st/county return 
            element { node-name($ct) } { $ct/@* }
          }
        

        输出是一样的。

        【讨论】:

        猜你喜欢
        • 2015-06-17
        • 1970-01-01
        • 2023-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多