Notes :
- This is a very simple concept demonstrator. It ignores lots of information from the OPML file.
- Information is stored in a tree made of TreeNodes. These are not OPML specific so you could read other formats into them. OTOH, you'll have to extend them for other information you might care about.
- TreeNodes must have a UID which comes from the generator fUidGen. The reason is, that the callback function used by the Tree widget requires you to be able to get hold of the appropriate information to go into the children of any node, based only on the information you have about the parent. My solution to this, my tree uses the same uids as the widget tree. And I look them up in a global variable inside the callback. There's probably a better way, but I can't see it without changing the Tree widget itself.
- The TreeWalker class should be more interesting, but isn't.
- There's another example program for using this Tree widget here : http://home.cfl.rr.com/genecash/tree_widget/treedemo-complex.py which allows some more interaction with the tree (like adding and deleting nodes). Probably the next place to look if you're going to take this further.
How to use :
- Make sure you've downloaded Gene Cash's Tree widget from here : http://home.cfl.rr.com/genecash/tree.html
- Put it in the folder under the name Tree.py
- Make sure you have an example OPML file in the same place called "test.opml"
- Copy the code below into another file (I called it OpmlView?.py) in the same directory.
- You should now be able to run OpmlView?.py and it should show your test.opml file in the widget.
Here's the code
from __future__ import generators from Tree import * from Tkinter import * from xml.dom.minidom import parse, parseString def fUidGen() : """Generate Unique IDs. """ x = 0 yield x while 1 : x = x + 1 yield x fUid = fUidGen() class TreeNode : """Phil's simple tree is built from these""" def __init__(self, text='') : self.text = text self.uid = fUid.next() self.tags = [] self.children = [] def addChild(self, tNode) : self.children.append(tNode) def noChildren(self) : return len(self.children) def getChild(self, i) : return self.children[i] def addTag(self, tag) : self.tags.append(tag) def addChildText(self, text) : node = TreeNode(text) self.addChild(node) def prettyPrint(self, l) : print "%s%s" % ('*' * l, self.text) for x in self.children : x.prettyPrint(l+1) class TreeWalker : def treeSearch(self, node, uid) : """Find the node with this uid""" if uid == node.uid : return node else : for x in node.children : y = self.treeSearch(x, uid) if y != None : return y return None class OpmlToTreeNodes : """Mindlessly simplistic and brittle. Uses the minidom""" def __init__(self) : pass def parse(self, s) : self.dom = parseString(s) return self.dom def recurse(self, parent, tag) : node = TreeNode(tag.getAttribute('text')) for child in tag.childNodes : if child.nodeType != child.TEXT_NODE : self.recurse(node, child) parent.addChild(node) def toTreeNodes(self) : root = TreeNode('root') tag = self.dom.getElementsByTagName('body')[0] for child in tag.childNodes : if child.nodeType != child.TEXT_NODE : self.recurse(root, child) return root if __name__ == '__main__': # default routine to get contents of subtree # supply this for a different type of app # argument is the node object being expanded # should call add_node() def get_contents(node): """Callback which fills the widget Tree from Phil's TreeNode tree. Because this takes only one argument, we need to refer to global "tn" which is my tree. Must be a better way of doing this but may need the widget to be rewritten""" pNode = TreeWalker().treeSearch(tn,node.id) if pNode != None : for x in pNode.children : node.widget.add_node(name=x.text, id=x.uid, flag=x.noChildren() > 0) root=Tk() root.title('Reading test.opml') f = open('test.opml') xml = f.read() f.close() o2t = OpmlToTreeNodes() o2t.parse(xml) tn = o2t.toTreeNodes() # create the control t=Tree(master=root, root_id=tn.uid, root_label=tn.text, get_contents_callback=get_contents, width=300) t.grid(row=0, column=0, sticky='nsew') # make expandable root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) # add scrollbars sb=Scrollbar(root) sb.grid(row=0, column=1, sticky='ns') t.configure(yscrollcommand=sb.set) sb.configure(command=t.yview) sb=Scrollbar(root, orient=HORIZONTAL) sb.grid(row=1, column=0, sticky='ew') t.configure(xscrollcommand=sb.set) sb.configure(command=t.xview) # must get focus so keys work for demo t.focus_set() # we could do without this, but it's nice and friendly to have Button(root, text='Quit', command=root.quit).grid(row=2, column=0, columnspan=2) # expand out the root t.root.expand() root.mainloop()