
/*
 *
 * bltTreeCmd.c --
 *
 * Copyright 1998-1999 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies or any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *	The "tree" data object was created by George A. Howlett.
 *      Massive modifications by Peter MacDonald
 *
 */

/*
  tree create t0 t1 t2
  tree names
  t0 destroy
     -or-
  tree destroy t0
  tree copy tree@node tree@node -recurse -tags

  tree move node after|before|into t2@node

  $t apply -recurse $root command arg arg			

  $t attach treename				

  $t children $n
  t0 copy node1 node2 node3 node4 node5 destName 
  $t delete $n...				
  $t depth $n
  $t dump $root
  $t dumpfile $root fileName
  $t dup $t2		
  $t find $root -name pat -name pattern
  $t firstchild $n
  $t get $n $key
  $t get $n $key(abc)
  $t index $n
  $t insert $parent $switches?
  $t isancestor $n1 $n2
  $t isbefore $n1 $n2
  $t isleaf $n
  $t lastchild $n
  $t move $n1 after|before|into $n2
  $t next $n
  $t nextsibling $n
  $t path $n1 $n2 $n3...
  $t parent $n
  $t previous $n
  $t prevsibling $n
  $t restore $root data -overwrite
  $t root ?$n?

  $t set $n $key $value ?$key $value?
  $t size $n
  $t slink $n $t2@$node				???
  $t sort -recurse $root		

  $t tag delete tag1 tag2 tag3...
  $t tag names
  $t tag nodes $tag
  $t tag set $n tag1 tag2 tag3...
  $t tag unset $n tag1 tag2 tag3...

  $t trace create $n $key how command		
  $t trace delete id1 id2 id3...
  $t trace names
  $t trace info $id

  $t unset $n key1 key2 key3...
  
  $t notify create -oncreate -ondelete -onmove command 
  $t notify create -oncreate -ondelete -onmove -onsort command arg arg arg 
  $t notify delete id1 id2 id3
  $t notify names
  $t notify info id

  for { set n [$t firstchild $node] } { $n >= 0 } { 
        set n [$t nextsibling $n] } {
  }
  foreach n [$t children $node] { 
	  
  }
  set n [$t next $node]
  set n [$t previous $node]

*/

#include <bltInt.h>

#ifndef NO_TREE

#include <bltTree.h>
#include <bltHash.h>
#include <bltList.h>
#include <bltVector.h>
#include "bltSwitch.h"
#include <ctype.h>

#define TREE_THREAD_KEY "BLT Tree Command Data"
#define TREE_MAGIC ((unsigned int) 0x46170277)

#ifndef WITH_SQLITE3
#define OMIT_SQLITE3_LOAD
#endif
enum TagTypes { TAG_TYPE_NONE, TAG_TYPE_ALL, TAG_TYPE_TAG, TAG_TYPE_LIST, TAG_TYPE_ROOTCHILDREN };

typedef struct {
    Blt_HashTable treeTable;	/* Hash table of trees keyed by address. */
    Tcl_Interp *interp;
} TreeCmdInterpData;

typedef struct {
    Tcl_Interp *interp;
    Tcl_Command cmdToken;	/* Token for tree's Tcl command. */
    Blt_Tree tree;		/* Token holding internal tree. */

    Blt_HashEntry *hashPtr;
    Blt_HashTable *tablePtr;

    TreeCmdInterpData *dataPtr;	/*  */

    int traceCounter;		/* Used to generate trace id strings.  */
    Blt_HashTable traceTable;	/* Table of active traces. Maps trace ids
				 * back to their TraceInfo records. */

    int notifyCounter;		/* Used to generate notify id strings. */
    Blt_HashTable notifyTable;	/* Table of event handlers. Maps notify ids
				 * back to their NotifyInfo records. */
    int oldLen;     /* Length of oldvalue before append/lappend. */
    int updTyp;     /* Type of update: 1=append, 2=lappend, 0=other. */
    int delete;
} TreeCmd;

typedef struct {
    TreeCmd *cmdPtr;
    Blt_TreeNode node;

    Blt_TreeTrace traceToken;

    char *withTag;		/* If non-NULL, the event or trace was
				 * specified with this tag.  This
				 * value is saved for informational
				 * purposes.  The tree's trace
				 * matching routines do the real
				 * checking, not the client's
				 * callback.  */

    char command[1];		/* Command prefix for the trace or notify
				 * Tcl callback.  Extra arguments will be
				 * appended to the end. Extra space will
				 * be allocated for the length of the string
				 */
} TraceInfo;

typedef struct {
    int init;
    int tagType;
    Blt_TreeNode root;
    Blt_HashSearch cursor;
    TreeCmd *cmdPtr;
    Tcl_Obj **objv, *objPtr;
    int objc;
    int idx;
    Blt_TreeNode node;
    Blt_TreeTagEntry *tPtr;
    int cnt;
    unsigned int inode;
} TagSearch;

typedef struct {
    TreeCmd *cmdPtr;
    int mask;
    Tcl_Obj **objv;
    int objc;
    Blt_TreeNode node;		/* Node affected by event. */
    Blt_TreeTrace notifyToken;
} NotifyInfo;


typedef struct {
    int mask;
} NotifyData;

static Blt_SwitchSpec notifySwitches[] = 
{
    {BLT_SWITCH_FLAG, "-bgerror", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_BGERROR},
    {BLT_SWITCH_FLAG, "-create", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_CREATE},
    {BLT_SWITCH_FLAG, "-delete", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_DELETE},
   {BLT_SWITCH_FLAG, "-disabletrace", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_TRACEACTIVE},
    {BLT_SWITCH_FLAG, "-get", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_GET},
    {BLT_SWITCH_FLAG, "-insert", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_INSERT},
    {BLT_SWITCH_FLAG, "-move", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_MOVE},
    {BLT_SWITCH_FLAG, "-movepost", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_MOVEPOST},
    {BLT_SWITCH_FLAG, "-sort", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_SORT},
    {BLT_SWITCH_FLAG, "-relabel", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_RELABEL},
    {BLT_SWITCH_FLAG, "-relabelpost", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_RELABELPOST},
    {BLT_SWITCH_FLAG, "-allevents", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_ALL},
    {BLT_SWITCH_FLAG, "-whenidle", Blt_Offset(NotifyData, mask), 0, 0, 
	TREE_NOTIFY_WHENIDLE},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static Blt_SwitchParseProc StringToChild;
#define INSERT_BEFORE	(ClientData)0
#define INSERT_AFTER	(ClientData)1
static Blt_SwitchCustom beforeSwitch =
{
    StringToChild, (Blt_SwitchFreeProc *)NULL, INSERT_BEFORE,
};
static Blt_SwitchCustom afterSwitch =
{
    StringToChild, (Blt_SwitchFreeProc *)NULL, INSERT_AFTER,
};

typedef struct {
    char *label;
    int insertPos;
    int inode;
    Tcl_Obj *tags;
    Tcl_Obj *tags2;
    Tcl_Obj *dataPairs;
    Tcl_Obj *names;
    Tcl_Obj *values;
    Blt_TreeNode parent;
    int flags;
    int fixed;
} InsertData;

static Blt_SwitchSpec insertSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-after", Blt_Offset(InsertData, insertPos), 0, 
	&afterSwitch},
    /*{BLT_SWITCH_INT_NONNEGATIVE, "-at", Blt_Offset(InsertData, insertPos), 0},*/
    {BLT_SWITCH_CUSTOM, "-before", Blt_Offset(InsertData, insertPos), 0,
	&beforeSwitch},
    {BLT_SWITCH_OBJ, "-data", Blt_Offset(InsertData, dataPairs), 0},
    {BLT_SWITCH_BOOLEAN, "-fixed", Blt_Offset(InsertData, fixed), 0},
    {BLT_SWITCH_STRING, "-label", Blt_Offset(InsertData, label), 0},
    {BLT_SWITCH_OBJ, "-names", Blt_Offset(InsertData, names), 0},
    {BLT_SWITCH_INT_NONNEGATIVE, "-node", Blt_Offset(InsertData, inode), 0},
    {BLT_SWITCH_INT_NONNEGATIVE, "-pos", Blt_Offset(InsertData, insertPos), 0},
    {BLT_SWITCH_OBJ, "-pretags", Blt_Offset(InsertData, tags), 0},
    {BLT_SWITCH_OBJ, "-tags", Blt_Offset(InsertData, tags2), 0},
    {BLT_SWITCH_OBJ, "-values", Blt_Offset(InsertData, values), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};

#define PATTERN_EXACT	(1)
#define PATTERN_GLOB	(2)
#define PATTERN_MASK	(0x7)
#define PATTERN_NONE	(0)
#define PATTERN_REGEXP	(3)
#define PATTERN_INLIST	(4)
#define MATCH_INVERT		(1<<8)
#define MATCH_LEAFONLY		(1<<4)
#define MATCH_NOCASE		(1<<5)
#define MATCH_PATHNAME		(1<<6)
#define MATCH_NOTOP		(1<<7)
#define MATCH_TREEONLY		(1<<9)
#define MATCH_COUNT		(1<<10)
#define MATCH_FIXED		(1<<11)
#define MATCH_NOTFIXED		(1<<12)
#define MATCH_ARRAY		(1<<13)
#define MATCH_RELDEPTH		(1<<14)
#define MATCH_ISMODIFIED		(1<<15)
#define MATCH_STRICT		(1<<16)
#define MATCH_ISNULL		(1<<17)

typedef struct {
    TreeCmd *cmdPtr;		/* Tree to examine. */
    Tcl_Obj *listObjPtr;	/* List to accumulate the indices of 
				 * matching nodes. */
    Tcl_Obj **objv;		/* Command converted into an array of 
				 * Tcl_Obj's. */
    int objc;			/* Number of Tcl_Objs in above array. */

    int nMatches;		/* Current number of matches. */

    unsigned int flags;		/* See flags definitions above. */

    /* Integer options. */
    int maxMatches;		/* If > 0, stop after this many matches. */
    int depth;		/* Only nodes at this level . */
    int minDepth;		/* Only nodes at this level or higher. */
    int maxDepth;		/* If > 0, don't descend more than this
				 * many levels. */
    int order;			/* Order of search: Can be either
				 * TREE_PREORDER, TREE_POSTORDER,
				 * TREE_INORDER, TREE_BREADTHFIRST. */
    /* String options. */
   /* Blt_List patternList;	* List of patterns to compare with labels
				 * or values.  */
    char *addTag;		/* If non-NULL, tag added to selected nodes. */

    char **command;		/* Command split into a Tcl list. */
    char **cmdArgs;
    int argc;
    char *eval;		/* Command to eval with %T and %N substitutions. */

    Blt_List keyList;		/* List of key name patterns. */
    Tcl_Obj *withTag;
    Tcl_Obj *withoutTag;
    char *retKey;
    Blt_TreeNode startNode;
    Tcl_Obj *name;
    char *subKey;
    Tcl_RegExp *regPtr;
    Tcl_Obj *execVar, *execObj, *nodesObj;
    int keyCount, iskey;
} FindData;

static Blt_SwitchParseProc StringToOrder;
static Blt_SwitchCustom orderSwitch =
{
    StringToOrder, (Blt_SwitchFreeProc *)NULL, (ClientData)0,
};

static Blt_SwitchParseProc StringToPattern;
static Blt_SwitchFreeProc FreePatterns;

static Blt_SwitchCustom regexpSwitch =
{
    StringToPattern, FreePatterns, (ClientData)PATTERN_REGEXP,
};
static Blt_SwitchCustom globSwitch =
{
    StringToPattern, FreePatterns, (ClientData)PATTERN_GLOB,
};
static Blt_SwitchCustom exactSwitch =
{
    StringToPattern, FreePatterns, (ClientData)PATTERN_EXACT,
};

static Blt_SwitchParseProc FStringToNode;
static Blt_SwitchCustom fNodeSwitch =
{
    FStringToNode, (Blt_SwitchFreeProc *)NULL, (ClientData)0,
};


