XML Inspect

MJ The following code defines an xml::inspect command which will create a Tk window with an XML document. The XML document can be drilled down into with the tree view or a specific view can be created by putting an xpath expression in the entry field at the bottom.

#!/usr/bin/env wish

namespace eval xml {
    proc inspect {xml title} {
      package require Tk
      package require tdom

      # parse XML
      set doc [dom parse $xml]
      set nsprefixlist {}
      foreach {item} [$doc selectNodes {//namespace::*}] {
        lassign $item fullprefix uri
        lassign [split $fullprefix :] _ prefix
        lappend nsprefixlist $prefix $uri
      }

      # build ui
      set tl [toplevel .$doc]
      wm protocol $tl WM_DELETE_WINDOW [namespace code [list cleanup $doc $tl]]
      ttk::panedwindow $tl.pane -orient horizontal
      ttk::treeview $tl.tv -selectmode extended -show tree
      bind $tl.tv <<TreeviewSelect>> [namespace code [list updateText $tl.txt %W]]
      text $tl.txt
      fillTree $tl.tv [list [$doc documentElement]]
      $tl.pane add $tl.tv -weight 1
      $tl.pane add $tl.txt -weight 2
      entry $tl.entry
      pack $tl.pane -expand 1 -fill both
      pack $tl.entry -expand 0 -fill x
      bind $tl.entry <Return> [namespace code [list evaluateXPath $doc $nsprefixlist %W $tl.tv]]

      if {$title ne {}} {
        wm title $tl $title
      }

      return $tl

    }

    proc evaluateXPath {doc nsprefixlist xpathWidget tv} {
      set xpath [$xpathWidget get]
      if {$xpath eq {} } {
        set nodes [list [$doc documentElement]]
      } else {
        set nodes [$doc selectNodes -namespaces $nsprefixlist $xpath]
      }
      fillTree $tv $nodes
    }

    proc cleanup {doc tl} {
      $doc delete
      destroy $tl
    }

    proc fillTree {tv nodes} {
      # puts stderr "Adding [llength $nodes] nodes"
      foreach item [$tv children {}] {
        $tv detach $item
      }
      $tv selection remove [$tv selection]
      if {[catch {addNodes $tv {} $nodes } result]} {
      # Just add the text of the XPath result if it's not a list of nodes.
      # Call it in a callback because the clearing of the selection will clear the txt view.
        puts stderr $result
        after 0 [list [winfo parent $tv].txt insert end [join $nodes \n]]
      }
    }

    proc updateText {txt lv} {
      $txt delete 1.0 end
      foreach node [$lv selection] {
        $txt insert end [$node asXML]
        $txt insert end \n
      }

    }

    proc addNodes {tv parent nodes} {
      set index 0
      # puts stderr $parent-$nodes
      foreach node $nodes {
        set name [$node nodeName]
        if {$name eq "#text"} {
          continue
        }
        if [$tv exists $node] {
          $tv move $node $parent $index
        } else {
          $tv insert $parent $index -id $node -text $name
        }
        set children [$node childNodes]
        # puts stderr "Children -> $children"
        if {$children ne {}} {
          addNodes $tv $node $children
        }
        incr index
      }
    }
  }

# Example usage to open an XML file

set file [lindex $argv 0]


set f [open $file]
set xml [read $f]
close $f
xml::inspect $xml $file