tcl regexp与paren-delimited groups

时间:2018-02-19 21:15:04

标签: regex tcl match

我有一组看起来像这样的字符串:

foo<xyz><123>
bar
pizza<oregano><tomato><mozzarella>

因此归结为前缀(foobarpizza,...),后跟任意数量的属性名称,用斜角括起来。 前缀和属性都可以包含任何字符,但有角括号(仅用于分隔属性名称)除外 前缀和属性名称都不能为空。

现在我想在我的Tcl应用程序中有一个正则表达式,它给了我前缀和所有属性(如果它们保留它们的分隔括号就可以了,但最后我必须将它们分成列表)。

琐碎的方法^(.+)(<.+>)*$不起作用,因为尾随的.+过于贪婪,并且会占用属性名称的所有匹配项。

所以我尝试排除乍一看正常工作的禁用尖括号^(\[^<>\]+)(<.+>)*$ - 但后来我发现这符合fnork<<>><x<>>违反规则,属性名称不得包含任何尖括号(分开)从界定的一个)。

第三,我将禁用字符扩展到属性名^(\[^<>\]+)(<\[^<>\]>)*$,但现在事情变得有点阴暗了:正则表达式只匹配有效字符串(因此前缀和属性名称都不能包含任何括号),我不再将属性名称作为匹配部分:

% regexp -all -inline "^(\[^<>\]+)(<\[^<>\]+>)*" "A<xyz><123>"
A<xyz><123> A <123>

无论出于何种原因,都不会返回<xyz>

知道如何解决这个问题吗?

边注

我正在尝试解析的实际字符串使用方括号和括号作为分隔符。例如:pizza[large](tomato)(olives)(cheese),其中[term]可以出现0或1次,而(term)可以出现0次或更多次。 但是由于方括号和括号的性质,这需要相当多的引用,这可能太过于分散注意力而无法在这里使用。)

3 个答案:

答案 0 :(得分:3)

在这种情况下,诀窍是使用相当简单的RE并对结果进行后处理:

(<[^<>]+>)*

你几乎就在那里,但是正在努力使用set str "foo<xyz><123> bar pizza<oregano><tomato><mozzarella>" # Find the matching lines and do the first-level extract on them foreach {- prefix attribs} [regexp -all -line -inline {^([^<>]+)((?:<[^<>]+>)*)$} $str] { # Split the attribute names set attributes [regexp -all -inline {[^<>]+} $attribs] # Show that we've matched them for real puts "prefix='$prefix', attributes=[join $attributes ,]" } ,因为它只会在匹配的时间内捕获组。(我不是&# 39;意识到它抓住了最后一场比赛,但由于我很少想要第一场或者最后一场,而是全部,我采用了不同的方法。)

将所有这些放在一起并假设您有一个大的多行字符串,其中包含您想要查看的所有部分(例如,因为您已从文件中读取它),您将获得:

{{1}}

产生此输出:

prefix='foo', attributes=xyz,123
prefix='bar', attributes=
prefix='pizza', attributes=oregano,tomato,mozzarella

答案 1 :(得分:1)

让我们对此进行标记。

package require string::token

set lex {[[] LB []] RB [(] LP [)] RP [^][()]+ t}
set str {pizza[large](tomato)(olives)(cheese)}

% set tokens [::string::token text $lex $str]
{t 0 4} {LB 5 5} {t 6 10} {RB 11 11} {LP 12 12} {t 13 18} {RP 19" 19} {LP 20 20} {t 21 26} {RP 27 27} {LP 28 28} {t 29 34} {RP 35 35}

有了标记化,我们可以用一种小语言解析或评估标记作为语句:

% set terms [lassign $tokens prefix]

proc t {str beg end} {
    string range $str $beg $end
}
proc LB {str beg end} {
    return "Optional term is: "
}
proc RB args {
    return \n
}
proc LP {str beg end} {
    rename LP {}
    proc LP args {
        return ", "
    }
    return "Arguments are: "
}
proc RP args {}

% puts "Prefix is: [eval [linsert $prefix 1 $str]]"
Prefix is: pizza
% % join [lmap term $terms {eval [linsert $term 1 $str]}] {}
Optional term is: large
Arguments are: tomato, olives, cheese

文档: evaljoinlassignlinsertlmap (for Tcl 8.5)lmappackageprocputsrenamereturnsetstring::token (package)

答案 2 :(得分:0)

我可能误解了要求,但考虑到你已经编码了#34;您的临时表示法中的所有结构细节,为什么不让Tcl列表机制完成工作?

set str {foo(xyz)(123)
bar
pizza[large](oregano)(tomato)(mozzarella)}

foreach line [split $str \n] {
    set line [string map {"[" " " "]" " " ")(" " " "(" " {" ")" "} "} $line]
    set suffix [lassign $line prefix]
    lassign $suffix a b
    if {[llength $suffix] == 2} {
      set optional $a
      set attributes $b
    } else {
      set optional ""
      set attributes $a
    }
    puts "prefix='$prefix', optional='$optional', attributes='[join $attributes ,]'"
}

我道歉,严格来说,我的答案并没有解决正则表达式的问题。并且比其他回复中的灵魂少;)