static Blt_SwitchSpec findSwitches[] = 
{
    {BLT_SWITCH_STRING, "-addtag", Blt_Offset(FindData, addTag), 0},
    {BLT_SWITCH_STRING, "-column", Blt_Offset(FindData, subKey), 0},
    {BLT_SWITCH_LIST, "-cmdargs", Blt_Offset(FindData, cmdArgs), 0},
    {BLT_SWITCH_LIST, "-command", Blt_Offset(FindData, command), 0},
    {BLT_SWITCH_FLAG, "-count", Blt_Offset(FindData, flags), 0, 0,
       MATCH_COUNT},
    {BLT_SWITCH_INT_NONNEGATIVE, "-depth", Blt_Offset(FindData, depth), 0},
    /*{BLT_SWITCH_CUSTOM, "-exact", Blt_Offset(FindData, patternList), 0,
        &exactSwitch}, */
    {BLT_SWITCH_FLAG, "-exact", Blt_Offset(FindData, flags), 0, 0, 
	PATTERN_EXACT},
    {BLT_SWITCH_OBJ, "-exec", Blt_Offset(FindData, execObj), 0},
    /*{BLT_SWITCH_CUSTOM, "-glob", Blt_Offset(FindData, patternList), 0, 
	&globSwitch},*/
    {BLT_SWITCH_FLAG, "-isarray", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_ARRAY},
    {BLT_SWITCH_FLAG, "-isempty", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_ISNULL},
    {BLT_SWITCH_FLAG, "-isfixed", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_FIXED},
    {BLT_SWITCH_FLAG, "-ismodified", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_ISMODIFIED},
    {BLT_SWITCH_FLAG, "-isnotfixed", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_NOTFIXED},
    {BLT_SWITCH_FLAG, "-glob", Blt_Offset(FindData, flags), 0, 0, 
	PATTERN_GLOB},
    {BLT_SWITCH_FLAG, "-invert", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_INVERT},
    {BLT_SWITCH_INT_NONNEGATIVE, "-keycount", Blt_Offset(FindData, keyCount), 0},
    {BLT_SWITCH_CUSTOM, "-key", Blt_Offset(FindData, keyList), 0, &exactSwitch},
    {BLT_SWITCH_CUSTOM, "-keyexact", Blt_Offset(FindData, keyList), 0, 
	&exactSwitch},
    {BLT_SWITCH_CUSTOM, "-keyglob", Blt_Offset(FindData, keyList), 0, 
	&globSwitch},
    {BLT_SWITCH_CUSTOM, "-keyregexp", Blt_Offset(FindData, keyList), 0, 
	&regexpSwitch},
    {BLT_SWITCH_FLAG, "-isleaf", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_LEAFONLY},
    {BLT_SWITCH_FLAG, "-istree", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_TREEONLY},
    {BLT_SWITCH_INT_NONNEGATIVE, "-limit", Blt_Offset(FindData, maxMatches), 0},
    {BLT_SWITCH_INT_NONNEGATIVE, "-maxdepth", Blt_Offset(FindData, maxDepth), 0},
    {BLT_SWITCH_INT_NONNEGATIVE, "-mindepth", Blt_Offset(FindData, minDepth), 0},
    {BLT_SWITCH_OBJ, "-name", Blt_Offset(FindData, name), 0},
    {BLT_SWITCH_FLAG, "-nocase", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_NOCASE},
    {BLT_SWITCH_OBJ, "-nodes", Blt_Offset(FindData, nodesObj), 0},
    {BLT_SWITCH_FLAG, "-notop", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_NOTOP},
    {BLT_SWITCH_CUSTOM, "-order", Blt_Offset(FindData, order), 0, &orderSwitch},
   /* {BLT_SWITCH_CUSTOM, "-regexp", Blt_Offset(FindData, patternList), 0, 
	&regexpSwitch}, */
    {BLT_SWITCH_FLAG, "-regexp", Blt_Offset(FindData, flags), 0, 0, 
	PATTERN_REGEXP},
    {BLT_SWITCH_FLAG, "-inlist", Blt_Offset(FindData, flags), 0, 0, 
	PATTERN_INLIST},
    {BLT_SWITCH_FLAG, "-reldepth", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_RELDEPTH},
    {BLT_SWITCH_STRING, "-return", Blt_Offset(FindData, retKey), 0},
    {BLT_SWITCH_FLAG, "-strict", Blt_Offset(FindData, flags), 0, 0, 
        MATCH_STRICT},
    {BLT_SWITCH_CUSTOM, "-top", Blt_Offset(FindData, startNode), 0, &fNodeSwitch},
    {BLT_SWITCH_FLAG, "-usepath", Blt_Offset(FindData, flags), 0, 0, 
	MATCH_PATHNAME},
    {BLT_SWITCH_OBJ, "-var", Blt_Offset(FindData, execVar), 0},
    {BLT_SWITCH_OBJ, "-withtag", Blt_Offset(FindData, withTag), 0},
    {BLT_SWITCH_OBJ, "-withouttag", Blt_Offset(FindData, withoutTag), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static Blt_SwitchParseProc StringToNode;
static Blt_SwitchCustom nodeSwitch =
{
    StringToNode, (Blt_SwitchFreeProc *)NULL, (ClientData)0,
    };


typedef struct {
    TreeCmd *cmdPtr;		/* Tree to move nodes. */
    Blt_TreeNode node;
    int movePos;
} MoveData;

static Blt_SwitchSpec moveSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-after", Blt_Offset(MoveData, node), 0, &nodeSwitch},
    {BLT_SWITCH_CUSTOM, "-before", Blt_Offset(MoveData, node), 0, &nodeSwitch},
    {BLT_SWITCH_OBJ, "-pos", Blt_Offset(MoveData, movePos), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};

typedef struct {
    Blt_TreeNode srcNode, destNode;
    Blt_Tree srcTree, destTree;
    TreeCmd *srcPtr, *destPtr;
    unsigned int flags;
    char *label;
} CopyData;

#define COPY_RECURSE	(1<<0)
#define COPY_TAGS	(1<<1)
#define COPY_OVERWRITE	(1<<2)
#define COPY_REVERSE (1<<3)

static Blt_SwitchSpec copySwitches[] = 
{
    {BLT_SWITCH_FLAG, "-reverse", Blt_Offset(CopyData, flags), 0, 0, 
	COPY_REVERSE},
    {BLT_SWITCH_STRING, "-label", Blt_Offset(CopyData, label), 0},
    {BLT_SWITCH_FLAG, "-recurse", Blt_Offset(CopyData, flags), 0, 0, 
	COPY_RECURSE},
    {BLT_SWITCH_FLAG, "-tags", Blt_Offset(CopyData, flags), 0, 0, 
	COPY_TAGS},
    {BLT_SWITCH_FLAG, "-overwrite", Blt_Offset(CopyData, flags), 0, 0, 
	COPY_OVERWRITE},
    {BLT_SWITCH_END, NULL, 0, 0}
};

typedef struct {
    TreeCmd *cmdPtr;		/* Tree to examine. */
    Tcl_Obj **preObjv;		/* Command converted into an array of 
				 * Tcl_Obj's. */
    int preObjc;		/* Number of Tcl_Objs in above array. */

    Tcl_Obj **postObjv;		/* Command converted into an array of 
				 * Tcl_Obj's. */
    int postObjc;		/* Number of Tcl_Objs in above array. */

    unsigned int flags;		/* See flags definitions above. */

    int maxDepth;		/* If > 0, don't descend more than this
				 * many levels. */
    /* String options. */
    Blt_List patternList;	/* List of label or value patterns. */
    char **preCmd;		/* Pre-command split into a Tcl list. */
    char **postCmd;		/* Post-command split into a Tcl list. */

    Blt_List keyList;		/* List of key-name patterns. */
    char *withTag;
} ApplyData;

static Blt_SwitchSpec applySwitches[] = 
{
    {BLT_SWITCH_LIST, "-precommand", Blt_Offset(ApplyData, preCmd), 0},
    {BLT_SWITCH_LIST, "-postcommand", Blt_Offset(ApplyData, postCmd), 0},
    {BLT_SWITCH_INT_NONNEGATIVE, "-depth", Blt_Offset(ApplyData, maxDepth), 0},
    {BLT_SWITCH_CUSTOM, "-exact", Blt_Offset(ApplyData, patternList), 0,
	&exactSwitch},
    {BLT_SWITCH_CUSTOM, "-glob", Blt_Offset(ApplyData, patternList), 0, 
	&globSwitch},
    {BLT_SWITCH_FLAG, "-invert", Blt_Offset(ApplyData, flags), 0, 0, 
	MATCH_INVERT},
    {BLT_SWITCH_CUSTOM, "-key", Blt_Offset(ApplyData, keyList), 0, 
	&exactSwitch},
    {BLT_SWITCH_CUSTOM, "-keyexact", Blt_Offset(ApplyData, keyList), 0, 
	&exactSwitch},
    {BLT_SWITCH_CUSTOM, "-keyglob", Blt_Offset(ApplyData, keyList), 0, 
	&globSwitch},
    {BLT_SWITCH_CUSTOM, "-keyregexp", Blt_Offset(ApplyData, keyList), 0, 
	&regexpSwitch},
    {BLT_SWITCH_FLAG, "-isleaf", Blt_Offset(ApplyData, flags), 0, 0, 
	MATCH_LEAFONLY},
    {BLT_SWITCH_FLAG, "-istree", Blt_Offset(ApplyData, flags), 0, 0, 
	MATCH_TREEONLY},
    {BLT_SWITCH_FLAG, "-nocase", Blt_Offset(ApplyData, flags), 0, 0, 
	MATCH_NOCASE},
    {BLT_SWITCH_CUSTOM, "-regexp", Blt_Offset(ApplyData, patternList), 0,
	&regexpSwitch},
    {BLT_SWITCH_STRING, "-tag", Blt_Offset(ApplyData, withTag), 0},
    {BLT_SWITCH_FLAG, "-usepath", Blt_Offset(ApplyData, flags), 0, 0, 
	MATCH_PATHNAME},
    {BLT_SWITCH_END, NULL, 0, 0}
};

typedef struct {
    unsigned int flags;
    Blt_HashTable idTable;
    Blt_TreeNode root;
    char *file;
    char *chan;
    char *tags, *notTags;
    Tcl_Obj *data, *keys, *notKeys, **kobjv, **nobjv;
    Tcl_Obj **tobjv, *addTags;
    int tobjc, kobjc, nobjc;
    Blt_HashTable tagTable;
} RestoreData;

#define RESTORE_NO_TAGS		(1<<0)
#define RESTORE_OVERWRITE	(1<<1)
#define RESTORE_NO_PATH		(1<<2)

static Blt_SwitchSpec restoreSwitches[] = 
{
    {BLT_SWITCH_FLAG, "-notags", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_NO_TAGS},
    {BLT_SWITCH_FLAG, "-overwrite", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_OVERWRITE},
    {BLT_SWITCH_OBJ, "-addtags", Blt_Offset(RestoreData, addTags), 0},
    {BLT_SWITCH_OBJ, "-data", Blt_Offset(RestoreData, data), 0},
    {BLT_SWITCH_STRING, "-channel", Blt_Offset(RestoreData, chan), 0},
    {BLT_SWITCH_STRING, "-file", Blt_Offset(RestoreData, file), 0},
    {BLT_SWITCH_OBJ, "-keys", Blt_Offset(RestoreData, keys), 0},
    {BLT_SWITCH_STRING, "-tag", Blt_Offset(RestoreData, tags), 0},
    {BLT_SWITCH_OBJ, "-skipkeys", Blt_Offset(RestoreData, notKeys), 0},
    {BLT_SWITCH_STRING, "-skiptag", Blt_Offset(RestoreData, notTags), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static Blt_SwitchSpec dumpSwitches[] = 
{
    {BLT_SWITCH_FLAG, "-notags", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_NO_TAGS},
    {BLT_SWITCH_FLAG, "-nopath", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_NO_PATH},
    {BLT_SWITCH_STRING, "-channel", Blt_Offset(RestoreData, chan), 0},
    {BLT_SWITCH_STRING, "-file", Blt_Offset(RestoreData, file), 0},
    {BLT_SWITCH_OBJ, "-keys", Blt_Offset(RestoreData, keys), 0},
    {BLT_SWITCH_STRING, "-tag", Blt_Offset(RestoreData, tags), 0},
    {BLT_SWITCH_OBJ, "-skipkeys", Blt_Offset(RestoreData, notKeys), 0},
    {BLT_SWITCH_STRING, "-skiptag", Blt_Offset(RestoreData, notTags), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};
static Blt_SwitchSpec dumptagSwitches[] = 
{
    {BLT_SWITCH_FLAG, "-notags", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_NO_TAGS},
    {BLT_SWITCH_FLAG, "-nopath", Blt_Offset(RestoreData, flags), 0, 0, 
	RESTORE_NO_PATH},
    {BLT_SWITCH_OBJ, "-keys", Blt_Offset(RestoreData, keys), 0},
    {BLT_SWITCH_STRING, "-tag", Blt_Offset(RestoreData, tags), 0},
    {BLT_SWITCH_OBJ, "-skipkeys", Blt_Offset(RestoreData, notKeys), 0},
    {BLT_SWITCH_STRING, "-skiptag", Blt_Offset(RestoreData, notTags), 0},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static Blt_SwitchParseProc StringToFormat;
static Blt_SwitchCustom formatSwitch =
{
    StringToFormat, (Blt_SwitchFreeProc *)NULL, (ClientData)0,
};

typedef struct {
    int sort;			/* If non-zero, sort the nodes.  */
    int withParent;		/* If non-zero, add the parent node id 
				 * to the output of the command.*/
    int withId;			/* If non-zero, echo the node id in the
				 * output of the command. */
} PositionData;

#define POSITION_SORTED		(1<<0)

static Blt_SwitchSpec positionSwitches[] = 
{
    {BLT_SWITCH_FLAG, "-sort", Blt_Offset(PositionData, sort), 0, 0,
       POSITION_SORTED},
    {BLT_SWITCH_CUSTOM, "-format", 0, 0, &formatSwitch},
    {BLT_SWITCH_END, NULL, 0, 0}
};


static Tcl_InterpDeleteProc TreeInterpDeleteProc;
static Blt_TreeApplyProc MatchNodeProc, SortApplyProc;
static Blt_TreeApplyProc ApplyNodeProc;
static Blt_TreeTraceProc TreeTraceProc;
static Tcl_CmdDeleteProc TreeInstDeleteProc;
static Blt_TreeCompareNodesProc CompareNodes;

static Tcl_ObjCmdProc TreeObjCmd;
static Tcl_ObjCmdProc CompareDictionaryCmd;
#ifdef BLT_USE_UTIL_EXIT  
static Tcl_ObjCmdProc ExitCmd;
#endif
static Blt_TreeNotifyEventProc TreeEventProc;

static int GetNode _ANSI_ARGS_((TreeCmd *cmdPtr, Tcl_Obj *objPtr, 
	Blt_TreeNode *nodePtr));

static int nLines;



/*
 *----------------------------------------------------------------------
 *
 * StringToChild --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToChild(
    ClientData clientData,	/* Flag indicating if the node is
				 * considered before or after the
				 * insertion position. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    InsertData *dataPtr = (InsertData *)record;
    Blt_TreeNode node;
    
    node = Blt_TreeFindChild(dataPtr->parent, string);
    if (node == NULL) {
	Tcl_AppendResult(interp, "can't find a child named \"", string, 
		 "\" in \"", Blt_TreeNodeLabel(dataPtr->parent), "\"",
		 (char *)NULL);	 
	return TCL_ERROR;
    }			  
    dataPtr->insertPos = Blt_TreeNodeDegree(node);
    if (clientData == INSERT_AFTER) {
	dataPtr->insertPos++;
    } 
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToNode --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToNode(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    MoveData *dataPtr = (MoveData *)record;
    Blt_TreeNode node;
    Tcl_Obj *objPtr;
    TreeCmd *cmdPtr = dataPtr->cmdPtr;
    int result;

    objPtr = Tcl_NewStringObj(string, -1);
    result = GetNode(cmdPtr, objPtr, &node);
    Tcl_DecrRefCount(objPtr);
    if (result != TCL_OK) {
	return TCL_ERROR;
    }
    dataPtr->node = node;
    return TCL_OK;
}

static int
FStringToNode(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    FindData *dataPtr = (FindData *)record;
    Blt_TreeNode node;
    Tcl_Obj *objPtr;
    TreeCmd *cmdPtr = dataPtr->cmdPtr;
    int result;

    objPtr = Tcl_NewStringObj(string, -1);
    result = GetNode(cmdPtr, objPtr, &node);
    Tcl_DecrRefCount(objPtr);
    if (result != TCL_OK) {
	return TCL_ERROR;
    }
    dataPtr->startNode = node;
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * StringToOrder --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToOrder(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    int *orderPtr = (int *)(record + offset);
    char c;

    c = string[0];
    if ((c == 'b') && (strcmp(string, "breadthfirst") == 0)) {
	*orderPtr = TREE_BREADTHFIRST;
    } else if ((c == 'i') && (strcmp(string, "inorder") == 0)) {
	*orderPtr = TREE_INORDER;
    } else if ((c == 'p') && (strcmp(string, "preorder") == 0)) {
	*orderPtr = TREE_PREORDER;
    } else if ((c == 'p') && (strcmp(string, "postorder") == 0)) {
	*orderPtr = TREE_POSTORDER;
    } else {
	Tcl_AppendResult(interp, "bad order \"", string, 
		 "\": should be breadthfirst, inorder, preorder, or postorder",
		 (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToPattern --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToPattern(
    ClientData clientData,	/* Flag indicating type of pattern. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    Blt_List *listPtr = (Blt_List *)(record + offset);

    if (*listPtr == NULL) {
	*listPtr = Blt_ListCreate(BLT_STRING_KEYS);
    }
    Blt_ListAppend(*listPtr, string, clientData);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FreePatterns --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
FreePatterns(char *object)
{
    Blt_List list = (Blt_List)object;

    if (list != NULL) {
	Blt_ListDestroy(list);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * StringToFormat --
 *
 *	Convert a string represent a node number into its integer
 *	value.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToFormat(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    char *switchName,		/* Not used. */
    char *string,		/* String representation */
    char *record,		/* Structure record */
    int offset)			/* Offset to field in structure */
{
    PositionData *dataPtr = (PositionData *)record;

    if (strcmp(string, "position") == 0) {
	dataPtr->withParent = FALSE;
	dataPtr->withId = FALSE;
    } else if (strcmp(string, "id+position") == 0) {
	dataPtr->withParent = FALSE;
	dataPtr->withId = TRUE;
    } else if (strcmp(string, "parent-at-position") == 0) {
	dataPtr->withParent = TRUE;
	dataPtr->withId = FALSE;
    } else if (strcmp(string, "id+parent-at-position") == 0) {
	dataPtr->withParent = TRUE;
	dataPtr->withId  = TRUE;
    } else {
	Tcl_AppendResult(interp, "bad format \"", string, 
 "\": should be position, parent-at-position, id+position, or id+parent-at-position",
		 (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetTreeCmdInterpData --
 *
 *---------------------------------------------------------------------- 
 */
static TreeCmdInterpData *
GetTreeCmdInterpData(Tcl_Interp *interp)
{
    TreeCmdInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (TreeCmdInterpData *)
	Tcl_GetAssocData(interp, TREE_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
	dataPtr = Blt_Calloc(1, sizeof(TreeCmdInterpData));
	assert(dataPtr);
	dataPtr->interp = interp;
	Tcl_SetAssocData(interp, TREE_THREAD_KEY, TreeInterpDeleteProc,
		 dataPtr);
	Blt_InitHashTable(&dataPtr->treeTable, BLT_ONE_WORD_KEYS);
    }
    return dataPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * GetTreeCmd --
 *
 *	Find the tree command associated with the Tcl command "string".
 *	
 *	We have to do multiple lookups to get this right.  
 *
 *	The first step is to generate a canonical command name.  If an
 *	unqualified command name (i.e. no namespace qualifier) is
 *	given, we should search first the current namespace and then
 *	the global one.  Most Tcl commands (like Tcl_GetCmdInfo) look
 *	only at the global namespace.
 *
 *	Next check if the string is 
 *		a) a Tcl command and 
 *		b) really is a command for a tree object.  
 *	Tcl_GetCommandInfo will get us the objClientData field that 
 *	should be a cmdPtr.  We can verify that by searching our hashtable 
 *	of cmdPtr addresses.
 *
 * Results:
 *	A pointer to the tree command.  If no associated tree command
 *	can be found, NULL is returned.  It's up to the calling routines
 *	to generate an error message.
 *
 *---------------------------------------------------------------------- 
 */
static TreeCmd *
GetTreeCmd(
    TreeCmdInterpData *dataPtr, 
    Tcl_Interp *interp, 
    CONST char *string)
{
    CONST char *name;
    Tcl_Namespace *nsPtr;
    Tcl_CmdInfo cmdInfo;
    Blt_HashEntry *hPtr;
    Tcl_DString dString;
    char *treeName;
    int result;

    /* Put apart the tree name and put is back together in a standard
     * format. */
    if (Blt_ParseQualifiedName(interp, string, &nsPtr, &name) != TCL_OK) {
	return NULL;		/* No such parent namespace. */
    }
    if (nsPtr == NULL) {
	nsPtr = Tcl_GetCurrentNamespace(interp);
    }
    Tcl_DStringInit(&dString);
    /* Rebuild the fully qualified name. */
    treeName = Blt_GetQualifiedName(nsPtr, name, &dString);
    result = Tcl_GetCommandInfo(interp, treeName, &cmdInfo);
    Tcl_DStringFree(&dString);

    if (!result) {
	return NULL;
    }
    hPtr = Blt_FindHashEntry(&dataPtr->treeTable, 
			     (char *)(cmdInfo.objClientData));
    if (hPtr == NULL) {
	return NULL;
    }
    return Blt_GetHashValue(hPtr);
}

static Blt_TreeNode
MaxNode(Blt_Tree tree) {
    Blt_TreeNode node, root, mnode;
    int max = 0;
    
    mnode = root = Blt_TreeRootNode(tree);
    for (node = root; node != NULL; node = Blt_TreeNextNode(root, node)) {
        if (node->inode > max) {
            max = node->inode;
            mnode = node;
        }
    }
    return mnode;
}


static Blt_TreeNode 
ParseModifiers(
    Tcl_Interp *interp,
    Blt_Tree tree,
    Blt_TreeNode node,
    char *modifiers)
{
    char *p, *np;

    p = modifiers;
    do {
	p += 2;			/* Skip the initial "->" */
	np = strstr(p, "->");
	if (np != NULL) {
	    *np = '\0';
	}
	if ((*p == 'p') && (strcmp(p, "parentnode") == 0)) {
	    node = Blt_TreeNodeParent(node);
	} else if ((*p == 'f') && (strcmp(p, "firstchild") == 0)) {
	    node = Blt_TreeFirstChild(node);
	} else if ((*p == 'l') && (strcmp(p, "lastchild") == 0)) {
	    node = Blt_TreeLastChild(node);
	} else if ((*p == 'n') && (strcmp(p, "nextnode") == 0)) {
	    node = Blt_TreeNextNode(Blt_TreeRootNode(tree), node);
	} else if ((*p == 'n') && (strcmp(p, "nextsibling") == 0)) {
	    node = Blt_TreeNextSibling(node);
	} else if ((*p == 'p') && (strcmp(p, "prevnode") == 0)) {
	    node = Blt_TreePrevNode(Blt_TreeRootNode(tree), node);
	} else if ((*p == 'p') && (strcmp(p, "prevsibling") == 0)) {
	    node = Blt_TreePrevSibling(node);
	} else if ((*p == 'm') && (strcmp(p, "maxnode") == 0)) {
	    node = MaxNode(tree);
	/* } else if (isdigit(UCHAR(*p))) {
	    int inode;
	    
	    if (Tcl_GetInt(interp, p, &inode) != TCL_OK) {
		node = NULL;
	    } else {
		node = Blt_TreeGetNode(tree, inode);
	    }*/
	} else {
	    char *endp;

	    if (np != NULL) {
		endp = np - 1;
	    } else {
		endp = p + strlen(p) - 1;
	    }
            if ((*p == '\'') && (*endp == '\'')) {
                *endp = '\0';
                node = Blt_TreeFindChild(node, p + 1);
                *endp = '\'';

	    } else if ((*p == '"') && (*endp == '"')) {
		*endp = '\0';
		node = Blt_TreeFindChild(node, p + 1);
		*endp = '"';
	    } else {
		node = Blt_TreeFindChild(node, p);
	    }		
	}
	if (node == NULL) {
	    goto error;
	}
	if (np != NULL) {
	    *np = '-';		/* Repair the string */
	}
	p = np;
    } while (np != NULL);
    return node;
 error:
    if (np != NULL) {
	*np = '-';		/* Repair the string */
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * GetForeignNode --
 *
 *---------------------------------------------------------------------- 
 */
static int 
GetForeignNode(
    Tcl_Interp *interp,
    Blt_Tree tree,
    Tcl_Obj *objPtr,
    Blt_TreeNode *nodePtr)
{
    char c;
    Blt_TreeNode node;
    char *string;
    char *p;

    string = Tcl_GetString(objPtr);
    c = string[0];

    /* 
     * Check if modifiers are present.
     */
    p = strstr(string, "->");
    if (isdigit(UCHAR(c))) {
	int inode;

	if (p != NULL) {
	    char save;
	    int result;

	    save = *p;
	    *p = '\0';
	    result = Tcl_GetInt(interp, string, &inode);
	    *p = save;
	    if (result != TCL_OK) {
		return TCL_ERROR;
	    }
	} else {
	    if (Tcl_GetIntFromObj(interp, objPtr, &inode) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	node = Blt_TreeGetNode(tree, inode);
	if (p != NULL) {
	    node = ParseModifiers(interp, tree, node, p);
	}
	if (node != NULL) {
	    *nodePtr = node;
	    return TCL_OK;
	}
    }
    Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ",
	 Blt_TreeName(tree), (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GetNode --
 *
 *---------------------------------------------------------------------- 
 */
static int 
GetNode(TreeCmd *cmdPtr, Tcl_Obj *objPtr, Blt_TreeNode *nodePtr)
{
    Tcl_Interp *interp = cmdPtr->interp;
    Blt_Tree tree = cmdPtr->tree;
    char c;
    Blt_TreeNode node;
    char *string;
    char *p;
    int inode;

    string = Tcl_GetString(objPtr);
    c = string[0];

    /* 
     * Check if modifiers are present.
     */
    p = strstr(string, "->");
    if (isdigit(UCHAR(c))) {

	if (p != NULL) {
	    char save;
	    int result;

	    save = *p;
	    *p = '\0';
	    result = Tcl_GetInt(interp, string, &inode);
	    *p = save;
	    if (result != TCL_OK) {
		return TCL_ERROR;
	    }
	} else {
	    if (Tcl_GetIntFromObj(interp, objPtr, &inode) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	node = Blt_TreeGetNode(tree, inode);
    }  else if (cmdPtr != NULL) {
	char save;

	save = '\0';		/* Suppress compiler warning. */
	if (p != NULL) {
	    save = *p;
	    *p = '\0';
	}
	if ((strcmp(string, "all") == 0 )) {
	    if (Blt_TreeSize(Blt_TreeRootNode(tree)) > 1) {
		Tcl_AppendResult(interp, "more than one node tagged as \"", 
				 string, "\"", (char *)NULL);
		if (p != NULL) {
		    *p = save;
		}
		return TCL_ERROR;
	    }
	    node = Blt_TreeRootNode(tree);
	} else if ((strcmp(string, "all") == 0 )
	   || (strcmp(string, "rootchildren") == 0)) {
	    if (Blt_TreeSize(Blt_TreeRootNode(tree)) > 2) {
		Tcl_AppendResult(interp, "more than one node tagged as \"", 
				 string, "\"", (char *)NULL);
		if (p != NULL) {
		    *p = save;
		}
		return TCL_ERROR;
	    }
	    node = Blt_TreeRootNode(tree);
	} else if (strcmp(string, "root") == 0) {
	    node = Blt_TreeRootNode(tree);
	} else {
	    Blt_HashTable *tablePtr;
	    Blt_HashSearch cursor;
	    Blt_HashEntry *hPtr;
	    int result;

	    node = NULL;
	    result = TCL_ERROR;
	    tablePtr = Blt_TreeTagHashTable(cmdPtr->tree, string);
	    if (tablePtr == NULL) {
		Tcl_AppendResult(interp, "can't find tag or id \"", string, 
			"\" in ", Blt_TreeName(cmdPtr->tree), (char *)NULL);
	    } else if (tablePtr->numEntries > 1) {
		Tcl_AppendResult(interp, "more than one node tagged as \"", 
			 string, "\"", (char *)NULL);
	    } else if (tablePtr->numEntries <= 0) {
		Tcl_AppendResult(interp, "there is no node tagged as \"", 
			 string, "\"", (char *)NULL);
	    } else {
		hPtr = Blt_FirstHashEntry(tablePtr, &cursor);
		node = Blt_GetHashValue(hPtr);
		result = TCL_OK;
	    }
	    if (result == TCL_ERROR) {
		if (p != NULL) {
		    *p = save;
		}
		return TCL_ERROR;
	    }
	}
	if (p != NULL) {
	    *p = save;
	}
    }
    if (node != NULL) {
	if (p != NULL) {
	    node = ParseModifiers(interp, tree, node, p);
	    if (node == NULL) {
		goto error;
	    }
	}
	*nodePtr = node;
	return TCL_OK;
    }
 error:
    Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ",
		 Blt_TreeName(tree), (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * FirstTaggedNode --
 *
 *	Returns the id of the first node tagged by the given tag in
 *	objPtr.  It basically hides much of the cumbersome special
 *	case details.  For example, the special tags "root" and "all"
 *      "nonroot"
 *	always exist, so they don't have entries in the tag hashtable.
 *	If it's a hashed tag (not "root" or "all"), we have to save
 *	the place of where we are in the table for the next call to
 *	NextTaggedNode.
 *
 *---------------------------------------------------------------------- 
 */
static Blt_TreeNode
FirstTaggedNode(TagSearch *cursorPtr)
{
    return cursorPtr->node;
}

static int
FindTaggedNodes(
    Tcl_Interp *interp,
    TreeCmd *cmdPtr,
    Tcl_Obj *objPtr,
    TagSearch *cursorPtr)
{
    Blt_TreeNode node, root;
    char *string;
    
    memset(cursorPtr, 0, sizeof(*cursorPtr));
    cursorPtr->init = 1;
    node = NULL;

    root = Blt_TreeRootNode(cmdPtr->tree);
    string = Tcl_GetString(objPtr);
    cursorPtr->tagType = TAG_TYPE_NONE;
    cursorPtr->root = root;

    /* Process strings with modifiers or digits as simple ids, not
     * tags. */
    if (string[0] == 0) {
        cursorPtr->node = NULL;
        return TCL_OK;
    }
    if ((strstr(string, "->") != NULL)) {
	if (GetNode(cmdPtr, objPtr, &node) != TCL_OK) {
	    return TCL_ERROR;
	}
        cursorPtr->node = node;
	return TCL_OK;
    }
    if (isdigit(UCHAR(*string))) {
        char *cp = string;
        int i, n;
        
        while (isdigit(UCHAR(*cp)) && *cp != 0) {
            cp++;
        }
        if (*cp != 0) {
            if (Tcl_ListObjGetElements(interp, objPtr, &cursorPtr->objc,
                &cursorPtr->objv) != TCL_OK) {
                return TCL_ERROR;
            }
            for (i=0; i<cursorPtr->objc; i++) {
                if (Tcl_GetIntFromObj(interp, cursorPtr->objv[i], &n) != TCL_OK) {
                    return TCL_ERROR;
                }
            }
            if (GetNode(cmdPtr, cursorPtr->objv[0], &node) != TCL_OK) {
                return TCL_ERROR;
            }
            Tcl_IncrRefCount(objPtr);
            cursorPtr->objPtr = objPtr;
            cursorPtr->cmdPtr = cmdPtr;
            cursorPtr->tagType = TAG_TYPE_LIST;
            cursorPtr->idx = 0;
            cursorPtr->node = node;
            return TCL_OK;
        }
        if (GetNode(cmdPtr, objPtr, &node) != TCL_OK) {
            return TCL_ERROR;
        }
        cursorPtr->node = node;
        return TCL_OK;
    }
    if (strcmp(string, "all") == 0) {
	cursorPtr->tagType = TAG_TYPE_ALL;
        cursorPtr->node = root;
        cursorPtr->inode = cursorPtr->node->inode;
        return TCL_OK;
    } else if (strcmp(string, "nonroot") == 0)  {
        cursorPtr->tagType = TAG_TYPE_ALL;
        cursorPtr->node = Blt_TreeNextNode(root, root);
        if (cursorPtr->node != NULL) {
            cursorPtr->inode = cursorPtr->node->inode;
        }
        return TCL_OK;
    } else if (strcmp(string, "root") == 0)  {
        cursorPtr->node = root;
        return TCL_OK;
    } else if (strcmp(string, "rootchildren") == 0)  {
        cursorPtr->tagType = TAG_TYPE_ROOTCHILDREN;
        cursorPtr->node = Blt_TreeNextNode(root, root);
        if (cursorPtr->node != NULL) {
            cursorPtr->inode = cursorPtr->node->inode;
        }
        return TCL_OK;
    } else {
	Blt_HashTable *tablePtr;
	
	tablePtr = Blt_TreeTagHashTable(cmdPtr->tree, string);
	if (tablePtr != NULL) {
	    Blt_HashEntry *hPtr;
	    
	    cursorPtr->tagType = TAG_TYPE_TAG;
	    hPtr = Blt_FirstHashEntry(tablePtr, &(cursorPtr->cursor)); 
	    if (hPtr == NULL) {
                cursorPtr->node = NULL;
                return TCL_OK;
            }
            cursorPtr->tPtr = Blt_TreeTagHashEntry(cmdPtr->tree, string);
	    Blt_TreeTagRefIncr(cursorPtr->tPtr);
	    node = Blt_GetHashValue(hPtr);
            cursorPtr->node = node;
            if (node) {
                cursorPtr->inode = node->inode;
            }
            return TCL_OK;
	}
    }
    Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ", 
	Blt_TreeName(cmdPtr->tree), (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * NextTaggedNode --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_TreeNode
NextTaggedNode(Blt_TreeNode node, TagSearch *cursorPtr)
{
    if (cursorPtr->cnt++ > 100000000) {
        /* Prevent infinite loop. */
        return NULL;
    }
    if (cursorPtr->tagType == TAG_TYPE_LIST) {
        if (++cursorPtr->idx >= cursorPtr->objc) {
            return NULL;
        }
        if (GetNode(cursorPtr->cmdPtr, cursorPtr->objv[cursorPtr->idx], &node) != TCL_OK) {
            return NULL;
        }
        return node;
    }
    if (cursorPtr->tagType == TAG_TYPE_TAG) {
	Blt_HashEntry *hPtr;

        if (cursorPtr->tPtr && cursorPtr->tPtr->refCount<=1) {
            /* Tag was deleted. */
            return NULL;
        }
	hPtr = Blt_NextHashEntry(&(cursorPtr->cursor));
	if (hPtr == NULL) {
	    return NULL;
	}
	return Blt_GetHashValue(hPtr);
    }
    if (cursorPtr->tagType == TAG_TYPE_ALL
        || cursorPtr->tagType == TAG_TYPE_ROOTCHILDREN) {
        if (node != cursorPtr->node) {
            fprintf(stderr, "node mismatch in nexttag");
        }
        /* If node is deleted/reallocated we terminate */
        if (Blt_TreeNodeDeleted(node) || node->inode != cursorPtr->node->inode) {
            return NULL;
        }
        if (cursorPtr->tagType == TAG_TYPE_ROOTCHILDREN) {
            cursorPtr->node = Blt_TreeNextSibling(node);
        } else {
            cursorPtr->node = Blt_TreeNextNode(cursorPtr->root, node);
        }
        if (cursorPtr->node != NULL) {
            cursorPtr->inode = cursorPtr->node->inode;
        }
        return cursorPtr->node;
    }
    return NULL;
}

/*
 * Release tagged searches.
 */
static void
DoneTaggedNodes(TagSearch *cursorPtr) {
    if (cursorPtr->init == 1) {
        cursorPtr->init = 0;
        if (cursorPtr->objPtr) {
            Tcl_DecrRefCount(cursorPtr->objPtr);
            cursorPtr->objPtr = NULL;
        }
        if (cursorPtr->tPtr) {
            Blt_TreeTagRefDecr(cursorPtr->tPtr);
            cursorPtr->tPtr = NULL;
        }
    }
}


static int
AddTag(TreeCmd *cmdPtr, Blt_TreeNode node, CONST char *tagName)
{
     return Blt_TreeAddTag(cmdPtr->tree, node, tagName);
}


/*
 *----------------------------------------------------------------------
 *
 * DeleteNode --
 *
 *---------------------------------------------------------------------- 
 */
static void
DeleteNode(TreeCmd *cmdPtr, Blt_TreeNode node)
{
    Blt_TreeNode root;

    if (!Blt_TreeTagTableIsShared(cmdPtr->tree)) {
	Blt_TreeClearTags(cmdPtr->tree, node);
    }
    root = Blt_TreeRootNode(cmdPtr->tree);
    if (node == root) {
	Blt_TreeNode next;
	/* Don't delete the root node. Simply clean out the tree. */
	for (node = Blt_TreeFirstChild(node); node != NULL; node = next) {
	    next = Blt_TreeNextSibling(node);
	    Blt_TreeDeleteNode(cmdPtr->tree, node);
	}	    
    } else if (Blt_TreeIsAncestor(root, node)) {
	Blt_TreeDeleteNode(cmdPtr->tree, node);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetNodePath --
 *
 *---------------------------------------------------------------------- 
 */
static char *
GetNodePathStr(
    TreeCmd *cmdPtr,
    Blt_TreeNode root, 
    Blt_TreeNode node,
    int rootFlag,		/* If non-zero, indicates to include
				 * the root name in the path */
    Tcl_DString *resultPtr,
    int lastOnly)
{
    char **nameArr;		/* Used to stack the component names. */
    char *staticSpace[64];
    register int i;
    int nLevels;

    nLevels = Blt_TreeNodeDepth(cmdPtr->tree, node) -
	Blt_TreeNodeDepth(cmdPtr->tree, root);
    if (rootFlag) {
	nLevels++;
    }
    if (nLevels > 64) {
	nameArr = Blt_Calloc(nLevels, sizeof(char *));
	assert(nameArr);
    } else {
	nameArr = staticSpace;
    }
    for (i = nLevels; i > 0; i--) {
	/* Save the name of each ancestor in the name array. 
	 * Note that we ignore the root. */
	if (lastOnly && i != nLevels) {
             nameArr[i - 1] = ".";
        } else {
             nameArr[i - 1] = Blt_TreeNodeLabel(node);
	}
	node = Blt_TreeNodeParent(node);
    }
    /* Append each the names in the array. */
    Tcl_DStringInit(resultPtr);
    for (i = 0; i < nLevels; i++) {
	Tcl_DStringAppendElement(resultPtr, nameArr[i]);
    }
    if (nameArr != staticSpace) {
	Blt_Free(nameArr);
    }
    return Tcl_DStringValue(resultPtr);
}

static char *
GetNodePath(
    TreeCmd *cmdPtr,
    Blt_TreeNode root, 
    Blt_TreeNode node,
    int rootFlag,		/* If non-zero, indicates to include
				 * the root name in the path */
    Tcl_DString *resultPtr)
{
    return GetNodePathStr(cmdPtr, root, node, rootFlag, resultPtr, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ParseNode5 --
 *
 *	Parses and creates a node based upon the first 3 fields of
 *	a five field entry.  This is the new restore file format.
 *
 *		parentId nodeId pathList dataList tagList
 *
 *	The purpose is to attempt to save and restore the node ids
 *	embedded in the restore file information.  The old format
 *	could not distinquish between two sibling nodes with the same
 *	label unless they were both leaves.  I'm trying to avoid
 *	dependencies upon labels.  
 *
 *	If you're starting from an empty tree, this obviously should
 *	work without a hitch.  We only need to map the file's root id
 *	to 0.  It's a little more complicated when adding node to an
 *	already full tree.  
 *
 *	First see if the node id isn't already in use.  Otherwise, map
 *	the node id (via a hashtable) to the real node. We'll need it
 *	later when subsequent entries refer to their parent id.
 *
 *	If a parent id is unknown (the restore file may be out of
 *	order), then follow plan B and use its path.
 *	
 *---------------------------------------------------------------------- 
 */
static Blt_TreeNode
ParseNode5(TreeCmd *cmdPtr, char **argv, RestoreData *dataPtr)
{
    Blt_HashEntry *hPtr;
    Blt_TreeNode node, parent;
    char **names;
    int nNames, isNew;
    int parentId, nodeId;

    if ((Tcl_GetInt(cmdPtr->interp, argv[0], &parentId) != TCL_OK) ||
	(Tcl_GetInt(cmdPtr->interp, argv[1], &nodeId) != TCL_OK) ||
	(Tcl_SplitList(cmdPtr->interp, argv[2], &nNames, &names) != TCL_OK)) {
	return NULL;
    }    

    if (parentId == -1) {	/* Dump marks root's parent as -1. */
	node = dataPtr->root;
	/* Create a mapping between the old id and the new node */
	hPtr = Blt_CreateHashEntry(&dataPtr->idTable, (char *)nodeId, 
		   &isNew);
	Blt_SetHashValue(hPtr, node);
	Blt_TreeRelabelNode(cmdPtr->tree, node, names[0]);
    } else {
	/* 
	 * Check if the parent has been translated to another id.
	 * This can happen when there's a id collision with an
	 * existing node. 
	 */
	hPtr = Blt_FindHashEntry(&dataPtr->idTable, (char *)parentId);
	if (hPtr != NULL) {
	    parent = Blt_GetHashValue(hPtr);
	} else {
	    /* Check if the id already exists in the tree. */
	    parent = Blt_TreeGetNode(cmdPtr->tree, parentId);
	    if (parent == NULL) {
		/* Parent id doesn't exist (partial restore?). 
		 * Plan B: Use the path to create/find the parent with 
		 *	   the requested parent id. */
		if (nNames > 1) {
		    int i;

		    for (i = 1; i < (nNames - 2); i++) {
			node = Blt_TreeFindChild(parent, names[i]);
			if (node == NULL) {
			    node = Blt_TreeCreateNode(cmdPtr->tree, parent, 
			      names[i], -1);
			}
			parent = node;
		    }
		    node = Blt_TreeFindChild(parent, names[nNames - 2]);
		    if (node == NULL) {
			node = Blt_TreeCreateNodeWithId(cmdPtr->tree, parent, 
				names[nNames - 2], parentId, -1);
		    }
		    parent = node;
		} else {
		    parent = dataPtr->root;
		}
	    }
	} 
	/* Check if old node id already in use. */
	node = NULL;
	if (dataPtr->flags & RESTORE_OVERWRITE &&
	    ((node = Blt_TreeFindChild(parent, names[nNames - 1])))) {
	    /* Create a mapping between the old id and the new node */
	    hPtr = Blt_CreateHashEntry(&dataPtr->idTable, (char *)nodeId, 
				       &isNew);
	    Blt_SetHashValue(hPtr, node);
	}
	if (node == NULL) {
	    node = Blt_TreeGetNode(cmdPtr->tree, nodeId);
	    if (node != NULL) {
		node = Blt_TreeCreateNode(cmdPtr->tree, parent, 
					  names[nNames - 1], -1);
		/* Create a mapping between the old id and the new node */
		hPtr = Blt_CreateHashEntry(&dataPtr->idTable, (char *)nodeId,
					   &isNew);
		Blt_SetHashValue(hPtr, node);
	    } else {
		/* Otherwise create a new node with the requested id. */
		node = Blt_TreeCreateNodeWithId(cmdPtr->tree, parent, 
						names[nNames - 1], nodeId, -1);
	    }
	}
    }
    Blt_Free(names);
    return node;
}

/*
 *----------------------------------------------------------------------
 *
 * ParseNode3 --
 *
 *	Parses and creates a node based upon the first field of
 *	a three field entry.  This is the old restore file format.
 *
 *		pathList dataList tagList
 *
 *----------------------------------------------------------------------
 */
static Blt_TreeNode
ParseNode3(TreeCmd *cmdPtr, char **argv, RestoreData *dataPtr)
{
    Blt_TreeNode node, parent;
    char **names;
    int i;
    int nNames;
    
    if (Tcl_SplitList(cmdPtr->interp, argv[0], &nNames, &names) != TCL_OK) {
	return NULL;
    }
    node = parent = dataPtr->root;
    /*  Automatically create nodes as needed except for the last node.  */
    for (i = 0; i < (nNames - 1); i++) {
	node = Blt_TreeFindChild(parent, names[i]);
	if (node == NULL) {
	    node = Blt_TreeCreateNode(cmdPtr->tree, parent, names[i], -1);
	}
	parent = node;
    }
    if (nNames > 0) {
	/* 
	 * By default, create duplicate nodes (two sibling nodes with
	 * the same label), unless the -overwrite flag was set.
	 */
	node = NULL;
	if (dataPtr->flags & RESTORE_OVERWRITE) {
	    node = Blt_TreeFindChild(parent, names[i]);
	}
	if (node == NULL) {
	    node = Blt_TreeCreateNode(cmdPtr->tree, parent, names[i], -1);
	}
    }
    Blt_Free(names);
    return node;
}

static int
RestoreNode(TreeCmd *cmdPtr, int argc, char **argv, RestoreData *dataPtr)
{
    Blt_TreeNode node;
    Tcl_Obj *valueObjPtr;
    char **elemArr, *string, *key;
    int nElem, result;
    register int i;

    if ((argc != 3) && (argc != 5)) {
	Tcl_AppendResult(cmdPtr->interp, "line #", Blt_Itoa(nLines), 
		": wrong # elements in restore entry", (char *)NULL);
	return TCL_ERROR;
    }
    /* Parse the path name. */
    if (argc == 3) {
	node = ParseNode3(cmdPtr, argv, dataPtr);
	argv++;
    } else if (argc == 5) {
	node = ParseNode5(cmdPtr, argv, dataPtr);
	argv += 3;
    } else {
	Tcl_AppendResult(cmdPtr->interp, "line #", Blt_Itoa(nLines), 
		": wrong # elements in restore entry", (char *)NULL);
	return TCL_ERROR;
    }
    if (node == NULL) {
	return TCL_ERROR;
    }
    if (argv[0][0]) {
        /* Parse the key-value list. */
        if (Tcl_SplitList(cmdPtr->interp, argv[0], &nElem, &elemArr) != TCL_OK) {
            DeleteNode(cmdPtr, node);
            return TCL_ERROR;
        }
        if (nElem%2) {
            Tcl_AppendResult(cmdPtr->interp, "odd # of name/values in keys: ",
                argv[0], 0);
            Blt_Free(elemArr);
            DeleteNode(cmdPtr, node);
            return TCL_ERROR;
        }
        for (i = 0; i < nElem; i += 2) {
            int n, keep;
            key = elemArr[i];
            keep = 1;
            if (dataPtr->keys != NULL) {
                keep = 0;
                for (n=0; n<dataPtr->kobjc; n++) {
                    string = Tcl_GetString(dataPtr->kobjv[n]);
                    if (Tcl_StringMatch(key, string)==1) {
                        keep = 1;
                        break;
                    }
                }
            }
            if (dataPtr->notKeys != NULL) {
                for (n=0; n<dataPtr->nobjc; n++) {
                    string = Tcl_GetString(dataPtr->nobjv[n]);
                    if (Tcl_StringMatch(key, string)==1) {
                        keep = 0;
                        break;
                    }
                }
            }
            if (!keep) continue;
            
            if ((i + 1) < nElem) {
                valueObjPtr = Tcl_NewStringObj(elemArr[i + 1], -1);
            } else {
                valueObjPtr = Tcl_NewStringObj("",-1);
            }
            Tcl_IncrRefCount(valueObjPtr);
            result = Blt_TreeSetValue(cmdPtr->interp, cmdPtr->tree, node, 
                elemArr[i], valueObjPtr);
                Tcl_DecrRefCount(valueObjPtr);
                if (result != TCL_OK) {
                Blt_Free(elemArr);
                DeleteNode(cmdPtr, node);
                return TCL_ERROR;
            }
        }
        Blt_Free(elemArr);
    }
    if (argv[1][0] && (!(dataPtr->flags & RESTORE_NO_TAGS))) {
	/* Parse the tag list. */
	if (Tcl_SplitList(cmdPtr->interp, argv[1], &nElem, &elemArr) 
	    != TCL_OK) {
            DeleteNode(cmdPtr, node);
	    return TCL_ERROR;
	}
	for (i = 0; i < nElem; i++) {
             key = elemArr[i];
             if (dataPtr->tags != NULL && Tcl_StringMatch(key, dataPtr->tags)!=1) {
                 continue;
             }
             if (dataPtr->notTags != NULL && Tcl_StringMatch(key,dataPtr->notTags)==1) {
                 continue;
             }
             if (AddTag(cmdPtr, node, elemArr[i]) != TCL_OK) {
		Blt_Free(elemArr);
                DeleteNode(cmdPtr, node);
		return TCL_ERROR;
	    }
	}
	Blt_Free(elemArr);
    }
    for (i=0; i<dataPtr->tobjc; i++) {
        if (AddTag(cmdPtr, node, Tcl_GetString(dataPtr->tobjv[i])) != TCL_OK) {
            DeleteNode(cmdPtr, node);
            return TCL_ERROR;
        }
    }
    if (Blt_TreeInsertPost(cmdPtr->tree, node) == NULL) {
        DeleteNode(cmdPtr, node);
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintNode --
 *
 *---------------------------------------------------------------------- 
 */
static void
PrintNode(
    TreeCmd *cmdPtr, 
    Blt_TreeNode root, 
    Blt_TreeNode node, 
    Tcl_DString *resultPtr,
    int tags,
    RestoreData *dataPtr
    )
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    char *pathName, *string;
    Tcl_DString dString;
    Tcl_Obj *valueObjPtr;
    register Blt_TreeKey key;
    Blt_TreeTagEntry *tPtr;
    Blt_TreeKeySearch keyIter;
    int keep, i;

    if (node == root) {
	Tcl_DStringAppendElement(resultPtr, "-1");
    } else {
	Blt_TreeNode parent;

	parent = Blt_TreeNodeParent(node);
	Tcl_DStringAppendElement(resultPtr, Blt_Itoa(Blt_TreeNodeId(parent)));
    }	
    Tcl_DStringAppendElement(resultPtr, Blt_Itoa(Blt_TreeNodeId(node)));

    pathName = GetNodePathStr(cmdPtr, root, node, TRUE, &dString,
        dataPtr->flags&RESTORE_NO_PATH);
    Tcl_DStringAppendElement(resultPtr, pathName);

    Tcl_DStringStartSublist(resultPtr);
    for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &keyIter); key != NULL; 
	 key = Blt_TreeNextKey(cmdPtr->tree, &keyIter)) {
	keep = 1;
        if (dataPtr->keys != NULL) {
            keep = 0;
            for (i=0; i<dataPtr->kobjc; i++) {
                string = Tcl_GetString(dataPtr->kobjv[i]);
                if (Tcl_StringMatch(key,string)==1) {
                    keep = 1;
                    break;
                }
            }
        }
        if (dataPtr->notKeys != NULL) {
            for (i=0; i<dataPtr->nobjc; i++) {
                string = Tcl_GetString(dataPtr->nobjv[i]);
                if (Tcl_StringMatch(key,string)==1) {
                    keep = 0;
                    break;
                }
            }
        }
        if (!keep) continue;
	if (Blt_TreeGetValueByKey((Tcl_Interp *)NULL, cmdPtr->tree, node, 
		key, &valueObjPtr) == TCL_OK) {
	    Tcl_DStringAppendElement(resultPtr, key);
	    Tcl_DStringAppendElement(resultPtr, Tcl_GetString(valueObjPtr));
	}
    }	    
    Tcl_DStringEndSublist(resultPtr);
    if (tags && dataPtr && dataPtr->tagTable.numEntries) {
        Tcl_DString *eStr;
        Blt_HashEntry *h2Ptr;
        
        eStr = NULL;
        h2Ptr = Blt_FindHashEntry(&dataPtr->tagTable, (char*)node);
        if (h2Ptr != NULL) {
            eStr = (Tcl_DString*)Blt_GetHashValue(h2Ptr);
        }
        Tcl_DStringAppendElement(resultPtr, eStr?Tcl_DStringValue(eStr):"");
    } else if (tags) {
        Tcl_DStringStartSublist(resultPtr);
        for (hPtr = Blt_TreeFirstTag(cmdPtr->tree, &cursor); hPtr != NULL; 
	    hPtr = Blt_NextHashEntry(&cursor)) {
            tPtr = Blt_GetHashValue(hPtr);
            if (Blt_FindHashEntry(&tPtr->nodeTable, (char *)node) != NULL) {
                Tcl_DStringAppendElement(resultPtr, tPtr->tagName);
            }
        }
        Tcl_DStringEndSublist(resultPtr);
    } else {
        Tcl_DStringAppendElement(resultPtr, "");
    }
    Tcl_DStringAppend(resultPtr, "\n", -1);
    Tcl_DStringFree(&dString);
}

/*
 *----------------------------------------------------------------------
 *
 * PrintTraceFlags --
 *
 *---------------------------------------------------------------------- 
 */
static void
PrintTraceFlags(unsigned int flags, char *string)
{
    register char *p;

    p = string;
    if (flags & TREE_TRACE_READ) {
	*p++ = 'r';
    } 
    if (flags & TREE_TRACE_WRITE) {
	*p++ = 'w';
    } 
    if (flags & TREE_TRACE_UNSET) {
	*p++ = 'u';
    } 
    if (flags & TREE_TRACE_CREATE) {
	*p++ = 'c';
    } 
    if (flags & TREE_TRACE_EXISTS) {
        *p++ = 'e';
    } 
    if (flags & TREE_TRACE_TAGDELETE) {
        *p++ = 'd';
    } 
    if (flags & TREE_TRACE_TAGADD) {
        *p++ = 't';
    } 
    if (flags & TREE_TRACE_TAGMULTIPLE) {
        *p++ = 'm';
    } 
    *p = '\0';
}

/*
 *----------------------------------------------------------------------
 *
 * GetTraceFlags --
 *
 *---------------------------------------------------------------------- 
 */
static int
GetTraceFlags(char *string)
{
    register char *p;
    unsigned int flags;

    flags = 0;
    for (p = string; *p != '\0'; p++) {
	switch (*p) {
	case 'r':
	    flags |= TREE_TRACE_READ;
	    break;
	case 'w':
	    flags |= TREE_TRACE_WRITE;
	    break;
	case 'u':
	    flags |= TREE_TRACE_UNSET;
	    break;
	case 'c':
	    flags |= TREE_TRACE_CREATE;
	    break;
	case 'e':
	    flags |= TREE_TRACE_EXISTS;
	    break;
	case 't':
	    flags |= TREE_TRACE_TAGADD;
	    break;
	case 'm':
	    flags |= TREE_TRACE_TAGMULTIPLE;
	    break;
	case 'd':
	    flags |= TREE_TRACE_TAGDELETE;
	    break;
	default:
	    return -1;
	}
    }
    return flags;
}

/*
 *----------------------------------------------------------------------
 *
 * SetValues --
 *
 *---------------------------------------------------------------------- 
 */
static int
SetValues(TreeCmd *cmdPtr, Blt_TreeNode node, int objc, Tcl_Obj *CONST *objv)
{
    register int i;
    int inode;
    char *string;

    inode = node->inode;
    for (i = 0; i < objc; i += 2) {
	string = Tcl_GetString(objv[i]);
	if ((i + 1) == objc) {
	    Tcl_AppendResult(cmdPtr->interp, "missing value for field \"", 
		string, "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	if (Blt_TreeSetValue(cmdPtr->interp, cmdPtr->tree, node, string, 
			     objv[i + 1]) != TCL_OK) {
	    return TCL_ERROR;
	}
     }
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * UpdateValues --
 *
 *---------------------------------------------------------------------- 
 */
static int
UpdateValues(TreeCmd *cmdPtr, Blt_TreeNode node, int objc, Tcl_Obj *CONST *objv)
{
    register int i;
    char *string;
    int result = TCL_OK, inode;
    Tcl_DString dStr;
    Tcl_Interp *interp = cmdPtr->interp;

    inode = node->inode;
    if (objc % 2) {
        Tcl_AppendResult(interp, "odd # values", (char *)NULL);
        return TCL_ERROR;
    }
    Tcl_DStringInit(&dStr);
    for (i = 0; i < objc; i += 2) {
	string = Tcl_GetString(objv[i]);
	if (Blt_TreeUpdateValue(interp, cmdPtr->tree, node, string, 
			     objv[i + 1]) != TCL_OK) {
            Tcl_DStringAppend(&dStr, Tcl_GetStringResult(interp), -1);
            Tcl_DStringAppend(&dStr, " ", -1);
            Tcl_ResetResult(interp);
	    result = TCL_ERROR;
	}
    }
    if (result != TCL_OK) {
        Tcl_DStringResult(interp, &dStr);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * UnsetValues --
 *
 *---------------------------------------------------------------------- 
 */
static int
UnsetValues(TreeCmd *cmdPtr, Blt_TreeNode node, int objc, Tcl_Obj *CONST *objv)
{
    if (objc == 0) {
	Blt_TreeKey key;
	Blt_TreeKeySearch cursor;
	
	int cnt, n;
	/* Careful, trace could add key back on end. */
	cnt = Blt_TreeCountKeys(cmdPtr->tree, node);
        n = 0;
	for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &cursor);
	    key != NULL && n<=cnt;
	    n++, key = Blt_TreeNextKey(cmdPtr->tree, &cursor)) {
	    if (Blt_TreeUnsetValueByKey(cmdPtr->interp, cmdPtr->tree, node, 
			key) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
    } else {
	register int i;

	for (i = 0; i < objc; i ++) {
	    if (Blt_TreeUnsetValue(cmdPtr->interp, cmdPtr->tree, node, 
		Tcl_GetString(objv[i])) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
    }
    return TCL_OK;
}

static char *
GetFirstKey(Blt_List patternList)
{
    Blt_ListNode node;
    char *pattern;

    for (node = Blt_ListFirstNode(patternList); node != NULL; 
	node = Blt_ListNextNode(node)) {
		
	pattern = (char *)Blt_ListGetKey(node);
	return pattern;
    }
    return NULL;
}

static int
ComparePatternList(Blt_List patternList, char *string, int nocase)
{
    Blt_ListNode node;
    int result, type;
    char *pattern;

    result = FALSE;
    for (node = Blt_ListFirstNode(patternList); node != NULL; 
	node = Blt_ListNextNode(node)) {
		
	type = (int)Blt_ListGetValue(node);
	pattern = (char *)Blt_ListGetKey(node);
	switch (type) {
	case 0:
	case PATTERN_EXACT:
	    if (nocase) {
                 result = (strcasecmp(string, pattern) == 0);
             } else {
                result = (strcmp(string, pattern) == 0);
            }
	    break;
	    
	case PATTERN_GLOB:
	    result = (Tcl_StringCaseMatch(string, pattern, nocase) == 1);
	    break;
		    
	case PATTERN_REGEXP:
            if (nocase) {
                string = Blt_Strdup(string);
                strtolower(string);
            }
	    result = (Tcl_RegExpMatch((Tcl_Interp *)NULL, string, pattern) == 1); 
            if (nocase) {
                Blt_Free(string);
            }
	    break;
	}
    }
    return result;
}

static int
ComparePattern(FindData *findData, char *string)
{
    int result, type, nocase;
    char *pattern;
    int objc, i;
    Tcl_Obj **objv, *obj;

    nocase = (findData->flags & MATCH_NOCASE);
    result = FALSE;
    type = (findData->flags & PATTERN_MASK);
    pattern = Tcl_GetString(findData->name);
    switch (type) {
        case 0:
	case PATTERN_EXACT:
	    if (nocase) {
                result = (strcasecmp(string, pattern) == 0);
             } else {
                result = (strcmp(string, pattern) == 0);
            }
	    break;
	    
	case PATTERN_GLOB:
            result = (Tcl_StringCaseMatch(string, pattern, 1) == 1);
	    break;
		    
	case PATTERN_REGEXP:
            if (nocase) {
                string = Blt_Strdup(string);
                strtolower(string);
            }
            obj = Tcl_NewStringObj(string, -1);
	    result = (Tcl_RegExpMatchObj((Tcl_Interp *)NULL, obj, findData->name) == 1); 
	    Tcl_DecrRefCount(obj);
            if (nocase) {
	       Blt_Free(string);
            }
	    break;
	case PATTERN_INLIST:
            if (Tcl_ListObjGetElements(NULL, findData->name, &objc, &objv) != TCL_OK) {
                return 1;
            }
            for (i = 0; i < objc; i++) {
                pattern = Tcl_GetString(objv[i]);
                if (!nocase) {
                    if (strcmp(string, pattern) == 0) {
                        return 1;
                    }
                } else {
                    if (strcasecmp(string, pattern) == 0) {
                        return 1;
                    }
                }
            }
            
	    break;
    }
    return result;
}


static void
PercentSubst(
    FindData *dataPtr,
    Blt_TreeNode node,
    char *command,
    Tcl_DString *resultPtr)
{
    register char *last, *p;
    Tcl_DString dString;
    Blt_TreeKey key;
    TreeCmd *cmdPtr = dataPtr->cmdPtr;
    Blt_TreeKeySearch cursor;
    Tcl_Obj *valueObjPtr;
    unsigned int inode = node->inode;
    int one = (command[0] == '%' && strlen(command)==2);

    /*
     * Get the full path name of the node, in case we need to
     * substitute for it.
     */
    Tcl_DStringInit(&dString);
    Tcl_DStringInit(resultPtr);
    /* Append the widget name and the node .t 0 */
    for (last = p = command; *p != '\0'; p++) {
	if (*p == '%') {
	    char *string;
	    char buf[3];

	    if (p > last) {
		*p = '\0';
		Tcl_DStringAppend(resultPtr, last, -1);
		*p = '%';
	    }
	    switch (*(p + 1)) {
	    case '%':		/* Percent sign */
		string = "%";
		break;
	    case 'W':		/* Tree name */
                Tcl_DStringFree(&dString);
                Tcl_DStringInit(&dString);
                string = dataPtr->cmdPtr->tree->treeObject->name;
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'P':		/* Tree node path */
                string = Blt_TreeNodePath(node, &dString);
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'T':	  /* Tag path form. */
                string = Blt_TreeNodePathStr(node, &dString, ".", ".");
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'R':	  /* Root path form. */
                string = Blt_TreeNodePathStr(node, &dString, "root->", "->");
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'r':	  /* 0 root path form. */
                string = Blt_TreeNodePathStr(node, &dString, "0->", "->");
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'D':		/* The full value */
                Tcl_DStringSetLength(&dString, 0);
                for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &cursor);
                    key != NULL; 
                    key = Blt_TreeNextKey(cmdPtr->tree, &cursor)) {
                    if (Blt_TreeGetValue((Tcl_Interp *)NULL, cmdPtr->tree, node,
                        key, &valueObjPtr) == TCL_OK) {
                            
                        Tcl_DStringAppendElement(&dString, key);
                        Tcl_DStringAppendElement(&dString, Tcl_GetString(valueObjPtr));
                    }
                    if (Blt_TreeNodeDeleted(node) || node->inode != inode) {
                        break;
                    }
                }	    
                string = Tcl_DStringValue(&dString);
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'p':		/* The label */
                string = (node->label ? node->label : "");
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case 'V':		/* The value or label */
                if (dataPtr->keyList == NULL || ((string = GetFirstKey(dataPtr->keyList)) == NULL)) {
                    string = (node->label ? node->label : "");
                } else {
                    Tcl_Obj *objPtr;
                    if  (Blt_TreeGetValue(dataPtr->cmdPtr->interp, cmdPtr->tree, node, string, &objPtr) == TCL_OK) {
                        string = Tcl_GetString(objPtr);
                    } else {
                        string = "";
                    }
                    if (string == NULL) { string = ""; }
                }
                if (!one) {
                    Tcl_DStringAppendElement(resultPtr, string);
                    string = NULL;
                }
                break;
	    case '#':		/* Node identifier */
                string = Blt_Itoa(Blt_TreeNodeId(node));
                break;
	    default:
		if (*(p + 1) == '\0') {
		    p--;
		}
		buf[0] = *p, buf[1] = *(p + 1), buf[2] = '\0';
		string = buf;
		break;
	    }
            if (string) {
                Tcl_DStringAppend(resultPtr, string, -1);
            }
	    p++;
	    last = p + 1;
	}
    }
    if (p > last) {
	*p = '\0';
	Tcl_DStringAppend(resultPtr, last, -1);
    }
    Tcl_DStringFree(&dString);
}


/*
 *----------------------------------------------------------------------
 *
 * MatchNodeProc --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
MatchNodeProc(Blt_TreeNode node, ClientData clientData, int order)
{
    FindData *dataPtr = clientData;
    Tcl_DString dString;
    TreeCmd *cmdPtr = dataPtr->cmdPtr;
    Tcl_Interp *interp = dataPtr->cmdPtr->interp;
    int result, invert, cntf;
    unsigned int inode;
    int strict, isnull;
    char *curValue = NULL;
    Tcl_Obj *curObj = NULL, *resObjPtr = NULL;
    
    isnull = ((dataPtr->flags&MATCH_ISNULL) != 0);
    strict = ((dataPtr->flags&MATCH_STRICT) != 0);
    cntf = ((dataPtr->flags & MATCH_COUNT) != 0);
    invert = (dataPtr->flags & MATCH_INVERT) ? TRUE : FALSE;
    if ((dataPtr->flags & MATCH_NOTOP) && node == dataPtr->startNode) {
        return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_FIXED) && 
        (node->flags & TREE_NODE_FIXED_FIELDS) == 0) {
        return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_NOTFIXED) && 
        (node->flags & TREE_NODE_FIXED_FIELDS) != 0) {
        return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_ISMODIFIED) && 
        (node->flags & TREE_NODE_UNMODIFIED) != 0) {
        return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_LEAFONLY) && (!Blt_TreeIsLeaf(node))) {
	return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_TREEONLY) && (Blt_TreeIsLeaf(node))) {
        return TCL_OK;
    }
    if ((dataPtr->maxDepth >= 0) &&
	(Blt_TreeNodeDepth(cmdPtr->tree, node) > dataPtr->maxDepth )) {
	return TCL_OK;
    }
    if ((dataPtr->minDepth >= 0) &&
        (Blt_TreeNodeDepth(cmdPtr->tree, node) < dataPtr->minDepth)) {
	return TCL_OK;
    }
    if ((dataPtr->depth >= 0) &&
        (Blt_TreeNodeDepth(cmdPtr->tree, node) != dataPtr->depth)) {
	return TCL_OK;
    }
    if ((dataPtr->withTag != NULL) &&
        !Blt_TreeHasTag(cmdPtr->tree, node, Tcl_GetString(dataPtr->withTag))) {
        return TCL_OK;
    }
    if ((dataPtr->withoutTag != NULL) &&
        Blt_TreeHasTag(cmdPtr->tree, node, Tcl_GetString(dataPtr->withoutTag))) {
        return TCL_OK;
    }
    if ((dataPtr->keyCount >= 0) &&
        Blt_TreeCountKeys(cmdPtr->tree, node) != dataPtr->keyCount) {
        return TCL_OK;
    } 

    result = TRUE;
    Tcl_DStringInit(&dString);
    inode = node->inode;
    if (dataPtr->subKey != NULL) {
        int empty;
        
        empty = (Blt_TreeGetValue(interp, cmdPtr->tree, node, dataPtr->subKey, &curObj)
            == TCL_OK);
        if (empty == isnull) {
            Tcl_DStringFree(&dString);
            return TCL_OK;
        }
        if (curObj != NULL && dataPtr->name) {
            curValue = (curObj == NULL) ? "" : Tcl_GetString(curObj);
            result = ComparePattern(dataPtr, curValue);
        }
    } else if (dataPtr->keyList != NULL) {
	Blt_TreeKey key;
	Blt_TreeKeySearch cursor;

	result = FALSE;		/* It's false if no keys match. */
	for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &cursor);
	    key != NULL; key = Blt_TreeNextKey(cmdPtr->tree, &cursor)) {
             
            curObj = NULL;
	    result = ComparePatternList(dataPtr->keyList, key, 0);
	    if (!result) {
		continue;
	    }
            if ((dataPtr->flags & (MATCH_ARRAY))) {
                Blt_HashTable *tablePtr;
                int res;
                
                res = TCL_ERROR;
                if (Blt_TreeGetValue(interp, cmdPtr->tree, node, key, &curObj)
                    == TCL_OK) {
                    res = Blt_GetArrayFromObj(NULL, curObj, &tablePtr);
                }
                if (Blt_TreeNodeDeleted(node) || node->inode != inode) {
                    Tcl_DStringFree(&dString);
                    return TCL_OK;
                }
                if ((dataPtr->flags & MATCH_ARRAY) && res != TCL_OK) {
                    result = FALSE;
                    continue;
                }
            }
	    if (dataPtr->name != NULL) {

		if (Blt_TreeGetValue(interp, cmdPtr->tree, node, key, &curObj) != TCL_OK) {
                    Tcl_DStringFree(&dString);
                    return TCL_ERROR;
                }
		curValue = (curObj == NULL) ? "" : Tcl_GetString(curObj);
		result = ComparePattern(dataPtr, curValue);
		if (!result) {
		    continue;
		}
            }
	    break;
	}
    } else if (dataPtr->name != NULL) {	    

	if (dataPtr->flags & MATCH_PATHNAME) {
	    curValue = GetNodePath(cmdPtr, Blt_TreeRootNode(cmdPtr->tree),
		 node, FALSE, &dString);
	} else {
	    curValue = Blt_TreeNodeLabel(node);
	}
	result = ComparePattern(dataPtr, curValue);
    }
    Tcl_DStringFree(&dString);

    if (result != invert) {
	Tcl_Obj *objPtr;

	if (dataPtr->addTag != NULL) {
	    if (AddTag(cmdPtr, node, dataPtr->addTag) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
        if (dataPtr->eval != NULL && dataPtr->eval[0] != 0) {
            if (dataPtr->execVar != NULL) {
                Tcl_Obj *intObj;
                intObj = Tcl_NewIntObj(node->inode);
                Tcl_IncrRefCount(intObj);
                if (Tcl_ObjSetVar2(interp, dataPtr->execVar, NULL, intObj, 0) == NULL) {
                    Tcl_DecrRefCount(intObj);
                    return TCL_ERROR;
                }
                result = Tcl_EvalObjEx(interp, dataPtr->execObj, 0);
                Tcl_DecrRefCount(intObj);
               /* if (Blt_TreeNodeDeleted(node) || node->inode != inode) {
                    return TCL_ERROR;
                }*/
                if (cmdPtr->delete) {
                    return TCL_ERROR;
                }
            } else {
                Tcl_DString sString;
                PercentSubst( dataPtr, node, dataPtr->eval, &sString);
                result = Tcl_EvalEx(interp, Tcl_DStringValue(&sString), -1, TCL_EVAL_DIRECT);
                Tcl_DStringFree(&sString);
                /*if (Blt_TreeNodeDeleted(node) || node->inode != inode) {
                    return TCL_ERROR;
                }*/
                if (cmdPtr->delete) {
                    return TCL_ERROR;
                }
            }
            if (result != TCL_CONTINUE  && result != TCL_OK) {
                return result;
            }
            if (result == TCL_OK && cntf == 0) {
                resObjPtr =  Tcl_GetObjResult(interp);
            } else {
                return TCL_OK;
            }
        } else {
            if (dataPtr->retKey != NULL) {
                Tcl_Obj *oPtr;
                
                oPtr = NULL;
                if (dataPtr->retKey[0] == 0) {
                    objPtr = Tcl_NewStringObj(Blt_TreeNodeLabel(node), -1);
                } else {
                    if (dataPtr->iskey) {
                        if (Blt_TreeGetValue(strict?interp:NULL, cmdPtr->tree, node, dataPtr->retKey, &oPtr) != TCL_OK) {
                            if (strict) {
                                return TCL_ERROR;
                            }
                        }
                    }
                    if (oPtr != NULL) {
                        if (!cntf) {
                            objPtr = Tcl_NewStringObj(Tcl_GetString(oPtr),-1);
                        }
                    } else if (cntf == 0 && dataPtr->retKey[0] == '%' &&
                         strlen(dataPtr->retKey)>=2) {
                        Tcl_DString sString;
                        PercentSubst( dataPtr, node, dataPtr->retKey, &sString);
                        objPtr = Tcl_NewStringObj(Tcl_DStringValue(&sString), -1);
                        Tcl_DStringFree(&sString);
                    } else if (!cntf) {
                        objPtr = Tcl_NewStringObj("" ,-1);
                    }
                }
            } else if (!cntf) {
                objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
            }
            if (!cntf) {
                resObjPtr = objPtr;
            }
        }
	if (dataPtr->objv != NULL) {
	    int ai;
	    char **p;
            objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
            Tcl_DecrRefCount(dataPtr->objv[dataPtr->objc - 1]);
            Tcl_IncrRefCount(objPtr);
            dataPtr->objv[dataPtr->objc - 1] = objPtr;
            p = dataPtr->cmdArgs;
            for (ai = 0; ai < dataPtr->argc && *p != NULL; ai++, p++) {
                if (Blt_TreeGetValue(interp, cmdPtr->tree, node, *p, &objPtr) != TCL_OK) {
                    objPtr = Tcl_NewStringObj("", -1);
                }
                Tcl_DecrRefCount(dataPtr->objv[dataPtr->objc + ai]);
                Tcl_IncrRefCount(objPtr);
                dataPtr->objv[dataPtr->objc + ai] = objPtr;
            }
	    result = Tcl_EvalObjv(interp, dataPtr->objc + dataPtr->argc, dataPtr->objv, 0);
            if (cmdPtr->delete) {
                return TCL_ERROR;
            }
            if (result == TCL_RETURN) {
                int eRes;
                if (Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp),
                    &eRes) != TCL_OK) {
                    return TCL_ERROR;
                }
                result = TCL_OK;
                if (eRes == 0) {
                    resObjPtr = NULL;
                } else if (cntf) {
                    goto finishNode;
                }
            } else {
                resObjPtr = NULL;
            }
            
	    if (result != TCL_OK) {
		return result;
	    }
	}
	if (resObjPtr != NULL) {
            Tcl_ListObjAppendElement(interp, dataPtr->listObjPtr, resObjPtr);
            finishNode:
            dataPtr->nMatches++;
            if ((dataPtr->maxMatches > 0) && 
            (dataPtr->nMatches >= dataPtr->maxMatches)) {
                return TCL_BREAK;
            }
        }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ApplyNodeProc --
 *
 *---------------------------------------------------------------------- 
 */
static int
ApplyNodeProc(Blt_TreeNode node, ClientData clientData, int order)
{
    ApplyData *dataPtr = clientData;
    TreeCmd *cmdPtr = dataPtr->cmdPtr;
    Tcl_Interp *interp = cmdPtr->interp;
    int invert, result;
    Tcl_DString dString;

    if ((dataPtr->flags & MATCH_LEAFONLY) && (!Blt_TreeIsLeaf(node))) {
	return TCL_OK;
    }
    if ((dataPtr->flags & MATCH_TREEONLY) && (Blt_TreeIsLeaf(node))) {
        return TCL_OK;
    }
    if ((dataPtr->maxDepth >= 0) &&
	(dataPtr->maxDepth < Blt_TreeNodeDepth(cmdPtr->tree, node))) {
	return TCL_OK;
    }
    Tcl_DStringInit(&dString);
    result = TRUE;
    if (dataPtr->keyList != NULL) {
	Blt_TreeKey key;
	Blt_TreeKeySearch cursor;

	result = FALSE;		/* It's false if no keys match. */
	for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &cursor);
	     key != NULL; key = Blt_TreeNextKey(cmdPtr->tree, &cursor)) {
	    
	    result = ComparePatternList(dataPtr->keyList, key, 0);
	    if (!result) {
		continue;
	    }
	    if (dataPtr->patternList != NULL) {
		char *string;
		Tcl_Obj *objPtr = NULL;

		if (Blt_TreeGetValue(interp, cmdPtr->tree, node, key, &objPtr) != TCL_OK) {
                    return TCL_ERROR;
                }
                string = (objPtr == NULL) ? "" : Tcl_GetString(objPtr);
		result = ComparePatternList(dataPtr->patternList, string, 
			 dataPtr->flags & MATCH_NOCASE);
		if (!result) {
		    continue;
		}
	    }
	    break;
	}
    } else if (dataPtr->patternList != NULL) {	    
	char *string;

	if (dataPtr->flags & MATCH_PATHNAME) {
	    string = GetNodePath(cmdPtr, Blt_TreeRootNode(cmdPtr->tree),
		 node, FALSE, &dString);
	} else {
	    string = Blt_TreeNodeLabel(node);
	}
	result = ComparePatternList(dataPtr->patternList, string, 
		dataPtr->flags & MATCH_NOCASE);		     
    }
    Tcl_DStringFree(&dString);
    if ((dataPtr->withTag != NULL) && 
	(!Blt_TreeHasTag(cmdPtr->tree, node, dataPtr->withTag))) {
	result = FALSE;
    }
    invert = (dataPtr->flags & MATCH_INVERT) ? 1 : 0;
    if (result != invert) {
	Tcl_Obj *objPtr;
	int res = TCL_OK;

	objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
	if (order == TREE_PREORDER) {
	    dataPtr->preObjv[dataPtr->preObjc - 1] = objPtr;
	    res = Tcl_EvalObjv(interp, dataPtr->preObjc, dataPtr->preObjv, 0);
	} else if (order == TREE_POSTORDER) {
	    dataPtr->postObjv[dataPtr->postObjc - 1] = objPtr;
	    res = Tcl_EvalObjv(interp, dataPtr->postObjc, dataPtr->postObjv,0);
	}
        if (cmdPtr->delete) {
            return TCL_ERROR;
        }
	return res;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReleaseTreeObject --
 *
 *---------------------------------------------------------------------- 
 */
static void
ReleaseTreeObject(TreeCmd *cmdPtr)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    TraceInfo *tracePtr;
    NotifyInfo *notifyPtr;
    int i;

    Blt_TreeReleaseToken(cmdPtr->tree);
    /* 
     * When the tree token is released, all the traces and
     * notification events are automatically removed.  But we still
     * need to clean up the bookkeeping kept for traces. Clear all
     * the tags and trace information.  
     */
    for (hPtr = Blt_FirstHashEntry(&(cmdPtr->traceTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	tracePtr = Blt_GetHashValue(hPtr);
	Blt_Free(tracePtr);
    }
    for (hPtr = Blt_FirstHashEntry(&(cmdPtr->notifyTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	notifyPtr = Blt_GetHashValue(hPtr);
	for (i = 0; i < notifyPtr->objc - 2; i++) {
	    Tcl_DecrRefCount(notifyPtr->objv[i]);
	}
	Blt_Free(notifyPtr->objv);
	Blt_Free(notifyPtr);
    }
    cmdPtr->tree = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeTraceProc --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TreeTraceProc(
    ClientData clientData,
    Tcl_Interp *interp,
    Blt_TreeNode node,		/* Node that has just been updated. */
    Blt_TreeKey key,		/* Field that's updated. */
    unsigned int flags)
{
    TraceInfo *tracePtr = clientData; 
    Tcl_DString dsCmd, dsName;
    char string[5];
    char *qualName;
    int result;

    Tcl_DStringInit(&dsCmd);
    Tcl_DStringAppend(&dsCmd, tracePtr->command, -1);
    Tcl_DStringInit(&dsName);
    qualName = Blt_GetQualifiedName(
	Blt_GetCommandNamespace(interp, tracePtr->cmdPtr->cmdToken), 
	Tcl_GetCommandName(interp, tracePtr->cmdPtr->cmdToken), &dsName);
    Tcl_DStringAppendElement(&dsCmd, qualName);
    Tcl_DStringFree(&dsName);
    if (node != NULL) {
	Tcl_DStringAppendElement(&dsCmd, Blt_Itoa(Blt_TreeNodeId(node)));
    } else {
	Tcl_DStringAppendElement(&dsCmd, "");
    }
    Tcl_DStringAppendElement(&dsCmd, key);
    PrintTraceFlags(flags, string);
    Tcl_DStringAppendElement(&dsCmd, string);
    result = Tcl_Eval(interp, Tcl_DStringValue(&dsCmd));
    Tcl_DStringFree(&dsCmd);
    if (tracePtr->cmdPtr && tracePtr->cmdPtr->delete) {
        return TCL_ERROR;
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeEventProc --
 *
 *---------------------------------------------------------------------- 
 */
static int
TreeEventProc(ClientData clientData, Blt_TreeNotifyEvent *eventPtr)
{
    TreeCmd *cmdPtr = clientData; 
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    NotifyInfo *notifyPtr;
    Blt_TreeNode node;
    char *string;

    switch (eventPtr->type) {
    case TREE_NOTIFY_CREATE:
	string = "-create";
	break;

    case TREE_NOTIFY_DELETE:
	node = Blt_TreeGetNode(cmdPtr->tree, eventPtr->inode);
	if (node != NULL) {
	    Blt_TreeClearTags(cmdPtr->tree, node);
	}
	string = "-delete";
	break;

    case TREE_NOTIFY_GET:
	string = "-get";
	break;
	
    case TREE_NOTIFY_MOVE:
	string = "-move";
	break;

    case TREE_NOTIFY_MOVEPOST:
	string = "-movepost";
	break;

    case TREE_NOTIFY_INSERT:
	string = "-insert";
	break;

    case TREE_NOTIFY_SORT:
	string = "-sort";
	break;

    case TREE_NOTIFY_RELABEL:
	string = "-relabel";
	break;

    case TREE_NOTIFY_RELABELPOST:
	string = "-relabelpost";
	break;

    default:
	/* empty */
	string = "???";
	break;
    }	

    for (hPtr = Blt_FirstHashEntry(&(cmdPtr->notifyTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	notifyPtr = Blt_GetHashValue(hPtr);
	if (notifyPtr->mask & eventPtr->type) {
	    int result, mkAct = 0;
	    Tcl_Obj *flagObjPtr, *nodeObjPtr;

	    flagObjPtr = Tcl_NewStringObj(string, -1);
	    nodeObjPtr = Tcl_NewIntObj(eventPtr->inode);
	    Tcl_IncrRefCount(flagObjPtr);
	    Tcl_IncrRefCount(nodeObjPtr);
	    notifyPtr->objv[notifyPtr->objc - 1] = flagObjPtr;
	    notifyPtr->objv[notifyPtr->objc - 2] = nodeObjPtr;
            if ((notifyPtr->mask&TREE_NOTIFY_TRACEACTIVE)) {
                node = Blt_TreeGetNode(cmdPtr->tree, eventPtr->inode);
                if ((node->flags&TREE_TRACE_ACTIVE) == 0) {
                    node->flags |= TREE_TRACE_ACTIVE;
                    mkAct = 1;
                }
            }

	    result = Tcl_EvalObjv(cmdPtr->interp, notifyPtr->objc, 
		notifyPtr->objv, 0);
	    if (mkAct) {
	        node->flags &= ~TREE_TRACE_ACTIVE;
            }
	    Tcl_DecrRefCount(nodeObjPtr);
	    Tcl_DecrRefCount(flagObjPtr);
            if (cmdPtr->delete) {
                return TCL_ERROR;
            }
            if (result != TCL_OK) {
		/*Tcl_BackgroundError(cmdPtr->interp); */
		return result;
	    }
	    Tcl_ResetResult(cmdPtr->interp);
	}
    }
    return TCL_OK;
}


/* Tree command operations. */

/*
 *----------------------------------------------------------------------
 *
 * ApplyOp --
 *
 * t0 apply root -precommand {command} -postcommand {command}
 *
 *---------------------------------------------------------------------- 
 */
static int
ApplyOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    int result;
    Blt_TreeNode node;
    int i;
    Tcl_Obj **objArr;
    int count;
    ApplyData data;
    int order;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    memset(&data, 0, sizeof(data));
    data.maxDepth = -1;
    data.cmdPtr = cmdPtr;
    
    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, applySwitches, objc - 3, objv + 3, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	return TCL_ERROR;
    }
    order = 0;
    if (data.flags & MATCH_NOCASE) {
	Blt_ListNode listNode;

	for (listNode = Blt_ListFirstNode(data.patternList); listNode != NULL;
	     listNode = Blt_ListNextNode(listNode)) {
	    strtolower((char *)Blt_ListGetKey(listNode));
	}
    }
    if (data.preCmd != NULL) {
	char **p;

	count = 0;
	for (p = data.preCmd; *p != NULL; p++) {
	    count++;
	}
	objArr = Blt_Calloc((count + 1), sizeof(Tcl_Obj *));
	for (i = 0; i < count; i++) {
	    objArr[i] = Tcl_NewStringObj(data.preCmd[i], -1);
	    Tcl_IncrRefCount(objArr[i]);
	}
	data.preObjv = objArr;
	data.preObjc = count + 1;
	order |= TREE_PREORDER;
    }
    if (data.postCmd != NULL) {
	char **p;

	count = 0;
	for (p = data.postCmd; *p != NULL; p++) {
	    count++;
	}
	objArr = Blt_Calloc((count + 1), sizeof(Tcl_Obj *));
	for (i = 0; i < count; i++) {
	    objArr[i] = Tcl_NewStringObj(data.postCmd[i], -1);
	    Tcl_IncrRefCount(objArr[i]);
	}
	data.postObjv = objArr;
	data.postObjc = count + 1;
	order |= TREE_POSTORDER;
    }
    result = Blt_TreeApplyDFS(node, ApplyNodeProc, &data, order);
    if (data.preObjv != NULL) {
	for (i = 0; i < (data.preObjc - 1); i++) {
	    Tcl_DecrRefCount(data.preObjv[i]);
	}
	Blt_Free(data.preObjv);
    }
    if (data.postObjv != NULL) {
	for (i = 0; i < (data.postObjc - 1); i++) {
	    Tcl_DecrRefCount(data.postObjv[i]);
	}
	Blt_Free(data.postObjv);
    }
    Blt_FreeSwitches(interp, applySwitches, (char *)&data, 0);
    if (result == TCL_ERROR) {
	return TCL_ERROR;
    }
    return TCL_OK;
}


/*ARGSUSED*/
static int
AncestorOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    int d1, d2, minDepth;
    register int i;
    Blt_TreeNode ancestor, node1, node2;

    if ((GetNode(cmdPtr, objv[2], &node1) != TCL_OK) ||
	(GetNode(cmdPtr, objv[3], &node2) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (node1 == node2) {
	ancestor = node1;
	goto done;
    }
    d1 = Blt_TreeNodeDepth(cmdPtr->tree, node1);
    d2 = Blt_TreeNodeDepth(cmdPtr->tree, node2);
    minDepth = MIN(d1, d2);
    if (minDepth == 0) {	/* One of the nodes is root. */
	ancestor = Blt_TreeRootNode(cmdPtr->tree);
	goto done;
    }
    /* 
     * Traverse back from the deepest node, until the both nodes are
     * at the same depth.  Check if the ancestor node found is the
     * other node.  
     */
    for (i = d1; i > minDepth; i--) {
	node1 = Blt_TreeNodeParent(node1);
    }
    if (node1 == node2) {
	ancestor = node2;
	goto done;
    }
    for (i = d2; i > minDepth; i--) {
	node2 = Blt_TreeNodeParent(node2);
    }
    if (node2 == node1) {
	ancestor = node1;
	goto done;
    }

    /* 
     * First find the mutual ancestor of both nodes.  Look at each
     * preceding ancestor level-by-level for both nodes.  Eventually
     * we'll find a node that's the parent of both ancestors.  Then
     * find the first ancestor in the parent's list of subnodes.  
     */
    for (i = minDepth; i > 0; i--) {
	node1 = Blt_TreeNodeParent(node1);
	node2 = Blt_TreeNodeParent(node2);
	if (node1 == node2) {
	    ancestor = node2;
	    goto done;
	}
    }
    Tcl_AppendResult(interp, "unknown ancestor", (char *)NULL);
    return TCL_ERROR;
 done:
    Tcl_SetIntObj(Tcl_GetObjResult(interp), Blt_TreeNodeId(ancestor));
    return TCL_OK;
}

#ifndef NO_ATTACHCMD
/*
 *----------------------------------------------------------------------
 *
 * AttachOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
AttachOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    if (objc >= 3) {
	CONST char *treeName;
	CONST char *name;
	Blt_Tree token;
	Tcl_Namespace *nsPtr;
	Tcl_DString dString;
	int result, notag = 0;

        if (objc == 3) {
            treeName = Tcl_GetString(objv[2]);
        } else {
            if (!strcmp("-notags", Tcl_GetString(objv[2]))) {
                Tcl_AppendResult(interp, "expected \"-notags\"", (char *)NULL);
                    return TCL_ERROR;
            }
	    treeName = Tcl_GetString(objv[3]);
	    notag = 1;
	}
	if (Blt_ParseQualifiedName(interp, treeName, &nsPtr, &name) 
	    != TCL_OK) {
	    Tcl_AppendResult(interp, "can't find namespace in \"", treeName, 
			     "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	if (nsPtr == NULL) {
	    nsPtr = Tcl_GetCurrentNamespace(interp);
	}
	treeName = Blt_GetQualifiedName(nsPtr, name, &dString);
         if (notag) {
             result = Blt_TreeGetToken(interp, treeName, &token);
         } else {
             result = Blt_TreeGetTokenTag(interp, treeName, &token);
	}
	Tcl_DStringFree(&dString);
	if (result != TCL_OK) {
	    return TCL_ERROR;
	}
        Blt_TreeNotifyAttach(token);
	ReleaseTreeObject(cmdPtr);
	cmdPtr->tree = token;
    }
    Tcl_SetResult(interp, Blt_TreeName(cmdPtr->tree), TCL_VOLATILE);
    return TCL_OK;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * ChildrenOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
ChildrenOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int labels = 0;
    char *str;
    
    str = Tcl_GetString(objv[2]);
    if (!strcmp(str, "-labels")) {
        labels = 1;
        objc--;
        objv++;
    }
   
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc == 3) {
	Tcl_Obj *objPtr, *listObjPtr;

	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
	for (node = Blt_TreeFirstChild(node); node != NULL;
	     node = Blt_TreeNextSibling(node)) {
	    if (labels) {
                objPtr = Tcl_NewStringObj(node->label, -1);
            } else {
                objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
            }
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
	Tcl_SetObjResult(interp, listObjPtr);
    } else if (objc == 4) {
	int childPos;
	int inode, count;
	
	/* Get the node at  */
	if (Tcl_GetIntFromObj(interp, objv[3], &childPos) != TCL_OK) {
		return TCL_ERROR;
	}
	count = 0;
	inode = -1;
	for (node = Blt_TreeFirstChild(node); node != NULL;
	     node = Blt_TreeNextSibling(node)) {
	    if (count == childPos) {
	       if (labels) {
                    Tcl_SetObjResult(interp, Tcl_NewStringObj(node->label, -1));
                    return TCL_OK;
                }
		inode = Blt_TreeNodeId(node);
		break;
	    }
	    count++;
	}
	Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
	return TCL_OK;
    } else if (objc == 5) {
	int firstPos, lastPos, count;
	Tcl_Obj *objPtr, *listObjPtr;
	char *string;

	firstPos = lastPos = Blt_TreeNodeDegree(node) - 1;
	string = Tcl_GetString(objv[3]);
	if ((strcmp(string, "end") != 0) &&
	    (Tcl_GetIntFromObj(interp, objv[3], &firstPos) != TCL_OK)) {
	    return TCL_ERROR;
	}
	string = Tcl_GetString(objv[4]);
	if ((strcmp(string, "end") != 0) &&
	    (Tcl_GetIntFromObj(interp, objv[4], &lastPos) != TCL_OK)) {
	    return TCL_ERROR;
	}

	count = 0;
	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
	for (node = Blt_TreeFirstChild(node); node != NULL;
	     node = Blt_TreeNextSibling(node)) {
	    if ((count >= firstPos) && (count <= lastPos)) {
	       if (labels) {
                    objPtr = Tcl_NewStringObj(node->label, -1);
                } else {
                    objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
                }
		Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	    }
	    count++;
	}
	Tcl_SetObjResult(interp, listObjPtr);
    }
    return TCL_OK;
}



static Blt_TreeNode 
CopyNodes(
    CopyData *dataPtr,
    Blt_TreeNode node,		/* Node to be copied. */
    Blt_TreeNode parent)	/* New parent for the copied node. */
{
    Blt_TreeNode newNode;	/* Newly created copy. */
    char *label;
    int isNew = 0;

    newNode = NULL;
    label = Blt_TreeNodeLabel(node);
    if (dataPtr->flags & COPY_OVERWRITE) {
	newNode = Blt_TreeFindChild(parent, label);
    }
    if (newNode == NULL) {	/* Create node in new parent. */
        isNew = 1;
	newNode = Blt_TreeCreateNode(dataPtr->destTree, parent, label, -1);
         if (newNode == NULL) {
             return NULL;
         }
     }
    /* Copy the data fields. */
    {
	Blt_TreeKey key;
	Tcl_Obj *objPtr;
	Blt_TreeKeySearch cursor;

	for (key = Blt_TreeFirstKey(dataPtr->srcTree, node, &cursor); 
	     key != NULL; key = Blt_TreeNextKey(dataPtr->srcTree, &cursor)) {
	    if (Blt_TreeGetValueByKey((Tcl_Interp *)NULL, dataPtr->srcTree, 
			node, key, &objPtr) == TCL_OK) {
		Blt_TreeSetValueByKey((Tcl_Interp *)NULL, dataPtr->destTree, 
			newNode, Blt_TreeKeyGet(NULL, dataPtr->destTree->treeObject, key), objPtr);
	    } 
	}
    }
    /* Add tags to destination tree command. */
    if ((dataPtr->destPtr != NULL) && (dataPtr->flags & COPY_TAGS)) {
	Blt_TreeTagEntry *tPtr;
	Blt_HashEntry *hPtr, *h2Ptr;
	Blt_HashSearch cursor;

	for (hPtr = Blt_TreeFirstTag(dataPtr->srcTree, &cursor); 
		hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	    tPtr = Blt_GetHashValue(hPtr);
	    h2Ptr = Blt_FindHashEntry(&tPtr->nodeTable, (char *)node);
	    if (h2Ptr != NULL) {
		if (AddTag(dataPtr->destPtr, newNode, tPtr->tagName)!= TCL_OK) {
		    return NULL;
		}
	    }
	}
    }
    if (isNew && Blt_TreeInsertPost(dataPtr->destTree, newNode) == NULL) {
        DeleteNode(dataPtr->srcPtr, newNode);
        return NULL;
    }
    if (dataPtr->flags & COPY_RECURSE) {
	Blt_TreeNode child;

	for (child = Blt_TreeFirstChild(node); child != NULL;
	     child = Blt_TreeNextSibling(child)) {
	    if (CopyNodes(dataPtr, child, newNode) == NULL) {
		return NULL;
	    }
	}
    }
    return newNode;
}

/*
 *----------------------------------------------------------------------
 *
 * CopyOp --
 * 
 *	t0 copy node tree node 
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
CopyOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TreeCmd *srcPtr, *destPtr;
    Blt_Tree srcTree, destTree;
    Blt_TreeNode srcNode, destNode, tNode;
    CopyData data;
    int nArgs, nSwitches;
    char *string;
    Blt_TreeNode root;
    register int i;

    if (GetNode(cmdPtr, objv[2], &srcNode) != TCL_OK) {
	return TCL_ERROR;
    }
    srcTree = cmdPtr->tree;
    srcPtr = cmdPtr;

    /* Find the first switch. */
    for(i = 3; i < objc; i++) {
	string = Tcl_GetString(objv[i]);
	if (string[0] == '-') {
	    break;
	}
    }
    nArgs = i - 2;
    nSwitches = objc - i;
    if (nArgs < 2) {
	string = Tcl_GetString(objv[0]);
	Tcl_AppendResult(interp, "must specify source and destination nodes: ",
			 "should be \"", string, 
			 " copy srcNode ?destTree? destNode ?switches?", 
			 (char *)NULL);
	return TCL_ERROR;
	
    }
    if (nArgs == 3) {
	/* 
	 * The tree name is either the name of a tree command (first choice)
	 * or an internal tree object.  
	 */
	string = Tcl_GetString(objv[3]);
	destPtr = GetTreeCmd(cmdPtr->dataPtr, interp, string);
	if (destPtr != NULL) {
	    destTree = destPtr->tree;
	} else {
	    /* Try to get the tree as an internal tree data object. */
	    if (Blt_TreeGetToken(interp, string, &destTree) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
	objv++;
    } else {
	destPtr = cmdPtr;
	destTree = destPtr->tree;
    }

    root = NULL;
    if (destPtr == NULL) {
	if (GetForeignNode(interp, destTree, objv[3], &destNode) != TCL_OK) {
	    goto error;
	}
    } else {
	if (GetNode(destPtr, objv[3], &destNode) != TCL_OK) {
	    goto error;
	}
    }
    if (srcNode == destNode) {
	Tcl_AppendResult(interp, "source and destination nodes are the same",
		 (char *)NULL);	     
	goto error;
    }
    memset((char *)&data, 0, sizeof(data));
    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, copySwitches, nSwitches, objv + 4, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	goto error;
    }
    if (data.flags & COPY_REVERSE) {
        data.destPtr = srcPtr;
        data.destTree = srcTree;
        data.srcPtr = destPtr;
        data.srcTree = destTree;
        tNode = srcNode;
        srcNode = destNode;
        destNode = tNode;
    } else {
        data.destPtr = destPtr;
        data.destTree = destTree;
        data.srcPtr = srcPtr;
        data.srcTree = srcTree;
    }

    if ((srcTree == destTree) && (data.flags & COPY_RECURSE) &&
	(Blt_TreeIsAncestor(srcNode, destNode))) {    
	Tcl_AppendResult(interp, "can't make cyclic copy: ",
			 "source node is an ancestor of the destination",
			 (char *)NULL);	     
	goto error;
    }

    /* Copy nodes to destination. */
    root = CopyNodes(&data, srcNode, destNode);
    if (root != NULL) {
	Tcl_Obj *objPtr;

	objPtr = Tcl_NewIntObj(Blt_TreeNodeId(root));
	if (data.label != NULL) {
	    Blt_TreeRelabelNode(data.destTree, root, data.label);
	}
	Tcl_SetObjResult(interp, objPtr);
    }
 error:
    if (destPtr == NULL) {
	Blt_TreeReleaseToken(destTree);
    }
    return (root == NULL) ? TCL_ERROR : TCL_OK;

}

/*
 *----------------------------------------------------------------------
 *
 * DepthOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
DegreeOp(cmdPtr, interp, objc, objv)
    TreeCmd *cmdPtr;
    Tcl_Interp *interp;
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;
{
    Blt_TreeNode node;
    int degree;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    degree = Blt_TreeNodeDegree(node);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), degree);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteOp --
 *
 *	Deletes one or more nodes from the tree.  Nodes may be
 *	specified by their id (a number) or a tag.
 *	
 *	Tags have to be handled carefully here.  We can't use the
 *	normal GetTaggedNode, NextTaggedNode, etc. routines because
 *	they walk hashtables while we're deleting nodes.  Also,
 *	remember that deleting a node recursively deletes all its
 *	children. If a parent and its children have the same tag, its
 *	possible that the tag list may contain nodes than no longer
 *	exist. So save the node indices in a list and then delete 
 *	then in a second pass.
 *
 *---------------------------------------------------------------------- 
 */
static int
DeleteOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int i, len;
    char *string;
    
    for (i = 2; i < objc; i++) {
	string = Tcl_GetStringFromObj(objv[i], &len);
	if (len == 0) continue;
	if (isdigit(UCHAR(string[0]))) {
             char *cp = string;
             int n, m, vobjc;
             Tcl_Obj **vobjv;
        
             while (isdigit(UCHAR(*cp)) && *cp != 0) {
                 cp++;
             }
             if (*cp == ' ' || *cp == '\t') {
                 if (Tcl_ListObjGetElements(interp, objv[i], &vobjc, &vobjv)
                    != TCL_OK) {
                    return TCL_ERROR;
                 }
                 for (n=0; n<vobjc; n++) {
                     if (Tcl_GetIntFromObj(interp, vobjv[n], &m) != TCL_OK) {
                         return TCL_ERROR;
                     }
                 }
                 for (n=0; n<vobjc; n++) {
                     if (GetNode(cmdPtr, vobjv[n], &node) == TCL_OK) {
                         DeleteNode(cmdPtr, node);
                     } else {
                         Tcl_ResetResult(interp);
                     }
                 }
                 continue;
             }
                 
             if (GetNode(cmdPtr, objv[i], &node) != TCL_OK) {
                 return TCL_ERROR;
             }
             DeleteNode(cmdPtr, node);
	} else {
	    Blt_HashEntry *hPtr;
	    Blt_HashTable *tablePtr;
	    Blt_HashSearch cursor;
	    Blt_Chain *chainPtr;
	    Blt_ChainLink *linkPtr, *nextPtr;
	    int inode;

	    if ((strcmp(string, "all") == 0) || (strcmp(string, "nonroot") == 0)
		|| (strcmp(string, "root") == 0)
		|| (strcmp(string, "rootchildren") == 0)) {
		node = Blt_TreeRootNode(cmdPtr->tree);
		DeleteNode(cmdPtr, node);
		continue;
	    }
	    tablePtr = Blt_TreeTagHashTable(cmdPtr->tree, string);
	    if (tablePtr == NULL) {
		goto error;
	    }
	    /* 
	     * Generate a list of tagged nodes. Save the inode instead
	     * of the node itself since a pruned branch may contain
	     * more tagged nodes.  
	     */
	    chainPtr = Blt_ChainCreate();
	    for (hPtr = Blt_FirstHashEntry(tablePtr, &cursor); 
		hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
		node = Blt_GetHashValue(hPtr);
		Blt_ChainAppend(chainPtr, (ClientData)Blt_TreeNodeId(node));
	    }   
	    /*  
	     * Iterate through this list to delete the nodes.  By
	     * side-effect the tag table is deleted and Uids are
	     * released.  
	     */
	    for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
		 linkPtr = nextPtr) {
		nextPtr = Blt_ChainNextLink(linkPtr);
		inode = (int)Blt_ChainGetValue(linkPtr);
		node = Blt_TreeGetNode(cmdPtr->tree, inode);
		if (node != NULL) {
		    DeleteNode(cmdPtr, node);
		}
	    }
	    Blt_ChainDestroy(chainPtr);
	}
    }
    return TCL_OK;
 error:
    Tcl_AppendResult(interp, "can't find tag or id \"", string, "\" in ", 
		     Blt_TreeName(cmdPtr->tree), (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DepthOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
DepthOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int depth;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    depth = Blt_TreeNodeDepth(cmdPtr->tree, node);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), depth);
    return TCL_OK;
}

/* Build a temporary hash table to speed tag lookups. */
static void
MakeTagTable(
    Blt_Tree tree,
    Blt_HashTable *nTable,
    char *match,
    char *skip
)
{
    Blt_HashEntry *hPtr, *h2Ptr, *h3Ptr;
    Blt_HashSearch cursor, tcursor;
    Tcl_DString *eStr;
    Blt_TreeNode snode;
    Blt_TreeTagEntry *tPtr;
    int isNew;
    char *name;
    
    Blt_InitHashTable(nTable, BLT_ONE_WORD_KEYS);
    for (hPtr = Blt_TreeFirstTag(tree, &cursor); hPtr != NULL;         
        hPtr = Blt_NextHashEntry(&cursor)) {
        
        tPtr = Blt_GetHashValue(hPtr);
        name = tPtr->tagName;
        
        if (match!=NULL && Tcl_StringMatch(name, match)!=1) continue;
        if (skip!=NULL && Tcl_StringMatch(name, skip)==1) continue;
        
        for (h2Ptr = Blt_FirstHashEntry(&tPtr->nodeTable, &tcursor);
        h2Ptr != NULL; h2Ptr = Blt_NextHashEntry(&tcursor)) {
            snode = Blt_GetHashValue(h2Ptr);
            if (snode == NULL) continue;
            h3Ptr = Blt_CreateHashEntry(nTable, (char*)snode, &isNew);
            if (h3Ptr == NULL) continue;
            if (isNew) {
                eStr = (Tcl_DString*)Blt_Calloc(sizeof(Tcl_DString),1);
                Tcl_DStringInit(eStr);
                Blt_SetHashValue(h3Ptr, eStr);
            } else {
                eStr = (Tcl_DString*)Blt_GetHashValue(h3Ptr);
            }
            Tcl_DStringAppendElement(eStr, tPtr->tagName);
        }
    }
}


static void
FreeTagTable(
    Blt_HashTable *nTable
)
{
    Blt_HashEntry *h2Ptr;
    Blt_HashSearch tcursor;
    Tcl_DString *eStr;

    for (h2Ptr = Blt_FirstHashEntry(nTable, &tcursor);
        h2Ptr != NULL; h2Ptr = Blt_NextHashEntry(&tcursor)) {
        eStr = (Tcl_DString*)Blt_GetHashValue(h2Ptr);
        Tcl_DStringFree(eStr);
        Blt_Free(eStr);
    }
    Blt_DeleteHashTable(nTable);
}

/*
 *----------------------------------------------------------------------
 *
 * DumpOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
DumpOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode top;
    Tcl_Channel channel = NULL;
    Tcl_DString dString;
    int result, isfile = 0, op, tags, doTbl = 0;
    register Blt_TreeNode node;
    RestoreData data;
    
    memset((char *)&data, 0, sizeof(data));

    if (GetNode(cmdPtr, objv[2], &top) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc > 3) {
        /* Process switches  */
        if (Blt_ProcessObjSwitches(interp, dumpSwitches, objc - 3, objv + 3, 
            (char *)&data, BLT_SWITCH_EXACT) < 0) {
            return TCL_ERROR;
        }
    }
    tags = ((data.flags&RESTORE_NO_TAGS) == 0);
    if (data.file != NULL && data.chan != NULL) {
        Tcl_AppendResult(interp, "can not use both -file and -channel", 0);
        return TCL_ERROR;
    }
    if (data.file != NULL) {
        if (Tcl_IsSafe(interp)) {
            Tcl_AppendResult(interp, "can use -file in safe interp", 0);
            return TCL_ERROR;
        }
        channel = Tcl_OpenFileChannel(interp, data.file, "w", 0644);
        if (channel == NULL) {
            return TCL_ERROR;
        }
        isfile = 1;
    } else if (data.chan != NULL) {
        op = 0;
        channel = Tcl_GetChannel(interp, data.chan, &op);
        if (channel == NULL) {
            return TCL_ERROR;
        }
        if ((op & TCL_WRITABLE) == 0) {
            Tcl_AppendResult(interp, "channel is not writable", 0);
            return TCL_ERROR;
        }
    }
    if (data.keys != NULL && Tcl_ListObjGetElements(interp, data.keys, &data.kobjc,
        &data.kobjv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (data.notKeys != NULL && Tcl_ListObjGetElements(interp, data.notKeys,
        &data.nobjc, &data.nobjv) != TCL_OK) {
        return TCL_ERROR;
    }
    /*if (data.keys != NULL && data.notKeys != NULL) {
        Tcl_AppendResult(interp, "can not use both -keys and -notkeys", 0);
        return TCL_ERROR;
    }*/

    if (tags && top->nChildren>0) {
        doTbl = 1;
        MakeTagTable(cmdPtr->tree, &data.tagTable, data.tags, data.notTags);
    }
    result = TCL_OK;
    Tcl_DStringInit(&dString);
    if (channel != NULL) {
        int cnt = 1;
        for (node = top; node != NULL && cnt > 0; node = Blt_TreeNextNode(top, node)) {
            PrintNode(cmdPtr, top, node, &dString, tags, &data);
            if (Tcl_DStringLength(&dString) >= 4096) {
                cnt = Tcl_Write(channel, Tcl_DStringValue(&dString), -1);
                Tcl_DStringSetLength(&dString, 0);
            }
        }
        if (cnt > 0 && Tcl_DStringLength(&dString) > 0) {
            cnt = Tcl_Write(channel, Tcl_DStringValue(&dString), -1);
        }
        Tcl_DStringFree(&dString);
        if (isfile) {
            Tcl_Close(interp, channel);
        }
        if (cnt <= 0) {
            result = TCL_ERROR;
        }
    } else {
        for (node = top; node != NULL; node = Blt_TreeNextNode(top, node)) {
            PrintNode(cmdPtr, top, node, &dString, tags, &data);
        }
        Tcl_DStringResult(interp, &dString);
    }
    if (doTbl) {
        FreeTagTable(&data.tagTable);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ExistsOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
ExistsOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int bool;
    
    bool = TRUE;
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	bool = FALSE;
    } else if (objc == 4) { 
	char *string;
	
	string = Tcl_GetString(objv[3]);
	if (!Blt_TreeValueExists(cmdPtr->tree, node, 
			     string)) {
	    bool = FALSE;
	}
    } 
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(bool));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FindOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
FindOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node, child;
    FindData data;
    int result;
    Tcl_Obj **objArr;

   /* if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }*/
    memset(&data, 0, sizeof(data));
    data.cmdPtr = cmdPtr;
    data.startNode = node = Blt_TreeRootNode(cmdPtr->tree);
    data.maxDepth = -1;
    data.minDepth = -1;
    data.depth = -1;
    data.keyCount = -1;
    data.flags = 0;
    data.order = TREE_PREORDER;
    objArr = NULL;

    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, findSwitches, objc - 2, objv + 2, 
		     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	return TCL_ERROR;
    }
    if (data.nodesObj != NULL) {
        if (data.flags & MATCH_NOTOP) {
            Tcl_AppendResult(interp, "-nodes can not use -notop", 0);
            return TCL_ERROR;
        }
    }
    if (data.execObj != NULL && data.command != NULL) {
        Tcl_AppendResult(interp, "can not use both -command and -exec", 0);
        return TCL_ERROR;
    }
    if (data.execObj != NULL) {
        data.eval = Tcl_GetString(data.execObj);
    } else if (data.execVar != NULL) {
        Tcl_AppendResult(interp, "-var must be used with -exec", 0);
        return TCL_ERROR;
    }
    if (data.startNode != NULL) {
        node = data.startNode;
        if (data.nodesObj != NULL) {
            Tcl_AppendResult(interp, "-nodes can not use -top", 0);
            return TCL_ERROR;
        }
    }
    if (data.keyList == NULL) {
        if (data.flags & (MATCH_ARRAY)) {
            Tcl_AppendResult(interp, "-array must be used with -key", 0);
            result = TCL_ERROR;
            goto done;
        }
    }
    if (data.retKey != NULL) {

        if (data.flags & MATCH_COUNT) {
            Tcl_AppendResult(interp, "can not use -return and -count", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.eval != NULL) {
            Tcl_AppendResult(interp, "can not use -return and -exec", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.command != NULL) {
            Tcl_AppendResult(interp, "can not use -return and -command", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.retKey[0] == '%') {
         } else if (Blt_TreeKeyGet(NULL, cmdPtr->tree->treeObject, data.retKey) != NULL) {
            data.iskey = 1;
         } else {
             Tcl_AppendResult(interp, "-return is not a key or a percent subst", 0);
             result = TCL_ERROR;
             goto done;
         }
    }

    if (data.flags & MATCH_RELDEPTH) {
        int dep;
        dep = Blt_TreeNodeDepth(cmdPtr->tree, node);
        if (data.maxDepth >= 0) {
            data.maxDepth += dep;
        }
        if (data.minDepth >= 0) {
            data.minDepth += dep;
        }
        if (data.depth >= 0) {
            data.depth += dep;
        }
    }
    /*if (data.maxDepth >= 0) {
	data.maxDepth += Blt_TreeNodeDepth(cmdPtr->tree, node);
    }*/
    /*if (data.flags & MATCH_NOCASE) {
	Blt_ListNode listNode;

	for (listNode = Blt_ListFirstNode(data.patternList); listNode != NULL;
	     listNode = Blt_ListNextNode(listNode)) {
	    strtolower((char *)Blt_ListGetKey(listNode));
	}
    }*/
    if (data.name != NULL && (data.flags & (MATCH_ARRAY))) {
        Tcl_AppendResult(interp, "-array must not be used with -name", 0);
        result = TCL_ERROR;
        goto done;
    }
    if (data.name == NULL && (data.flags & (PATTERN_REGEXP|PATTERN_GLOB|PATTERN_INLIST))) {
        Tcl_AppendResult(interp, "must provide -name with -regexp -glob -inlist", 0);
        result = TCL_ERROR;
        goto done;
    }
    if ((data.flags & (MATCH_ISNULL))) {
        if (data.subKey == NULL) {
            Tcl_AppendResult(interp, "-isempty must be used with -column", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.name != NULL) {
            Tcl_AppendResult(interp, "-isempty can not be used with -name", 0);
            result = TCL_ERROR;
            goto done;
        }
        if ((data.flags & (MATCH_INVERT))) {
            Tcl_AppendResult(interp, "-isempty can not be used with -invert", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.retKey) {
            Tcl_AppendResult(interp, "-isempty can not be used with -return", 0);
            result = TCL_ERROR;
            goto done;
        }
        if (data.command) {
            Tcl_AppendResult(interp, "-isempty can not be used with -command", 0);
            result = TCL_ERROR;
            goto done;
        }
    }
    if ((data.flags & PATTERN_MASK) == PATTERN_REGEXP) {
        if (Tcl_RegExpMatch(interp, "", Tcl_GetString(data.name)) == -1) {
            result = TCL_ERROR;
            goto done;
        }
    }
    if ((data.flags & PATTERN_MASK) == PATTERN_INLIST) {
        int cobjc;
        Tcl_Obj **cobjv;
        if (Tcl_ListObjGetElements(interp, data.name,
            &cobjc, &cobjv) != TCL_OK) {
            result = TCL_ERROR;
            goto done;
        }
    }
    if (data.addTag != NULL) {
        if (AddTag(cmdPtr, NULL, data.addTag) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (data.command != NULL) {
	int count, acnt;
	char **p;
	register int i;

	count = 0;
	acnt = 0;
	for (p = data.command; *p != NULL; p++) {
	    count++;
	}
        for (p = data.cmdArgs; *p != NULL; p++) {
            
            acnt++;
        }
	/* Leave room for node Id argument to be appended */
	objArr = Blt_Calloc(count + acnt + 2, sizeof(Tcl_Obj *));
	for (i = 0; i < count; i++) {
	    objArr[i] = Tcl_NewStringObj(data.command[i], -1);
	    Tcl_IncrRefCount(objArr[i]);
	}
        objArr[i] = Tcl_NewStringObj("", -1);
        Tcl_IncrRefCount(objArr[i]);
        i++;
        for (; i < (count+acnt+1); i++) {
	    objArr[i] = Tcl_NewStringObj("", 0);
	    Tcl_IncrRefCount(objArr[i]);
	}
	data.objv = objArr;
	data.objc = count + 1;
	data.argc = acnt;
    } else if (data.cmdArgs != NULL) {
        Tcl_AppendResult(interp, "-cmdargs must be used with -command", 0);
        result = TCL_ERROR;
        goto done;
    }
    data.listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    data.cmdPtr = cmdPtr;
    result = TCL_OK;
    if (data.nodesObj != NULL) {
        TagSearch tagIter = {0};

        if (FindTaggedNodes(interp, cmdPtr, data.nodesObj, &tagIter) != TCL_OK) {
            result = TCL_ERROR;
            goto done;
        }
        for (child = FirstTaggedNode(&tagIter);
            child != NULL && result == TCL_OK;
            child = NextTaggedNode(child, &tagIter)) {
            result = MatchNodeProc(child, &data, -1);
        }
        DoneTaggedNodes(&tagIter);
        
    } else if (data.order == TREE_BREADTHFIRST) {
	result = Blt_TreeApplyBFS(node, MatchNodeProc, &data);
    } else {
	result = Blt_TreeApplyDFS(node, MatchNodeProc, &data, data.order);
    }
    if (data.command != NULL) {
	Tcl_Obj **objPtrPtr;

	for (objPtrPtr = objArr; *objPtrPtr != NULL; objPtrPtr++) {
	    Tcl_DecrRefCount(*objPtrPtr);
	}
	Blt_Free(objArr);
    }
done:
    Blt_FreeSwitches(interp, findSwitches, (char *)&data, 0);
    if (result == TCL_ERROR) {
	return TCL_ERROR;
    }
    if (data.flags & MATCH_COUNT) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(data.nMatches));
    } else {
        Tcl_SetObjResult(interp, data.listObjPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FindChildOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
FindChildOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node, child;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    child = Blt_TreeFindChild(node, Tcl_GetString(objv[3]));
    if (child != NULL) {
	inode = Blt_TreeNodeId(child);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * FirstChildOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
FirstChildOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreeFirstChild(node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
GetOp(
    TreeCmd *cmdPtr, 
    Tcl_Interp *interp, 
    int objc, 
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;

    if (objc>2) {
        if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
            return TCL_ERROR;
        }
    } else {
        node = Blt_TreeRootNode(cmdPtr->tree);
    }
    if (Blt_TreeNotifyGet(cmdPtr->tree, node) != TCL_OK) {
        return TCL_ERROR;
    }
    if (objc <= 3) {
	Blt_TreeKey key;
	Tcl_Obj *valueObjPtr, *listObjPtr;
	Blt_TreeKeySearch cursor;

	/* Add the key-value pairs to a new Tcl_Obj */
	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
	for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &cursor); key != NULL; 
	     key = Blt_TreeNextKey(cmdPtr->tree, &cursor)) {
	    if (Blt_TreeGetValue((Tcl_Interp *)NULL, cmdPtr->tree, node, key,
				 &valueObjPtr) == TCL_OK) {
		Tcl_Obj *objPtr;

		objPtr = Tcl_NewStringObj(key, -1);
		Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
		Tcl_ListObjAppendElement(interp, listObjPtr, valueObjPtr);
	    }
	}	    
	Tcl_SetObjResult(interp, listObjPtr);
	return TCL_OK;
    } else {
	Tcl_Obj *valueObjPtr;
	char *string;

	string = Tcl_GetString(objv[3]); 
	if (Blt_TreeGetValue((Tcl_Interp *)NULL, cmdPtr->tree, node, string,
		     &valueObjPtr) != TCL_OK) {
	    if (objc == 4) {
		Tcl_DString dString;
		char *path;

		Tcl_DStringInit(&dString);
		path = (cmdPtr->tree == NULL ? "" : GetNodePath(cmdPtr, Blt_TreeRootNode(cmdPtr->tree), node, FALSE, &dString));		
		Tcl_AppendResult(interp, "can't find field \"", string, 
			"\" in \"", path, "\"", (char *)NULL);
		Tcl_DStringFree(&dString);
		return TCL_ERROR;
	    } 
	    /* Default to given value */
	    valueObjPtr = objv[4];
	} 
	Tcl_SetObjResult(interp, valueObjPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
IndexOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    inode = -1;
    if (GetNode(cmdPtr, objv[2], &node) == TCL_OK) {
	inode = Blt_TreeNodeId(node);
    } else {
	register int i;
	int nObjs;
	Tcl_Obj **objArr;
	Blt_TreeNode parent;
	char *string;

	if (Tcl_ListObjGetElements(interp, objv[2], &nObjs, &objArr) 
	    != TCL_OK) {
	    goto done;		/* Can't split object. */
	}
	parent = Blt_TreeRootNode(cmdPtr->tree);
	for (i = 0; i < nObjs; i++) {
	    string = Tcl_GetString(objArr[i]);
	    if (string[0] == '\0') {
		continue;
	    }
	    node = Blt_TreeFindChild(parent, string);
	    if (node == NULL) {
		goto done;	/* Can't find component */
	    }
	    parent = node;
	}
	inode = Blt_TreeNodeId(node);
    }
 done:
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * InsertOp --
 *
 *---------------------------------------------------------------------- 
 */

static int
InsertOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode parent, child;
    InsertData data;
    int inode, dobjc, tobjc, pobjc, nobjc, vobjc, i;
    Tcl_Obj **dobjv, **pobjv, **tobjv, **nobjv, **vobjv;

    child = NULL;
    /*if (!strcmp(Tcl_GetString(objv[2]), "end")) {
        parent = Blt_TreeRootNode(cmdPtr->tree);
    } else*/
    if (GetNode(cmdPtr, objv[2], &parent) != TCL_OK) {
	return TCL_ERROR;
    }
    /* Initialize switch flags */
    memset(&data, 0, sizeof(data));
    data.insertPos = -1;	/* Default to append node. */
    data.parent = parent;
    data.inode = -1;

    if (Blt_ProcessObjSwitches(interp, insertSwitches, objc - 3, objv + 3, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	goto error;
    }
    if (data.tags != NULL) {
        if (Tcl_ListObjGetElements(interp, data.tags, &pobjc, &pobjv) 
        != TCL_OK) {
            goto error;		/* Can't split object. */
        }
    }
    if (data.tags2 != NULL) {
        if (Tcl_ListObjGetElements(interp, data.tags2, &tobjc, &tobjv) 
        != TCL_OK) {
            goto error;		/* Can't split object. */
        }
    }
    if (data.dataPairs != NULL) {
        if (Tcl_ListObjGetElements(interp, data.dataPairs, &dobjc, &dobjv) 
            != TCL_OK) {
            goto error;		/* Can't split object. */
        }
        if (dobjc%2) {
            Tcl_AppendResult(interp, "-data must have an even number of values",0);
            goto error;
        }
        if (data.names != NULL || data.values != NULL) {
            Tcl_AppendResult(interp, "-data incompatible with -names/-values",0);
            goto error;
        }
    }
    if (data.names != NULL) {
        if (Tcl_ListObjGetElements(interp, data.names, &nobjc, &nobjv) 
        != TCL_OK) {
            goto error;		/* Can't split object. */
        }
        if (data.dataPairs != NULL) {
            Tcl_AppendResult(interp, "-data incompatible with -names/-values",0);
            goto error;
        }
        if (data.values == NULL) {
            Tcl_AppendResult(interp, "-names must be used with -values",0);
            goto error;
        }
    }
    if (data.values != NULL) {
        if (Tcl_ListObjGetElements(interp, data.values, &vobjc, &vobjv) 
        != TCL_OK) {
            goto error;		/* Can't split object. */
        }
        if (data.dataPairs != NULL) {
            Tcl_AppendResult(interp, "-data incompatible with -names/-values",0);
            goto error;
        }
        if (data.names == NULL) {
            Tcl_AppendResult(interp, "-values must be used with -names",0);
            goto error;
        }
        if (vobjc != nobjc) {
            Tcl_AppendResult(interp, "-values and -names must be the same length",0);
            goto error;
        }
    }
    if (data.inode > 0) {
	Blt_TreeNode node;

	node = Blt_TreeGetNode(cmdPtr->tree, data.inode);
	if (node != NULL) {
	    Tcl_AppendResult(interp, "can't reissue node id \"", 
		Blt_Itoa(data.inode), "\": already exists.", (char *)NULL);
	    goto error;
	}
	child = Blt_TreeCreateNodeWithId(cmdPtr->tree, parent, data.label, 
		data.inode, data.insertPos);
    } else {
	child = Blt_TreeCreateNode(cmdPtr->tree, parent, data.label, 
		data.insertPos);
    }
    if (child == NULL) {
	Tcl_AppendResult(interp, "can't allocate new node", (char *)NULL);
	goto error;
    }
    inode = child->inode;
    if (data.label == NULL) {
	char string[200];

	sprintf(string, "%d", Blt_TreeNodeId(child));
	Blt_TreeRelabelNode2(child, string);
    } 
    if (data.tags != NULL) {

         for (i=0; i<pobjc; i++) {
	    if (AddTag(cmdPtr, child, Tcl_GetString(pobjv[i])) != TCL_OK) {
		goto error;
	    }
	}
    }
    if (data.dataPairs != NULL) {
	char *key;

	for (i=0; i<dobjc; i += 2) {
	    key = Tcl_GetString(dobjv[i]);
            if (Blt_TreeSetValue(interp, cmdPtr->tree, child, key, dobjv[i+1]) 
		!= TCL_OK) {
		goto error;
	    }
         }
    } else if (data.names != NULL) {
        char *key;

        for (i=0; i<nobjc; i++) {
            key = Tcl_GetString(nobjv[i]);
            if (Blt_TreeSetValue(interp, cmdPtr->tree, child, key, vobjv[i]) 
            != TCL_OK) {
                goto error;
            }
        }
    }
    if (data.tags2 != NULL) {
         for (i=0; i<tobjc; i++) {
	    if (AddTag(cmdPtr, child, Tcl_GetString(tobjv[i])) != TCL_OK) {
                goto error;
            }
        }
    }
    if (Blt_TreeInsertPost(cmdPtr->tree, child) == NULL) {
        goto error;
    }
    
    if (data.fixed || (cmdPtr->tree->treeObject->flags & TREE_FIXED_KEYS)) {
        child->flags |= TREE_NODE_FIXED_FIELDS;
    }

    Blt_FreeSwitches(interp, insertSwitches, (char *)&data, 0);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), Blt_TreeNodeId(child));
    return TCL_OK;

 error:
    if (child != NULL) {
        child = Blt_TreeGetNode(cmdPtr->tree, inode);
    }
    if (child != NULL) {
	DeleteNode(cmdPtr, child);
    }
    Blt_FreeSwitches(interp, insertSwitches, (char *)&data, 0);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * CreateOp --
 *
 *---------------------------------------------------------------------- 
 */

static int
CreateOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode parent = NULL, child = NULL;
    int cnt = 0, i, n, m, vobjc, tobjc, dobjc, pobjc, optInd, iPos = -1, incr = 0;
    int hasoffs = 0, seqlabel = 0, seqVal = 0, hasstart = 0, start;
    int fixed = 0, hasnum = 0, hcnt = 0;
    char *prefix = NULL, *string;
    Tcl_Obj **vobjv = NULL;
    Tcl_Obj **tobjv = NULL;
    Tcl_Obj **dobjv = NULL;
    Tcl_Obj **pobjv = NULL;

    enum optInd {
        OP_DATA, OP_FIXED, OP_SEQLABEL, OP_NODES, OP_NUM, OP_OFFSET,
        OP_PARENT, OP_PATH, OP_POS, OP_PREFIX, OP_START, OP_TAGS
    };
    static char *optArr[] = {
        "-data", "-fixed", "-labelstart", "-nodes", "-num", "-offset",
        "-parent", "-path", "-pos", "-prefix", "-start", "-tags",
        0
    };

    i = 1;
    
    string = Tcl_GetString(objv[2]);
    while (objc>=3 && string[0] == '-') {
        if (Tcl_GetIndexFromObj(interp, objv[2], optArr, "option",
            0, &optInd) != TCL_OK) {
                return TCL_ERROR;
        }
        switch (optInd) {
        
        case OP_POS:
            if (objc<4) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &iPos) != TCL_OK) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_DATA:
            if (objc<4) { goto missingArg; }
            if (Tcl_ListObjGetElements(interp, objv[3], &dobjc, &dobjv) 
                != TCL_OK) {
                return TCL_ERROR;		/* Can't split object. */
            }
            if (dobjc%2) {
                Tcl_AppendResult(interp, "data must have even length", (char *)NULL);
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_FIXED:
            fixed = 1;
            objc -= 1;
            objv += 1;
            break;
        case OP_SEQLABEL:
            if (objc<4) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &seqVal) != TCL_OK) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            seqlabel = 1;
            break;
        case OP_OFFSET:
            if (objc<4) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &incr) != TCL_OK) {
                return TCL_ERROR;
            }
            hasoffs = 1;
            objc -= 2;
            objv += 2;
            break;
        case OP_NUM:
            if (objc<4) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &cnt) != TCL_OK) {
                return TCL_ERROR;
            }
            hasnum = 1;
            hcnt++;
            if (cnt<0 || cnt>10000000) {
                Tcl_AppendResult(interp, "count must be >= 0 and <= 10M", (char *)NULL);
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_NODES:
            hcnt++;
            if (objc<4) { goto missingArg; }
            if (Tcl_ListObjGetElements(interp, objv[3], &vobjc, &vobjv) 
                != TCL_OK) {
                return TCL_ERROR;		/* Can't split object. */
            }
            for (i=0; i<vobjc; i++)  {
                if (Tcl_GetIntFromObj(interp, vobjv[i], &n)) {
                    return TCL_ERROR;
                }
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_PARENT:
            if (objc<4) { goto missingArg; }
            if (GetNode(cmdPtr, objv[3], &parent) != TCL_OK) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_PREFIX:
            if (objc<4) { goto missingArg; }
            prefix = Tcl_GetString(objv[3]);
            objc -= 2;
            objv += 2;
            break;
        case OP_PATH:
            if (objc<4) { goto missingArg; }
            hcnt++;
            if (Tcl_ListObjGetElements(interp, objv[3], &pobjc, &pobjv) 
                != TCL_OK) {
                return TCL_ERROR;		/* Can't split object. */
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_START:
            if (objc<4) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &start) != TCL_OK) {
                return TCL_ERROR;
            }
            if (start<=0) {
                Tcl_AppendResult(interp, "start must be > 0", (char *)NULL);
                return TCL_ERROR;
            }
            hasstart = 1;
            objc -= 2;
            objv += 2;
            break;
        case OP_TAGS:
            if (objc<4) { goto missingArg; }
            if (Tcl_ListObjGetElements(interp, objv[3], &tobjc, &tobjv) 
                != TCL_OK) {
                return TCL_ERROR;		/* Can't split object. */
            }
            objc -= 2;
            objv += 2;
            break;
        default:
            return TCL_ERROR;
        }
    }
    if (hcnt != 1) {
        Tcl_AppendResult(interp, "must use exactly one of -nodes, -num or -path", 0);
        return TCL_ERROR;
    }
    if (vobjv != NULL) {
        cnt = vobjc;
    }
    if (pobjv != NULL) {
        cnt = pobjc;
        if (prefix != NULL) {
            Tcl_AppendResult(interp, "-prefix is incompatible with -path", 0);
            return TCL_ERROR;
        }
        if (seqlabel) {
            Tcl_AppendResult(interp, "-labelstart is incompatible with -path", 0);
            return TCL_ERROR;
        }
        if (parent != NULL) {
            Tcl_AppendResult(interp, "-parent is incompatible with -path", 0);
            return TCL_ERROR;
        }
    }
    if (hasoffs && vobjv == NULL) {
        Tcl_AppendResult(interp, "must use -nodes with -offset", 0);
        return TCL_ERROR;
    }

    if (parent == NULL) {
         parent = Blt_TreeRootNode(cmdPtr->tree);
    }
    if (prefix == NULL) {
        prefix = "";
    }

    for (n = 0, m = 0; n<cnt; n++, i++)  {
        char label[200];
        char *labStr;
        labStr = NULL;
        if (vobjv != NULL) {
            if (Tcl_GetIntFromObj(interp, vobjv[n], &i)) {
                return TCL_ERROR;
            }
            i += incr;
        }
        if (pobjv != NULL) {
            labStr = Tcl_GetString(pobjv[n]);
            child = Blt_TreeFindChild( parent, labStr);
            if (child != NULL) {
                parent = child;
                continue;
            }
        }
        if (vobjv != NULL) {
            child = Blt_TreeCreateNodeWithId(cmdPtr->tree, parent, labStr, i, iPos);
        } else if (hasstart) {
            child = Blt_TreeCreateNodeWithId(cmdPtr->tree, parent, labStr, n+start, iPos);
        } else {
            child = Blt_TreeCreateNode(cmdPtr->tree, parent, labStr, iPos);
        }
        if (child == NULL) {
            return TCL_ERROR;
        }
        if (pobjv != NULL) {
            parent = child;
        }
        if (labStr == NULL) {
            if (!seqlabel) {
                i = Blt_TreeNodeId(child);
            } else {
                i = n + seqVal;
            }
            if (prefix[0]) {
                sprintf(label, "%.100s%d", prefix, i);
            } else {
                sprintf(label, "%d", i);
            }
            Blt_TreeRelabelNode2(child, label);
        }
        if (dobjv != NULL) {
            int j;
            Blt_TreeKey key;
            for (j = 0; j<dobjc; j += 2) {
                key = Tcl_GetString(dobjv[j]);
                if (Blt_TreeSetValue(interp, cmdPtr->tree, child, key, dobjv[j+1]) 
                != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }
        if (tobjv != NULL) {
            int j;
            for (j = 0; j<tobjc; j++) {
                if (AddTag(cmdPtr, child, Tcl_GetString(tobjv[j])) != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }
        if (Blt_TreeInsertPost(cmdPtr->tree, child) == NULL) {
            DeleteNode(cmdPtr, child);
            return TCL_ERROR;
        }
        if (fixed || (cmdPtr->tree->treeObject->flags & TREE_FIXED_KEYS)) {
            child->flags |= TREE_NODE_FIXED_FIELDS;
        }
        m++;
    }
    if (child != NULL) {
        Tcl_AppendResult(interp, Blt_Itoa(Blt_TreeNodeId(child)), 0);
    }
    return TCL_OK;

missingArg:
    Tcl_AppendResult(interp, "missing argument for populate option \"",
        Tcl_GetString(objv[3]), "\"", (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * IsAncestorOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
IsAncestorOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node1, node2;
    int bool;

    if ((GetNode(cmdPtr, objv[3], &node1) != TCL_OK) ||
	(GetNode(cmdPtr, objv[4], &node2) != TCL_OK)) {
	return TCL_ERROR;
    }
    bool = Blt_TreeIsAncestor(node1, node2);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsBeforeOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
IsBeforeOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node1, node2;
    int bool;

    if ((GetNode(cmdPtr, objv[3], &node1) != TCL_OK) ||
	(GetNode(cmdPtr, objv[4], &node2) != TCL_OK)) {
	return TCL_ERROR;
    }
    bool = Blt_TreeIsBefore(node1, node2);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsLeafOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
IsLeafOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;

    if (GetNode(cmdPtr, objv[3], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), Blt_TreeIsLeaf(node));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsRootOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
IsRootOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int bool;

    if (GetNode(cmdPtr, objv[3], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    bool = (node == Blt_TreeRootNode(cmdPtr->tree));
    Tcl_SetIntObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsOp --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec isOps[] =
{
    {"ancestor", 1, (Blt_Op)IsAncestorOp, 5, 5, "node1 node2",},
    {"before", 1, (Blt_Op)IsBeforeOp, 5, 5, "node1 node2",},
    {"leaf", 1, (Blt_Op)IsLeafOp, 4, 4, "node",},
    {"root", 1, (Blt_Op)IsRootOp, 4, 4, "node",},
};

static int nIsOps = sizeof(isOps) / sizeof(Blt_OpSpec);

static int
IsOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nIsOps, isOps, BLT_OP_ARG2, objc, objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (cmdPtr, interp, objc, objv);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * KeysOp --
 *
 *	Returns the key names of values for a node or array value.
 *
 *---------------------------------------------------------------------- 
 */
static int
KeysOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_HashTable keyTable;
    Blt_TreeKey key;
    Blt_TreeKeySearch keyIter;
    Blt_TreeNode node;
    Tcl_Obj *listObjPtr, *objPtr;
    register int i;
    int isNew, len;
    char *string;
    TagSearch tagIter = {0};

    Blt_InitHashTableWithPool(&keyTable, BLT_ONE_WORD_KEYS);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 2; i < objc; i++) {
        string = Tcl_GetStringFromObj(objv[i],&len);
        if (len == 0) continue;
        if (FindTaggedNodes(interp, cmdPtr, objv[i], &tagIter) != TCL_OK) {
            Blt_DeleteHashTable(&keyTable);
            Tcl_DecrRefCount(listObjPtr);
            return TCL_ERROR;
	}
	for ( node = FirstTaggedNode(&tagIter);
	 node != NULL; node = NextTaggedNode(node, &tagIter)) {
	    for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &keyIter); 
		 key != NULL; key = Blt_TreeNextKey(cmdPtr->tree, &keyIter)) {
		Blt_CreateHashEntry(&keyTable, key, &isNew);
                if (!isNew) continue;
                objPtr = Tcl_NewStringObj(key, -1);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	    }
	}
        DoneTaggedNodes(&tagIter);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    Blt_DeleteHashTable(&keyTable);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * LabelOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
LabelOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc == 4) {
	if (Blt_TreeRelabelNode(cmdPtr->tree, node, Tcl_GetString(objv[3])) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    Tcl_SetStringObj(Tcl_GetObjResult(interp), Blt_TreeNodeLabel(node), -1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FixedOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
FixedOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int fixed;

    if (strlen(Tcl_GetString(objv[2]))) {
        if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
            return TCL_ERROR;
        }
        if (objc != 4) {
            fixed = ((node->flags & TREE_NODE_FIXED_FIELDS) != 0);
        } else {
            if (Tcl_GetIntFromObj(interp, objv[3], &fixed) != TCL_OK) {
                return TCL_ERROR;
            }
            if (fixed) {
                node->flags |= TREE_NODE_FIXED_FIELDS;
            } else {
                node->flags &= ~TREE_NODE_FIXED_FIELDS;
            }
        }
    } else {
        if (objc != 3) {
            fixed = ((cmdPtr->tree->treeObject->flags & TREE_FIXED_KEYS) != 0);
        } else {
            if (Tcl_GetIntFromObj(interp, objv[2], &fixed) != TCL_OK) {
                return TCL_ERROR;
            }
            if (fixed) {
                cmdPtr->tree->treeObject->flags |= TREE_FIXED_KEYS;
            } else {
                cmdPtr->tree->treeObject->flags &= ~TREE_FIXED_KEYS;
            }
        }
    }

    Tcl_SetObjResult(interp, Tcl_NewIntObj(fixed));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DictsetOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
DictsetOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    int dict;
    

    if (objc != 3) {
        dict = ((cmdPtr->tree->treeObject->flags & TREE_DICT_KEYS) != 0);
    } else {
	if (Tcl_GetIntFromObj(interp, objv[2], &dict) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (dict) {
             cmdPtr->tree->treeObject->flags |= TREE_DICT_KEYS;
	} else {
             cmdPtr->tree->treeObject->flags &= ~TREE_DICT_KEYS;
	}
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(dict));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * LastChildOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
LastChildOp(cmdPtr, interp, objc, objv)
    TreeCmd *cmdPtr;
    Tcl_Interp *interp;
    int objc;			/* Not used. */
    Tcl_Obj *CONST *objv;
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreeLastChild(node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MoveOp --
 *
 *	The big trick here is to not consider the node to be moved in
 *	determining it's new location.  Ideally, you would temporarily
 *	pull from the tree and replace it (back in its old location if
 *	something went wrong), but you could still pick the node by 
 *	its serial number.  So here we make lots of checks for the 
 *	node to be moved.
 * 
 *
 *---------------------------------------------------------------------- 
 */
static int
MoveOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode root, parent, node;
    Blt_TreeNode before;
    MoveData data;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    if (GetNode(cmdPtr, objv[3], &parent) != TCL_OK) {
	return TCL_ERROR;
    }
    root = Blt_TreeRootNode(cmdPtr->tree);
    if (node == root) {
	Tcl_AppendResult(interp, "can't move root node", (char *)NULL);
	return TCL_ERROR;
    }
    if (parent == node) {
	Tcl_AppendResult(interp, "can't move node to self", (char *)NULL);
	return TCL_ERROR;
    }
    data.node = NULL;
    data.cmdPtr = cmdPtr;
    data.movePos = -1;
    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, moveSwitches, objc - 4, objv + 4, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	return TCL_ERROR;
    }
    /* Verify they aren't ancestors. */
    if (Blt_TreeIsAncestor(node, parent)) {
	Tcl_AppendResult(interp, "can't move node: \"", 
		 Tcl_GetString(objv[2]), (char *)NULL);
	Tcl_AppendResult(interp, "\" is an ancestor of \"", 
		 Tcl_GetString(objv[3]), "\"", (char *)NULL);
	return TCL_ERROR;
    }
    before = NULL;		/* If before is NULL, this appends the
				 * node to the parent's child list.  */

    if (data.node != NULL) {	/* -before or -after */
	if (Blt_TreeNodeParent(data.node) != parent) {
	    Tcl_AppendResult(interp, Tcl_GetString(objv[2]), 
		     " isn't the parent of ", Blt_TreeNodeLabel(data.node),
		     (char *)NULL);
	    return TCL_ERROR;
	}
	if (Blt_SwitchChanged(moveSwitches, interp, "-before", (char *)NULL)) {
	    before = data.node;
	    if (before == node) {
		Tcl_AppendResult(interp, "can't move node before itself", 
				 (char *)NULL);
		return TCL_ERROR;
	    }
	} else {
	    before = Blt_TreeNextSibling(data.node);
	    if (before == node) {
		Tcl_AppendResult(interp, "can't move node after itself", 
				 (char *)NULL);
		return TCL_ERROR;
	    }
	}
    } else if (data.movePos >= 0) { /* -at */
	int count;		/* Tracks the current list index. */
	Blt_TreeNode child;

	/* 
	 * If the node is in the list, ignore it when determining the
	 * "before" node using the -at index.  An index of -1 means to
	 * append the node to the list.
	 */
	count = 0;
	for(child = Blt_TreeFirstChild(parent); child != NULL; 
	    child = Blt_TreeNextSibling(child)) {
	    if (child == node) {
		continue;	/* Ignore the node to be moved. */
	    }
	    if (count == data.movePos) {
		before = child;
		break;		
	    }
	    count++;	
	}
    }
    if (Blt_TreeMoveNode(cmdPtr->tree, node, parent, before) != TCL_OK) {
	Tcl_AppendResult(interp, "can't move node ", Tcl_GetString(objv[2]), 
		 " to ", Tcl_GetString(objv[3]), (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NextOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
NextOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreeNextNode(Blt_TreeRootNode(cmdPtr->tree), node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NextSiblingOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
NextSiblingOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreeNextSibling(node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyCreateOp --
 *
 *	tree0 notify create ?flags? command arg
 *---------------------------------------------------------------------- 
 */
static int
NotifyCreateOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    NotifyInfo *notifyPtr;
    NotifyData data;
    char *string;
    char idString[200];
    int isNew, nArgs;
    Blt_HashEntry *hPtr;
    int count;
    register int i;

    count = 0;
    for (i = 3; i < objc; i++) {
	string = Tcl_GetString(objv[i]);
	if (string[0] != '-') {
	    break;
	}
	count++;
    }
    data.mask = 0;
    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, notifySwitches, count, objv + 3, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	return TCL_ERROR;
    }
    notifyPtr = Blt_Calloc(1, sizeof(NotifyInfo));

    nArgs = objc - i;

    /* Stash away the command in structure and pass that to the notifier. */
    notifyPtr->objv = Blt_Calloc((nArgs + 2), sizeof(Tcl_Obj *));
    for (count = 0; i < objc; i++, count++) {
	Tcl_IncrRefCount(objv[i]);
	notifyPtr->objv[count] = objv[i];
    }
    notifyPtr->objc = nArgs + 2;
    notifyPtr->cmdPtr = cmdPtr;
    if (data.mask == 0) {
	data.mask = TREE_NOTIFY_ALL;
    }
    notifyPtr->mask = data.mask;

    sprintf(idString, "notify%d", cmdPtr->notifyCounter++);
    hPtr = Blt_CreateHashEntry(&(cmdPtr->notifyTable), idString, &isNew);
    Blt_SetHashValue(hPtr, notifyPtr);

    Tcl_SetStringObj(Tcl_GetObjResult(interp), idString, -1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyDeleteOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
NotifyDeleteOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    NotifyInfo *notifyPtr;
    Blt_HashEntry *hPtr;
    register int i, j;
    char *string;

    for (i = 3; i < objc; i++) {
	string = Tcl_GetString(objv[i]);
	hPtr = Blt_FindHashEntry(&(cmdPtr->notifyTable), string);
	if (hPtr == NULL) {
	    Tcl_AppendResult(interp, "unknown notify name \"", string, "\"", 
			     (char *)NULL);
	    return TCL_ERROR;
	}
	notifyPtr = Blt_GetHashValue(hPtr);
	Blt_DeleteHashEntry(&(cmdPtr->notifyTable), hPtr);
	for (j = 0; j < (notifyPtr->objc - 2); j++) {
	    Tcl_DecrRefCount(notifyPtr->objv[j]);
	}
	Blt_Free(notifyPtr->objv);
	Blt_Free(notifyPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyInfoOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
NotifyInfoOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    NotifyInfo *notifyPtr;
    Blt_HashEntry *hPtr;
    Tcl_DString dString;
    char *string;
    int i;

    string = Tcl_GetString(objv[3]);
    hPtr = Blt_FindHashEntry(&(cmdPtr->notifyTable), string);
    if (hPtr == NULL) {
	Tcl_AppendResult(interp, "unknown notify name \"", string, "\"", 
			 (char *)NULL);
	return TCL_ERROR;
    }
    notifyPtr = Blt_GetHashValue(hPtr);

    Tcl_DStringInit(&dString);
    Tcl_DStringAppendElement(&dString, string);	/* Copy notify Id */
    Tcl_DStringStartSublist(&dString);
    if (notifyPtr->mask & TREE_NOTIFY_CREATE) {
	Tcl_DStringAppendElement(&dString, "-create");
    }
    if (notifyPtr->mask & TREE_NOTIFY_GET) {
        Tcl_DStringAppendElement(&dString, "-get");
    }
    if (notifyPtr->mask & TREE_NOTIFY_INSERT) {
        Tcl_DStringAppendElement(&dString, "-insert");
    }
    if (notifyPtr->mask & TREE_NOTIFY_DELETE) {
	Tcl_DStringAppendElement(&dString, "-delete");
    }
    if (notifyPtr->mask & TREE_NOTIFY_MOVE) {
	Tcl_DStringAppendElement(&dString, "-move");
    }
    if (notifyPtr->mask & TREE_NOTIFY_MOVEPOST) {
        Tcl_DStringAppendElement(&dString, "-movepost");
    }
    if (notifyPtr->mask & TREE_NOTIFY_SORT) {
	Tcl_DStringAppendElement(&dString, "-sort");
    }
    if (notifyPtr->mask & TREE_NOTIFY_RELABEL) {
	Tcl_DStringAppendElement(&dString, "-relabel");
    }
    if (notifyPtr->mask & TREE_NOTIFY_RELABELPOST) {
        Tcl_DStringAppendElement(&dString, "-relabelpost");
    }
    if (notifyPtr->mask & TREE_NOTIFY_WHENIDLE) {
        Tcl_DStringAppendElement(&dString, "-whenidle");
    }
    if (notifyPtr->mask & TREE_NOTIFY_TRACEACTIVE) {
        Tcl_DStringAppendElement(&dString, "-disabletrace");
    }
    if (notifyPtr->mask & TREE_NOTIFY_BGERROR) {
        Tcl_DStringAppendElement(&dString, "-bgerror");
    }
    Tcl_DStringEndSublist(&dString);
    Tcl_DStringStartSublist(&dString);
    for (i = 0; i < (notifyPtr->objc - 2); i++) {
	Tcl_DStringAppendElement(&dString, Tcl_GetString(notifyPtr->objv[i]));
    }
    Tcl_DStringEndSublist(&dString);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyNamesOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
NotifyNamesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Tcl_Obj *objPtr, *listObjPtr;
    char *notifyId;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&(cmdPtr->notifyTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	notifyId = Blt_GetHashKey(&(cmdPtr->notifyTable), hPtr);
	objPtr = Tcl_NewStringObj(notifyId, -1);
	Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NotifyOp --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec notifyOps[] =
{
    {"create", 1, (Blt_Op)NotifyCreateOp, 4, 0, "?flags? command",},
    {"delete", 1, (Blt_Op)NotifyDeleteOp, 3, 0, "notifyId...",},
    {"info", 1, (Blt_Op)NotifyInfoOp, 4, 4, "notifyId",},
    {"names", 1, (Blt_Op)NotifyNamesOp, 3, 3, "",},
};

static int nNotifyOps = sizeof(notifyOps) / sizeof(Blt_OpSpec);

static int
NotifyOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nNotifyOps, notifyOps, BLT_OP_ARG2, objc, 
	objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (cmdPtr, interp, objc, objv);
    return result;
}


/*ARGSUSED*/
static int
ParentOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreeNodeParent(node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PathOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
PathOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    Tcl_DString dString;
    char *prefix = NULL, *delim = NULL;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc >3) {
        delim = Tcl_GetString(objv[3]);
    }
    if (objc >4) {
        prefix = Tcl_GetString(objv[4]);
    }
    Tcl_DStringInit(&dString);
    if (objc>3) {
        Blt_TreeNodePathStr( node, &dString, prefix, delim);
    } else {
        GetNodePath(cmdPtr, Blt_TreeRootNode(cmdPtr->tree), node, FALSE, &dString); 
    }
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}


static int
ComparePositions(Blt_TreeNode *n1Ptr, Blt_TreeNode *n2Ptr)
{
    if (*n1Ptr == *n2Ptr) {
        return 0;
    }
    if (Blt_TreeIsBefore(*n1Ptr, *n2Ptr)) {
        return -1;
    }
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * PositionOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
PositionOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    PositionData data;
    Blt_TreeNode *nodeArr, *nodePtr;
    Blt_TreeNode node;
    Blt_TreeNode parent, lastParent;
    Tcl_Obj *listObjPtr, *objPtr;
    int i;
    int position;
    Tcl_DString dString;
    int n;

    Tcl_DStringInit(&dString);
    memset((char *)&data, 0, sizeof(data));
    /* Process switches  */
    n = Blt_ProcessObjSwitches(interp, positionSwitches, objc - 2, objv + 2, 
	     (char *)&data, BLT_SWITCH_EXACT|BLT_SWITCH_OBJV_PARTIAL);
    if (n < 0) {
	return TCL_ERROR;
    }
    objc -= n + 2, objv += n + 2;

    /* Collect the node ids into an array */
    nodeArr = Blt_Calloc((objc + 1),  sizeof(Blt_TreeNode));
    for (i = 0; i < objc; i++) {
	if (GetNode(cmdPtr, objv[i], &node) != TCL_OK) {
	    Blt_Free(nodeArr);
	    return TCL_ERROR;
	}
	nodeArr[i] = node;
    }
    nodeArr[i] = NULL;

    if (data.sort) {		/* Sort the nodes by depth-first order 
				 * if requested. */
	qsort((char *)nodeArr, objc, sizeof(Blt_TreeNode), 
	      (QSortCompareProc *)ComparePositions);
    }

    position = 0;		/* Suppress compiler warning. */
    lastParent = NULL;
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    Tcl_DStringInit(&dString);
    for (nodePtr = nodeArr; *nodePtr != NULL; nodePtr++) {
	parent = Blt_TreeNodeParent(*nodePtr);
	if ((parent != NULL) && (parent == lastParent)) {
	    /* 
	     * Since we've sorted the nodes already, we can safely
	     * assume that if two consecutive nodes have the same
	     * parent, the first node came before the second. If
	     * this is the case, use the last node as a starting
	     * point.  
	     */
	    
	    /*
	     * Note that we start comparing from the last node,
	     * not its successor.  Some one may give us the same
	     * node more than once.  
	     */
	    node = *(nodePtr - 1); /* Can't get here unless there's
				    * more than one node. */
	    for(/*empty*/; node != NULL; node = Blt_TreeNextSibling(node)) {
		if (node == *nodePtr) {
		    break;
		}
		position++;
	    }
	} else {
	    /* The fallback is to linearly search through the
	     * parent's list of children, counting the number of
	     * preceding siblings. Except for nodes with many
	     * siblings (100+), this should be okay. */
	    position = Blt_TreeNodePosition(*nodePtr);
	}
	if (data.sort) {
	    lastParent = parent; /* Update the last parent. */
	}	    
	/* 
	 * Add an element in the form "parent -at position" to the
	 * list that we're generating.
	 */
	if (data.withId) {
	    objPtr = Tcl_NewIntObj(Blt_TreeNodeId(*nodePtr));
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
	if (data.withParent) {
	    char *string;

	    Tcl_DStringSetLength(&dString, 0); /* Clear the string. */
	    string = (parent == NULL) ? "" : Blt_Itoa(Blt_TreeNodeId(parent));
	    Tcl_DStringAppendElement(&dString, string);
	    Tcl_DStringAppendElement(&dString, "-at");
	    Tcl_DStringAppendElement(&dString, Blt_Itoa(position));
	    objPtr = Tcl_NewStringObj(Tcl_DStringValue(&dString), -1);
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	} else {
	    objPtr = Tcl_NewIntObj(position);
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
    }
    Tcl_DStringFree(&dString);
    Blt_Free(nodeArr);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PreviousOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
PreviousOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreePrevNode(Blt_TreeRootNode(cmdPtr->tree), node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

/*ARGSUSED*/
static int
PrevSiblingOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int inode;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    inode = -1;
    node = Blt_TreePrevSibling(node);
    if (node != NULL) {
	inode = Blt_TreeNodeId(node);
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), inode);
    return TCL_OK;
}

static int
ReadEntry(
    Tcl_Interp *interp,
    Tcl_Channel channel,
    int *argcPtr,
    char ***argvPtr)
{
    Tcl_DString dString;
    int result, cmpl = 1;
    char *entry;

    if (*argvPtr != NULL) {
	Blt_Free(*argvPtr);
	*argvPtr = NULL;
    }
    Tcl_DStringInit(&dString);
    entry = NULL;
    while (Tcl_Gets(channel, &dString) > 0) {
	nLines++;
	Tcl_DStringAppend(&dString, "\n", 1);
	entry = Tcl_DStringValue(&dString);
	cmpl = 0;
	if (Tcl_CommandComplete(entry)) {
	    result = Tcl_SplitList(interp, entry, argcPtr, argvPtr);
	    Tcl_DStringFree(&dString);
	    return result;
	}
    }
    Tcl_DStringFree(&dString);
    if (entry == NULL) {
	*argcPtr = 0;		/* EOF */
	return TCL_OK;
    }
    Tcl_AppendResult(interp, "error reading file: ",
        (cmpl ? Tcl_PosixError(interp) : "missing brace"), (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * RestoreOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
RestoreOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode root;
    int nElem;
    char **elemArr, *entry, *eol, *next, saved;
    int result, isfile = 0, op, dd;
    Tcl_Channel channel = NULL;
    RestoreData data;

    if (GetNode(cmdPtr, objv[2], &root) != TCL_OK) {
	return TCL_ERROR;
    }
    memset((char *)&data, 0, sizeof(data));

    if (objc < 4) {
        Tcl_AppendResult(interp, "too few args", 0);
        return TCL_ERROR;
    }
    /* Process switches  */
    if (Blt_ProcessObjSwitches(interp, restoreSwitches, objc - 3, objv + 3, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	result = TCL_ERROR;
	goto done;
    }
    dd = 0;
    if (data.file == NULL) dd++;
    if (data.data == NULL) dd++;
    if (data.chan == NULL) dd++;
    if (dd != 2) {
        Tcl_AppendResult(interp, "one of -file, -data, -channel is required", 0);
        return TCL_ERROR;
    }
    if (data.file != NULL) {
        if (Tcl_IsSafe(interp)) {
            Tcl_AppendResult(interp, "can use -file in safe interp", 0);
            return TCL_ERROR;
        }
        channel = Tcl_OpenFileChannel(interp, data.file, "r", 0644);
        if (channel == NULL) {
            return TCL_ERROR;
        }
        isfile = 1;
    }
    if (data.chan != NULL) {
        op = 0;
        channel = Tcl_GetChannel(interp, data.chan, &op);
        if (channel == NULL) {
            return TCL_ERROR;
        }
        if ((op & TCL_READABLE) == 0) {
            Tcl_AppendResult(interp, "channel is not readable", 0);
            return TCL_ERROR;
        }
    }
    if (data.data != NULL) {
        if (!Tcl_CommandComplete(Tcl_GetString(data.data))) {
            Tcl_AppendResult(interp, "data is not complete (missing brace?)", 0);
            return TCL_ERROR;
        }
    }
    if (data.addTags != NULL) {
        if (Tcl_ListObjGetElements(interp, data.addTags, &data.tobjc, &data.tobjv) 
            != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (data.keys != NULL && Tcl_ListObjGetElements(interp, data.keys, &data.kobjc,
        &data.kobjv) != TCL_OK) {
            return TCL_ERROR;
    }
    if (data.notKeys != NULL && Tcl_ListObjGetElements(interp, data.notKeys,
        &data.nobjc, &data.nobjv) != TCL_OK) {
            return TCL_ERROR;
    }
    /*if (data.keys != NULL && data.notKeys != NULL) {
        Tcl_AppendResult(interp, "can not use both -keys and -notkeys", 0);
        return TCL_ERROR;
    }*/

    Blt_InitHashTable(&data.idTable, BLT_ONE_WORD_KEYS);
    data.root = root;
    elemArr = NULL;
    nLines = 0;
    result = TCL_OK;
    if (channel != NULL) {
        for (;;) {
            result = ReadEntry(interp, channel, &nElem, &elemArr);
            if ((result != TCL_OK) || (nElem == 0)) {
                break;
            }
            result = RestoreNode(cmdPtr, nElem, elemArr, &data);
            if (result != TCL_OK) {
                break;
            }
        }
    } else {
        entry = eol = Tcl_GetString(data.data);
        next = entry;
        while (*eol != '\0') {
            /* Find the next end of line. */
            for (eol = next; (*eol != '\n') && (*eol != '\0'); eol++) {
                /*empty*/
            }
            /* 
            * Since we don't own the string (the Tcl_Obj could be shared),
                * save the current end-of-line character (it's either a NUL
                * or NL) so we can NUL-terminate the line for the call to
                * Tcl_SplitList and repair it when we're done.
                */
                saved = *eol;
                *eol = '\0';
                next = eol + 1;
                nLines++;
                if (Tcl_CommandComplete(entry)) {
                char **elArr;
                int nEl;
	    
                if (Tcl_SplitList(interp, entry, &nEl, &elArr) != TCL_OK) {
                    *eol = saved;
                    return TCL_ERROR;
                }
                if (nEl > 0) {
                    result = RestoreNode(cmdPtr, nEl, elArr, &data);
                    Blt_Free(elArr);
                    if (result != TCL_OK) {
                        *eol = saved;
                        break;
                    }
                }
                entry = next;
            }
            *eol = saved;
        }
    }
    Blt_DeleteHashTable(&data.idTable);
done:
    if (elemArr != NULL) {
	Blt_Free(elemArr);
    }
    if (isfile) {
        Tcl_Close(interp, channel);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * RootOp -- Get/set the root.
 *
 *---------------------------------------------------------------------- 
 */
static int
RootOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode root;

    if (objc == 3) {
	Blt_TreeNode node;

	if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	    return TCL_ERROR;
	}
	Blt_TreeChangeRoot(cmdPtr->tree, node);
    }
    root = Blt_TreeRootNode(cmdPtr->tree);
    Tcl_SetIntObj(Tcl_GetObjResult(interp), Blt_TreeNodeId(root));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NodeidOp -- Get/set the next id to be allocated.
 *
 *---------------------------------------------------------------------- 
 */
static int
NodeSeqOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    int num;

    if (objc == 3) {

	if (Tcl_GetIntFromObj(interp, objv[2], &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (num <= 0) {
             Tcl_AppendResult(interp, "must be > 0", 0);
             return TCL_ERROR;
         }
         cmdPtr->tree->treeObject->nextInode = num;
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), cmdPtr->tree->treeObject->nextInode);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IncriOp -- Increment, with initalization to 0 if not exists.
 *
 *---------------------------------------------------------------------- 
 */
static int
IncriOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    double dVal, dIncr = 1.0;
    int iVal, iIncr = 1, isInt = 0, count = 0, len;
    Tcl_Obj *objPtr, *valueObjPtr;
    TagSearch cursor = {0};
    
    string = Tcl_GetStringFromObj(objv[2],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        iIncr = 1; isInt = 0;
        count++;
        string = Tcl_GetString(objv[3]); 
        if (Blt_TreeGetValue(NULL, cmdPtr->tree, node, string,
            &valueObjPtr) != TCL_OK) {
                if (Blt_TreeSetValue(NULL, cmdPtr->tree, node, string,
                Tcl_NewIntObj(0)) != TCL_OK ||
                Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
                &valueObjPtr) != TCL_OK) {
                    goto error;
            }
        }
        if (Tcl_GetIntFromObj(NULL, valueObjPtr, &iVal) == TCL_OK &&
        (objc <= 4 || Tcl_GetIntFromObj(NULL, objv[4], &iIncr) == TCL_OK)) {
            isInt = 1;
        } else {
            if (objc > 4 && Tcl_GetDoubleFromObj(interp, objv[4], &dIncr) != TCL_OK) {
                goto error;
            }
            if (Tcl_GetDoubleFromObj(interp, valueObjPtr, &dVal) != TCL_OK) {
                goto error;
            }
        }
        if (isInt) {
            iVal += iIncr;
            objPtr = Tcl_NewIntObj(iVal);
        } else {
            dVal += dIncr;
            objPtr = Tcl_NewDoubleObj(dVal);
        }
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, objPtr) != TCL_OK) {
            goto error;
        }
    }
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
    
error:    
    DoneTaggedNodes(&cursor);
    return TCL_ERROR;;
}
/*
 *----------------------------------------------------------------------
 *
 * IncrOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
IncrOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    double dVal, dIncr = 1.0;
    int iVal, iIncr = 1, isInt = 0;
    Tcl_Obj *objPtr, *valueObjPtr;
	
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]); 
    if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
        &valueObjPtr) != TCL_OK) {
            return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(NULL, valueObjPtr, &iVal) == TCL_OK &&
         (objc <= 4 || Tcl_GetIntFromObj(NULL, objv[4], &iIncr) == TCL_OK)) {
        isInt = 1;
    } else {
        if (objc > 4 && Tcl_GetDoubleFromObj(interp, objv[4], &dIncr) != TCL_OK) {
            return TCL_ERROR;
        }
        if (Tcl_GetDoubleFromObj(interp, valueObjPtr, &dVal) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (isInt) {
        iVal += iIncr;
        objPtr = Tcl_NewIntObj(iVal);
    } else {
        dVal += dIncr;
        objPtr = Tcl_NewDoubleObj(dVal);
    }
    if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, objPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, objPtr);
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * IsSetOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
IsSetOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
	
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]); 
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj((Blt_TreeGetValue(NULL, cmdPtr->tree, node, string, &valuePtr) == TCL_OK)));
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * LappendOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
LappendOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
    int len = 0, alloc = 0;
	
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]); 
    if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
        &valuePtr) != TCL_OK) {
            return TCL_ERROR;
    }
    if (objc<=4) return TCL_OK;
    if (valuePtr != NULL && Tcl_ListObjLength(interp, valuePtr, &len) != TCL_OK) {
        return TCL_ERROR;
    }
    if (!(node->flags & TREE_TRACE_ACTIVE)) {
        cmdPtr->updTyp = 2;
        if (valuePtr == NULL) {
            cmdPtr->oldLen = 0;
        } else {
            cmdPtr->oldLen = len;
        }
    }
    if (Tcl_IsShared(valuePtr)) {
        alloc = 1;
        valuePtr = Tcl_DuplicateObj(valuePtr);
    }

    if (Tcl_ListObjReplace(interp, valuePtr, len, 0, objc-4, objv+4) != TCL_OK) {
        if (alloc) {
            Tcl_DecrRefCount(valuePtr);
        }
        return TCL_ERROR;
    }
    /*for (i=4 ; i<objc ; i++) {
        if (Tcl_ListObjAppendElement(interp, valuePtr, objv[i]) != TCL_OK) {
            if (alloc) {
                Tcl_DecrRefCount(valuePtr);
            }
            return TCL_ERROR;
        }
    }*/
    if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, valuePtr) != TCL_OK) {
        if (alloc) {
            Tcl_DecrRefCount(valuePtr);
        }
        return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, valuePtr);
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * LappendiOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
LappendiOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
    int len = 0, alloc = 0, count = 0;
    TagSearch cursor = {0};
    
    string = Tcl_GetStringFromObj(objv[2],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    
    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        count++;
	
        string = Tcl_GetString(objv[3]); 
        if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
            &valuePtr) != TCL_OK) {
                if (Blt_TreeSetValue(NULL, cmdPtr->tree, node, string,
                Tcl_NewListObj(0, NULL)) != TCL_OK ||
                Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
                &valuePtr) != TCL_OK) {
                    DoneTaggedNodes(&cursor);
                    return TCL_ERROR;
            }
        }
        if (objc<=4) {
            DoneTaggedNodes(&cursor);
            return TCL_OK;
        }
        if (valuePtr != NULL && Tcl_ListObjLength(interp, valuePtr, &len) != TCL_OK) {
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
        if (!(node->flags & TREE_TRACE_ACTIVE)) {
            cmdPtr->updTyp = 2;
            if (valuePtr == NULL) {
                cmdPtr->oldLen = 0;
            } else {
                cmdPtr->oldLen = len;
            }
        }
        if (Tcl_IsShared(valuePtr)) {
            alloc = 1;
            valuePtr = Tcl_DuplicateObj(valuePtr);
        }

        if (Tcl_ListObjReplace(interp, valuePtr, len, 0, objc-4, objv+4) != TCL_OK) {
            if (alloc) {
                Tcl_DecrRefCount(valuePtr);
            }
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, valuePtr) != TCL_OK) {
            if (alloc) {
                Tcl_DecrRefCount(valuePtr);
            }
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
    }
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * AppendOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
AppendOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
    int i, alloc = 0;
	
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]); 
    if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
        &valuePtr) != TCL_OK) {
            return TCL_ERROR;
    }
    if (objc<=4) return TCL_OK;
    if (!(node->flags & TREE_TRACE_ACTIVE)) {
        cmdPtr->updTyp = 1;
        if (valuePtr == NULL) {
            cmdPtr->oldLen = 0;
        } else {
            Tcl_GetStringFromObj(valuePtr, &cmdPtr->oldLen);
        }
    }
    if (Tcl_IsShared(valuePtr)) {
        alloc = 1;
        valuePtr = Tcl_DuplicateObj(valuePtr);
    }

    for (i=4 ; i<objc ; i++) {
        Tcl_AppendObjToObj(valuePtr, objv[i]);
    }
    if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, valuePtr) != TCL_OK) {
        if (alloc) {
            Tcl_DecrRefCount(valuePtr);
        }
        return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, valuePtr);
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * AppendiOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
AppendiOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
    int i, alloc = 0, count = 0, len;
    TagSearch cursor = {0};
	
    string = Tcl_GetStringFromObj(objv[2],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    
    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL;
        node = NextTaggedNode(node, &cursor)) {
        count++;
        string = Tcl_GetString(objv[3]); 
        if (Blt_TreeGetValue(NULL, cmdPtr->tree, node, string,
            &valuePtr) != TCL_OK) {
                if (Blt_TreeSetValue(NULL, cmdPtr->tree, node, string,
                Tcl_NewStringObj("", -1)) != TCL_OK ||
                Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
                &valuePtr) != TCL_OK) {
                    DoneTaggedNodes(&cursor);
                    return TCL_ERROR;
            }
        }
        if (objc<=4) {
            DoneTaggedNodes(&cursor);
            return TCL_OK;
        }
        if (!(node->flags & TREE_TRACE_ACTIVE)) {
            cmdPtr->updTyp = 1;
            if (valuePtr == NULL) {
                cmdPtr->oldLen = 0;
            } else {
                Tcl_GetStringFromObj(valuePtr, &cmdPtr->oldLen);
            }
        }
        if (Tcl_IsShared(valuePtr)) {
            alloc = 1;
            valuePtr = Tcl_DuplicateObj(valuePtr);
        }

        for (i=4 ; i<objc ; i++) {
            Tcl_AppendObjToObj(valuePtr, objv[i]);
        }
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, valuePtr) != TCL_OK) {
            if (alloc) {
                Tcl_DecrRefCount(valuePtr);
            }
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
    }
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}
/*
 *----------------------------------------------------------------------
 *
 * UpdateOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
updateOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv,
    int cmpstr)
{
    Blt_TreeNode node;
    char *string;
    Tcl_Obj *valuePtr;
    int i, result = TCL_OK, inode;
    Tcl_DString dStr;
	
    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
        return TCL_ERROR;
    }
    if ((objc%2) == 0) {
        Tcl_AppendResult(interp, "odd number of key/value pairs", 0);
        return TCL_ERROR;
    }
    if (objc<=3) { return TCL_OK; }
    if (!(node->flags & TREE_TRACE_ACTIVE)) {
        cmdPtr->updTyp = 0;
    }
    Tcl_DStringInit(&dStr);
    inode = node->inode;
    for (i=3; i<objc; i+=2) {
        string = Tcl_GetString(objv[i]); 
        if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string,
            &valuePtr) != TCL_OK) {
            result = TCL_ERROR;
            Tcl_DStringAppend(&dStr, Tcl_GetStringResult(interp), -1);
            Tcl_ResetResult(interp);
            continue;
        }
        if (cmpstr && valuePtr != NULL && strcmp(Tcl_GetString(objv[i+1]), Tcl_GetString(valuePtr)) == 0) continue;
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, objv[i+1]) != TCL_OK) {
            Tcl_DStringAppend(&dStr, Tcl_GetStringResult(interp), -1);
            Tcl_DStringAppend(&dStr, " ", -1);
            Tcl_ResetResult(interp);
            result = TCL_ERROR;
        }
    }
    if (result != TCL_OK) {
        Tcl_DStringResult(interp, &dStr);
    }
    return result;
}

static int
UpdateOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    return updateOp(cmdPtr, interp, objc, objv, 0);
}

static int
UpdatesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    return updateOp(cmdPtr, interp, objc, objv, 1);
}

/*
 *----------------------------------------------------------------------
 *
 * SetOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
SetOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    int count = 0, len;
    TagSearch cursor = {0};
	
    string = Tcl_GetStringFromObj(objv[2],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    
    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        if (!(node->flags & TREE_TRACE_ACTIVE)) {
            cmdPtr->updTyp = 0;
        }
        count++;
        if (SetValues(cmdPtr, node, objc - 3, objv + 3) != TCL_OK) {
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
    }
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IsModifiedOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
IsModifiedOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int ismod;
    Blt_TreeObject treeObjPtr = cmdPtr->tree->treeObject;
    TagSearch cursor = {0};
	
    if (objc == 2) {
        ismod = ((treeObjPtr->flags&TREE_UNMODIFIED) == 0);
        Tcl_SetObjResult(interp, Tcl_NewIntObj(ismod));
        return TCL_OK;
    }
    
    if (objc == 3) {
        if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
            return TCL_ERROR;
        }
        ismod = ((node->flags&TREE_NODE_UNMODIFIED) == 0);
        Tcl_SetObjResult(interp, Tcl_NewIntObj(ismod));
        return TCL_OK;
    }
    
    if (Tcl_GetBooleanFromObj(interp, objv[3], &ismod) != TCL_OK) {
        return TCL_ERROR;
    }

    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        if (ismod) {
            node->flags &= ~TREE_NODE_UNMODIFIED;
        } else {
            node->flags |= TREE_NODE_UNMODIFIED;
        }
    }
    if (!strcmp("all", Tcl_GetString(objv[2]))) {
        if (ismod) {
            treeObjPtr->flags &= ~TREE_UNMODIFIED;
        } else {
            treeObjPtr->flags |= TREE_UNMODIFIED;
        }
    }
    DoneTaggedNodes(&cursor);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SumOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
SumOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string, *runKey = NULL;
    double start = 0, cur = 0, mindif = 1e-13;
    int istart = 0, icur = 0, len;
    int i, n, useint = 0, force = 0;
    Tcl_Obj *valuePtr, *valPtr;
    TagSearch cursor = {0};

    i = 2;
    while (i<objc) {
        string = Tcl_GetString(objv[i]);
        if (strcmp(string, "-runtotal") == 0) {
            if ((i+1)>=objc) {
                Tcl_AppendResult(interp, "missing value", (char *)NULL);
                return TCL_ERROR;
            }
            runKey = Tcl_GetString(objv[i+1]);
            i += 2;
        } else if (strcmp(string, "-int") == 0) {
            useint = 1;
            i += 1;
        } else if (strcmp(string, "-force") == 0) {
            force = 1;
            i += 1;
        } else if (strcmp(string, "-start") == 0) {
            if ((i+1)>=objc) {
                Tcl_AppendResult(interp, "missing value", (char *)NULL);
                return TCL_ERROR;
            }
            if (useint) {
                if (Tcl_GetIntFromObj(interp, objv[i+1], &istart) != TCL_OK) {
                    return TCL_ERROR;
                }
            } else {
                if (Tcl_GetDoubleFromObj(interp, objv[i+1], &start) != TCL_OK) {
                    return TCL_ERROR;
                }
            }
            i += 2;
        } else if (strcmp(string, "-diff") == 0) {
            if ((i+1)>=objc) {
                Tcl_AppendResult(interp, "missing value", (char *)NULL);
                return TCL_ERROR;
            }
            if (Tcl_GetDoubleFromObj(interp, objv[i+1], &mindif) != TCL_OK) {
                return TCL_ERROR;
            }
            i += 2;
        } else if (string[0] == '-') {
            Tcl_AppendResult(interp, "option not one of: -runtotal -start -int", (char *)NULL);
            return TCL_ERROR;
        } else {
            break;
        }
    }
    if (useint) {
        if (start != 0 && istart == 0) {
            istart = (int)start;
        }
    }
    if ((i+2) != objc) {
        Tcl_AppendResult(interp, "usage: ?options? nodelst key", (char *)NULL);
        return TCL_ERROR;
    }

    string = Tcl_GetStringFromObj(objv[i+1], &len);
    if (len == 0) goto done;
    
    if (FindTaggedNodes(interp, cmdPtr, objv[i], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        
        for (n = i+1; n < objc; n++) {
            string = Tcl_GetString(objv[n]);
            if (Blt_TreeGetValue(NULL, cmdPtr->tree, node, string, &valuePtr) 
                != TCL_OK) {
                continue;
            }
            if (useint) {
                if (Tcl_GetIntFromObj(NULL, valuePtr, &icur) != TCL_OK) {
                    continue;
                }
                istart += icur;
            } else {
                if (Tcl_GetDoubleFromObj(NULL, valuePtr, &cur) != TCL_OK) {
                    continue;
                }
                start += cur;
            }
            if (runKey != NULL) {
                if (force == 0 && Blt_TreeGetValue(NULL, cmdPtr->tree, node,
                    runKey, &valPtr) == TCL_OK) {
                    if (useint) {
                        if (Tcl_GetIntFromObj(NULL, valPtr, &icur) == TCL_OK &&
                            istart == icur) {
                            continue;
                        }
                    } else {
                        if (Tcl_GetDoubleFromObj(NULL, valPtr, &cur) == TCL_OK &&
                            fabs(start - cur)<mindif) {
                            continue;
                        }
                    }
                }
                Blt_TreeSetValue(NULL, cmdPtr->tree, node, runKey,
                    (useint ? Tcl_NewIntObj(istart) : Tcl_NewDoubleObj(start)));
            }
        }
    }
done:
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, (useint ? Tcl_NewIntObj(istart) : Tcl_NewDoubleObj(start)));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ModifyOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
ModifyOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    int count = 0, result = TCL_OK, len;
    Tcl_DString dStr;
    TagSearch cursor = {0};

    if ((objc % 2) == 0) {
        Tcl_AppendResult(interp, "odd # values", (char *)NULL);
        return TCL_ERROR;
    }
    if (objc<=3) return TCL_OK;
    string = Tcl_GetStringFromObj(objv[2], &len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    node = FirstTaggedNode(&cursor);
    if (node != NULL && !(node->flags & TREE_TRACE_ACTIVE)) {
        cmdPtr->updTyp = 0;
    }
    Tcl_DStringInit(&dStr);
    for (/* empty */; node != NULL; node = NextTaggedNode(node, &cursor)) {
        count++;
        if (UpdateValues(cmdPtr, node, objc - 3, objv + 3) != TCL_OK) {
            Tcl_DStringAppend(&dStr, Tcl_GetStringResult(interp), -1);
            Tcl_DStringAppend(&dStr, " ", -1);
            Tcl_ResetResult(interp);
            result = TCL_ERROR;
        }
    }
    if (result != TCL_OK) {
        Tcl_DStringResult(interp, &dStr);
    } else {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    }
    DoneTaggedNodes(&cursor);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * SizeOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
SizeOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_SetIntObj(Tcl_GetObjResult(interp), Blt_TreeSize(node));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagForgetOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TagForgetOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    register int i;

    for (i = 3; i < objc; i++) {
	if (Blt_TreeForgetTag(cmdPtr->tree, Tcl_GetString(objv[i])) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagNamesOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagNamesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_HashSearch cursor;
    Blt_TreeTagEntry *tPtr;
    Tcl_Obj *listObjPtr, *objPtr, *patPtr = NULL;
    char *string, *pattern = NULL, ptype = 0;
    int nocase = 0, result;

    while (objc>3) {
        string = Tcl_GetString(objv[3]);
        if (strcmp(string, "-glob") == 0 || strcmp(string, "-regexp") == 0) {
            if (objc == 4) {
                Tcl_AppendResult(interp, "missing pattern", 0);
                return TCL_ERROR;
            }
            ptype = string[1];
            patPtr = objv[4];
            pattern = Tcl_GetString(objv[4]);
            if (ptype == 'r') {
                result = Tcl_RegExpMatch(interp, "", pattern); 
                if (result == -1) {
                    return TCL_ERROR;
                }
            }
            objc -= 2;
            objv += 2;
        } else if (strcmp(string, "-nocase") == 0) {
            objc -= 1;
            objv += 1;
            nocase = 1;
        } else {
            break;
        }
    }
    
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (0 && patPtr == NULL) {
        objPtr = Tcl_NewStringObj("all", -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        objPtr = Tcl_NewStringObj("nonroot", -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        objPtr = Tcl_NewStringObj("rootchildren", -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    if (objc == 3) {
	Blt_HashEntry *hPtr;

        if (0 && patPtr == NULL) {
            objPtr = Tcl_NewStringObj("root", -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
	for (hPtr = Blt_TreeFirstTag(cmdPtr->tree, &cursor); hPtr != NULL; 
	     hPtr = Blt_NextHashEntry(&cursor)) {
	    tPtr = Blt_GetHashValue(hPtr);
            if (pattern != NULL) {
                if (ptype == 'g') {
                    if (!Tcl_StringCaseMatch(tPtr->tagName, pattern, nocase)) {
                        continue;
                    }
                } else {
                    string = tPtr->tagName;
                    if (nocase) {
                        string = Blt_Strdup(string);
                        strtolower(string);
                    }
                    result = (Tcl_RegExpMatch(interp, string, pattern) == 1); 
                    if (nocase) {
                        Blt_Free(string);
                    }
                    if (result == 0) continue;
                }
            }
	    objPtr = Tcl_NewStringObj(tPtr->tagName, -1);
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
    } else {
	register int i;
	Blt_TreeNode node;
	Blt_HashEntry *hPtr;
	Blt_HashTable uniqTable;
	int isNew;

	Blt_InitHashTable(&uniqTable, BLT_STRING_KEYS);
	for (i = 3; i < objc; i++) {
	    if (GetNode(cmdPtr, objv[i], &node) != TCL_OK) {
		goto error;
	    }
	    if (node == Blt_TreeRootNode(cmdPtr->tree)) {
		Blt_CreateHashEntry(&uniqTable, "root", &isNew);
	    }
	    for (hPtr = Blt_TreeFirstTag(cmdPtr->tree, &cursor); hPtr != NULL;
		 hPtr = Blt_NextHashEntry(&cursor)) {
		tPtr = Blt_GetHashValue(hPtr);
	        if (Blt_TreeHasTag(cmdPtr->tree, node, tPtr->tagName)) {
		    Blt_CreateHashEntry(&uniqTable, tPtr->tagName, &isNew);
		}
	    }
	}
	for (hPtr = Blt_FirstHashEntry(&uniqTable, &cursor); hPtr != NULL;
	    hPtr = Blt_NextHashEntry(&cursor)) {
            string = (char*)Blt_GetHashKey(&uniqTable, hPtr);
            if (pattern != NULL) {
                if (ptype == 'g') {
                    if (!Tcl_StringCaseMatch(string, pattern, nocase)) {
                        continue;
                    }
                } else {
                    if (nocase) {
                        string = Blt_Strdup(string);
                        strtolower(string);
                    }
                    result = (Tcl_RegExpMatch(interp, string, pattern) == 1); 
                    if (nocase) {
                        Blt_Free(string);
                    }
                    if (result == 0) continue;
                }
            }
	    objPtr = Tcl_NewStringObj(string, -1);
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
	Blt_DeleteHashTable(&uniqTable);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
 error:
    Tcl_DecrRefCount(listObjPtr);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * TagLookupsOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagLookupsOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_HashEntry *hPtr, *h2Ptr;
    Blt_HashSearch cursor, tcursor;
    Blt_TreeNode node;
    Blt_TreeTagEntry *tPtr;
    Tcl_Obj *listObjPtr, *objPtr;
    Tcl_DString dStr;
    int result = TCL_OK, cnt;
    Blt_HashTable tagTable;
    Tcl_DString *eStr;
    char *string;
    
    if (objc == 4) {
        string = Tcl_GetString(objv[3]);
        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        Tcl_DStringInit(&dStr);

        for (hPtr = Blt_TreeFirstTag(cmdPtr->tree, &cursor); hPtr != NULL;         
            hPtr = Blt_NextHashEntry(&cursor)) {
        
            tPtr = Blt_GetHashValue(hPtr);
            if (Tcl_StringMatch(tPtr->tagName, string)!=1) {
                continue;
            }

            objPtr = Tcl_NewStringObj(tPtr->tagName, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            Tcl_DStringSetLength(&dStr, 0);
        
            cnt = 0;
            for (h2Ptr = Blt_FirstHashEntry(&tPtr->nodeTable, &tcursor); h2Ptr != NULL;
            h2Ptr = Blt_NextHashEntry(&tcursor)) {
                node = Blt_GetHashValue(h2Ptr);
                cnt++;
                if (cnt>1) {
                    Tcl_DStringAppend(&dStr, " ", -1);
                }
                Tcl_DStringAppend(&dStr, Blt_Itoa(node->inode), -1);
            }
        
            objPtr = Tcl_NewStringObj(Tcl_DStringValue(&dStr), -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_DStringFree(&dStr);
        Tcl_SetObjResult(interp, listObjPtr);
        return result;
    }
    
    MakeTagTable(cmdPtr->tree, &tagTable, 0, 0);

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    
    for (h2Ptr = Blt_FirstHashEntry(&tagTable, &tcursor);
        h2Ptr != NULL; h2Ptr = Blt_NextHashEntry(&tcursor)) {
        node = Blt_GetHashKey(&tagTable, h2Ptr);
        eStr = (Tcl_DString*)Blt_GetHashValue(h2Ptr);
        
        objPtr = Tcl_NewStringObj(Blt_Itoa(node->inode), -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);

        objPtr = Tcl_NewStringObj(Tcl_DStringValue(eStr), -1);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        
        Tcl_DStringFree(eStr);
        Blt_Free(eStr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TagNodesOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagNodesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor = {0};
    Blt_HashTable nodeTable;
    Blt_TreeNode node;
    Tcl_Obj *listObjPtr;
    Tcl_Obj *objPtr;
    char *string;
    int isNew;
    register int i;
	
    Blt_InitHashTable(&nodeTable, BLT_ONE_WORD_KEYS);
    for (i = 3; i < objc; i++) {
        TagSearch tcursor = {0};
        
        string = Tcl_GetString(objv[i]);
        if (FindTaggedNodes(interp, cmdPtr, objv[i], &tcursor) != TCL_OK) {
            Tcl_ResetResult(interp);
            DoneTaggedNodes(&tcursor);
            continue;
        }
        for (node = FirstTaggedNode(&tcursor);
            node != NULL; node = NextTaggedNode(node, &tcursor)) {
            Blt_CreateHashEntry(&nodeTable, (char *)node, &isNew);
	}
        DoneTaggedNodes(&tcursor);
     }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&nodeTable, &cursor); hPtr != NULL; 
	 hPtr = Blt_NextHashEntry(&cursor)) {
	node = (Blt_TreeNode)Blt_GetHashKey(&nodeTable, hPtr);
	objPtr = Tcl_NewIntObj(Blt_TreeNodeId(node));
	Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    Blt_DeleteHashTable(&nodeTable);
    return TCL_OK;

/* error:
    Blt_DeleteHashTable(&nodeTable);
    return TCL_ERROR;*/
}

/*
 *----------------------------------------------------------------------
 *
 * TagExistsOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagExistsOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    char *string;
    int exists = 0;
    Blt_TreeNode node;
	
    string = Tcl_GetString(objv[3]);
    if (objc == 4) {
        if (strcmp(string, "all") == 0 || (strcmp(string, "root") == 0)
            || (strcmp(string, "nonroot") == 0)
            || (strcmp(string, "rootchildren") == 0)) {
            exists = 1;
        } else {
            TagSearch tcursor = {0};
            if (FindTaggedNodes(interp, cmdPtr, objv[3], &tcursor) == TCL_OK) {
                exists = 1;
            }
            DoneTaggedNodes(&tcursor);
        }
    } else {
        if (GetNode(cmdPtr, objv[4], &node) != TCL_OK) {
            return TCL_ERROR;
        }
        exists = Blt_TreeHasTag(cmdPtr->tree, node, string);
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(exists));
    return TCL_OK;

}

/*
 *----------------------------------------------------------------------
 *
 * TagAddOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagAddOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    register int i;
    char *string;
    int count = 0;
    TagSearch cursor = {0};

    string = Tcl_GetString(objv[3]);
    if (isdigit(UCHAR(string[0]))) {
	Tcl_AppendResult(interp, "bad tag \"", string, 
		 "\": can't start with a digit", (char *)NULL);
	return TCL_ERROR;
    }
    if (strstr(string,"->") != NULL) {
        Tcl_AppendResult(cmdPtr->interp, "invalid tag \"", string, 
            "\": can't contain \"->\"", (char *)NULL);
            return TCL_ERROR;
    }
    if (string[0] == '@') {
        Tcl_AppendResult(cmdPtr->interp, "invalid tag \"", string, 
            "\": can't start with \"@\"", (char *)NULL);
            return TCL_ERROR;
    } 
    if ((strcmp(string, "all") == 0) || (strcmp(string, "root") == 0)
        || (strcmp(string, "nonroot") == 0)
        || (strcmp(string, "rootchildren") == 0)) {
	Tcl_AppendResult(cmdPtr->interp, "can't add reserved tag \"",
			 string, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (objc == 4) {
        return Blt_TreeAddTag(cmdPtr->tree, NULL, string);
    }
    for (i = 4; i < objc; i++) {
        string = Tcl_GetString(objv[3]);
        if (isdigit(UCHAR(string[0])) && strchr(string,' ')) {
        }
        if (FindTaggedNodes(interp, cmdPtr, objv[i], &cursor) != TCL_OK) {
            return TCL_ERROR;
        }
        for (node = FirstTaggedNode(&cursor);
            node != NULL;
            node = NextTaggedNode(node, &cursor)) {
	    count++;
	    if (AddTag(cmdPtr, node, string) != TCL_OK) {
                 DoneTaggedNodes(&cursor);
                 return TCL_ERROR;
	    }
	}
        DoneTaggedNodes(&cursor);
     }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TagDeleteOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagDeleteOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    char *string;
    Blt_HashTable *tablePtr;
    int count = 0;

    string = Tcl_GetString(objv[3]);
    if ((strcmp(string, "all") == 0) || (strcmp(string, "root") == 0)
        || (strcmp(string, "nonroot") == 0)
        || (strcmp(string, "childrenroot") == 0)) {
	/* Tcl_AppendResult(interp, "can't delete reserved tag \"", string, "\"", 
			 (char *)NULL);
        return TCL_ERROR; */
        Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
        return TCL_OK;
    }
    tablePtr = Blt_TreeTagHashTable(cmdPtr->tree, string);
    if (tablePtr != NULL) {
        register int i;
        Blt_TreeNode node;
        Blt_HashEntry *hPtr;
        TagSearch cursor = {0};
      
        for (i = 4; i < objc; i++) {
            if (FindTaggedNodes(interp, cmdPtr, objv[i], &cursor) != TCL_OK) {
                return TCL_ERROR;
            }
            for (node = FirstTaggedNode(&cursor);
                node != NULL; 	
		node = NextTaggedNode(node, &cursor)) {
	        hPtr = Blt_FindHashEntry(tablePtr, (char *)node);
	        if (hPtr != NULL) {
	            int result;
                    result = Blt_TreeTagDelTrace(cmdPtr->tree, node, string);
                    if (result != TCL_OK) {
                        if (result != TCL_BREAK) {
                            DoneTaggedNodes(&cursor);
                            return TCL_ERROR;
                        }
                        continue;
                    }
                    Blt_DeleteHashEntry(tablePtr, hPtr);
                    count++;
	        }
	   }
           DoneTaggedNodes(&cursor);
        }
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TagDumpOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TagDumpOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    register Blt_TreeNode root, node;
    Tcl_DString dString;
    int tags = 1, result = TCL_OK;
    TagSearch cursor = {0};
    RestoreData data;
    
    memset((char *)&data, 0, sizeof(data));

    Tcl_DStringInit(&dString);
    root = Blt_TreeRootNode(cmdPtr->tree);
    if (objc > 4) {
        /* Process switches  */
        if (Blt_ProcessObjSwitches(interp, dumptagSwitches, objc - 4, objv + 4, 
            (char *)&data, BLT_SWITCH_EXACT) < 0) {
                return TCL_ERROR;
        }
    }
    if (data.keys != NULL && Tcl_ListObjGetElements(interp, data.keys, &data.kobjc,
        &data.kobjv) != TCL_OK) {
            return TCL_ERROR;
    }
    if (data.notKeys != NULL && Tcl_ListObjGetElements(interp, data.notKeys,
        &data.nobjc, &data.nobjv) != TCL_OK) {
            return TCL_ERROR;
    }
    /*if (data.keys != NULL && data.notKeys != NULL) {
        Tcl_AppendResult(interp, "can not use both -keys and -notkeys", 0);
        return TCL_ERROR;
    }*/

    tags = ((data.flags&RESTORE_NO_TAGS) == 0);
    if (FindTaggedNodes(interp, cmdPtr, objv[3], &cursor) != TCL_OK) {
        result = TCL_ERROR;
        goto done;
    }
    if (tags) {
        MakeTagTable(cmdPtr->tree, &data.tagTable, data.tags, data.notTags);
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        PrintNode(cmdPtr, root, node, &dString, tags, &data);
    }
    DoneTaggedNodes(&cursor);
    if (tags) {
         FreeTagTable(&data.tagTable);
     }
done:
     Tcl_DStringResult(interp, &dString);
     Tcl_DStringFree(&dString);
     return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TagOp --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec tagOps[] = {
    {"add", 1, (Blt_Op)TagAddOp, 4, 0, "tag ?node?...",},
    {"delete", 2, (Blt_Op)TagDeleteOp, 5, 0, "tag node...",},
    {"dump", 2, (Blt_Op)TagDumpOp, 4, 0, "tag ?switches?",},
    {"exists", 2, (Blt_Op)TagExistsOp, 4, 5, "tag ?node?",},
    {"forget", 1, (Blt_Op)TagForgetOp, 4, 0, "tag...",},
    {"lookups", 2, (Blt_Op)TagLookupsOp, 3, 4, "?pattern?",},
    {"names", 2, (Blt_Op)TagNamesOp, 3, 0, "?-glob pat? ?-regexp pat? ?node...?",},
    {"nodes", 2, (Blt_Op)TagNodesOp, 4, 0, "tag ?tag...?",},
};

static int nTagOps = sizeof(tagOps) / sizeof(Blt_OpSpec);

static int
TagOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nTagOps, tagOps, BLT_OP_ARG2, objc, objv, 
	0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (cmdPtr, interp, objc, objv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TraceCreateOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TraceCreateOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_HashEntry *hPtr;
    Blt_TreeNode node;
    TraceInfo *tracePtr;
    char *string, *key, *command;
    char *tagName;
    char idString[200];
    int flags, isNew;
    int length, idle;

    idle = 0;
    if (objc > 7) {
        if (!strcmp("-bgerror", Tcl_GetString(objv[7]))) {
            idle = 1;
        } else {
            Tcl_AppendResult(interp, "expected \"-bgerror\": " , Tcl_GetString(objv[7]), 0);
            return TCL_ERROR;
        }
    }
    string = Tcl_GetString(objv[3]);
    if (isdigit(UCHAR(*string))) {
	if (GetNode(cmdPtr, objv[3], &node) != TCL_OK) {
	    return TCL_ERROR;
	}
	tagName = NULL;
    } else {
	tagName = Blt_Strdup(string);
	node = NULL;
    }
    key = Tcl_GetString(objv[4]);
    string = Tcl_GetString(objv[5]);
    flags = GetTraceFlags(string);
    if (flags < 0) {
	Tcl_AppendResult(interp, "unknown flag in \"", string, "\"", 
		     (char *)NULL);
	return TCL_ERROR;
    }
    command = Tcl_GetStringFromObj(objv[6], &length);
    /* Stash away the command in structure and pass that to the trace. */
    tracePtr = Blt_Calloc(1, length + sizeof(TraceInfo));
    strcpy(tracePtr->command, command);
    tracePtr->cmdPtr = cmdPtr;
    tracePtr->withTag = tagName;
    tracePtr->node = node;
    if (idle) {
        flags |= TREE_TRACE_BGERROR;
    }
    tracePtr->traceToken = Blt_TreeCreateTrace(cmdPtr->tree, node, key, tagName,
	flags, TreeTraceProc, tracePtr);

    sprintf(idString, "trace%d", cmdPtr->traceCounter++);
    hPtr = Blt_CreateHashEntry(&(cmdPtr->traceTable), idString, &isNew);
    Blt_SetHashValue(hPtr, tracePtr);

    Tcl_SetStringObj(Tcl_GetObjResult(interp), idString, -1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TraceDeleteOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
TraceDeleteOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TraceInfo *tracePtr;
    Blt_HashEntry *hPtr;
    register int i;
    char *key;

    for (i = 3; i < objc; i++) {
	key = Tcl_GetString(objv[i]);
	hPtr = Blt_FindHashEntry(&(cmdPtr->traceTable), key);
	if (hPtr == NULL) {
	    Tcl_AppendResult(interp, "unknown trace \"", key, "\"", 
			     (char *)NULL);
	    return TCL_ERROR;
	}
	tracePtr = Blt_GetHashValue(hPtr);
	Blt_DeleteHashEntry(&(cmdPtr->traceTable), hPtr); 
	Blt_TreeDeleteTrace(tracePtr->traceToken);
	if (tracePtr->withTag != NULL) {
	    Blt_Free(tracePtr->withTag);
	}
	Blt_Free(tracePtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TraceNamesOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TraceNamesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;

    for (hPtr = Blt_FirstHashEntry(&(cmdPtr->traceTable), &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	Tcl_AppendElement(interp, Blt_GetHashKey(&(cmdPtr->traceTable), hPtr));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TraceInfoOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TraceInfoOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    TraceInfo *tracePtr;
    struct Blt_TreeTraceStruct *tokenPtr;
    Blt_HashEntry *hPtr;
    Tcl_DString dString;
    char string[5];
    char *key;

    key = Tcl_GetString(objv[3]);
    hPtr = Blt_FindHashEntry(&(cmdPtr->traceTable), key);
    if (hPtr == NULL) {
	Tcl_AppendResult(interp, "unknown trace \"", key, "\"", 
			 (char *)NULL);
	return TCL_ERROR;
    }
    Tcl_DStringInit(&dString);
    tracePtr = Blt_GetHashValue(hPtr);
    if (tracePtr->withTag != NULL) {
	Tcl_DStringAppendElement(&dString, tracePtr->withTag);
    } else {
	int inode;

	inode = Blt_TreeNodeId(tracePtr->node);
	Tcl_DStringAppendElement(&dString, Blt_Itoa(inode));
    }
    tokenPtr = (struct Blt_TreeTraceStruct *)tracePtr->traceToken;
    Tcl_DStringAppendElement(&dString, tokenPtr->key);
    PrintTraceFlags(tokenPtr->mask, string);
    Tcl_DStringAppendElement(&dString, string);
    Tcl_DStringAppendElement(&dString, tracePtr->command);
    Tcl_DStringResult(interp, &dString);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TraceOp --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec traceOps[] =
{
    {"create", 1, (Blt_Op)TraceCreateOp, 7, 8, "node key how command ?-whenidle?",},
    {"delete", 1, (Blt_Op)TraceDeleteOp, 3, 0, "id...",},
    {"info", 1, (Blt_Op)TraceInfoOp, 4, 4, "id",},
    {"names", 1, (Blt_Op)TraceNamesOp, 3, 3, "",},
};

static int nTraceOps = sizeof(traceOps) / sizeof(Blt_OpSpec);

static int
TraceOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nTraceOps, traceOps, BLT_OP_ARG2, objc, 
	objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (cmdPtr, interp, objc, objv);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * TypeOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TypeOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    Tcl_Obj *valueObjPtr;
    char *string;

    if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
	return TCL_ERROR;
    }

    string = Tcl_GetString(objv[3]);
    if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string, &valueObjPtr) 
	!= TCL_OK) {
	return TCL_ERROR;
    }
    if (valueObjPtr->typePtr != NULL) {
	Tcl_SetResult(interp, valueObjPtr->typePtr->name, TCL_VOLATILE);
    } else {
	Tcl_SetResult(interp, "string", TCL_STATIC);
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * UnsetOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
UnsetOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    char *string;
    int count = 0;
    TagSearch cursor = {0};
	
    string = Tcl_GetString(objv[2]);

    if (FindTaggedNodes(interp, cmdPtr, objv[2], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    node = FirstTaggedNode(&cursor);
    if (node && !(node->flags & TREE_TRACE_ACTIVE)) {
        cmdPtr->updTyp = 0;
    }
    for (/* empty */; node != NULL; node = NextTaggedNode(node, &cursor)) {
        if (UnsetValues(cmdPtr, node, objc - 3, objv + 3) != TCL_OK) {
            DoneTaggedNodes(&cursor);
            return TCL_ERROR;
        }
        count++;
    }
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}


typedef struct {
    TreeCmd *cmdPtr;
    unsigned int flags;
    int type;
    int mode;
    char *key;
    char *command;
} SortData;

#define SORT_RECURSE		(1<<2)
#define SORT_DECREASING		(1<<3)
#define SORT_PATHNAME		(1<<4)

enum SortTypes { SORT_DICTIONARY, SORT_REAL, SORT_INTEGER, SORT_ASCII, 
	SORT_COMMAND };

enum SortModes { SORT_FLAT, SORT_REORDER };

static Blt_SwitchSpec sortSwitches[] = 
{
    {BLT_SWITCH_FLAG, "-ascii", Blt_Offset(SortData, type), 0, 0, 
	SORT_ASCII},
    {BLT_SWITCH_STRING, "-command", Blt_Offset(SortData, command), 0},
    {BLT_SWITCH_FLAG, "-decreasing", Blt_Offset(SortData, flags), 0, 0, 
	SORT_DECREASING},
    {BLT_SWITCH_FLAG, "-dictionary", Blt_Offset(SortData, type), 0, 0, 
	SORT_DICTIONARY},
    {BLT_SWITCH_FLAG, "-integer", Blt_Offset(SortData, type), 0, 0, 
	SORT_INTEGER},
    {BLT_SWITCH_STRING, "-key", Blt_Offset(SortData, key), 0},
    {BLT_SWITCH_FLAG, "-real", Blt_Offset(SortData, type), 0, 0, 
	SORT_REAL},
    {BLT_SWITCH_FLAG, "-recurse", Blt_Offset(SortData, flags), 0, 0, 
	SORT_RECURSE},
    {BLT_SWITCH_VALUE, "-reorder", Blt_Offset(SortData, mode), 0, 0, 
	SORT_REORDER},
    {BLT_SWITCH_FLAG, "-usepath", Blt_Offset(SortData, flags), 0, 0, 
	SORT_PATHNAME},
    {BLT_SWITCH_END, NULL, 0, 0}
};

static SortData sortData;

static int
CompareNodes(Blt_TreeNode *n1Ptr, Blt_TreeNode *n2Ptr)
{
    TreeCmd *cmdPtr = sortData.cmdPtr;
    char *s1, *s2;
    int result;
    Tcl_DString dString1, dString2;

    s1 = s2 = "";
    result = 0;

    if (sortData.flags & SORT_PATHNAME) {
	Tcl_DStringInit(&dString1);
	Tcl_DStringInit(&dString2);
    }
    if (sortData.key != NULL) {
	Tcl_Obj *valueObjPtr;

	if (Blt_TreeGetValue((Tcl_Interp *)NULL, cmdPtr->tree, *n1Ptr, 
	     sortData.key, &valueObjPtr) == TCL_OK) {
	    s1 = Tcl_GetString(valueObjPtr);
	}
	if (Blt_TreeGetValue((Tcl_Interp *)NULL, cmdPtr->tree, *n2Ptr, 
	     sortData.key, &valueObjPtr) == TCL_OK) {
	    s2 = Tcl_GetString(valueObjPtr);
	}
    } else if (sortData.flags & SORT_PATHNAME)  {
	Blt_TreeNode root;
	
	root = Blt_TreeRootNode(cmdPtr->tree);
	s1 = GetNodePath(cmdPtr, root, *n1Ptr, FALSE, &dString1);
	s2 = GetNodePath(cmdPtr, root, *n2Ptr, FALSE, &dString2);
    } else {
	s1 = Blt_TreeNodeLabel(*n1Ptr);
	s2 = Blt_TreeNodeLabel(*n2Ptr);
    }
    switch (sortData.type) {
    case SORT_ASCII:
	result = strcmp(s1, s2);
	break;

    case SORT_COMMAND:
	if (sortData.command == NULL) {
	    result = Blt_DictionaryCompare(s1, s2);
	} else {
	    Tcl_DString dsCmd, dsName;
	    char *qualName;

	    result = 0;	/* Hopefully this will be Ok even if the
			 * Tcl command fails to return the correct
			 * result. */
	    Tcl_DStringInit(&dsCmd);
	    Tcl_DStringAppend(&dsCmd, sortData.command, -1);
	    Tcl_DStringInit(&dsName);
	    qualName = Blt_GetQualifiedName(
		Blt_GetCommandNamespace(cmdPtr->interp, cmdPtr->cmdToken), 
		Tcl_GetCommandName(cmdPtr->interp, cmdPtr->cmdToken), &dsName);
	    Tcl_DStringAppendElement(&dsCmd, qualName);
	    Tcl_DStringFree(&dsName);
	    Tcl_DStringAppendElement(&dsCmd, Blt_Itoa(Blt_TreeNodeId(*n1Ptr)));
	    Tcl_DStringAppendElement(&dsCmd, Blt_Itoa(Blt_TreeNodeId(*n2Ptr)));
	    Tcl_DStringAppendElement(&dsCmd, s1);
	    Tcl_DStringAppendElement(&dsCmd, s2);
	    result = Tcl_GlobalEval(cmdPtr->interp, Tcl_DStringValue(&dsCmd));
	    Tcl_DStringFree(&dsCmd);
	    
            if (cmdPtr->delete) {
                return TCL_ERROR;
            }
            if ((result != TCL_OK) ||
		(Tcl_GetInt(cmdPtr->interp, 
		    Tcl_GetStringResult(cmdPtr->interp), &result) != TCL_OK)) {
		Tcl_BackgroundError(cmdPtr->interp);
	    }
	    Tcl_ResetResult(cmdPtr->interp);
	}
	break;

    case SORT_DICTIONARY:
	result = Blt_DictionaryCompare(s1, s2);
	break;

    case SORT_INTEGER:
	{
	    int i1, i2;

	    if (Tcl_GetInt(NULL, s1, &i1) == TCL_OK) {
		if (Tcl_GetInt(NULL, s2, &i2) == TCL_OK) {
		    result = i1 - i2;
		} else {
		    result = -1;
		} 
	    } else if (Tcl_GetInt(NULL, s2, &i2) == TCL_OK) {
		result = 1;
	    } else {
		result = Blt_DictionaryCompare(s1, s2);
	    }
	}
	break;

    case SORT_REAL:
	{
	    double r1, r2;

	    if (Tcl_GetDouble(NULL, s1, &r1) == TCL_OK) {
		if (Tcl_GetDouble(NULL, s2, &r2) == TCL_OK) {
		    result = (r1 < r2) ? -1 : (r1 > r2) ? 1 : 0;
		} else {
		    result = -1;
		} 
	    } else if (Tcl_GetDouble(NULL, s2, &r2) == TCL_OK) {
		result = 1;
	    } else {
		result = Blt_DictionaryCompare(s1, s2);
	    }
	}
	break;
    }
    if (result == 0) {
	result = Blt_TreeNodeId(*n1Ptr) - Blt_TreeNodeId(*n2Ptr);
    }
    if (sortData.flags & SORT_DECREASING) {
	result = -result;
    } 
    if (sortData.flags & SORT_PATHNAME) {
	Tcl_DStringFree(&dString1);
	Tcl_DStringFree(&dString2);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * SortApplyProc --
 *
 *	Sorts the subnodes at a given node.
 *
 * Results:
 *	Always returns TCL_OK.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SortApplyProc(
    Blt_TreeNode node,
    ClientData clientData,
    int order)			/* Not used. */
{
    TreeCmd *cmdPtr = clientData;

    if (!Blt_TreeIsLeaf(node)) {
	Blt_TreeSortNode(cmdPtr->tree, node, CompareNodes);
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SortOp --
 *  
 *---------------------------------------------------------------------- 
 */
static int
SortOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode top;
    SortData data;
    int result;

    if (GetNode(cmdPtr, objv[2], &top) != TCL_OK) {
	return TCL_ERROR;
    }
    /* Process switches  */
    memset(&data, 0, sizeof(data));
    data.cmdPtr = cmdPtr;
    if (Blt_ProcessObjSwitches(interp, sortSwitches, objc - 3, objv + 3, 
	     (char *)&data, BLT_SWITCH_EXACT) < 0) {
	return TCL_ERROR;
    }
    if (data.command != NULL) {
	data.type = SORT_COMMAND;
    }
    data.cmdPtr = cmdPtr;
    sortData = data;
    if (data.mode == SORT_FLAT) {
	Blt_TreeNode *p, *nodeArr, node;
	int nNodes;
	Tcl_Obj *objPtr, *listObjPtr;
	int i;

	if (data.flags & SORT_RECURSE) {
	    nNodes = Blt_TreeSize(top);
	} else {
	    nNodes = Blt_TreeNodeDegree(top);
	}
	nodeArr = Blt_Calloc(nNodes, sizeof(Blt_TreeNode));
	assert(nodeArr);
	p = nodeArr;
	if (data.flags & SORT_RECURSE) {
	    for(node = top; node != NULL; node = Blt_TreeNextNode(top, node)) {
		*p++ = node;
	    }
	} else {
	    for (node = Blt_TreeFirstChild(top); node != NULL;
		 node = Blt_TreeNextSibling(node)) {
		*p++ = node;
	    }
	}
	qsort((char *)nodeArr, nNodes, sizeof(Blt_TreeNode),
	      (QSortCompareProc *)CompareNodes);
	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
	for (p = nodeArr, i = 0; i < nNodes; i++, p++) {
	    objPtr = Tcl_NewIntObj(Blt_TreeNodeId(*p));
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}
	Tcl_SetObjResult(interp, listObjPtr);
	Blt_Free(nodeArr);
	result = TCL_OK;
    } else {
	if (data.flags & SORT_RECURSE) {
	    result = Blt_TreeApply(top, SortApplyProc, cmdPtr);
	} else {
	    result = SortApplyProc(top, cmdPtr, TREE_PREORDER);
	}
    }
    Blt_FreeSwitches(interp, sortSwitches, (char *)&data, 0);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * WithOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
WithOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    Tcl_Obj *listObjPtr = NULL, **vobjv, **kobjv;
    Tcl_Obj *keyList = NULL, *arrName = NULL, *aListObjPtr = NULL;

    Blt_TreeKey key;
    Tcl_Obj *valuePtr, *varObj, *nullObj = NULL, *nodeObj = NULL;
    Tcl_Obj *hashObj = NULL, *starObj = NULL;
    Blt_TreeKeySearch keyIter;
    int vobjc, kobjc, i, result = TCL_OK, len, cnt = 0, isar;
    int nobreak = 0, noupdate = 0, unset = 0, init = 0, aLen;
    char *var, *string, *aName, *aPat = NULL;
    int klen, kl, j;
    int *keySet = NULL;
    unsigned int inode;
    TagSearch cursor = {0};

    varObj = objv[2];
    var = Tcl_GetString(varObj);

    while (objc > 5) {
        string = Tcl_GetStringFromObj(objv[3],&len);
        if (strcmp(string, "-break") == 0) {
            nobreak = 1;
            objc -= 1;
            objv += 1;
        } else if (strcmp(string, "-noupdate") == 0) {
            noupdate = 1;
            objc -= 1;
            objv += 1;
        } else if (strcmp(string, "-unset") == 0) {
            unset = 1;
            objc -= 1;
            objv += 1;
        } else if (strcmp(string, "-init") == 0) {
            if (objc<5) goto wrongargs;
            nullObj = objv[4];
            init = 1;
            objc -= 2;
            objv += 2;
        } else if (strcmp(string, "-glob") == 0) {
            if (objc<5) goto wrongargs;
            aPat = Tcl_GetString(objv[4]);
            objc -= 2;
            objv += 2;
        } else if (strcmp(string, "-keys") == 0) {
            if (objc<5) goto wrongargs;
            keyList = objv[4];
            objc -= 2;
            objv += 2;
        } else if (strcmp(string, "-array") == 0) {
            if (objc<5) goto wrongargs;
            arrName = objv[4];
            aName = Tcl_GetStringFromObj(objv[4], &aLen);
            objc -= 2;
            objv += 2;
        }  else {
            Tcl_AppendResult(interp, "expected -init, -keys, -array, -glob, -unset, -noupdate, or -break: ", string, 0);
            return TCL_ERROR;
        }
    }
    if (var[0] == 0 && unset && keyList == NULL) {
        Tcl_AppendResult(interp, "can not use -unset with empty var", 0);
        goto error;
    }
    if (aPat != NULL && keyList != NULL) {
        Tcl_AppendResult(interp, "can not use -keys and -glob", 0);
        return TCL_ERROR;
    }
    if (init && keyList == NULL) {
        Tcl_AppendResult(interp, "must use -keys with -init", 0);
        return TCL_ERROR;
    }
    if (objc != 5) {
        wrongargs:
        Tcl_AppendResult(interp, "wrong number of args ", 0);
        return TCL_ERROR;
    }
    string = Tcl_GetStringFromObj(objv[3],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    
    if (FindTaggedNodes(interp, cmdPtr, objv[3], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    if (keyList != NULL) {
        keyList = Tcl_DuplicateObj(keyList);
        Tcl_IncrRefCount(keyList);
        if (Tcl_ListObjGetElements(interp, keyList, &kobjc, &kobjv) 
            != TCL_OK) {
            Tcl_DecrRefCount(keyList);
            return TCL_ERROR;
        }
        if (init && kobjc>0) {
            keySet = (int*) Blt_Calloc(kobjc, sizeof(int));
        }
    }
    if (nullObj != NULL) {
        nullObj = Tcl_DuplicateObj(nullObj);
        Tcl_IncrRefCount(nullObj);
    }
    hashObj = Tcl_NewStringObj("#",-1);
    starObj = Tcl_NewStringObj("*",-1);
    nodeObj = Tcl_NewIntObj(0);
    Tcl_IncrRefCount(hashObj);
    Tcl_IncrRefCount(starObj);
    Tcl_IncrRefCount(nodeObj);
    for (node = FirstTaggedNode(&cursor);
        node != NULL; result = TCL_OK, node = NextTaggedNode(node, &cursor)) {
            
        inode = node->inode;
        
        if (Blt_TreeNotifyGet(cmdPtr->tree, node) != TCL_OK) {
            goto error;
        }
               
        cnt++;

        if (unset) {
            if (var[0]) {
                Tcl_UnsetVar(interp, var, 0);
            } else {
                for (i=0; i<kobjc; i++) {
                    char *kstr;
                    kstr = Tcl_GetStringFromObj(kobjv[i], &klen);
                    Tcl_UnsetVar(interp, kstr, 0);
                }
            }
        }
        if (init) {
            if (keySet != NULL) {
                for (i=0; i<kobjc; i++) {
                    keySet[i] = 0;
                }
            } else {
                for (i=0; i<kobjc; i++) {
                    if (var[0]) {
                        Tcl_ObjSetVar2(interp, varObj, kobjv[i], nullObj, 0);
                    } else {
                        Tcl_ObjSetVar2(interp, kobjv[i], NULL, nullObj, 0);
                    }
                }
            }
        }
        if (var[0]) {
            if (nodeObj->refCount ==  2) {
                Tcl_DecrRefCount(nodeObj);
                Tcl_SetIntObj(nodeObj, Blt_TreeNodeId(node));
                Tcl_IncrRefCount(nodeObj);
            } else {
                Tcl_DecrRefCount(nodeObj);
                nodeObj = Tcl_NewIntObj(Blt_TreeNodeId(node));
                Tcl_IncrRefCount(nodeObj);
            }
            if (Tcl_ObjSetVar2(interp, varObj, hashObj, nodeObj, 0) == NULL) {
                goto error;
            }
        }
    
        if ((keyList == NULL && var[0] != 0) || noupdate == 0) {
            if (listObjPtr && Tcl_IsShared(listObjPtr)) {
                Tcl_DecrRefCount(listObjPtr);
                listObjPtr = Tcl_NewListObj(0, NULL);
                Tcl_IncrRefCount(listObjPtr);
            } else {
                if (listObjPtr != NULL) {
                    Tcl_SetListObj(listObjPtr, 0, NULL);
                } else {
                    listObjPtr = Tcl_NewListObj(0, NULL);
                    Tcl_IncrRefCount(listObjPtr);
                }
            }
        }
        
        isar = 0;
        if (arrName != NULL) {
            kl = strlen(aName);
                
            aListObjPtr = Tcl_NewListObj(0, NULL);
            Tcl_IncrRefCount(aListObjPtr);
            if (Blt_TreeArrayNames(NULL, cmdPtr->tree, node, aName,
                aListObjPtr, aPat) != TCL_OK) {
                    if (Blt_TreeGetValue(NULL, cmdPtr->tree, node,
                    aName, &valuePtr) != TCL_OK || valuePtr == NULL) {
                        continue;
                }
                Tcl_AppendResult(interp, "failed to split \"", aName, "\" for node ", Blt_Itoa(Blt_TreeNodeId(node)), 0);
                goto error;
            }
            if (Tcl_ListObjGetElements(interp, aListObjPtr, &vobjc, &vobjv) 
            != TCL_OK) {
                goto error;		/* Can't split object. */
            }
            for (j=0; j<vobjc; j++) {
                char *skey;
                Tcl_Obj *skeyObj;
                skeyObj = vobjv[j];
                skey = Tcl_GetString(skeyObj);
                if (keyList != NULL) {
                    char *kstr;
                    kl = strlen(skey);
                    for (i=0; i<kobjc; i++) {
                        kstr = Tcl_GetStringFromObj(kobjv[i], &klen);
                        if (kl == klen && kstr[0] == skey[0] && strcmp(kstr, skey) == 0) {
                            break;
                        }
                    }
                    if (i>=kobjc) continue;
                    if (keySet != NULL) keySet[i] = 1;
                }
                if (listObjPtr != NULL) {
                    Tcl_ListObjAppendElement(interp, listObjPtr, skeyObj);
                }
                if (Blt_TreeGetArrayValue(interp, cmdPtr->tree, node, aName,
                    skey, &valuePtr) != TCL_OK) {
                        goto error;
                }
                if (var[0]) {
                    if (Tcl_ObjSetVar2(interp, varObj, skeyObj, valuePtr,0) == NULL) {
                        goto error;
                    }
                } else {
                    if (Tcl_ObjSetVar2(interp, skeyObj, NULL, valuePtr,0) == NULL) {
                        goto error;
                    }
                }
            }
            goto doit;
        }
        
        if (keyList != NULL) {
            Tcl_Obj *skeyObj;
            for (i=0; i<kobjc; i++) {
                skeyObj = kobjv[i];
                key = Tcl_GetStringFromObj(skeyObj, &klen);
                if (Blt_TreeGetValue(NULL, cmdPtr->tree, node, key,
                    &valuePtr) != TCL_OK) {
                        continue;
                }


                if (i>=kobjc) continue;
                if (aPat != NULL && !Tcl_StringMatch(key, aPat)) {
                    continue;
                }
                if (keySet != NULL) keySet[i] = 1;
                if (listObjPtr != NULL) {
                    Tcl_ListObjAppendElement(interp, listObjPtr, skeyObj);
                }
                if (var[0]) {
                    if (Tcl_ObjSetVar2(interp, varObj, skeyObj, valuePtr,0) == NULL) {
                        goto error;
                    }
                } else {
                    if (Tcl_ObjSetVar2(interp, skeyObj, NULL, valuePtr,0) == NULL) {
                        goto error;
                    }
                }

            }
        } else {
            for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &keyIter); key != NULL; 
            key = Blt_TreeNextKey(cmdPtr->tree, &keyIter)) {
                Tcl_Obj *skeyObj;
            
                skeyObj = NULL;
                
                if (keyList != NULL) {
                    char *kstr;
                    kl = strlen(key);
                    for (i=0; i<kobjc; i++) {
                        skeyObj = kobjv[i];
                        kstr = Tcl_GetStringFromObj(skeyObj, &klen);
                        if (kl == klen && kstr[0] == key[0] && strcmp(kstr, key) == 0) {
                            break;
                        }
                    }
                    if (i>=kobjc) continue;
                    if (keySet != NULL) keySet[i] = 1;
                }
                if (aPat != NULL && !Tcl_StringMatch(key, aPat)) {
                    continue;
                }
                if (skeyObj == NULL) {
                    skeyObj = Tcl_NewStringObj(key, -1);
                }
                if (listObjPtr != NULL) {
                    Tcl_ListObjAppendElement(interp, listObjPtr, skeyObj);
                }
                if (Blt_TreeGetValue(NULL, cmdPtr->tree, node, key, &valuePtr) 
                != TCL_OK) {
                    goto error;
                }

                /* Don't expose the array (maybe we should do this for all types?) */
                /*if (Blt_IsArrayObj(valuePtr)) {
                    isar = 1;
                    valuePtr = Tcl_NewStringObj(Tcl_GetString(valuePtr), -1);
                }*/
                if (var[0]) {
                    if (Tcl_ObjSetVar2(interp, varObj, skeyObj, valuePtr,0) == NULL) {
                        goto error;
                    }
                } else {
                    if (Tcl_ObjSetVar2(interp, skeyObj, NULL, valuePtr,0) == NULL) {
                        goto error;
                    }
                }
            }
        }

doit:
    
        if (init && keySet != NULL) {
            for (i=0; i<kobjc; i++) {
                if (keySet[i] == 0) {
                    if (var[0]) {
                        Tcl_ObjSetVar2(interp, varObj, kobjv[i], nullObj, 0);
                    } else {
                        Tcl_ObjSetVar2(interp, kobjv[i], NULL, nullObj, 0);
                    }
                }
            }
        }
        if (keyList == NULL && var[0]) {
            if (cnt > 1) {
                if (Tcl_ObjSetVar2(interp, varObj, starObj, listObjPtr, 0) == NULL) {
                    goto error;
                }
            } else {
                if (Tcl_SetVar2Ex(interp, var, "*", listObjPtr, 0) == NULL) {
                    goto error;
                }
            }
        }

        result = Tcl_EvalObjEx(interp, objv[4], 0);
        if (cmdPtr->delete) {
            goto error;
        }
        if (result != TCL_OK && result != TCL_BREAK && result != TCL_CONTINUE && result != TCL_RETURN) {
            break;
        } else if (noupdate == 0 && Blt_TreeNodeDeleted(node)==0  &&
            node->inode == inode) {
            Tcl_Obj *newValuePtr;
        
            if (Tcl_ListObjGetElements(interp, listObjPtr, &vobjc, &vobjv) 
            != TCL_OK) {
                goto error;		/* Can't split object. */
            }
            for (i = 0; i<vobjc; i++) {
                char *string1, *string2, *skey;
                int l1, l2;
                
                skey = Tcl_GetString(vobjv[i]);
                if (arrName != NULL) {
                    if (Blt_TreeGetArrayValue(NULL, cmdPtr->tree, node, aName,
                        skey, &valuePtr) != TCL_OK) {
                        continue;
                    }
                } else {
                    if (Blt_TreeGetValue(NULL, cmdPtr->tree, node,
                        skey, &valuePtr) != TCL_OK) {
                        continue;
                    }
                }
                if (var[0]) {
                    if ((newValuePtr = Tcl_ObjGetVar2(interp, varObj, vobjv[i], 0)) == NULL) {
                        Tcl_ResetResult(interp);
                        continue;
                    }
                    
                } else {
                    if ((newValuePtr = Tcl_ObjGetVar2(interp, vobjv[i], NULL, 0)) == NULL) {
                        Tcl_ResetResult(interp);
                        continue;
                    }
                }
                /*if (isar && Blt_IsArrayObj(valuePtr)) {
                } else {
                    if (newValuePtr == valuePtr) continue;
                }*/
                if (newValuePtr == valuePtr) continue;
                
                string1 = (char*) Tcl_GetStringFromObj(newValuePtr, &l1);
                string2 = (char*) Tcl_GetStringFromObj(valuePtr, &l2);
                if (l1 != l2 || memcmp(string1, string2, l1) != 0) {
                    if (arrName != NULL) {
                        if (Blt_TreeSetArrayValue(interp, cmdPtr->tree, node,
                            aName, skey, newValuePtr) != TCL_OK) {
                                goto error;
                        }
                    } else {
                        if (Blt_TreeSetValue(NULL, cmdPtr->tree, node,
                            Tcl_GetString(vobjv[i]), newValuePtr) != TCL_OK) {
                            goto error;
                        }
                    }
                }
            }
        }
        if (nobreak && result == TCL_CONTINUE) continue;
        if (result != TCL_OK) {
            break;
        }
    }
    if (nobreak && result == TCL_BREAK) {
        result = TCL_OK;
    }

    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(cnt));
    }
doneall:
    if (listObjPtr != NULL) { Tcl_DecrRefCount(listObjPtr); }
    if (aListObjPtr != NULL) { Tcl_DecrRefCount(aListObjPtr); }
    if (nullObj != NULL) { Tcl_DecrRefCount(nullObj); }
    if (nodeObj != NULL) { Tcl_DecrRefCount(nodeObj); }
    if (starObj != NULL) { Tcl_DecrRefCount(starObj); }
    DoneTaggedNodes(&cursor);
    if (keyList != NULL) {
        Tcl_DecrRefCount(keyList);
    }
    if (keySet != NULL) {
        Blt_Free(keySet);
    }
    return result;
    
error:
    result = TCL_ERROR;
    goto doneall;
}

/*
 *----------------------------------------------------------------------
 *
 * ForeachOp --
 *
 *---------------------------------------------------------------------- 
 */
static int
ForeachOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    int len, result;
    char *var, *string;
    TagSearch cursor = {0};

    var = Tcl_GetString(objv[2]);

    if (objc != 5) {
        Tcl_AppendResult(interp, "wrong number of args ", 0);
        return TCL_ERROR;
    }
    string = Tcl_GetStringFromObj(objv[3],&len);
    if (len == 0) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
        return TCL_OK;
    }
    
    if (FindTaggedNodes(interp, cmdPtr, objv[3], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    result = TCL_OK;
    for (node = FirstTaggedNode(&cursor);
        node != NULL; result = TCL_OK, node = NextTaggedNode(node, &cursor)) {

        if (Tcl_SetVar2Ex(interp, var, 0, Tcl_NewIntObj(Blt_TreeNodeId(node)),
            0) == NULL) {
            goto error;
        }
        result = Tcl_EvalObjEx(interp, objv[4], 0);
        if (cmdPtr->delete) {
            goto error;
        }
        if (result == TCL_BREAK) { result = TCL_OK; break; }
        if (result == TCL_CONTINUE ) continue;
	if (result == TCL_ERROR) {
            Tcl_AppendResult(interp,
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION <= 5
            "\n    (\"tree foreach\" body line ", Blt_Itoa(interp->errorLine), ")\n", 0);
#else // 8.6 or later
            "\n    (\"tree foreach\" body line ", Blt_Itoa(Tcl_GetErrorLine(interp)), ")\n", 0);
#endif
             break;
        }
        if (result != TCL_OK) {
            break;
        }
    }

    DoneTaggedNodes(&cursor);
    return result;
    
error:
    DoneTaggedNodes(&cursor);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * NamesOp --
 *
 *	Returns the names of keys for a node or array value.
 *
 *---------------------------------------------------------------------- 
 */
static int
NamesOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_TreeNode node;
    Tcl_Obj *listObjPtr;
    
    if (objc>2) {
        if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
            return TCL_ERROR;
        }
    } else {
        node = Blt_TreeRootNode(cmdPtr->tree);
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (objc >= 4) { 
	char *string;

	string = Tcl_GetString(objv[3]);
	if (Blt_TreeArrayNames(interp, cmdPtr->tree, node, string, listObjPtr,
	       objc>4?Tcl_GetString(objv[4]):NULL)
	    != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	Blt_TreeKey key;
	Tcl_Obj *objPtr;
	Blt_TreeKeySearch keyIter;

	for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &keyIter); key != NULL; 
	     key = Blt_TreeNextKey(cmdPtr->tree, &keyIter)) {
	    objPtr = Tcl_NewStringObj(key, -1);
	    Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
	}	    
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
*----------------------------------------------------------------------
*
* ValuesOp --
*
*	Returns the values of keys for a node or array value.
*
*---------------------------------------------------------------------- 
*/
static int
ValuesOp(
TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
    {
    Blt_TreeNode node;
    Tcl_Obj *listObjPtr;
    int names = 0;
    
    if (objc>2) {
        if (GetNode(cmdPtr, objv[2], &node) != TCL_OK) {
            return TCL_ERROR;
        }
    } else {
        node = Blt_TreeRootNode(cmdPtr->tree);
    }
    if (objc>4 && Tcl_GetBooleanFromObj(interp, objv[4], &names) != TCL_OK) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (objc >= 4) { 
        char *string;

        string = Tcl_GetString(objv[3]);
        if (Blt_TreeArrayValues(interp, cmdPtr->tree, node, string, listObjPtr, names)
        != TCL_OK) {
            Tcl_DecrRefCount(listObjPtr);
            return TCL_ERROR;
        }
    } else {
        Blt_TreeKey key;
        Tcl_Obj *objPtr;
        Blt_TreeKeySearch keyIter;

        for (key = Blt_TreeFirstKey(cmdPtr->tree, node, &keyIter); key != NULL; 
        key = Blt_TreeNextKey(cmdPtr->tree, &keyIter)) {
            if (Blt_TreeGetValueByKey(interp, cmdPtr->tree, node, key, &objPtr) != TCL_OK) {
                Tcl_DecrRefCount(listObjPtr);
                return TCL_ERROR;
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }	    
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * OldValueOp --
 *
 *	Returns the old value before last update
 *
 *---------------------------------------------------------------------- 
 */
static int
OldValueOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Tcl_Obj *objPtr = NULL;
    int len;
    
    if (objc>2) {
        Blt_TreeOldValue(interp, cmdPtr->tree, &objPtr, objv[2]);
        return TCL_OK;
    }
    Blt_TreeOldValue(interp, cmdPtr->tree, &objPtr, NULL);
    if (objPtr == NULL) {
        return TCL_OK;
    }
    /* If last update was append/lappend, return truncated value. */
    if (cmdPtr->updTyp == 1) {
        Tcl_GetStringFromObj(objPtr, &len);
        if (len>cmdPtr->oldLen && cmdPtr->oldLen>=0) {
            objPtr = Tcl_DuplicateObj(objPtr);
            Tcl_SetObjLength(objPtr, cmdPtr->oldLen);
        }
    } else if (cmdPtr->updTyp == 2) {
        if (Tcl_ListObjLength(interp, objPtr, &len) != TCL_OK) {
            return TCL_ERROR;
        }
        if (len>cmdPtr->oldLen && cmdPtr->oldLen>=0) {
            objPtr = Tcl_DuplicateObj(objPtr);
            if (Tcl_ListObjReplace(interp, objPtr, cmdPtr->oldLen, len-cmdPtr->oldLen, 0, NULL) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    } 
    Tcl_SetObjResult(interp, objPtr);
    return TCL_OK;
}

typedef struct SqlData {
    TreeCmd *cmdPtr;
    Blt_TreeNode parent;
    int insertPos;
    Tcl_Interp *interp;
    int cnt;
    int rc;
    int max;
    int llen;
    char *labelcol;
    char *tagcol;
    char *key;
    int rinc;
    char *nullstr;
    Tcl_Obj *tags;
    int vobjc;
    Tcl_Obj **vobjv;
    Tcl_Obj *skipcols;
    Tcl_Obj *treecols;
    Tcl_Obj *pathcol ;
    int fixed;
} SqlData;

#ifdef BLT_USE_OLD_SQLITE
/*
 * Callback for sqlload.
 *
 */
static int SqlCallback(SqlData *sqlPtr, int argc, char **argv, char **azColName)
{
    int i, rid, lid, n, tcid;
    Tcl_Interp *interp = sqlPtr->interp;
    TreeCmd *cmdPtr = sqlPtr->cmdPtr;
    Blt_TreeNode node;
    char string[200];
    Tcl_Obj *objPtr;
    Tcl_DString dStr;
    char *label = NULL;
    
    rid = -1;
    n = -1;
    lid = -1;
    tcid = -1;
    if (sqlPtr->rinc>0) {
        for(i=0; i<argc; i++){
            if (argv[i]==NULL) continue;
            if (azColName[i][0] == 'r' && !strcmp(azColName[i], "rowid")) {
                rid = atoi(argv[i])+sqlPtr->rinc;
                n = i;
                break;
            }
        }
    }
    if (sqlPtr->labelcol != NULL && sqlPtr->labelcol[0]) {
        for(i=0; i<argc; i++){
            if (argv[i]==NULL) continue;
            if (azColName[i][0] == sqlPtr->labelcol[0]
            && !strcmp(azColName[i], sqlPtr->labelcol)) {
                lid = i;
                label = argv[i];
                break;
            }
        }
    }
    if (sqlPtr->tagcol != NULL && sqlPtr->tagcol[0]) {
        for(i=0; i<argc; i++){
            if (argv[i]==NULL) continue;
            if (azColName[i][0] == sqlPtr->tagcol[0]
            && !strcmp(azColName[i], sqlPtr->agcol)) {
                tcid = i;
                break;
            }
        }
    }
    if (sqlPtr->tagcol != NULL) {
        if (AddTag(cmdPtr, node, Tcl_GetString(sqlPtr->vobjv[i])) != TCL_OK) {
            return 1;
        }
    }     


    if (rid>1) {
        node = Blt_TreeCreateNodeWithId(cmdPtr->tree, sqlPtr->parent, label,
            rid, sqlPtr->insertPos);
        if (node == NULL) {
            Tcl_AppendResult(interp, "node can not be created", 0);
            sqlPtr->rc = TCL_ERROR;
            return  1;
        }
    } else {
        node = Blt_TreeCreateNode(cmdPtr->tree, sqlPtr->parent, label,
            sqlPtr->insertPos);
        if (node == NULL) {
            Tcl_AppendResult(interp, "node can not be created", 0);
            sqlPtr->rc = TCL_ERROR;
            return 1;
        }
    }
    if (label == NULL) {
        sprintf(string, "%d", Blt_TreeNodeId(node));
        Blt_TreeRelabelNode2(node, string);
    }
    

    if (sqlPtr->key != NULL) {
        Tcl_DStringInit(&dStr);
        for(i=0; i<argc; i++){
            if (argv[i] == NULL && sqlPtr->nullstr == NULL) continue;
            if (n == i) continue;
            Tcl_DStringAppendElement(&dStr, azColName[i]);
            Tcl_DStringAppendElement(&dStr, argv[i]?argv[i]:sqlPtr->nullstr);
        }
        objPtr = Tcl_NewStringObj(Tcl_DStringValue(&dStr), -1);
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, sqlPtr->key, objPtr) 
            != TCL_OK) {
            Tcl_DecrRefCount(objPtr);
            sqlPtr->rc = TCL_ERROR;
            return 1;
        }
    } else {
        for(i=0; i<argc; i++){
            if (argv[i] == NULL && sqlPtr->nullstr == NULL) continue;
            if (n == i) continue; */
            objPtr = Tcl_NewStringObj(argv[i]?argv[i]:sqlPtr->nullstr, -1);
            if (Blt_TreeSetValue(interp, cmdPtr->tree, node, azColName[i], objPtr) 
            != TCL_OK) {
                Tcl_DecrRefCount(objPtr);
                sqlPtr->rc = TCL_ERROR;
                return 1;
            }
        }
    }
    if (sqlPtr->tags != NULL) {        
        for (i=0; i<sqlPtr->vobjc; i++) {
            if (AddTag(cmdPtr, node, Tcl_GetString(sqlPtr->vobjv[i])) != TCL_OK) {
                return 1;
            }
        }
    }
    if (Blt_TreeInsertPost(cmdPtr->tree, node) == NULL) {
        DeleteNode(cmdPtr, node);
        return 1;
    }
    if (sqlPtr->fixed) {
        node->flags |= TREE_NODE_FIXED_FIELDS;
    }
    
    sqlPtr->cnt++;
    if (sqlPtr->max > 0 && sqlPtr->cnt >= sqlPtr->max) {
        sqlPtr->rc = TCL_BREAK;
        return 1;
    }

    return 0;
}
#else
/*
 * Obj Callback for sqlload.
 *
 */
static int SqlCallbackObj(SqlData *sqlPtr, int argc, Tcl_Obj **objv, Tcl_Obj **azColName)
{
    int i, j, rid, lid, n, tcid, vobjc, tobjc;
    Tcl_Interp *interp = sqlPtr->interp;
    TreeCmd *cmdPtr = sqlPtr->cmdPtr;
    Blt_TreeNode node;
    char string[200];
    Tcl_Obj *objPtr, **vobjv, **tobjv;
    Tcl_DString dStr;
    char *label = NULL, *cn, *ridStr = "";
    int treeCols[100], pcol = -1;
    
    rid = -1;
    n = -1;
    lid = -1;
    tcid = -1;
    if (sqlPtr->rinc>0) {
        for(i=0; i<argc; i++){
            if (objv[i]==NULL) continue;
            cn = Tcl_GetString(azColName[i]);
            if (cn[0] == 'r' && !strcmp(cn, "rowid")) {
                if (Tcl_GetIntFromObj(NULL, objv[i], &rid) == TCL_OK) {
                    rid += sqlPtr->rinc;
                    ridStr = Tcl_GetString(objv[i]);
                    n = i;
                    break;
                }
            }
        }
    }
    if (sqlPtr->labelcol != NULL && sqlPtr->labelcol[0]) {
        for(i=0; i<argc; i++){
            if (objv[i]==NULL) continue;
            cn = Tcl_GetString(azColName[i]);
            if (cn[0] == sqlPtr->labelcol[0]
            && !strcmp(cn, sqlPtr->labelcol)) {
                lid = i;
                label = (objv[i] ? Tcl_GetString(objv[i]) : sqlPtr->nullstr );
                break;
            }
        }
    }
    if (sqlPtr->treecols) {
            
        if (Tcl_ListObjGetElements(interp, sqlPtr->treecols, &tobjc, &tobjv) 
        != TCL_OK || tobjc>=100) {
            sqlPtr->rc = TCL_ERROR;
            return 1;
        }
        for (i=0; i<tobjc; i++) {
            treeCols[i] = -1;
        }
    }
    if (rid>=1) {
        node = Blt_TreeCreateNodeWithId(cmdPtr->tree, sqlPtr->parent, label,
            rid, sqlPtr->insertPos);
        if (node == NULL) {
            Tcl_AppendResult(interp, "node can not be created with id: ", ridStr, 0);
            sqlPtr->rc = TCL_ERROR;
            return  1;
        }
    } else {
        node = Blt_TreeCreateNode(cmdPtr->tree, sqlPtr->parent, label,
            sqlPtr->insertPos);
        if (node == NULL) {
            Tcl_AppendResult(interp, "node can not be created", 0);
            sqlPtr->rc = TCL_ERROR;
            return 1;
        }
    }
    if (label == NULL) {
        sprintf(string, "%d", Blt_TreeNodeId(node));
        Blt_TreeRelabelNode2(node, string);
    }
    

    Tcl_DStringInit(&dStr);
    for(i=0; i<argc; i++){
        int clen;
        if (objv[i] == NULL && sqlPtr->nullstr == NULL) continue;
        cn = Tcl_GetStringFromObj(azColName[i], &clen);
        if (sqlPtr->treecols) {
            for (j=0; j<tobjc; j++) {
                if (!strcmp(Tcl_GetString(azColName[i]), Tcl_GetString(tobjv[j]))) {
                    treeCols[j] = i;
                    break;
                }
            }
        }
        if (sqlPtr->pathcol) {
            if (!strcmp(Tcl_GetString(azColName[i]), Tcl_GetString(sqlPtr->pathcol))) {
                pcol = i;
            }
        }
        if (sqlPtr->tagcol != NULL && sqlPtr->tagcol[0] &&
            strcmp(cn, sqlPtr->tagcol) == 0) {
            if (AddTag(cmdPtr, node, Tcl_GetString(objv[i])) != TCL_OK) {
                goto error;
            }
            continue;
        }

        if (sqlPtr->skipcols) {
            char *sc;
            int slen;
            
            if (Tcl_ListObjGetElements(interp, sqlPtr->skipcols, &vobjc, &vobjv) 
            != TCL_OK) {
                goto error;
            }
            for (j=0; j<vobjc; j++) {
                sc = Tcl_GetStringFromObj(vobjv[j], &slen);
                if (slen == clen && *sc == *cn && strcmp(cn, sc) == 0) {
                    break;
                }
            }
            if (j<vobjc) continue;
        }
        if (sqlPtr->key != NULL) {
            Tcl_DStringAppendElement(&dStr, cn);
            Tcl_DStringAppendElement(&dStr, objv[i]?Tcl_GetString(objv[i]):sqlPtr->nullstr);
        } else {
            if (objv[i] != NULL) {
                objPtr = objv[i];
            } else {
                objPtr = Tcl_NewStringObj(sqlPtr->nullstr, -1);
            }
            if (Blt_TreeSetValue(interp, cmdPtr->tree, node, cn, objPtr) 
                != TCL_OK) {
                Tcl_DecrRefCount(objPtr);
                sqlPtr->rc = TCL_ERROR;
                goto error;;
            }
        }
    }
    if (sqlPtr->key != NULL) {
        /* Set all values in a single key. */
        objPtr = Tcl_NewStringObj(Tcl_DStringValue(&dStr), -1);
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, sqlPtr->key, objPtr) 
        != TCL_OK) {
            Tcl_DecrRefCount(objPtr);
            sqlPtr->rc = TCL_ERROR;
            goto error;
        }
    }

    if (sqlPtr->tags != NULL) {        
        for (i=0; i<sqlPtr->vobjc; i++) {
            if (AddTag(cmdPtr, node, Tcl_GetString(sqlPtr->vobjv[i])) != TCL_OK) {
                goto error;
            }
        }
    }
    
    if (sqlPtr->treecols) {
        Blt_TreeNode snode, parent = sqlPtr->parent;
        for (i=0; i<tobjc; i++) {
            if (treeCols[i] == -1) {
                Tcl_AppendResult(interp, "missing col in -treecols", 0);
                goto error;
            }
        }
        for (i=0; i<tobjc; i++) {
            char *tcp;
            tcp = Tcl_GetString(objv[treeCols[i]]);
            snode = Blt_TreeFindChild(parent, tcp);
            if (snode != NULL) {
                parent = snode;
                continue;
            }
            snode = Blt_TreeCreateNode(cmdPtr->tree, parent, tcp, -1);
            if (snode == NULL) {
                goto error;
            }
            parent = snode;
        }
        if (Blt_TreeMoveNode(cmdPtr->tree, node, parent, NULL) != TCL_OK) {
            Tcl_AppendResult(interp, "relocate failed in -treecols", 0);
            goto error;
        }
    }
    if (sqlPtr->pathcol && pcol >= 0) {
        Blt_TreeNode snode, parent = sqlPtr->parent;
        if (Tcl_ListObjGetElements(interp, vobjv[pcol], &tobjc, &tobjv) 
            != TCL_OK) {
            goto error;
        }
        for (i=0; i<tobjc; i++) {
            char *tcp;
            tcp = Tcl_GetString(tobjv[i]);
            snode = Blt_TreeFindChild(parent, tcp);
            if (snode != NULL) {
                parent = snode;
                continue;
            }
            snode = Blt_TreeCreateNode(cmdPtr->tree, parent, tcp, -1);
            if (snode == NULL) {
                goto error;
            }
            parent = snode;
        }
        if (Blt_TreeMoveNode(cmdPtr->tree, node, parent, NULL) != TCL_OK) {
            Tcl_AppendResult(interp, "relocate failed in -pathcol", 0);
            goto error;
        }
    }

    if (Blt_TreeInsertPost(cmdPtr->tree, node) == NULL) {
        goto error;
    }
    Tcl_DStringFree(&dStr);
    if (sqlPtr->fixed || (cmdPtr->tree->treeObject->flags & TREE_FIXED_KEYS)) {
        node->flags |= TREE_NODE_FIXED_FIELDS;
    }
    
    sqlPtr->cnt++;
    if (sqlPtr->max > 0 && sqlPtr->cnt >= sqlPtr->max) {
        sqlPtr->rc = TCL_BREAK;
        return 1;
    }
    return 0;
    
error:
    DeleteNode(cmdPtr, node);
    Tcl_DStringFree(&dStr);
    return 1;
}
#endif

#ifndef OMIT_SQLITE3_LOAD
/* #include <sqlite3.h> */

extern void *sqlite3_tclhandle();
extern int sqlite3_open();
extern char *sqlite3_errmsg();
extern void sqlite3_close(void *);
extern int sqlite3_exec_tclobj();
#endif

/*
 *----------------------------------------------------------------------
 *
 * SqlloadOp --
 *
 *	Load sql
 * TODO: use sqlite header
 *
 *---------------------------------------------------------------------- 
 */
static int
SqlloadOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
#ifdef OMIT_SQLITE3_LOAD
    Tcl_AppendResult(interp, "sqlload unsupported", 0);
    return TCL_ERROR;
#else     
    char *string, *cp;
    int rc, result, optInd, isFile = 0, sclen, tclen;
    void *db = NULL;
    Tcl_Obj *zErrPtr = NULL;
    /*const char *zErrMsg = 0; */
    SqlData data;
    Tcl_DString dStr;
    Tcl_Obj *CONST *oobjv = objv;
    
    enum optInd {
        OP_TAGS, OP_FIXED, OP_KEY, OP_LABELCOL, OP_ROWID, OP_MAX,
        OP_NULLSTR, OP_PARENT, OP_PATHCOL, OP_INSPOS, OP_SKIPCOL, OP_TAGCOL,
        OP_TREECOLS        
    };
    static char *optArr[] = {
        "-addtags", "-fixed", "-key", "-labelcol", "-maprowid", "-max",
        "-nullvalue", "-parent", "-pathcol", "-pos", "-skipcols", "-tagcol",
        "-treecols",
        0
    };

    data.cmdPtr = cmdPtr;
    data.insertPos = -1;
    data.parent = Blt_TreeRootNode(cmdPtr->tree);
    data.interp = interp;
    data.max = 100000;
    data.cnt = 0;
    data.rc = TCL_OK;
    data.rinc = 0;
    data.fixed = 0;
    data.key = NULL;
    data.labelcol = NULL;
    data.tagcol = NULL;
    data.skipcols = NULL;
    data.nullstr = NULL;
    data.tags = NULL;
    data.pathcol = NULL;
    data.treecols = NULL;
    
    result = TCL_OK;
    string = Tcl_GetString(objv[2]);
    if (objc<4) {
        Tcl_WrongNumArgs(interp, 2, objv, "?options? dbhfile selectstmt");
        return TCL_ERROR;
    }
    while (objc>4 && string[0] == '-') {
        if (Tcl_GetIndexFromObj(interp, objv[2], optArr, "option",
            0, &optInd) != TCL_OK) {
                return TCL_ERROR;
        }
        switch (optInd) {
        
        case OP_FIXED:
            data.fixed = 1;
            objc -= 1;
            objv += 1;
            break;
        
        case OP_INSPOS:
            if (objc<6) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &data.insertPos)) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;

        case OP_KEY:
            if (objc<6) { goto missingArg; }
            data.key = Tcl_GetString(objv[3]);
            objc -= 2;
            objv += 2;
            break;
        
        case OP_LABELCOL:
            if (objc<6) { goto missingArg; }
            data.labelcol = Tcl_GetStringFromObj(objv[3], &data.llen);
            objc -= 2;
            objv += 2;
            break;
        
        case OP_MAX:
            if (objc<6) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &data.max)) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
            
        case OP_NULLSTR:
            if (objc<6) { goto missingArg; }
            data.nullstr = Tcl_GetStringFromObj(objv[3], &data.llen);
            objc -= 2;
            objv += 2;
            break;
            
        case OP_ROWID:
            if (objc<6) { goto missingArg; }
            if (Tcl_GetIntFromObj(interp, objv[3], &data.rinc)) {
                return TCL_ERROR;
            }
            if (data.rinc<=0) {
                Tcl_AppendResult(interp, "-rowidmap must be > 0", 0);
                return TCL_ERROR;
            }

            objc -= 2;
            objv += 2;
            break;
            
        case OP_PARENT:
            if (objc<6) { goto missingArg; }
            if (GetNode(cmdPtr, objv[3], &data.parent) != TCL_OK) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
            
        case OP_SKIPCOL:
            if (objc<6) { goto missingArg; }
            if (Tcl_ListObjLength(interp, objv[3], &sclen) != TCL_OK) {
                return TCL_ERROR;
            }
            data.skipcols = objv[3];
            objc -= 2;
            objv += 2;
            break;
            
        case OP_TAGCOL:
            if (objc<6) { goto missingArg; }
            data.tagcol = Tcl_GetStringFromObj(objv[3], &data.llen);
            objc -= 2;
            objv += 2;
            break;
        
        case OP_TAGS:
            if (objc<6) { goto missingArg; }
            data.tags = objv[3];
            if (Tcl_ListObjGetElements(interp, data.tags, &data.vobjc, &data.vobjv)
                != TCL_OK) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_TREECOLS:
            if (objc<6) { goto missingArg; }
            if (Tcl_ListObjLength(interp, objv[3], &tclen) != TCL_OK) {
                return TCL_ERROR;
            }
            if (tclen>0) {
                data.treecols = objv[3];
            }
            objc -= 2;
            objv += 2;
            break;
        case OP_PATHCOL:
            if (objc<6) { goto missingArg; }
            data.pathcol = objv[3];
            objc -= 2;
            objv += 2;
            break;
        }
        if (objc>2) {
            string = Tcl_GetString(objv[2]);
        }

    }
    if (objc!=4) {
        Tcl_WrongNumArgs(interp, 2, oobjv, "?options? dbhfile selectstmt");
        return TCL_ERROR;
    }
    if (data.pathcol && data.treecols) {
        Tcl_AppendResult(interp, "can not use -pathcol and -treecols", 0);
        return TCL_ERROR;
    }
#ifndef OMIT_SQLITE3_HANDLE    
    db = sqlite3_tclhandle(interp, string);
#endif    
    if (db == NULL) {
        isFile = 1;
        Tcl_DStringInit(&dStr);
        Tcl_DStringAppendElement(&dStr, "file");
        Tcl_DStringAppendElement(&dStr, "exists");
        Tcl_DStringAppendElement(&dStr, string);
        if (Tcl_Eval( interp, Tcl_DStringValue(&dStr)) != TCL_OK) {
            return TCL_ERROR;
        }
        cp =  Tcl_GetStringResult(interp);
        if (cp[0] != '1') {
            Tcl_AppendResult(interp, "file must exists ", string, 0);
            return TCL_ERROR;
        }
        Tcl_ResetResult(interp);
        if (cmdPtr->delete) {
            return TCL_ERROR;
        }
        rc = sqlite3_open(string, &db);
        if (rc) {
            Tcl_AppendResult(interp, "database open failed for ", string, sqlite3_errmsg(db), 0);
            sqlite3_close(db);
            return TCL_ERROR;
        }
    }
    if (db == NULL) {
        Tcl_AppendResult(interp, "database open failed for ", string, 0);
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]);
#ifdef BLT_USE_OLD_SQLITE
    rc = sqlite3_exec(db, string, SqlCallback, &data, &zErrMsg);
#else
    rc = sqlite3_exec_tclobj(db, objv[3], SqlCallbackObj, &data, &zErrPtr);
#endif
    if (data.rc == TCL_OK && rc != 0 /* SQLITE_OK */ ){
        Tcl_AppendResult(interp, "SQL error: ", (zErrPtr?Tcl_GetString(zErrPtr):0), 0);
        result = TCL_ERROR;
    } else if (data.rc != TCL_BREAK) {
        result = data.rc;
    }
    if (isFile) {
        sqlite3_close(db);
    }
    if (result == TCL_OK) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(data.cnt));
    }
    if (zErrPtr != NULL) {
        Tcl_DecrRefCount(zErrPtr);
    }
    return result;
    
missingArg:
    if (zErrPtr != NULL) {
        Tcl_DecrRefCount(zErrPtr);
    }
    Tcl_AppendResult(interp, "missing argument for find option \"",
        Tcl_GetString(objv[3]), "\"", (char *)NULL);
    return TCL_ERROR;
#endif
}
/*
 *----------------------------------------------------------------------
 *
 * VecloadOp --
 *
 *	Returns the old value before last update
 *k
 *---------------------------------------------------------------------- 
 */
static int
VecloadOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Tcl_Obj *objPtr = NULL;
    int i, count = 0, len;
    Blt_TreeNode node;
    Blt_Vector *vec;
    double d;
    char *string;
    TagSearch cursor = {0};

    if (Blt_GetVector(interp, Tcl_GetString(objv[2]), &vec) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]);
    if (objc==4) {
        /* 1-1 dump of inodes. */
        for (i=0; i<vec->numValues; i++) {
            d = vec->valueArr[i];
            node = Blt_TreeGetNode(cmdPtr->tree, i);
            if (node == NULL) continue;
            objPtr = Tcl_NewDoubleObj(d);
            count++;
            if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, 
                objPtr) != TCL_OK) {
                    return TCL_ERROR;
            }
        }
        Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
        return TCL_OK;
    }
    string = Tcl_GetStringFromObj(objv[4],&len);
    if (len == 0) goto done;
    
    if (FindTaggedNodes(interp, cmdPtr, objv[4], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        count++;
        if (count > vec->numValues) {
            break;
        }
        d = vec->valueArr[count-1];
        objPtr = Tcl_NewDoubleObj(d);
        if (Blt_TreeSetValue(interp, cmdPtr->tree, node, string, 
            objPtr) != TCL_OK) {
                continue;
        }
    }
done:
    DoneTaggedNodes(&cursor);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}

static int
VecdumpOp(
    TreeCmd *cmdPtr,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Tcl_Obj *objPtr = NULL;
    int i, count = 0, max = 0, len;
    Blt_TreeNode node, root;
    Blt_Vector *vec;
    double d;
    char *string;
    TagSearch cursor = {0};

    if (Blt_GetVector(interp, Tcl_GetString(objv[2]), &vec) != TCL_OK) {
        return TCL_ERROR;
    }
    string = Tcl_GetString(objv[3]);
    
    if (objc==4) {
        /* 1-1 dump of inodes. */
        node = MaxNode(cmdPtr->tree);
        root = Blt_TreeRootNode(cmdPtr->tree);

        max = node->inode;
        max++;
        if (max != vec->numValues) {
            if (Blt_ResizeVector(vec, max) != TCL_OK) {
                return TCL_ERROR;
            }
        }
        for (i=0; i<vec->numValues; i++) {
            vec->valueArr[i] = 0.0;
        }
        for (node = root; node != NULL; node = Blt_TreeNextNode(root, node)) {
            i = node->inode;
            if (i>=vec->numValues) continue;
            node = Blt_TreeGetNode(cmdPtr->tree, i);
            if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string, 
                &objPtr) != TCL_OK) {
                    continue;
            }
            if (Tcl_GetDoubleFromObj(NULL, objPtr, &d) != TCL_OK) {
                continue;
            }
            vec->valueArr[i] = d;
            count++;
        }
        Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
        return TCL_OK;
    }
    string = Tcl_GetStringFromObj(objv[4],&len);
    if (len == 0) goto done;
    
    if (FindTaggedNodes(interp, cmdPtr, objv[4], &cursor) != TCL_OK) {
        return TCL_ERROR;
    }
    for (node = FirstTaggedNode(&cursor);
        node != NULL; node = NextTaggedNode(node, &cursor)) {
        if (count >= vec->numValues) {
            if (Blt_ResizeVector(vec, count+100) != TCL_OK) {
                DoneTaggedNodes(&cursor);
                return TCL_ERROR;
            }
        }
        count++;
        vec->valueArr[count-1] = 0;
        if (Blt_TreeGetValue(interp, cmdPtr->tree, node, string, 
            &objPtr) != TCL_OK) {
                continue;
        }
        if (Tcl_GetDoubleFromObj(NULL, objPtr, &d) != TCL_OK) {
            continue;
        }
        vec->valueArr[count-1] = d;
    }
    DoneTaggedNodes(&cursor);
    if (Blt_ResizeVector(vec, count) != TCL_OK) {
        return TCL_ERROR;
    }
done:
    Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
    return TCL_OK;
}


/*
 * --------------------------------------------------------------
 *
 * TreeInstObjCmd --
 *
 * 	This procedure is invoked to process commands on behalf of
 *	the tree object.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 * --------------------------------------------------------------
 */
static Blt_OpSpec treeOps[] =
{
    {"ancestor", 2, (Blt_Op)AncestorOp, 4, 4, "node1 node2",},
    {"append", 6, (Blt_Op)AppendOp, 4, 0, "node key string ?...?",},
    {"appendi", 7, (Blt_Op)AppendiOp, 4, 0, "node key string ?...?",},
    {"apply", 4, (Blt_Op)ApplyOp, 3, 0, "node ?switches?",},
#ifndef NO_ATTACHCMD
    {"attach", 4, (Blt_Op)AttachOp, 2, 4, "?-notags? ?tree?",},
#endif
    {"children", 2, (Blt_Op)ChildrenOp, 3, 6, "?-labels? node ?first? ?last?",},
    {"copy", 2, (Blt_Op)CopyOp, 4, 0, 
	"srcNode ?destTree? destNode ?switches?",},
    {"create", 3, (Blt_Op)CreateOp, 3, 0, "?switches?",},
    {"degree", 3, (Blt_Op)DegreeOp, 3, 3, "node",},
    {"delete", 3, (Blt_Op)DeleteOp, 3, 0, "node ?node...?",},
    {"depth", 3, (Blt_Op)DepthOp, 3, 3, "node",},
    {"dictset", 3, (Blt_Op)DictsetOp, 2, 3, "?makeDict?",},
    {"dump", 4, (Blt_Op)DumpOp, 3, 0, "node ?switches?",},
    {"exists", 1, (Blt_Op)ExistsOp, 3, 4, "node ?key?",},
    {"find", 4, (Blt_Op)FindOp, 2, 0, "?switches?",},
    {"findchild", 5, (Blt_Op)FindChildOp, 4, 4, "node name",},
    {"firstchild", 3, (Blt_Op)FirstChildOp, 3, 3, "node",},
    {"fixed", 3, (Blt_Op)FixedOp, 3, 4, "node ?isFixed?",},
    {"foreach", 2, (Blt_Op)ForeachOp, 5, 5, "var node script",},
    {"get", 1, (Blt_Op)GetOp, 3, 5, "node ?key? ?defaultValue?",},
    {"incr", 4, (Blt_Op)IncrOp, 4, 5, "node key ?amount?",},
    {"incri", 5, (Blt_Op)IncriOp, 4, 5, "node key ?amount?",},
    {"index", 3, (Blt_Op)IndexOp, 3, 3, "name",},
    {"insert", 3, (Blt_Op)InsertOp, 3, 0, "parent ?switches?",},
    {"is", 2, (Blt_Op)IsOp, 2, 0, "oper args...",},
    {"ismodified", 3, (Blt_Op)IsModifiedOp, 2, 4, "?nodeOrTag? ?bool?",},
    {"isset", 3, (Blt_Op)IsSetOp, 4, 4, "nodeOrTag key",},
    {"keys", 1, (Blt_Op)KeysOp, 3, 0, "node ?node...?",},
    {"label", 3, (Blt_Op)LabelOp, 3, 4, "node ?newLabel?",},
    {"lappend", 7, (Blt_Op)LappendOp, 4, 0, "node key value ?...?",},
    {"lappendi", 8, (Blt_Op)LappendiOp, 4, 0, "node key value ?...?",},
    {"lastchild", 3, (Blt_Op)LastChildOp, 3, 3, "node",},
    {"modify", 2, (Blt_Op)ModifyOp, 3, 0, "node ?key value...?",},
    {"move", 2, (Blt_Op)MoveOp, 4, 0, "node newParent ?switches?",},
    {"names", 2, (Blt_Op)NamesOp, 3, 5, "node ?key? ?pattern?",},
    {"next", 4, (Blt_Op)NextOp, 3, 3, "node",},
    {"nextsibling", 5, (Blt_Op)NextSiblingOp, 3, 3, "node",},
    {"nodeseq", 3, (Blt_Op)NodeSeqOp, 2, 3, "?num?",},
    {"notify", 3, (Blt_Op)NotifyOp, 2, 0, "args...",},
    {"oldvalue", 2, (Blt_Op)OldValueOp, 2, 3, "?value?",},
    {"parent", 3, (Blt_Op)ParentOp, 3, 3, "node",},
    {"path", 3, (Blt_Op)PathOp, 3, 5, "node ?delim? ?prefix?",},
    {"position", 3, (Blt_Op)PositionOp, 3, 0, "?switches? node...",},
    {"previous", 5, (Blt_Op)PreviousOp, 3, 3, "node",},
    {"prevsibling", 5, (Blt_Op)PrevSiblingOp, 3, 3, "node",},
    {"restore", 3, (Blt_Op)RestoreOp, 5, 0, "node ?switches?",},
    {"root", 2, (Blt_Op)RootOp, 2, 3, "?node?",},
    {"set", 3, (Blt_Op)SetOp, 3, 0, "node ?key value...?",},
    {"size", 2, (Blt_Op)SizeOp, 3, 3, "node",},
    {"sort", 2, (Blt_Op)SortOp, 3, 0, "node ?flags...?",},
    {"sqlload", 4, (Blt_Op)SqlloadOp, 4, 0, "db sql",},
    {"sum", 3, (Blt_Op)SumOp, 4, 0, "node key ?-runtotal key? ?-start num? ?-int?",},
    {"supdate", 3, (Blt_Op)UpdatesOp, 3, 0, "node ?key value ...?",},
    {"tag", 2, (Blt_Op)TagOp, 3, 0, "args...",},
    {"trace", 2, (Blt_Op)TraceOp, 2, 0, "args...",},
    {"type", 2, (Blt_Op)TypeOp, 4, 4, "node key",},
    {"unset", 2, (Blt_Op)UnsetOp, 3, 0, "node ?key...?",},
    {"update", 2, (Blt_Op)UpdateOp, 3, 0, "node ?key value ...?",},
    {"values", 2, (Blt_Op)ValuesOp, 3, 5, "node ?key? ?withnames?",},
    {"vecdump", 4, (Blt_Op)VecdumpOp, 4, 5, "vector key ?tag?",},
    {"vecload", 4, (Blt_Op)VecloadOp, 4, 5, "vector key ?tag?",},
    {"with", 2, (Blt_Op)WithOp, 5, 0, "avar ?-keys keylist? ?-array key? ?-noupdate? ?-unset? ?-break? node script",},
};

static int nTreeOps = sizeof(treeOps) / sizeof(Blt_OpSpec);

static int
TreeInstObjCmd(
    ClientData clientData,	/* Information about the widget. */
    Tcl_Interp *interp,		/* Interpreter to report errors back to. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *CONST *objv)	/* Vector of argument strings. */
{
    Blt_Op proc;
    TreeCmd *cmdPtr = clientData;
    int result;

    proc = Blt_GetOpFromObj(interp, nTreeOps, treeOps, BLT_OP_ARG1, objc, objv,
 	BLT_OP_LINEAR_SEARCH);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    Tcl_Preserve(cmdPtr);
    result = (*proc) (cmdPtr, interp, objc, objv);
    Tcl_Release(cmdPtr);
    return result;
}

/*
 * ----------------------------------------------------------------------
 *
 * TreeInstDeleteProc --
 *
 *	Deletes the command associated with the tree.  This is
 *	called only when the command associated with the tree is
 *	destroyed.
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------
 */
static void
TreeInstDeleteProc(ClientData clientData)
{
    TreeCmd *cmdPtr = clientData;

    ReleaseTreeObject(cmdPtr);
    if (cmdPtr->hashPtr != NULL) {
	Blt_DeleteHashEntry(cmdPtr->tablePtr, cmdPtr->hashPtr);
    }
    Blt_DeleteHashTable(&(cmdPtr->traceTable));
    Blt_Free(cmdPtr);
}

/*
 * ----------------------------------------------------------------------
 *
 * GenerateName --
 *
 *	Generates an unique tree command name.  Tree names are in
 *	the form "treeN", where N is a non-negative integer. Check 
 *	each name generated to see if it is already a tree. We want
 *	to recycle names if possible.
 *	
 * Results:
 *	Returns the unique name.  The string itself is stored in the
 *	dynamic string passed into the routine.
 *
 * ----------------------------------------------------------------------
 */
static CONST char *
GenerateName(
    Tcl_Interp *interp,
    CONST char *prefix, 
    CONST char *suffix,
    Tcl_DString *resultPtr)
{

    int n;
    Tcl_Namespace *nsPtr;
    char string[200];
    Tcl_CmdInfo cmdInfo;
    Tcl_DString dString;
    CONST char *treeName, *name;

    /* 
     * Parse the command and put back so that it's in a consistent
     * format.  
     *
     *	t1         <current namespace>::t1
     *	n1::t1     <current namespace>::n1::t1
     *	::t1	   ::t1
     *  ::n1::t1   ::n1::t1
     */
    treeName = NULL;		/* Suppress compiler warning. */
    Tcl_DStringInit(&dString);
    for (n = 0; n < INT_MAX; n++) {
	Tcl_DStringSetLength(&dString, 0);
	Tcl_DStringAppend(&dString, prefix, -1);
	sprintf(string, "tree%d", n);
	Tcl_DStringAppend(&dString, string, -1);
	Tcl_DStringAppend(&dString, suffix, -1);
	treeName = Tcl_DStringValue(&dString);
	if (Blt_ParseQualifiedName(interp, treeName, &nsPtr, &name) != TCL_OK) {
	    Tcl_AppendResult(interp, "can't find namespace in \"", treeName, 
		"\"", (char *)NULL);
            Tcl_DStringFree(&dString);
	    return NULL;
	}
	if (nsPtr == NULL) {
	    nsPtr = Tcl_GetCurrentNamespace(interp);
	}
	treeName = Blt_GetQualifiedName(nsPtr, name, resultPtr);
	/* 
	 * Check if the command already exists. 
	 */
	if (Tcl_GetCommandInfo(interp, (char *)treeName, &cmdInfo)) {
	    continue;
	}
	if (!Blt_TreeExists(interp, treeName)) {
	    /* 
	     * We want the name of the tree command and the underlying
	     * tree object to be the same. Check that the free command
	     * name isn't an already a tree object name.  
	     */
	    break;
	}
    }
    Tcl_DStringFree(&dString);
    return treeName;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeCreateOp --
 *
 * TODO: add support for -nocommand by storing in interp has table.
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TreeCreateOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TreeCmdInterpData *dataPtr = clientData;
    CONST char *treeName, *string;
    Tcl_DString dString;
    Blt_Tree token;
    int fixed = 0;
    int keyhash = 0;
    int dict = 0;

    while (objc>=3) {
        string = Tcl_GetString(objv[2]);
        if (string[0] != '-') break;
        if (string[1] == 'k' && strcmp(string, "-keyhash") == 0) {
            if (objc<4) {
                Tcl_AppendResult(interp, "missing value for -keyhash", 0);
                return TCL_ERROR;
            }
            if (Tcl_GetIntFromObj(interp, objv[3], &keyhash)) {
                return TCL_ERROR;
            }
            objc -= 2;
            objv += 2;
        } else if (string[1] == 'f' && strcmp(string, "-fixed") == 0) {
            fixed = 1;
            objc--;
            objv++;
        } else if (string[1] == 'd' && strcmp(string, "-dictset") == 0) {
            dict = 1;
            objc--;
            objv++;
        } else {
            Tcl_AppendResult(interp, "option not one of: -keyhash -fixed -dictset", 0);
            return TCL_ERROR;
        }
    }
    if (objc != 2 && objc != 3) {
        Tcl_AppendResult(interp, "too many args", 0);
        return TCL_ERROR;
    }
    treeName = NULL;
    Tcl_DStringInit(&dString);

    if (objc == 3) {
	treeName = Tcl_GetString(objv[2]);
    }
    if (treeName == NULL) {
	treeName = GenerateName(interp, "", "", &dString);
    } else {
	char *p;

	p = strstr(treeName, "#auto");
	if (p != NULL) {
	    *p = '\0';
	    treeName = GenerateName(interp, treeName, p + 5, &dString);
	    *p = '#';
	} else {
	    CONST char *name;
	    Tcl_CmdInfo cmdInfo;
	    Tcl_Namespace *nsPtr;

	    nsPtr = NULL;
	    /* 
	     * Parse the command and put back so that it's in a consistent
	     * format.  
	     *
	     *	t1         <current namespace>::t1
	     *	n1::t1     <current namespace>::n1::t1
	     *	::t1	   ::t1
	     *  ::n1::t1   ::n1::t1
	     */
	    if (Blt_ParseQualifiedName(interp, treeName, &nsPtr, &name) 
		!= TCL_OK) {
		Tcl_AppendResult(interp, "can't find namespace in \"", treeName,
			 "\"", (char *)NULL);
                Tcl_DStringFree(&dString);
		return TCL_ERROR;
	    }
	    if (nsPtr == NULL) {
		nsPtr = Tcl_GetCurrentNamespace(interp);
	    }
	    treeName = Blt_GetQualifiedName(nsPtr, name, &dString);
	    /* 
	     * Check if the command already exists. 
	     */
	    if (Tcl_GetCommandInfo(interp, (char *)treeName, &cmdInfo)) {
		Tcl_AppendResult(interp, "a command \"", treeName,
				 "\" already exists", (char *)NULL);
		goto error;
	    }
	    if (Blt_TreeExists(interp, treeName)) {
		Tcl_AppendResult(interp, "a tree \"", treeName, 
			"\" already exists", (char *)NULL);
		goto error;
	    }
	} 
    } 
    if (treeName == NULL) {
	goto error;
    }
    if (Blt_TreeCreate(interp, treeName, &token) == TCL_OK) {
	int isNew;
	TreeCmd *cmdPtr;

	cmdPtr = Blt_Calloc(1, sizeof(TreeCmd));
	assert(cmdPtr);
	cmdPtr->dataPtr = dataPtr;
	cmdPtr->tree = token;
	cmdPtr->interp = interp;
	cmdPtr->tree->treeObject->maxKeyList = keyhash;
	if (fixed) {
	   cmdPtr->tree->treeObject->flags |= TREE_FIXED_KEYS;
	}
        if (dict) {
             cmdPtr->tree->treeObject->flags |= TREE_DICT_KEYS;
        }
        Blt_InitHashTable(&(cmdPtr->traceTable), BLT_STRING_KEYS);
	Blt_InitHashTable(&(cmdPtr->notifyTable), BLT_STRING_KEYS);
	cmdPtr->cmdToken = Tcl_CreateObjCommand(interp, (char *)treeName, 
		(Tcl_ObjCmdProc *)TreeInstObjCmd, cmdPtr, TreeInstDeleteProc);
	cmdPtr->tablePtr = &dataPtr->treeTable;
	cmdPtr->hashPtr = Blt_CreateHashEntry(cmdPtr->tablePtr, (char *)cmdPtr,
	      &isNew);
	Blt_SetHashValue(cmdPtr->hashPtr, cmdPtr);
	Tcl_SetResult(interp, (char *)treeName, TCL_VOLATILE);
	Tcl_DStringFree(&dString);
	Blt_TreeCreateEventHandler(cmdPtr->tree, TREE_NOTIFY_ALL, 
	     TreeEventProc, cmdPtr);
	return TCL_OK;
    }
 error:
    Tcl_DStringFree(&dString);
    return TCL_ERROR;
}

static void
destroyTreeCmd(char *cmdObj) {
    TreeCmd *cmdPtr = (TreeCmd*) cmdObj;
    Tcl_DeleteCommandFromToken(cmdPtr->interp, cmdPtr->cmdToken);
}

/*
 *----------------------------------------------------------------------
 *
 * TreeDestroyOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TreeDestroyOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TreeCmdInterpData *dataPtr = clientData;
    TreeCmd *cmdPtr;
    char *string;
    register int i;

    for (i = 2; i < objc; i++) {
	string = Tcl_GetString(objv[i]);
	cmdPtr = GetTreeCmd(dataPtr, interp, string);
	if (cmdPtr == NULL) {
	    Tcl_AppendResult(interp, "can't find a tree named \"", string,
			     "\"", (char *)NULL);
	    return TCL_ERROR;
	}
	cmdPtr->delete = 1;
	Tcl_EventuallyFree(cmdPtr, destroyTreeCmd);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeNamesOp --
 *
 *---------------------------------------------------------------------- 
 */
/*ARGSUSED*/
static int
TreeNamesOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TreeCmdInterpData *dataPtr = clientData;
    TreeCmd *cmdPtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Tcl_Obj *objPtr, *listObjPtr;
    Tcl_DString dString;
    char *qualName;

    Tcl_DStringInit(&dString);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&dataPtr->treeTable, &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	cmdPtr = Blt_GetHashValue(hPtr);
	qualName = Blt_GetQualifiedName(
		Blt_GetCommandNamespace(interp, cmdPtr->cmdToken), 
		Tcl_GetCommandName(interp, cmdPtr->cmdToken), &dString);
	if (objc == 3) {
	    if (Tcl_StringMatch(qualName, Tcl_GetString(objv[2])) != 1) {
		continue;
	    }
	}
	objPtr = Tcl_NewStringObj(qualName, -1);
	Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    Tcl_DStringFree(&dString);
    return TCL_OK;
}

static int
TreeOpOp(clientData, interp, objc, objv)
    ClientData clientData;
    Tcl_Interp *interp;
    int objc;
    Tcl_Obj *CONST *objv;
{
    int i, result, sub = 0;
    Tcl_Obj **nobjv;
    char *string, *s;
    TreeCmd *cmdPtr;
    TreeCmdInterpData *dataPtr = clientData;

    s = Tcl_GetString(objv[2]);
    string = Tcl_GetString(objv[3]);
    if (objc>4 && (strncmp(s,"tr",2) == 0 || strcmp(s,"tag") == 0 || strcmp(s,"is") == 0 || strncmp(s,"no",2) == 0)) {
        string = Tcl_GetString(objv[4]);
        cmdPtr = GetTreeCmd(dataPtr, interp, string);
        sub = 1;
    } else {
        cmdPtr = GetTreeCmd(dataPtr, interp, string);
    }
    if (cmdPtr == NULL) {
        /* TODO: lookup TreeObject and build a temp CmdPtr */
        Tcl_AppendResult(interp, "can't find a tree named \"", string,
            "\"", (char *)NULL);
            return TCL_ERROR;
    }
    nobjv = (Tcl_Obj **) ckalloc((unsigned)(objc) * sizeof(Tcl_Obj *));

    if (sub) {
        nobjv[0] = objv[4];
        nobjv[1] = objv[2];
        nobjv[2] = objv[3];
    } else {
        nobjv[0] = objv[3];
        nobjv[1] = objv[2];
    }
    for (i = 2+sub;  i < (objc-2);  i++) {
        nobjv[i] = objv[i+2];
    }
    nobjv[objc-2] = 0;
    result = TreeInstObjCmd((ClientData)cmdPtr, interp, objc-2, nobjv);
    ckfree((char*) nobjv);
    return result;
}



/*
 *----------------------------------------------------------------------
 *
 * TreeObjCmd --
 *
 *---------------------------------------------------------------------- 
 */
static Blt_OpSpec treeCmdOps[] =
{
    {"create", 1, (Blt_Op)TreeCreateOp, 2, 0, "?-keyhash N? ?-fixed? ?-dictset? ?name?",},
    {"destroy", 1, (Blt_Op)TreeDestroyOp, 3, 0, "name...",},
    {"names", 1, (Blt_Op)TreeNamesOp, 2, 3, "?pattern?...",},
    {"op", 1, (Blt_Op)TreeOpOp, 4, 0, "subcmd tree args ...",},
};

static int nCmdOps = sizeof(treeCmdOps) / sizeof(Blt_OpSpec);

/*ARGSUSED*/
static int
TreeObjCmd(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;

    proc = Blt_GetOpFromObj(interp, nCmdOps, treeCmdOps, BLT_OP_ARG1, objc, 
	objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    return (*proc) (clientData, interp, objc, objv);
}

/*
 * -----------------------------------------------------------------------
 *
 * TreeInterpDeleteProc --
 *
 *	This is called when the interpreter hosting the "tree" command
 *	is deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Removes the hash table managing all tree names.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
TreeInterpDeleteProc(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp)
{
    TreeCmdInterpData *dataPtr = clientData;

    /* All tree instances should already have been destroyed when
     * their respective Tcl commands were deleted. */
    Blt_DeleteHashTable(&dataPtr->treeTable);
    Tcl_DeleteAssocData(interp, TREE_THREAD_KEY);
    Blt_Free(dataPtr);
}

/*ARGSUSED*/
static int
CompareDictionaryCmd(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    int result;
    char *s1, *s2;

    s1 = Tcl_GetString(objv[1]);
    s2 = Tcl_GetString(objv[2]);
    result = Blt_DictionaryCompare(s1, s2);
    result = (result > 0) ? -1 : (result < 0) ? 1 : 0;
    Tcl_SetIntObj(Tcl_GetObjResult(interp), result);
    return TCL_OK;
}

#ifdef BLT_USE_UTIL_EXIT  
/*ARGSUSED*/
static int
ExitCmd(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    int code;

    if (Tcl_GetIntFromObj(interp, objv[1], &code) != TCL_OK) {
	return TCL_ERROR;
    }
#ifdef TCL_THREADS
    Tcl_Exit(code);
#else 
    exit(code);
#endif
    /*NOTREACHED*/
    return TCL_OK;
}
#endif

/*
 * -----------------------------------------------------------------------
 *
 * Blt_TreeInit --
 *
 *	This procedure is invoked to initialize the "tree" command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Creates the new command and adds a new entry into a global Tcl
 *	associative array.
 *
 * ------------------------------------------------------------------------
 */
extern int bltTreeUseLocalKeys;
int
Blt_TreeInit(Tcl_Interp *interp)
{
    TreeCmdInterpData *dataPtr;	/* Interpreter-specific data. */
    static Blt_ObjCmdSpec cmdSpec = { 
	"tree", TreeObjCmd, 
    };
    static Blt_ObjCmdSpec compareSpec = { 
	"compare", CompareDictionaryCmd, 
    };
#ifdef BLT_USE_UTIL_EXIT  
    static Blt_ObjCmdSpec exitSpec = { 
	"exit", ExitCmd, 
    };
    if (Blt_InitObjCmd(interp, "blt::util", &exitSpec) == NULL) {
	return TCL_ERROR;
    }
#endif
    if (Blt_InitObjCmd(interp, "blt::util", &compareSpec) == NULL) {
	return TCL_ERROR;
    }

    dataPtr = GetTreeCmdInterpData(interp);
    cmdSpec.clientData = dataPtr;
    if (Blt_InitObjCmd(interp, "blt", &cmdSpec) == NULL) {
	return TCL_ERROR;
    }
    if (!Tcl_IsSafe(interp)) {
        Tcl_LinkVar(interp, "blt::treeKeysLocal", (char*)&bltTreeUseLocalKeys, TCL_LINK_INT);
    }
    return TCL_OK;
}

int
Blt_TreeCmdGetToken(
    Tcl_Interp *interp,
    CONST char *string,
    Blt_Tree  *treePtr)
{
    TreeCmdInterpData *dataPtr;
    TreeCmd *cmdPtr;

    dataPtr = GetTreeCmdInterpData(interp);
    cmdPtr = GetTreeCmd(dataPtr, interp, string);
    if (cmdPtr == NULL) {
	Tcl_AppendResult(interp, "can't find a tree associated with \"",
		 string, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    *treePtr = cmdPtr->tree;
    return TCL_OK;
}

/* Dump tree to dbm */
/* Convert node data to datablock */

#endif /* NO_TREE */

