Python Code Sample
# Tile Art XML Editor
# Edits Tile Art XML Files.
#
# Created 2009-04-01
# Last Modified 2009-04-05
#
# Created by Ruben Garza

import Tkinter
import ImageTk
import os
from elementtree import ElementTree as et
from elementtree.ElementTree import Element, SubElement
import sys
import tkMessageBox
import tkFileDialog   

pathImages = "C:/perforce/UO/UOSA/Mainline/Runtime/Data/TileArtLegacy"
pathXML = "C:/perforce/UO/UOSA/Mainline/Runtime/Data/Definitions/TileArt" 
tgaList = [""]
changeList = ""

# Create Checkbox List
chkBoxList = ["animation","hover_over","background","noshoot","bridge","partialhue","container"
,"roof","door","stair_back","stair_right","generic","equipment","surface","foliage","wall","impassable"
,"wet","lightsource","wearable","window","mongen","map","usenewart"]
chkBoxList.sort()

entryList =[["quality", "value"],["quantity", "value"],["weight", "value"],
["height", "value"],["Value", "value"],["ACWC", "value"],["slot", "value"],["hue", "value"],
["appearance", "value"],["gender", "value"],["race", "value"],["sittingData", "x"],
["sittingData", "y"],["legacy","offsetX"],["legacy","offsetY"],["sittingData","backSplitPosition"]]
entryList.sort(lambda x, y: cmp(x[0].lower(),y[0].lower()))

whereToLook = ["defaultInstanceProperties","defaultInstancePropertiesLegacy"]

root = Tkinter.Tk()
root.title('Tile Art XML Editor')

# Lets you set root's size
#w = 550
#h = 640
#x = 0
#y = 0
#root.geometry("%dx%d+%d+%d" % (w, h, x, y))

# XML Formatter
def indent(elem, level=0):
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for elem in elem:
            indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

# Function: Create a new changelist in p4
def CreateNewChangeList(description):
    "Create a new changelist and returns the changelist number as a string"
    p4in, p4out = os.popen2("p4 changelist -i")
    p4in.write("change: new\n")
    p4in.write("description: " + description)
    p4in.close()
    changelist = p4out.readline().split()[1]
    return changelist

# Function: Open File for edit
def OpenFileForEdit(file, changelist = ""):
    "Open a file for edit, if a changelist is passed in then open it in that list"
    cmd = "p4 edit "
    if changelist:
        cmd += " -c " + changelist + " "
    ret = os.popen(cmd + file).readline().strip()
    if not ret.endswith("opened for edit"):
        print "Couldn't open", file, "for edit:"
        print ret
        raise ValueError
    
# Function: Find Character in String
def findCharInString(inString,findString):
    foundIt = False
    for findMe in range(0,len(findString)) :
        if inString == findString[findMe]: foundIt = True
    return foundIt

# Function: Remove 0's
def removeZero(inString):
    for findMe in range(0,(len(inString)-1)) :
        if inString[0] == "0" : inString = inString [1:]
    return inString

# Function to sort listbox items case insensitive
def sort_list(listbox1):
    temp_list = list(listbox1.get(0, Tkinter.END))
    temp_list.sort(key=str.lower)

    # delete contents of present listbox
    listbox1.delete(0, Tkinter.END)

    # load listbox with sorted data
    for item in temp_list:
        listbox1.insert(Tkinter.END, item)
    return
        
# Function: Open selected files for edit.
def openForEdit():
    global changeList
    if changeList == "" : changeList = CreateNewChangeList("Changing Tileart NO QA")
    for listItem in range (0, len(listbx.curselection())) :
        assetName = listbx.get(listbx.curselection()[listItem])
        doIadd = True
        try:
            saveList = cklistbx.get(0,Tkinter.END)

            for checkItem in range (0, len(saveList)):
                if assetName == saveList[checkItem] : doIadd = False
        except:
            pass
        
        if doIadd == True :
            try:
                OpenFileForEdit ((pathXML + "/" + assetName + ".tileart"),changeList)
            except:
                pass
            cklistbx.insert(Tkinter.END, assetName)
            sort_list(cklistbx)
    return

# Function: Remove nonnumerical characters - not very bullet proof.
def trimFileName(inName):
    outName = ((inName.split()[0]).split("_")[0]).split(".")[0]
    outName = int(outName)
    return outName

