XQuery / BaseX - Ogranicz głębokość wyniku
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
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.
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.
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>