我有一组看起来像这样的字符串:
foo<xyz><123>
bar
pizza<oregano><tomato><mozzarella>
因此归结为前缀(foo
,bar
,pizza
,...),后跟任意数量的属性名称,用斜角括起来。
前缀和属性都可以包含任何字符,但有角括号(仅用于分隔属性名称)除外
前缀和属性名称都不能为空。
现在我想在我的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次或更多次。
但是由于方括号和括号的性质,这需要相当多的引用,这可能太过于分散注意力而无法在这里使用。)
答案 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
文档: eval, join, lassign, linsert, lmap (for Tcl 8.5), lmap, package, proc, puts, rename, return, set, string::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 ,]'"
}
我道歉,严格来说,我的答案并没有解决正则表达式的问题。并且比其他回复中的灵魂少;)