# Function: Find Entry Items XML
def entryItem(tree,elem, attr):
    name = tree.getiterator(elem)
    if len(name) > 0 : nameValue = name[0].get(attr)
    else : nameValue = ""
    return nameValue

# Function: Find checkbox Items XML
def checkBoxSet(tree,buttonSet,attr):
    name = tree.getiterator(attr)
    if len(name) > 0 : buttonSet.select()
    else : buttonSet.deselect()

# Function: Turn All Batch Checkboxes On
def batchAllOn ():
    saName.select()
    for chkBx in range (0,len(chkBoxList)) :
        UIsaveButton[chkBx].select()
    for chkBx in range (0,len(entryList)) :
        UIsaveEntry[chkBx].select()
        
# Function: Turn Off Batch Checkboxes On
def batchAllOff ():
     saName.deselect()
     for chkBx in range (0,len(chkBoxList)) :
        UIsaveButton[chkBx].deselect()
     for chkBx in range (0,len(entryList)) :
        UIsaveEntry[chkBx].deselect()
        
# Function: Update XML and image when listbox clicked on.
def listUpdate(listToUse,entryChanged):

    if entryChanged == True :
        try:
            typedVal = int(eListBx.get())
            newVal = typedVal
           
            bigList = listToUse.get(0,Tkinter.END)
            bigInt = []
            
            for testInt in bigList:
                newInt = int(testInt)
                bigInt.append(newInt)
                
            try:
                newVal = bigInt.index(typedVal)
            except:
                pass

            listToUse.see(newVal)
        except:
            listVal = eListBx.get()
            evListBx.set(listVal[:-1])              
    else:
        try:
            assetName = listToUse.get(listToUse.curselection()[0])
        
            # Update Image
            tgaPathAndName = os.path.join(pathImages, removeZero(assetName))
            tgaList[0]=ImageTk.PhotoImage(file=(tgaPathAndName+".tga"))
            imgLabel.config(image=tgaList[0])

            # Parse XML
            tree = et.parse(pathXML + "/" + assetName + ".tileart")

            # Checkboxes
            for chkBx in range (0,len(chkBoxList)) :
                checkBoxSet(tree,UIchkBx[chkBx],chkBoxList[chkBx])
 
            # Entry Items
            evName.set((entryItem(tree,"art", "typeName")))
            for entList in range (0,len(entryList)) :
                varEntry[entList].set((entryItem(tree,entryList[entList][0],entryList[entList][1])))
        except:
            pass

# Function: Write XML values to selected files.
def writeVals():
    if len(cklistbx.curselection()) > 0 :
        print "Process " + str(len(cklistbx.curselection())) + " files"
        for listItem in range (0, len(cklistbx.curselection())) :
            assetName = cklistbx.get(cklistbx.curselection()[listItem])
            print assetName
            tree = et.parse(pathXML + "/" + assetName + ".tileart")

            # Write / Delete Checkboxes
            for checkBoxSet in range(0,len(chkBoxList)):
                # Only change value if batch is checked
                if saveChkBoxToggle[checkBoxSet].get() == 1 :
                    findElem = tree.getiterator(chkBoxList[checkBoxSet])
                    
                    # Add Element
                    if varChkBox[checkBoxSet].get() == 1 :
                        if len(findElem) == 0 :
                            for addElem in range(0,len(whereToLook)):
                                subElem = tree.getiterator(whereToLook[addElem])
                                subElem[0].append(Element(chkBoxList[checkBoxSet]))
                    # Remove Element
                    else:
                        if len(findElem) > 0 :
                            for remElem in range(0,len(whereToLook)):
                                subElem = tree.getiterator(whereToLook[remElem])
                                delElem = subElem[0].find(chkBoxList[checkBoxSet])
                                subElem[0].remove(delElem)
                                
            # Write / Delete Entry Items
            for entrySet in range(0,len(entryList)):
                # Only change value if batch is checked
                if saveEntryToggle[entrySet].get() == 1 :
                    findElem = tree.getiterator(entryList[entrySet][0])
                    # Add or Modify Element
                    if varEntry[entrySet].get() != "" :
                        if len(findElem) == 0 :
                            #Add Element Name
                            for addElem in range(0,len(whereToLook)):
                                subElem = tree.getiterator(whereToLook[addElem])
                                subElem[0].append(Element(entryList[entrySet][0]))                             
                        #Modify Element (Value)
                        for elt in tree.getiterator(entryList[entrySet][0]):
                            if elt.attrib.has_key (entryList[entrySet][1]):
                                elt.set(entryList[entrySet][1],varEntry[entrySet].get())
                            #Add Element
                            else:
                                 elt.set(entryList[entrySet][1],varEntry[entrySet].get())
                    # Remove Element
                    else:
                        if len(findElem) > 0 :
                            for remElem in range(0,len(whereToLook)):
                                subElem = tree.getiterator(whereToLook[remElem])
                                delElem = subElem[0].find(entryList[entrySet][0])
                                subElem[0].remove(delElem)
            # Write XML                
            root = tree.getroot()
            indent(root)
            tree.write(pathXML + "/" + assetName + ".tileart")
    else:tkMessageBox.showwarning("Save - Please select from Lower list","Please select some files from the lower list then click on save.")
    return

