2011-04-05 40 views
1

我想寫一個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來做到這一點。

謝謝!

+0

_「遞歸正則表達式」 _聽起來有點矛盾......沒關係的語言是否遵循必要的或聲明的範例,順便說一句。 – 2011-04-05 16:26:42

+0

@Alejandro:是的,我知道這是一種矛盾... :) PCRE是我通常習慣的,並支持遞歸模式(不管是否_technically_ regular) – jong 2011-04-05 16:33:44

回答

0

做到這一點的一種方法是首先拆分,然後將帶有不平衡括號的標記加入右手邊的鄰居。

下面的代碼將爲您帶來理想的結果。它使用fn:tokenize進行分割,然後(tail-)遞歸地處理結果標記,當前一個標記具有不匹配的計數「(」和「)」時連接。這種方法存在一些缺陷,即未能正確匹配左右括號,將$分隔符視爲模式和文字。更多的編碼是必要的,以妥善處理,但你可能會明白。

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) 
}; 
+0

類似於我的方法,但更簡單和更好。 – jong 2011-04-06 08:40:08

+0

+1好答案:標記連接「不平衡」項目。 – 2011-04-06 17:03:39

1

這XQuery表達式:

tokenize(replace('1,(2,(3,4),5),6','([]+|\(.*\))(,)?','$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

漂亮的伎倆,但是這在'1,(2,3),(4,5),6''上失敗了,它給出了'1(2,3),(4,5)6'而不是預期的'1 (2,3)(4,5)6' – jong 2011-04-05 16:18:33

+0

@jong:如果你要有不同的字符串比那些提供那麼你將需要一個解析器。 – 2011-04-05 16:24:35

+0

@Alejandro:感謝您的幫助。希望我不必去解析器路線,但認爲我的另一個解決方案做的工作,將做更多的測試。 – jong 2011-04-05 16:34:58

0

一直在玩和下面的功能似乎工作,雖然我不禁想着有一個更簡單的方法。

此代碼使用functx:索引字符串函數來查找所有分隔符的索引。然後它會嘗試每個查找第一個分隔符,其中左側的所有內容都具有相同數量的開始和結束括號。在找到之後,將重複此操作,此分隔符右側的所有內容都會重複。

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,'\)')) 
}; 
相關問題