1

次の形式の XML データがあります。

<products>
  <product version="1.2.3"/>
  <product version="1.10.0"/>
  <product version="2.1.6"/>
</products>

...等々。これらを XQuery でバージョン番号順に並べたいと思います。問題は、単に を実行するorder by $thing/@versionと、1.10.0 を 1.2.3 の前に置く辞書式比較が行われることです。これは間違っています。

私が本当にやりたいことは次のようなものです:

order by tokenize($thing/@version, '\.') ! number(.)

残念ながら、XQuery では順序付けキーとしてシーケンス全体を使用できないため、これは機能しません。どうすればこのようなものを手に入れることができますか?

すべてのバージョン番号のドット数が同じであることに依存しないソリューションが望ましいですが、取得できるものを使用します。

4

4 に答える 4

3

All you can do is normalize the version numbers so you can apply lexical ordering.

  • Determine maximum string length in a version step
  • Pad it with 0's (or space if you prefer, but you will have to change the code for this)
  • Tokenize each version, pad each version step, rejoin them
  • Compare based on padded version

I didn't clean up that code and pulled two functions from functx, but it works and should be fine for embedding as needed. The code is also able to deal with single-letters, if necessary you could replace all occurences of "alpha", ... for example by "a", ...

declare namespace functx = "http://www.functx.com"; 
declare function functx:repeat-string 
  ( $stringToRepeat as xs:string? ,
    $count as xs:integer )  as xs:string {

   string-join((for $i in 1 to $count return $stringToRepeat),
                        '')
 } ;
declare function functx:pad-integer-to-length 
  ( $integerToPad as xs:anyAtomicType? ,
    $length as xs:integer )  as xs:string {

   if ($length < string-length(string($integerToPad)))
   then error(xs:QName('functx:Integer_Longer_Than_Length'))
   else concat
         (functx:repeat-string(
            '0',$length - string-length(string($integerToPad))),
          string($integerToPad))
 } ;


declare function local:version-compare($a as xs:string, $max-length as xs:integer)
as xs:string*
{
  string-join(tokenize($a, '\.') ! functx:pad-integer-to-length(., $max-length), '.')
};

let $bs := ("1.42", "1.5", "1", "1.42.1", "1.43", "2")
let $max-length := max(
                     for $b in $bs
                     return tokenize($b, '\.') ! string-length(.)
                   )
for $b in $bs
let $normalized := local:version-compare($b, $max-length)
order by $normalized
return $b

Returns:

1 1.5 1.42 1.42.1 1.43 2

于 2013-08-14T20:31:23.043 に答える
2

Jensの答えに似たことをしました:

let $products := //product
let $max-length := max($products/@version ! string-length(.))
for $product in $products
order by string-join(
    for $part in tokenize($product/@version, '\.')
    return string-join((
        for $_ in 1 to $max-length - string-length($part) return ' ',
        $part)))
return $product
于 2013-08-14T22:01:14.743 に答える
1

これは、数値であり、すべてのバージョン文字列が同じ数のセグメントを持っている限り、任意の数のセグメントを処理するバージョンです。また、999 を超えるコンポーネントが 1 つもないと仮定しています。

これは単純に各数値セグメントを 1 つの大きな数値に結合し、それによって並べ替えます。

declare function local:version-order ($version as xs:string) as xs:double
{
    fn:sum (
        let $toks := fn:tokenize ($version, "\.")
        let $count := fn:count ($toks)
        for $tok at $idx in $toks
        return xs:double ($tok) * math:pow (1000, ($count - $idx))
    )
};

let $products := 
    <products>
        <product version="1.10.0"/>
        <product version="2.1.6"/>
        <product version="1.2.3"/>
    </products>

for $p in $products/product
order by local:version-order ($p/@version)
return $p
于 2013-08-19T11:45:40.700 に答える