# Build Frames
frameButtons = Tkinter.Frame(root)
frameButtons.grid(row=0, column=0, columnspan=3, padx=5, pady=5,sticky=Tkinter.W)

frameLstBx = Tkinter.Frame(root)
frameLstBx.grid(row=1, column=0, rowspan=2,  padx=5, pady=5,sticky=Tkinter.N)

frameChkBx = Tkinter.Frame(root)
frameChkBx.grid(row=1, column=1, padx=5, pady=5,sticky=Tkinter.N)

frameChkPic = Tkinter.Frame(root)
frameChkPic.grid(row=1, column=2, padx=5, pady=5,sticky=Tkinter.N)

frameEntry = Tkinter.Frame(frameChkPic)
frameEntry.grid(row=0, column=0, padx=5, pady=5, sticky=Tkinter.N+Tkinter.W)

frameImage = Tkinter.Frame(frameChkPic,borderwidth=5,relief=Tkinter.GROOVE)
frameImage.grid(row=1, column=0, sticky=Tkinter.N)


# Find Drectory
# testDir = tkFileDialog.askdirectory()
# pathXML = testDir

# Get list of assets
assetList = []
for assetName in os.listdir(pathXML):
    assetName = assetName[:-8]
    if findCharInString (assetName[0],"0123456789") : assetList.append(assetName)

assetList.sort(lambda a,b: cmp(trimFileName(a), trimFileName(b)))

# Build Listbox and Scrollbar
listAtitle = Tkinter.Label(frameLstBx, text="Tile Browser")
listAtitle.grid(row=0, column=0)
listscroll = Tkinter.Scrollbar(frameLstBx)
listbx = Tkinter.Listbox(frameLstBx, selectmode=Tkinter.EXTENDED,yscrollcommand=listscroll.set,height=30)
listbx.grid(row=1, column=0)

listscroll.grid(row=1, column=1,padx=0, pady=5,sticky=Tkinter.N+Tkinter.S+Tkinter.W)
listscroll.config(command=listbx.yview)

evListBx = Tkinter.StringVar()
eListBx = Tkinter.Entry(frameLstBx, textvariable=evListBx)
eListBx.grid(row=2, column=0)


listBtitle = Tkinter.Label(frameLstBx, text="Save checked out files:")
listBtitle.grid(row=3, column=0)


cklistscroll = Tkinter.Scrollbar(frameLstBx)
cklistbx = Tkinter.Listbox(frameLstBx, selectmode=Tkinter.EXTENDED,yscrollcommand=cklistscroll.set,height=10)
cklistbx.grid(row=4, column=0)

cklistscroll.grid(row=4, column=1,padx=0, pady=5,sticky=Tkinter.N+Tkinter.S+Tkinter.W)
cklistscroll.config(command=cklistbx.yview)


# Assign all files to listbox
for assetName in assetList :
    listbx.insert(Tkinter.END, assetName)
listbx.select_set(0)

# Build Buttons
changeVals = Tkinter.Button(frameButtons, text="Check Out", command=openForEdit).grid(row=0, column=0,padx=1)
saveFile = Tkinter.Button(frameButtons, text="Save", command=writeVals).grid(row=0, column=1,padx=1)
#bExit = Tkinter.Button(frameButtons, text="QUIT", fg="red", command=root.quit).grid(row=0, column=2)
bBatchAllOn = Tkinter.Button(frameButtons, text="BatchAll", command=batchAllOn).grid(row=0, column=2,padx=1)
bBatchAllOn = Tkinter.Button(frameButtons, text="BatchNone", command=batchAllOff).grid(row=0, column=3,padx=1)
spacer = Tkinter.Label(frameButtons, text = "       Batch:").grid(row=0, column=4)

