XQuery / BaseX - Limite de profundidade do resultado
Ao usar XPath ou XQuery, há uma maneira de limitar a profundidade do resultado?
Estou usando o BaseX, que oferece suporte a XQuery 3.1 e XSLT 2.0.
Por exemplo, dado este documento de entrada:
<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>
Se eu executar esta consulta:, /country/stateobtenho o seguinte resultado:
<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>
Eu gostaria de limitar a profundidade do resultado. Idealmente, haveria uma maneira de especificar a profundidade, em vez de codificar uma consulta XPath.
Como exemplo, gostaria de limitar o resultado aos nós de resultado e seus filhos, mas não incluindo os netos, então o resultado seria:
<state name="California">
<county name="Alameda" />
<county name="Los Angeles" />
<county name="Marin" />
<county name="Sacramento" />
</state>
Respostas
Uma maneira fácil e direta é usar o XSLT-2.0 com um modelo vazio cancelando todos os filhos de <county>. O <xsl:strip-space>remove o espaço que seria usado pelas crianças.
<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>
O resultado é:
<?xml version="1.0" encoding="UTF-8"?>
<state name="California">
<county name="Alameda"/>
<county name="Los Angeles"/>
<county name="Marin"/>
<county name="Sacramento"/>
</state>
Com XQuery, uma solução poderia ser assim:
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/@* }
}
A saída é a mesma.
Na verdade, o resultado da sua consulta é um único nó, o statenó no documento de origem. Alguns softwares estão exibindo os resultados da consulta - ou seja, o statenó - em algum formato específico, mas, em princípio, os resultados podem ser exibidos de uma maneira diferente sem alterar a consulta. Por exemplo, estou ciente de um software que exibiria os resultados desta consulta como
/country[1]/state[1]
Portanto, você precisa separar duas questões: quais nós a consulta retorna e como eles são exibidos? Em alguns casos, pode fazer sentido criar um pipeline de processamento em que a primeira etapa seleciona os nós de interesse e a segunda etapa controla a apresentação dos resultados.
Pessoalmente, eu sempre faria a segunda etapa em XSLT, mas algumas pessoas preferem XQuery. Faça sua escolha.
A postagem de @ zx845 me colocou no caminho certo. Meu objetivo final era limitar a profundidade do resultado, com a intenção de obter um "resumo" e os metadados de que preciso para obter resultados mais profundos, se necessário.
BaseX tem uma função "db: node-id" que retornará o ID do nó interno de qualquer nó dado. Existe outra função, "db: open-id" que retorna o nó com um determinado ID.
Suponha que esta entrada seja dada:
<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>
Eu defini essa função, que me permite controlar a profundidade e retornar o id do nó para cada nó.
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)
}
};
Se eu executar o seguinte:
declare variable $input := /country/state; for $result in $input return local:abbreviated($result, 1)
Então eu obtenho este resultado:
<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>
Agora, quando eu processo os resultados, se o usuário deseja mais detalhes para um stateelemento, posso processar cada elemento do 'nó' e executar esta consulta para obter o conteúdo real do nó
local:abbreviated(db:open-id('states', 5), 2)
Resultando em:
<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>