XQuery / BaseX - Ogranicz głębokość wyniku

Oct 04 2020

Czy podczas korzystania z XPath lub XQuery istnieje sposób na ograniczenie głębokości wyniku?

Używam BaseX, który obsługuje XQuery 3.1 i XSLT 2.0.

Na przykład, biorąc pod uwagę ten dokument wejściowy:

<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>

Jeśli wykonam to zapytanie /country/state:, otrzymam następujący wynik:

<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>

Chciałbym ograniczyć głębokość wyniku. Najlepiej byłoby, gdyby był sposób, w jaki mógłbym określić głębokość, zamiast na stałe kodować zapytanie XPath.

Jako przykład chciałbym ograniczyć wynik do węzłów wynikowych i ich dzieci, ale nie uwzględniając wnuków, więc wynik byłby taki:

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

Odpowiedzi

5 zx485 Oct 04 2020 at 05:58

Jednym prostym sposobem jest użycie XSLT-2.0 z pustym szablonem anulującym wszystkie elementy podrzędne <county>. <xsl:strip-space>Usuwa miejsca, które zostały wykorzystane przez dzieci.

<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>

Wynik to:

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

W przypadku XQuery rozwiązanie mogłoby wyglądać następująco:

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/@* }
  }

Wynik jest taki sam.

1 MichaelKay Oct 04 2020 at 18:14

W rzeczywistości wynik twojego zapytania to pojedynczy węzeł, statewęzeł w dokumencie źródłowym. Niektóre programy wyświetlają wtedy wyniki zapytania - to znaczy statewęzeł - w określonym formacie, ale w zasadzie wyniki mogą być wyświetlane w inny sposób bez zmiany zapytania. Na przykład znam oprogramowanie, które wyświetla wyniki tego zapytania jako pliki

/country[1]/state[1]

Musisz więc oddzielić dwa pytania: jakie węzły zwraca zapytanie i jak są wyświetlane? W niektórych przypadkach sensowne może być utworzenie potoku przetwarzania, w którym pierwszy krok wybiera interesujące węzły, a drugi krok kontroluje prezentację wyników.

Osobiście zawsze robiłbym drugi krok w XSLT, ale niektórzy wolą XQuery. Wybieraj.

1 MikeChristiansen Oct 06 2020 at 20:05

Post @ zx845 poprowadził mnie na właściwą ścieżkę. Moim ostatecznym celem było ograniczenie głębokości wyniku, z zamiarem uzyskania „podsumowania” i metadanych potrzebnych do uzyskania głębszych wyników, jeśli to konieczne.

BaseX ma funkcję „db: id-węzła”, która zwraca wewnętrzny identyfikator węzła dowolnego podanego węzła. Jest jeszcze jedna funkcja „db: open-id”, która zwraca węzeł o podanym identyfikatorze.

Załóżmy, że podane dane wejściowe:

<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>

Zdefiniowałem tę funkcję, która pozwala mi kontrolować głębokość i zwracać identyfikator węzła dla każdego węzła.

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)
    }
};

Jeśli wykonam następujące czynności:

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

Wtedy otrzymuję ten wynik:

<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>

Teraz, gdy przetwarzam wyniki, jeśli użytkownik chce uzyskać więcej informacji na temat stateelementu, mogę przetworzyć każdy element „węzła” i wykonać to zapytanie, aby uzyskać rzeczywistą zawartość węzła

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

Wynik:

<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>