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

Order by 不接受序列,但您可以显式标记版本并将它们添加到 order by,用逗号分隔(注意排除括号)。

let $products := 
<products>
  <product version="1.2.3"/>
  <product version="1.10.0"/>
  <product version="2.1.6"/>
</products>
for $p in $products/product
let $toks := tokenize($p/@version, '\.')
let $main := xs:integer($toks[1])
let $point := xs:integer($toks[2])
let $sub := xs:integer($toks[3])
order by $main, $point, $sub
return $p

更新:对于可变数量的令牌,您可以使order by更健壮:

order by 
  if (count($toks) gt 0) then $main else (),
  if (count($toks) gt 1) then $point else (),
  if (count($toks) gt 2) then $sub else ()
于 2013-08-14T19:17:17.290 回答
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。

这只是将每个数字段组合成一个大数字并按此排序。

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