# Build Label
#v = Tkinter.StringVar()
#lbStat = Tkinter.Label(frameButtons, textvariable=v)
#lbStat.grid(row=0, column=1)
#v.set("Status:")

# Build Label used to hold tile image
imgLabel = Tkinter.Label(frameImage)
imgLabel.grid(row=0, column=0)

# Bind Click and up and down arrows to list    
listbx.bind('',  lambda test : listUpdate(listbx, False))
listbx.bind('',  lambda test : listUpdate(listbx, False))
listbx.bind('',  lambda test : listUpdate(listbx, False))
#cklistbx.bind('',lambda test : listUpdate(cklistbx, False))
#cklistbx.bind('',  lambda test : listUpdate(cklistbx, False))
#cklistbx.bind('',  lambda test : listUpdate(cklistbx, False))
eListBx.bind('',  lambda test : listUpdate(listbx, True))

# Check Box Widgets

lbStat = Tkinter.Label(frameChkBx, text="Batch | Enabled | Property")
lbStat.grid(row=4, column=0, padx=1, pady=1, sticky=Tkinter.W, columnspan=2) 

saveChkBoxToggle = []   #Batch Save Variable
UIsaveButton = []       #UI Batch Save Check button
varChkBox = []          #Check Box Variable
UIchkBx = []            #UI Checkbox


for chkBx in range (0,len(chkBoxList)) :
    saveChkBoxToggle.append(Tkinter.IntVar())
    UIsaveButton.append(Tkinter.Checkbutton(frameChkBx, text="", variable=saveChkBoxToggle[chkBx]))
    UIsaveButton[chkBx].grid(row=(5+chkBx), column=0, padx=1, pady=1, sticky=Tkinter.W)                     
    varChkBox.append(Tkinter.IntVar())
    UIchkBx.append((Tkinter.Checkbutton(frameChkBx, text=chkBoxList[chkBx], variable=varChkBox[chkBx])))
    UIchkBx[chkBx].grid(row=(5+chkBx), column=1, padx=1, pady=1, sticky=Tkinter.W)


# Entry Widgets

EvStat = Tkinter.Label(frameEntry, text="Batch | Property | Value")
EvStat.grid(row=2, column=1, padx=1, pady=1, sticky=Tkinter.W, columnspan=3)

#Name
savName = Tkinter.IntVar()
saName = Tkinter.Checkbutton(frameButtons, text="", variable=savName)
saName.grid(row=0, column=6, sticky=Tkinter.E)
lName = Tkinter.Label(frameButtons, text="Name").grid(row=0, column=7, sticky=Tkinter.W)
evName = Tkinter.StringVar()
eName = Tkinter.Entry(frameButtons,width=25, textvariable=evName)
eName.grid(row=0, column=8, sticky=Tkinter.W)


saveEntryToggle = []    #Batch Save Variable
UIsaveEntry = []        #UI Batch Save Check button
UIentryLabel = []       #UI Entry Label
varEntry = []           #Entry Variable
UIEntry = []            #UI Checkbox


for chkBx in range (0,len(entryList)) :
    saveEntryToggle.append(Tkinter.IntVar())
    UIsaveEntry.append(Tkinter.Checkbutton(frameEntry, text="", variable=saveEntryToggle[chkBx]))
    UIsaveEntry[chkBx].grid(row=(3+chkBx), column=1, padx=1, pady=1, sticky=Tkinter.W)
    if entryList[chkBx][1] == "value" : tempText = entryList[chkBx][0]
    else: tempText = entryList[chkBx][0] + " " + entryList[chkBx][1]
    UIentryLabel.append(Tkinter.Label(frameEntry, text=tempText).grid(row=(3+chkBx), column=2,padx=5, pady=1, sticky=Tkinter.W))
    varEntry.append(Tkinter.StringVar())
    UIEntry.append(Tkinter.Entry(frameEntry,width=5, textvariable=varEntry[chkBx]))
    UIEntry[chkBx].grid(row=(3+chkBx), column=3,padx=5, pady=1, sticky=Tkinter.W)

root.mainloop()