使用expat在TCL中解析XML的多个子节点

时间:2016-05-11 00:38:23

标签: xml tcl

我在代码的底部提供了一个简短的XML文档,这正是我需要解析的内容,除了10个参数,以及大约10,000" cmds"。我正在解析文档,并将其写入.txt文件。我将节点的值打印到.txt中:

CMD_ID     Param_1_Name     Param_1_Value     Param_N_Name     Param_N_Value ...

脚本似乎只打印最后一个参数名称和值,而不是每个。我假设需要实现一个foreach循环,但我已经失去了它应该在哪里。这是我的TCL脚本:

t1 = getTime();
for(int i = 0; i < 9999999; i++)
{}
t2 = getTime();

输出结果为:

CMD_ID_1     Param_4      ON/OFF
CMD_ID_2     Param_4      ON/OFF

2 个答案:

答案 0 :(得分:0)

您每次都会在以下代码中覆盖数组

# Here, you are overwriting the 'type' variable index of array
switch -- $type {
        id - name - value {set g($type) $str}
}

因此保留了最后的条目。

相反,您可以维护一个数组,其中索引以命令名为前缀,如

CMD_ID_1,name
CMD_ID_1,value
.
.
.
CMD_ID_N,name
CMD_ID_N,value

对于每个索引,追加名称和值......比如,

::g(CMD_ID_1,name)  = Param_1 Param_2 Param_3 Param_4
::g(CMD_ID_1,value) = ON/OFF ON/OFF ON/OFF ON/OFF
::g(CMD_ID_2,name)  = Param_1 Param_2 Param_3 Param_4
::g(CMD_ID_2,value) = ON/OFF ON/OFF ON/OFF ON/OFF

您应该做的最后一件事是跟踪命令名称。所以,只需要一个变量来保存命令的名称,你就可以使用变量的前缀循环数组。

<强> extractXMLInfo.tcl

package require tdom
 proc parse xml {
    set ::S {};                 # Global stack ::S maintained to track tag hierarchy
    set p [expat -elementstartcommand els \
                 -characterdatacommand  ch \
                 -elementendcommand  ele ]
    if [catch {$p parse $xml} res] {
        puts "Error: $res" ;            # Error catch and put   
    }
 }
#---- Callbacks for start, end, character data
 proc els {name atts} {
    lappend ::S $name ;             # PUSH - els pushes current tag name
    # No need of the below statement - Dinesh 
    #if {$name eq "cmd"} {array unset ::g};  # When cmd element ends, g reset
 }
 proc ele name {
    global g
    set ::S [lrange $::S 0 end-1] ;     # POP - ele pops current tag name

    # No need of the below statement - Dinesh 
    # if {$name eq "cmd"} {
            # puts $g(id)\t$g(name)\t$g(value)
    # }
 }
 proc ch str { 
    global g
    set type [lindex $::S end]
    set current_cmd_name {}
    switch -- $type {
        id {
            if {[lsearch $::cmd_id $str]==-1} {
                lappend ::cmd_id $str
            }
        }
        name - 
        value {
            set current_cmd_name [lindex $::cmd_id end]
            if {$current_cmd_name eq {}} {
                puts "ERROR : No command name found"
            } else {
                lappend g($current_cmd_name,$type) $str
            }
        }
    }
 }

# Variable to track the command's names.
set ::cmd_id {}
array set ::g {}
 parse "
<cmds>
    <cmd>
        <id>CMD_ID_1</id>
        <params>
            <param>
                <name>Param_1</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_2</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_3</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_4</name>
                <value>ON/OFF</value>
            </param>
        </params>
    </cmd>
        <cmd>
        <id>CMD_ID_2</id>
        <params>
            <param>
                <name>Param_1</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_2</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_3</name>
                <value>ON/OFF</value>
            </param>
            <param>
                <name>Param_4</name>
                <value>ON/OFF</value>
            </param>
        </params>
    </cmd>
</cmds>"
parray ::g
puts "cmd_ids : $::cmd_id"
# Looping through the commands and printing the 
# name and value pairs
foreach cmd $::cmd_id {
    puts =========================================
    puts "Command : $cmd"
    foreach name $::g($cmd,name) value $::g($cmd,value) {
        puts "$name = $value"
    }
}

输出

::g(CMD_ID_1,name)  = Param_1 Param_2 Param_3 Param_4
::g(CMD_ID_1,value) = ON/OFF ON/OFF ON/OFF ON/OFF
::g(CMD_ID_2,name)  = Param_1 Param_2 Param_3 Param_4
::g(CMD_ID_2,value) = ON/OFF ON/OFF ON/OFF ON/OFF
cmd_ids : CMD_ID_1 CMD_ID_2
=========================================
Command : CMD_ID_1
Param_1 = ON/OFF
Param_2 = ON/OFF
Param_3 = ON/OFF
Param_4 = ON/OFF
=========================================
Command : CMD_ID_2
Param_1 = ON/OFF
Param_2 = ON/OFF
Param_3 = ON/OFF
Param_4 = ON/OFF

答案 1 :(得分:0)

一种可能的方式:

ele内的第20行,替换

puts $g(id)\t$g(name)\t$g(value)

    foreach name $g(name) value $g(value) {
        puts $g(id)\t$name\t$value
    }

ch内的第27行,替换

    id - name - value {set g($type) $str}

    id - name - value {lappend g($type) $str}

set命令会覆盖之前的值,lappend会收集它们。然后,您只需要迭代这些值以将其打印出来。

<强>附录

以下代码在某种程度上简化了您的逻辑,至少适用于您的测试数据:

proc parse xml {
    global data
    set p [expat -characterdatacommand ch -elementendcommand ele]
    $p parse $xml
    dict for {id values} $data {
        foreach {name value} $values {
            puts $id\t$name\t$value
        }
    }
}

proc ele name {
    global data idx str
    switch $name {
        id {
            set idx $str
        }
        name -
        value {
            dict lappend data $idx $str
        }
    }
}

proc ch data {
    global str
    set str $data
}

(请注意,此代码取决于始终位于name元素之前的value元素。它非常可能,但需要更多参与,以使代码独立于此。 )

附录2

全球比赛很糟糕。面向对象的解决方案更整洁:

package require tdom

oo::class create ParserHelper {
    variable p str idx data
    constructor args {
        set data {}
        set p [expat \
            -characterdatacommand [namespace code {my characterdata}] \
            -elementendcommand [namespace code {my elementend}]]
    }
    destructor {
        catch {$p free}
    }
    method parse xml {
        $p reset
        $p parse $xml
    }
    method characterdata s {
        set str $s
    }
    method elementend {s args} {
        switch $s {
            id {
                set idx $str
            }
            name -
            value {
                dict lappend data $idx $str
            }
        }
    }
    method puts {} {
        dict for {id values} $data {
            foreach {name value} $values {
                puts $id\t$name\t$value
            }
        }
    }
}

ParserHelper create ph
ph parse $xml
ph puts

我现在停止修补它......

文档:dictforeachglobalmynamespaceoo::classoo::define,{{3 }},procputsset