0

我有一个文件,其中包含以下格式的记录:

{TOKEN 
    { NAME {name of this token} }
    { GROUPS {Group 1} }
    { VALUE value }
    { REPEATING {
        { MAX 3 }
        { TIME {nmin 30} }
    } }
    { WINDOW */*/*/* }
    { ACTION {
        { EXEC {code to run here} }
    } }
}
{TOKEN 
    { NAME {name of next token} }
    { GROUPS {Group 1} }
    { VALUE value }
    { WINDOW 0/0:30-2:00,3:30-7:30/*/* }
    { HOST {localhost} }
    { ACTION {
        { email {
            { FROM cloverleaf@healthvision.com }
            { TO me@xxxx.org }
            { SUBJ {email subject test} }
            { MSG {this is the email body} }
        } }
    } }

并非所有记录都具有相同的关键字,但它们都是嵌套的键控列表,我需要将它们解析为 .csv 文件以便于查看。但是,当我读入文件时,它以单个字符串而不是键控列表的形式出现。在空格或换行符上拆分无济于事,因为它们也位于键控列表内。我试图在 }\n 和 {T 之间插入一个管道 (|) 并在管道上拆分,但我仍然得到了字符串。

我希望有人能指出正确的方向来解析这些 s-expression 文件。

提前致谢!

Ĵ

4

4 回答 4

1

问题

这是我如何理解你的问题。

  • 您有一个充满记录的文本文件。每条记录都是{TOKEN ...}
  • 每条记录几乎都是一个键控列表,但不完全是:字符串TOKEN使它成为一个无效的键控列表。如果我们删除这个字符串,那么剩下的将是一个有效的键控列表。
  • 每个键控列表可能是嵌套的。也就是说,该值可能是另一个键控列表。
  • 您希望将每条记录写成 CSV 文件中的一行。但是,在 CSV 文件中,每一行应包含相同数量的列,但此处并非如此。我会把它留给你,以了解如何最好地处理它。

解决方案

我的建议是把它变成一个字典,它是一个扁平的,而不是嵌套的结构。这应该使工作更容易。一旦你有了一个平面列表,处理它就变得更容易了。这是我的解决方案:

# myscript.tcl

package require Tclx

proc makeKey {prefix key} {
    return [string trim "$prefix $key"]
}   

proc keyedlist2dict {klname {keyPrefix ""}} {
    upvar 1 $klname kl
    set d {}
    foreach key [keylkeys kl] {
        set value [keylget kl $key]
        if {[catch {keylkeys value}]} {
            # value is not a nested keyed list
            lappend d [makeKey $keyPrefix $key] $value
        } else {
            # value is a nested keyed list
            set d [concat $d [keyedlist2dict value $key]] ;# TCL 8.4
        }   
    }   

    return $d
}   

set contents [read [open data.txt]]
foreach item $contents { 
    # Each item starts with "TOKEN", which we need to remove otherwise
    # the keyed list is invalid
    set item [lrange $item 1 end]

    # Convert a keyed list to a dict, then to a csv row. We can then 
    # display the row or to write it to a file.
    set rec [keyedlist2dict item]

    # Display it
    foreach {key value} $rec { ;# TCL 8.4
        puts "$key: $value"
    }   
    puts ""
}   

运行脚本

tclsh myscript.tcl

输出

NAME: name of this token
GROUPS: Group 1
VALUE: value
REPEATING MAX: 3
REPEATING TIME: nmin 30
WINDOW: */*/*/*
ACTION EXEC: code to run here

NAME: name of next token
GROUPS: Group 1
VALUE: value
WINDOW: 0/0:30-2:00,3:30-7:30/*/*
HOST: localhost
email FROM: cloverleaf@healthvision.com
email TO: hardej@mmc.org
email SUBJ: email subject test
email MSG: this is the email body

讨论

  • 我假设你的数据是data.txt
  • 这里的主力是keyedlist2dict,我在其中获取一个键控列表并将其展平以成为字典。
    • 在此过程中,如果值不是嵌套键列表,我只需将键和值附加到字典
    • 如果该值确实是一个嵌套键列表,那么我递归调用keyedlist2dict
    • 看看输出,你会看到我是如何形成新键的
  • 此脚本需要 TCL 8.5 或更高版本

更新

我对标记为TCL 8.4的两行进行了更改。该脚本现在应该可以在 TCL 8.4 系统上运行。

于 2013-12-25T08:27:01.117 回答
1

这看起来像一个 TclX 键控列表的列表,这是早期尝试做现代 Tcl 对字典所做的事情。键控列表嵌套得很好——那是一棵树,而不是一个表——所以映射到 CSV 不会最有效,但它们的语法是这样的,处理它们的最简单方法是使用 TclX 代码。

预赛:

package require TclX
package require csv;        # From Tcllib

列出我们将感兴趣的列。注意.名称的分隔位。

set columns {
    TOKEN.NAME TOKEN.GROUPS TOKEN.VALUE TOKEN.REPEATING.MAX TOKEN.REPEATING.TIME
    TOKEN.WINDOW TOKEN.HOST TOKEN.ACTION.EXEC TOKEN.ACTION.email.FROM
    TOKEN.ACTION.email.TO TOKEN.ACTION.email.SUBJ TOKEN.ACTION.email.MSG
}
# Optionally, put a header row in:
puts [csv::join $columns]

将真实数据加载到 Tcl 中:

set f [open "thefile.dta"]
set data [read $f]
close $f

遍历列表,提取信息,并以 CSV 格式发送到标准输出:

foreach item $data {
    # Ugly hack to munge data into real TclX format
    set item [list [list [lindex $item 0] [lrange $item 1 end]]]
    set row {}
    foreach label $columns {
        if {![keylget item $label value]} {set value ""}
        lappend row $value
    }
    puts [csv::join $row]
}

或类似的东西。

于 2013-12-25T16:50:02.640 回答
1

我意识到此时这已经几个月了,但是我看到您正在尝试解析 Cloverleaf 配置文件(这就是我自己偶然发现的方式)。

对于尝试做类似事情的任何其他人,Cloverleaf 提供的实际上有可用于处理此问题的库,尽管文档中的任何地方都没有提到它们。

查看 $HCIROOT/tcl/lib/cloverleaf。处理警报配置看起来像是在 configIO.tlib 中。NetConfig 的东西在 nci.tlib 和 netData.tlib 中。

于 2014-03-27T15:04:31.620 回答
0

您可以将数据视为普通列表并逐行读取。该info complete命令在这里有帮助:

set fh [open your.file r]
while {[gets $fh line] != -1} {
    append kl $line
    if {[info complete $kl]} {
        lappend lists $kl
        set kl ""
    }
}
close $fh
puts [llength $lists]                ;# 2
puts [llength [lindex $lists 0]]     ;# 1
puts [llength [lindex $lists 0 0]]   ;# 7
puts $lists

{{TOKEN { NAME {此令牌的名称} } { GROUPS {Group 1} } { VALUE value } { REPEATING { { MAX 3 } { TIME {nmin 30} } } } { WINDOW / /*/* } { ACTION { { EXEC {要在此处运行的代码} } }}} {{TOKEN { NAME {下一个令牌的名称}} { GROUPS {Group 1} } { VALUE value } { WINDOW 0/0:30-2:00,3: 30-7:30/ / } { HOST {localhost} } { ACTION { { email { { FROM cloverleaf@healthvision.com } { TO me@xxxx.org } { SUBJ {email subject test} } { MSG {这是电子邮件正文} } } } } }}}

于 2013-12-27T16:35:54.753 回答