Search code examples
xmlparsingxpathtcltdom

TCL tDom Empty XML Tag


I'm using tDom to loop through some XML and pull out each element's text().

    set xml {
<systems>
 <object>
  <type>Hardware</type>
  <name>Server Name</name>
  <attributes>
   <vendor></vendor>
  </attributes>
 </object>
 <object>
  <type>Hardware</type>
  <name>Server Two Name</name>
  <attributes>
   <vendor></vendor>
  </attributes>
 </object>
</systems>
};

    set doc  [dom parse $xml]
    set root [$doc documentElement]

    set nodeList [$root selectNodes /systems/object]

    foreach node $nodeList {

     set nType  [$node selectNodes type/text()]
     set nName  [$node selectNodes name/text()]
     set nVendor [$node selectNodes attributes/vendor/text()]

     # Etc...
     puts "Type: " 
     puts [$nType data] 

     # Etc ..

     puts [$nVendor data]
    }

But when it tries to print out the Vendor, which is empty, it thows the error invalid command name "". How can I ignore this and just set $nVendor to an empty string?


Solution

  • The selectNodes method of a node returns a list of nodes that match your pattern. When you use the results directly as a command

    set nName  [$node selectNodes name/text()]
    puts [$nType data] 
    

    what you are really doing is taking advantage of the fact that a list of 1 item (the number of name items) is the same as one item. When there are no matching nodes, you get back an empty list

    set nVendor [$node selectNodes attributes/vendor/text()]  ;# -> {}
    

    and, when you call that, it's throwing an error because you're calling a command with the name {}.

    set nVendor [$node selectNodes attributes/vendor/text()]  ;# -> {}
    puts [$nVendor data]  ;# -> winds up calling
    {} data
    

    As noted by Hai Vu, you can test that there was a result by checking the result against "". A "more correct" way would probably be to check it against the empty list

    set nVendor [$node selectNodes attributes/vendor/text()]
    if {[llength $nVendor] == 1} {
        puts [$nVendor data]
    }
    

    or, to be even more complete (if you're not sure about the input XML)

    set nVendor [$node selectNodes attributes/vendor/text()]
    switch -exact -- [llength $nVendor] {
        0 { 
            # no nVendor, do nothing
        }
        1 {
            # 1 vendor, print it
            puts [$nVendor data]
        }
        default {
            # weird, we got more than one vendor node... throw an error
            error "More than one vendor node"
        }
    }