source: trunk/lang/tcl/scripts/library.tcl @ 2080

Last change on this file since 2080 was 2080, checked in by mmc, 14 years ago

Part 1 of a major reorganization of content. Moving "instant" to "builder"
and setting up "builder" more like the "gui" part as a package. Moving the
Rappture::object stuff from the builder into the main installation, so it
can be shared by the tester as well. Moving "driver" into gui/scripts
where it belongs. Creating a new "launcher.tcl" script that decides
which of the three parts to launch based on command line options. Still
need to sort out the Makefiles to get this all right...

File size: 34.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: library - provides access to the XML library
3#
4#  These routines make it easy to load the XML description for a
5#  series of tool parameters and browse through the results.
6# ======================================================================
7#  AUTHOR:  Michael McLennan, Purdue University
8#  Copyright (c) 2004-2005  Purdue Research Foundation
9#
10#  See the file "license.terms" for information on usage and
11#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12# ======================================================================
13package require tdom
14package require Itcl
15
16namespace eval Rappture {
17    variable stdlib ""
18}
19
20# automatically load all Rappture object types
21if {[catch {Rappture::objects::init} err]} {
22    puts stderr "Error loading object definitions:\n$err"
23    exit 1
24}
25
26# ----------------------------------------------------------------------
27# USAGE: library <file>
28# USAGE: library standard
29# USAGE: library isvalid <object>
30#
31# Used to open a <file> containing an XML description of tool
32# parameters.  Loads the file and returns the name of the LibraryObj
33# file that represents it.
34#
35# If you use the word "standard" in place of the file name, this
36# function returns the standard Rappture library object, which
37# contains material definitions.
38#
39# The isvalid operation checks an <object> to see if it is a valid
40# library object.  Returns 1 if so, and 0 otherwise.
41# ----------------------------------------------------------------------
42proc Rappture::library {args} {
43    # handle the isvalid operation...
44    if {[llength $args] > 1 && [lindex $args 0] == "isvalid"} {
45        if {[llength $args] != 2} {
46            error "wrong # args: should be \"library isvalid object\""
47        }
48        set obj [lindex $args 1]
49        #
50        # BE CAREFUL with the object test:
51        # The command should look like a LibraryObj formed by #auto.
52        # We want to avoid things like "split" or "set", which are
53        # valid Tcl commands but won't respond well to isa.
54        #
55        if {[regexp {libraryObj[0-9]+$} $obj]
56              && [catch {$obj isa ::Rappture::LibraryObj} valid] == 0
57              && $valid} {
58            return 1
59        }
60        return 0
61    }
62
63    if {[llength $args] != 1} {
64        error "wrong # args: should be \"library file\" or \"library isvalid object\""
65    }
66    set fname [lindex $args 0]
67
68    if {$fname == "standard"} {
69        variable stdlib
70        if {$stdlib != ""} {
71            return $stdlib
72        }
73        set fname [file join $Rappture::installdir lib library.xml]
74
75        set fid [::open $fname r]
76        set info [read $fid]
77        close $fid
78
79        set stdlib [Rappture::LibraryObj ::#auto $info]
80        return $stdlib
81    }
82
83    if {[regexp {^<\?[Xx][Mm][Ll]} $fname]} {
84        set info $fname
85    } else {
86        # otherwise, try to open the file and create its LibraryObj
87        set fid [::open $fname r]
88        set info [read $fid]
89        close $fid
90    }
91
92    set obj [Rappture::LibraryObj ::#auto $info]
93    return $obj
94}
95
96# ----------------------------------------------------------------------
97# USAGE: entities ?-as <fval>? <object> <path>
98#
99# Used to sift through an XML <object> for "entities" within the
100# Rappture description.  Entities are things like strings, numbers,
101# etc., which show up in the GUI as controls.
102#
103# Returns a list of all entities found beneath <path>.
104#
105# By default, this method returns the component name "type(id)".
106# This is changed by setting the -as argument to "id" (for name
107# of the tail element), to "type" (for the type of the tail element),
108# to "object" (for an object representing the DOM node referenced by
109# the path.
110# ----------------------------------------------------------------------
111proc Rappture::entities {args} {
112    array set params {
113        -as component
114    }
115    while {[llength $args] > 1} {
116        set first [lindex $args 0]
117        if {[string index $first 0] == "-"} {
118            set choices [array names params]
119            if {[lsearch $choices $first] < 0} {
120                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
121            }
122            set params($first) [lindex $args 1]
123            set args [lrange $args 2 end]
124        } else {
125            break
126        }
127    }
128    if {[llength $args] > 2} {
129        error "wrong # args: should be \"entities ?-as fval? obj ?path?\""
130    }
131    set xmlobj [lindex $args 0]
132    set path [lindex $args 1]
133
134    set rlist ""
135    lappend queue $path
136    while {[llength $queue] > 0} {
137        set path [lindex $queue 0]
138        set queue [lrange $queue 1 end]
139
140        foreach cpath [$xmlobj children -as path $path] {
141            switch -- [$xmlobj element -as type $cpath] {
142                group - phase {
143                    lappend queue $cpath
144                }
145                structure {
146                    # add this to the return list with the right flavor
147                    if {$params(-as) == "component"} {
148                        lappend rlist $cpath
149                    } else {
150                        lappend rlist [$xmlobj element -as $params(-as) $cpath]
151                    }
152
153                    if {[$xmlobj element $cpath.current.parameters] != ""} {
154                        lappend queue $cpath.current.parameters
155                    }
156                }
157                drawing {
158                    # add this to the return list with the right flavor
159                    if {$params(-as) == "component"} {
160                        lappend rlist $cpath
161                    } else {
162                        lappend rlist [$xmlobj element -as $params(-as) $cpath]
163                    }
164
165                    foreach child [$xmlobj children $cpath.current] {
166                        if {[string match about* $child]} {
167                            continue
168                        }
169                        if {[$xmlobj element $cpath.current.$child.parameters] != ""} {
170                            lappend queue $cpath.current.$child.parameters
171                        }
172                    }
173                }
174                default {
175                    # add this to the return list with the right flavor
176                    if {$params(-as) == "component"} {
177                        lappend rlist $cpath
178                    } else {
179                        lappend rlist [$xmlobj element -as $params(-as) $cpath]
180                    }
181
182                    # if this element has embedded groups, add them to the queue
183                    foreach ccpath [$xmlobj children -as path $cpath] {
184                        set cctype [$xmlobj element -as type $ccpath]
185                        if {$cctype == "group" || $cctype == "phase"} {
186                            lappend queue $ccpath
187                        }
188                    }
189                }
190            }
191        }
192    }
193    return $rlist
194}
195
196# ----------------------------------------------------------------------
197itcl::class Rappture::LibraryObj {
198    constructor {info} { # defined below }
199    destructor { # defined below }
200
201    public method element {args}
202    public method parent {args}
203    public method children {args}
204    public method get {args}
205    public method put {args}
206    public method copy {path from args}
207    public method remove {{path ""}}
208    public method xml {{path ""}}
209
210    public method diff {libobj}
211    public proc value {libobj path}
212
213    public proc path2list {path}
214    protected method find {path}
215    protected method node2name {node}
216    protected method node2comp {node}
217    protected method node2path {node}
218    protected method childnodes {node type}
219
220    private variable _root 0       ;# non-zero => this obj owns document
221    private variable _document ""  ;# XML DOM tree
222    private variable _node ""      ;# node within
223}
224
225# ----------------------------------------------------------------------
226# CONSTRUCTOR
227# ----------------------------------------------------------------------
228itcl::body Rappture::LibraryObj::constructor {info} {
229    if {[regexp {<?[Xx][Mm][Ll]} $info]} {
230        set _root 1
231        set _document [dom parse $info]
232        set _node [$_document documentElement]
233    } elseif {[regexp {^domNode} $info]} {
234        set _root 0
235        set _document [$info ownerDocument]
236        set _node $info
237    } else {
238        error "bad info: should be XML text or DOM node"
239    }
240}
241
242# ----------------------------------------------------------------------
243# DESTRUCTOR
244# ----------------------------------------------------------------------
245itcl::body Rappture::LibraryObj::destructor {} {
246    if {$_root && $_document != ""} {
247        $_document delete
248    }
249}
250
251# ----------------------------------------------------------------------
252# USAGE: element ?-as <fval>? ?<path>?
253#
254# Clients use this to query a particular element within the entire
255# data structure.  The path is a string of the form
256# "structure.box(source).corner".  This example represents the tag
257# <corner> within a tag <box id="source"> within a tag <structure>,
258# which must be found at the top level within this document.
259#
260# By default, this method returns the component name "type(id)".
261# This is changed by setting the -as argument to "id" (for name
262# of the tail element), to "type" (for the type of the tail element),
263# to "object" (for an object representing the DOM node referenced by
264# the path).
265# ----------------------------------------------------------------------
266itcl::body Rappture::LibraryObj::element {args} {
267    array set params {
268        -as component
269    }
270    while {[llength $args] > 1} {
271        set first [lindex $args 0]
272        if {[string index $first 0] == "-"} {
273            set choices [array names params]
274            if {[lsearch $choices $first] < 0} {
275                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
276            }
277            set params($first) [lindex $args 1]
278            set args [lrange $args 2 end]
279        } else {
280            break
281        }
282    }
283    if {[llength $args] > 1} {
284        error "wrong # args: should be \"element ?-as fval? ?path?\""
285    }
286    set path [lindex $args 0]
287
288    set node [find $path]
289    if {$node == ""} {
290        return ""
291    }
292
293    switch -- $params(-as) {
294      object {
295          return [::Rappture::LibraryObj ::#auto $node]
296      }
297      component {
298          return [node2comp $node]
299      }
300      id {
301          return [node2name $node]
302      }
303      path {
304          return [node2path $node]
305      }
306      type {
307          return [$node nodeName]
308      }
309      default {
310          error "bad flavor \"$params(-as)\": should be component, id, object, path, type"
311      }
312    }
313}
314
315# ----------------------------------------------------------------------
316# USAGE: parent ?-as <fval>? ?<path>?
317#
318# Clients use this to query the parent of a particular element.
319# This is just like the "element" method, but it returns the parent
320# of the element instead of the element itself.
321#
322# By default, this method returns a list of component names "type(id)".
323# This is changed by setting the -as argument to "id" (for tail
324# names of all children), to "type" (for the types of all children),
325# to "object" (for a list of objects representing the DOM nodes for
326# all children).
327# ----------------------------------------------------------------------
328itcl::body Rappture::LibraryObj::parent {args} {
329    array set params {
330        -as component
331    }
332    while {[llength $args] > 1} {
333        set first [lindex $args 0]
334        if {[string index $first 0] == "-"} {
335            set choices [array names params]
336            if {[lsearch $choices $first] < 0} {
337                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
338            }
339            set params($first) [lindex $args 1]
340            set args [lrange $args 2 end]
341        } else {
342            break
343        }
344    }
345    if {[llength $args] > 1} {
346        error "wrong # args: should be \"parent ?-as fval? ?path?\""
347    }
348    set path [lindex $args 0]
349
350    set node [find $path]
351    if {$node == ""} {
352        return ""
353    }
354    set node [$node parentNode]
355    if {$node == ""} {
356        return ""
357    }
358
359    switch -- $params(-as) {
360      object {
361          return [::Rappture::LibraryObj ::#auto $node]
362      }
363      component {
364          return [node2comp $node]
365      }
366      id {
367          return [node2name $node]
368      }
369      path {
370          return [node2path $node]
371      }
372      type {
373          return [$node nodeName]
374      }
375      default {
376          error "bad flavor \"$params(-as)\": should be component, id, object, path, type"
377      }
378    }
379}
380
381# ----------------------------------------------------------------------
382# USAGE: children ?-as <fval>? ?-type <name>? ?<path>?
383#
384# Clients use this to query the children of a particular element
385# within the entire data structure.  This is just like the "element"
386# method, but it returns the children of the element instead of the
387# element itself.  If the optional -type argument is specified, then
388# the return list is restricted to children of the specified type.
389#
390# By default, this method returns a list of component names "type(id)".
391# This is changed by setting the -as argument to "id" (for tail
392# names of all children), to "type" (for the types of all children),
393# to "object" (for a list of objects representing the DOM nodes for
394# all children).
395# ----------------------------------------------------------------------
396itcl::body Rappture::LibraryObj::children {args} {
397    array set params {
398        -as component
399        -type ""
400    }
401    while {[llength $args] > 1} {
402        set first [lindex $args 0]
403        if {[string index $first 0] == "-"} {
404            set choices [array names params]
405            if {[lsearch $choices $first] < 0} {
406                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
407            }
408            set params($first) [lindex $args 1]
409            set args [lrange $args 2 end]
410        } else {
411            break
412        }
413    }
414    if {[llength $args] > 1} {
415        error "wrong # args: should be \"children ?-as fval? ?-type name? ?path?\""
416    }
417    set path [lindex $args 0]
418    set node [find $path]
419    if {$node == ""} {
420        return ""
421    }
422
423    set nlist ""
424    foreach n [$node childNodes] {
425        set type [$n nodeName]
426        if {[regexp {^#} $type]} {
427            continue
428        }
429        if {$params(-type) != "" && $params(-type) != $type} {
430            continue
431        }
432        lappend nlist $n
433    }
434
435    set rlist ""
436    switch -- $params(-as) {
437      object {
438          foreach n $nlist {
439              lappend rlist [::Rappture::LibraryObj ::#auto $n]
440          }
441      }
442      component {
443          foreach n $nlist {
444              lappend rlist [node2comp $n]
445          }
446      }
447      id {
448          foreach n $nlist {
449              lappend rlist [node2name $n]
450          }
451      }
452      path {
453          foreach n $nlist {
454              lappend rlist [node2path $n]
455          }
456      }
457      type {
458          foreach n $nlist {
459              lappend rlist [$n nodeName]
460          }
461      }
462      default {
463          error "bad flavor \"$params(-as)\": should be component, id, object, path, type"
464      }
465    }
466    return $rlist
467}
468
469# ----------------------------------------------------------------------
470# USAGE: get ?-decode yes? ?<path>?
471#
472# Clients use this to query the value of a node. Clients can specify
473# if they want the data to be automatically decoded or no using the
474# -decode flag. This is useful for situations where you want to keep
475# the data encoded to pass to another system, like dx data in fields
476# sending data to nanovis. If the path is not
477# specified, it returns the value associated with the root node.
478# Otherwise, it returns the value for the element specified by the
479# path.
480# ----------------------------------------------------------------------
481itcl::body Rappture::LibraryObj::get {args} {
482    array set params {
483        -decode yes
484    }
485    while {[llength $args] > 0} {
486        set first [lindex $args 0]
487        if {[string index $first 0] == "-"} {
488            set choices [array names params]
489            if {[lsearch $choices $first] < 0} {
490                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
491            }
492            set params($first) [lindex $args 1]
493            set args [lrange $args 2 end]
494        } else {
495            break
496        }
497    }
498    if {[llength $args] > 1} {
499        error "wrong # args: should be \"get ?-decode yes? ?path?\""
500    }
501    if {[llength $args] == 1} {
502        set path [lindex $args 0]
503    } else {
504        set path ""
505    }
506
507    set node [find $path]
508    if {$node == ""} {
509        return ""
510    }
511    if {$params(-decode) == "yes"} {
512        return [Rappture::encoding::decode -- [string trim [$node text]]]
513    } else {
514        return [string trim [$node text]]
515    }
516}
517
518# ----------------------------------------------------------------------
519# USAGE: put ?-append yes? ?-id num? ?-type string|file? ?-compress no? ?<path>? <string>
520#
521# Clients use this to set the value of a node.  If the path is not
522# specified, it sets the value for the root node.  Otherwise, it sets
523# the value for the element specified by the path.  If the value is a
524# string, then it is treated as the text within the tag at the tail
525# of the path.  If it is a DOM node or a library, then it is inserted
526# into the tree at the specified path.
527#
528# If the optional id is specified, then it sets the identifier for
529# the tag at the tail of the path.  If the optional append flag is
530# specified, then the value is appended to the current value.
531# Otherwise, the value replaces the current value.
532# ----------------------------------------------------------------------
533itcl::body Rappture::LibraryObj::put {args} {
534    array set params {
535        -id ""
536        -append no
537        -type string
538        -compress no
539    }
540    while {[llength $args] > 1} {
541        set first [lindex $args 0]
542        if {[string index $first 0] == "-"} {
543            set choices [array names params]
544            if {[lsearch $choices $first] < 0} {
545                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
546            }
547            set params($first) [lindex $args 1]
548            set args [lrange $args 2 end]
549        } else {
550            break
551        }
552    }
553    if {[llength $args] < 1 || [llength $args] > 2} {
554        error "wrong # args: should be \"put ?-append bval? ?-id num? ?-type string|file? ?-compress bval? ?path? string\""
555    }
556    if {[llength $args] == 2} {
557        set path [lindex $args 0]
558        set str [lindex $args 1]
559    } else {
560        set path ""
561        set str [lindex $args 0]
562    }
563
564    if {$params(-type) == "file"} {
565        set fileName $str
566        set fid [open $fileName r]
567        fconfigure $fid -translation binary -encoding binary
568        set str [read $fid]
569        close $fid
570    }
571
572    if {$params(-compress) || [Rappture::encoding::is binary $str]} {
573        set str [Rappture::encoding::encode -- $str]
574    }
575
576    set node [find -create $path]
577
578    #
579    # Clean up any nodes that don't belong.  If we're appending
580    # the value, then clean up only child <tag> nodes.  Otherwise,
581    # clean up all nodes.
582    #
583    set nlist ""
584    if {$params(-append)} {
585        foreach n [$node childNodes] {
586            if {[$n nodeType] != "TEXT_NODE"} {
587                lappend nlist $n
588            }
589        }
590    } else {
591        set nlist [$node childNodes]
592    }
593    foreach n $nlist {
594        $n delete
595    }
596
597    if {[Rappture::library isvalid $str]} {
598        foreach n [[$str info variable _node -value] childNodes] {
599            $node appendXML [$n asXML]
600        }
601    } else {
602        set n [$_document createText $str]
603        $node appendChild $n
604        if {"" != $params(-id)} {
605            $node setAttribute id $params(-id)
606        }
607    }
608    return ""
609}
610
611# ----------------------------------------------------------------------
612# USAGE: copy <path> from ?<xmlobj>? <path>
613#
614# Clients use this to copy the value from one xmlobj/path to another.
615# If the <xmlobj> is not specified, it is assumed to be the same as
616# the current object.
617# ----------------------------------------------------------------------
618itcl::body Rappture::LibraryObj::copy {path from args} {
619    if {[llength $args] == 1} {
620        set xmlobj $this
621        set fpath [lindex $args 0]
622    } elseif {[llength $args] == 2} {
623        set xmlobj [lindex $args 0]
624        set fpath [lindex $args 1]
625    } else {
626        error "wrong # args: should be \"copy path from ?xmlobj? path\""
627    }
628    if {$from != "from"} {
629        error "bad syntax: should be \"copy path from ?xmlobj? path\""
630    }
631
632    if {[llength [$xmlobj children $fpath]] == 0} {
633        set val [$xmlobj get $fpath]
634        put $path $val
635    } else {
636        set obj [$xmlobj element -as object $fpath]
637        put $path $obj
638        itcl::delete object $obj
639    }
640}
641
642# ----------------------------------------------------------------------
643# USAGE: remove ?<path>?
644#
645# Clients use this to remove the specified node.  Removes the node
646# from the tree.
647# ----------------------------------------------------------------------
648itcl::body Rappture::LibraryObj::remove {{path ""}} {
649    set node [find $path]
650    if {$node != ""} {
651        $node delete
652    }
653}
654
655# ----------------------------------------------------------------------
656# USAGE: xml ?<path>?
657#
658# Returns a string representing the XML information for the information
659# in this library.
660# ----------------------------------------------------------------------
661itcl::body Rappture::LibraryObj::xml {{path ""}} {
662    if {"" != $path} {
663        set n [find $path]
664    } else {
665        set n $_node
666    }
667    return [$n asXML]
668}
669
670# ----------------------------------------------------------------------
671# USAGE: diff <libobj>
672#
673# Compares the entities in this object to those in another and
674# returns a list of differences.  The result is a list of the form:
675# {op1 path1 oldval1 newval1 ...} where each "op" is +/-/c for
676# added/subtracted/changed, "path" is the path within the library
677# that is different, and "oldval"/"newval" give the values for the
678# object at the path.
679# ----------------------------------------------------------------------
680itcl::body Rappture::LibraryObj::diff {libobj} {
681    set rlist ""
682
683    # query the values for all entities in both objects
684    set thisv [Rappture::entities $this input]
685    set otherv [Rappture::entities $libobj input]
686
687    # scan through values for this object, and compare against other one
688    foreach path $thisv {
689        set i [lsearch -exact $otherv $path]
690        if {$i < 0} {
691            foreach {raw norm} [value $this $path] break
692            lappend rlist - $path $raw ""
693        } else {
694            foreach {traw tnorm} [value $this $path] break
695            foreach {oraw onorm} [value $libobj $path] break
696            if {![string equal $tnorm $onorm]} {
697                lappend rlist c $path $traw $oraw
698            }
699            set otherv [lreplace $otherv $i $i]
700        }
701    }
702
703    # add any values left over in the other object
704    foreach path $otherv {
705        foreach {oraw onorm} [value $libobj $path] break
706        lappend rlist + $path "" $oraw
707    }
708    return $rlist
709}
710
711# ----------------------------------------------------------------------
712# USAGE: value <object> <path>
713#
714# Used to query the "value" associated with the <path> in an XML
715# <object>.  This is a little more complicated than the object's
716# "get" method.  It handles things like structures and values
717# with normalized units.
718#
719# Returns a list of two items:  {raw norm} where "raw" is the raw
720# value from the "get" method and "norm" is the normalized value
721# produced by this routine.  Example:  {300K 300}
722#
723# Right now, it is a handy little utility used by the "diff" method.
724# Eventually, it should be moved to a better object-oriented
725# implementation, where each Rappture type could overload the
726# various bits of processing below.  So we leave it as a "proc"
727# now instead of a method, since it should be deprecated soon.
728# ----------------------------------------------------------------------
729itcl::body Rappture::LibraryObj::value {libobj path} {
730    switch -- [$libobj element -as type $path] {
731        structure {
732            set raw $path
733            # try to find a label to represent the structure
734            set val [$libobj get $path.about.label]
735            if {"" == $val} {
736                set val [$libobj get $path.current.about.label]
737            }
738            if {"" == $val} {
739                if {[$libobj element $path.current] != ""} {
740                    set comps [$libobj children $path.current.components]
741                    set val "<structure> with [llength $comps] components"
742                } else {
743                    set val "<structure>"
744                }
745            }
746            return [list $raw $val]
747        }
748        number {
749            # get the usual value...
750            set raw ""
751            set val ""
752            if {"" != [$libobj element $path.current]} {
753                set raw [$libobj get $path.current]
754            } elseif {"" != [$libobj element $path.default]} {
755                set raw [$libobj get $path.default]
756            }
757            if {"" != $raw} {
758                set val $raw
759                # then normalize to default units
760                set units [$libobj get $path.units]
761                if {"" != $units} {
762                    set val [Rappture::Units::convert $val \
763                        -context $units -to $units -units off]
764                }
765            }
766            return [list $raw $val]
767        }
768    }
769
770    # for all other types, get the value (current, or maybe default)
771    set raw ""
772    if {"" != [$libobj element $path.current]} {
773        set raw [$libobj get $path.current]
774    } elseif {"" != [$libobj element $path.default]} {
775        set raw [$libobj get $path.default]
776    }
777    return [list $raw $raw]
778}
779
780# ----------------------------------------------------------------------
781# USAGE: find ?-create? <path>
782#
783# Used internally to find a particular element within the root node
784# according to the path, which is a string of the form
785# "typeNN(id).typeNN(id). ...", where each "type" is a tag <type>;
786# if the optional NN is specified, it indicates an index for the
787# <type> tag within its parent; if the optional (id) part is included,
788# it indicates a tag of the form <type id="id">.
789#
790# By default, it looks for an element along the path and returns None
791# if not found.  If the create flag is set, it creates various elements
792# along the path as it goes.  This is useful for "put" operations.
793#
794# If you include "#" instead of a specific number, a node will be
795# created automatically with a new number.  For example, the path
796# "foo.bar#" called the first time will create "foo.bar", the second
797# time "foo.bar1", the third time "foo.bar2" and so forth.
798#
799# Returns an object representing the element indicated by the path,
800# or "" if the path is not found.
801# ----------------------------------------------------------------------
802itcl::body Rappture::LibraryObj::find {args} {
803    set create 0
804    while {[llength $args] > 1} {
805        set first [lindex $args 0]
806        set args [lrange $args 1 end]
807        if {$first == "-create"} {
808            set create 1
809        } else {
810            error "bad option \"$first\": should be -create"
811        }
812    }
813    if {[llength $args] != 1} {
814        error "wrong # args: should be \"find ?-create? path\""
815    }
816    set path [lindex $args 0]
817
818    if {$path == ""} {
819        return $_node
820    }
821    set path [path2list $path]
822
823    #
824    # Follow the given path and look for all of the parts.
825    #
826    set lastnode $_node
827    set node $lastnode
828    foreach part $path {
829        if {![regexp {^(([a-zA-Z0-9_]*[a-zA-Z_]+#?)([0-9]*))?(\((.*)\))?$} $part \
830               match dummy type index dummy name]} {
831            error "bad path component \"$part\""
832        }
833        #
834        # If the name is like "type2", then look for elements with
835        # the type name and return the one with the given index.
836        # If the name is like "type", then assume the index is 0.
837        #
838        if {$name == ""} {
839            if {$index == ""} {
840                set index 0
841            }
842            set nlist [childnodes $node $type]
843            set node [lindex $nlist $index]
844        } else {
845            #
846            # If the name is like "type(id)", then look for elements
847            # that match the type and see if one has the requested name.
848            # if the name is like "(id)", then look for any elements
849            # with the requested name.
850            #
851            if {$type != ""} {
852                set nlist [childnodes $node $type]
853            } else {
854                set nlist [$node childNodes]
855            }
856            set found 0
857            foreach n $nlist {
858                if {[catch {$n getAttribute id} tag]} { set tag "" }
859                if {$tag == $name} {
860                    set found 1
861                    break
862                }
863            }
864            set node [expr {($found) ? $n : ""}]
865        }
866
867        if {$node == ""} {
868            if {!$create} {
869                return ""
870            }
871
872            #
873            # If the "create" flag is set, then create a node
874            # with the specified "type(id)" and continue on.
875            # If the type is "type#", then create a node with
876            # an automatic number.
877            #
878            if {![regexp {^([^\(]+)\(([^\)]+)\)$} $part match type name]} {
879                set type $part
880                set name ""
881            }
882
883            if {[string match *# $type]} {
884                set type [string trimright $type #]
885                set node [$_document createElement $type]
886
887                # find the last node of same type and append there
888                set pos ""
889                foreach n [$lastnode childNodes] {
890                    if {[$n nodeName] == $type} {
891                        set pos $n
892                    }
893                }
894                if {$pos != ""} {
895                    set pos [$pos nextSibling]
896                }
897                if {$pos != ""} {
898                    $lastnode insertBefore $node $pos
899                } else {
900                    $lastnode appendChild $node
901                }
902            } else {
903                set node [$_document createElement $type]
904                $lastnode appendChild $node
905            }
906            if {"" != $name} {
907                $node setAttribute id $name
908            }
909        }
910        set lastnode $node
911    }
912    return $node
913}
914
915# ----------------------------------------------------------------------
916# USAGE: path2list <path>
917#
918# Converts a path of the form "foo(a).bar.baz" into a list of the
919# form "foo(a) bar baz".  This is a little more complicated than
920# splitting on the .'s, since the stuff in ()'s might have embedded
921# .'s.  Returns a proper Tcl list for all elements of the path.
922# ----------------------------------------------------------------------
923itcl::body Rappture::LibraryObj::path2list {path} {
924    #
925    # Normally, we just split on .'s within the path.  But there
926    # might be some .'s embedded within ()'s in the path.  Change
927    # any embedded .'s to an out-of-band character, then split on
928    # the .'s, and change the embedded .'s back.
929    #
930    if {[regexp {(\([^\)]*)\.([^\)]*\))} $path]} {
931        while {[regsub -all {(\([^\)]*)\.([^\)]*\))} $path "\\1\007\\2" path]} {
932            # keep fixing...
933        }
934    }
935    set path [split $path .]
936    regsub -all {\007} $path {.} path
937
938    return $path
939}
940
941# ----------------------------------------------------------------------
942# USAGE: node2name <node>
943#
944# Used internally to create a name for the specified node.  If the
945# node doesn't have a specific name ("id" attribute) then a name of
946# the form "type123" is constructed.
947# ----------------------------------------------------------------------
948itcl::body Rappture::LibraryObj::node2name {node} {
949    if {[catch {$node getAttribute id} name]} { set name "" }
950    if {$name == ""} {
951        set pnode [$node parentNode]
952        if {$pnode == ""} {
953            return ""
954        }
955        set type [$node nodeName]
956        set siblings [childnodes $pnode $type]
957        set index [lsearch $siblings $node]
958        if {$index == 0} {
959            set name $type
960        } else {
961            set name "$type$index"
962        }
963    }
964    return $name
965}
966
967# ----------------------------------------------------------------------
968# USAGE: node2comp <node>
969#
970# Used internally to create a path component name for the specified
971# node.  A path component name has the form "type(id)" or just
972# "type##" if the node doesn't have a name.  This name can be used
973# in a path to uniquely address the component.
974# ----------------------------------------------------------------------
975itcl::body Rappture::LibraryObj::node2comp {node} {
976    set type [$node nodeName]
977    if {[catch {$node getAttribute id} name]} { set name "" }
978    if {$name == ""} {
979        set pnode [$node parentNode]
980        if {$pnode == ""} {
981            return ""
982        }
983        set siblings [childnodes $pnode $type]
984        set index [lsearch $siblings $node]
985        if {$index == 0} {
986            set name $type
987        } else {
988            set name "$type$index"
989        }
990    } else {
991        set name "${type}($name)"
992    }
993    return $name
994}
995
996# ----------------------------------------------------------------------
997# USAGE: node2path <node>
998#
999# Used internally to create a full path name for the specified node.
1000# The path is relative to the current object, so it stops when the
1001# parent is the root node for this object.
1002# ----------------------------------------------------------------------
1003itcl::body Rappture::LibraryObj::node2path {node} {
1004    set path [node2comp $node]
1005    set node [$node parentNode]
1006    while {$node != "" && $node != $_node} {
1007        set path "[node2comp $node].$path"
1008        set node [$node parentNode]
1009    }
1010    return $path
1011}
1012
1013# ----------------------------------------------------------------------
1014# USAGE: childnodes <node> <type>
1015#
1016# Used internally to return a list of children for the given <node>
1017# that match a specified <type>.  Similar to XML getElementsByTagName,
1018# but returns only direct children of the <node>.
1019# ----------------------------------------------------------------------
1020itcl::body Rappture::LibraryObj::childnodes {node type} {
1021    set rlist ""
1022    foreach cnode [$node childNodes] {
1023        if {[$cnode nodeName] == $type} {
1024            lappend rlist $cnode
1025        }
1026    }
1027    return $rlist
1028}
Note: See TracBrowser for help on using the repository browser.