Маркировка строки в XQuery вне вложенного выражения в скобках - PullRequest
1 голос
/ 05 апреля 2011

Я пытаюсь написать функцию XQuery для токенизации строки в разделителе, игнорируя разделители внутри вложенных выражений в скобках, например,

tokenizeOutsideBrackets("1,(2,3)" , ",")         => ( "1" , "(2,3)" ) 
tokenizeOutsideBrackets("1,(2,(3,4))" , ",")     => ( "1" , "(2,(3,4))" )
tokenizeOutsideBrackets("1,(2,(3,(4,5)))" , ",") => ( "1" , "(2,(3,(4,5)))" )
tokenizeOutsideBrackets("1,(2,(3,4),5),6" , ",") => ( "1" , "(2,(3,4),5)" , "6" )

Если бы у меня были рекурсивные регулярные выражения или императивный язык, это было бы довольно тривиальноно я изо всех сил пытаюсь найти простой и легкий способ сделать это в XQuery.

Спасибо!

Ответы [ 3 ]

1 голос
/ 05 апреля 2011

Это выражение XQuery:

tokenize(replace('1,(2,(3,4),5),6','([0123456789]+|\(.*\))(,)?','$1;'),';')

Выход:

1 (2,(3,4),5) 6

Обновление : Если будут строки вроде '1,(2,3),(4,5),6', то вам понадобится парсер для этой грамматики:

exp ::= term ( ',' term ) *

term ::= num | '(' exp ')'

num ::= ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ) +
0 голосов
/ 05 апреля 2011

Поиграл, и функция ниже, кажется, работает, хотя я не могу не думать, что есть более легкий путь.

Этот код использует функцию functx: index-of-string для поиска индексов всех разделителей. Затем каждый пытается найти первый разделитель, где все слева имеет одинаковое количество открывающих и закрывающих скобок. После того, как это найдено, это повторяется со всем справа от этого разделителя.

declare function local:tokenizeOutsideBrackets(
  $arg as xs:string?,
  $delimiter as xs:string) as xs:string*
{
  if (contains($arg, $delimiter))
  then
    (:find positions of all the delimiters:)
    let $delimiterPositions := (
      functx:index-of-string($arg,$delimiter),
      string-length($arg)+1 (:Add in end of string too:)
    )

    (:strip out all the fragments that have matching
      brackets to the left of each delimiter:)
    let $fragments :=
      for $endPos in $delimiterPositions
      let $candidateString := substring($arg,1,$endPos - 1)
      return
        if (local:hasMatchedBrackets($candidateString))
        then $candidateString
        else ()
    let $firstFragment := $fragments[1]
    let $endPos := string-length($firstFragment)

    (:recursively return the first matching fragment,
      plus the fragments in the remaining string:)
    return
    (
      $firstFragment,
      local:tokenizeOutsideBrackets(
        substring(
          $arg,
          $endPos+string-length($delimiter)+1,
          string-length($arg) - $endPos -(string-length($delimiter))
        ),
        $delimiter
      )
    )
  else if ($arg='') then () else ($arg)
};

declare function local:hasMatchedBrackets($arg as xs:string) as xs:boolean 
{
  count(tokenize($arg,'\(')) = count(tokenize($arg,'\)'))
};
0 голосов
/ 05 апреля 2011

Один из способов сделать это - сначала разделить, а затем соединить токены с несбалансированными скобками с соседями по правой стороне.

Приведенный ниже код даст вам желаемые результаты.Он использует fn: tokenize для разделения, а затем (tail-) рекурсивно обрабатывает полученные токены, объединяя их, когда предыдущий токен имеет несовпадающее число «(» и «)».У этого подхода есть некоторые недостатки, а именно: неправильное сопоставление левой и правой скобок и трактовка $ delimiter как шаблона, так и литерала.Для правильной обработки необходимо больше кодирования, однако вы можете понять это.

declare function local:tokenizeOutsideBrackets($string, $delimiter)
{
  local:joinBrackets(tokenize($string, $delimiter), $delimiter, ())
};

declare function local:joinBrackets($tokens, $delimiter, $result)
{
  if (empty($tokens)) then
    $result
  else 
    let $last := $result[last()]
    let $new-result :=
      if (string-length(translate($last, "(", "")) 
        = string-length(translate($last, ")", ""))) then
       ($result, $tokens[1])
      else
       ($result[position() < last()], concat($last, $delimiter, $tokens[1]))
    return local:joinBrackets($tokens[position() > 1], $delimiter, $new-result)
};
...