I would like to recursively add nested dictionary data made of DOM nodes into Tkinter's Treeview items. I was expecting to have a tree structure like:
html -> Head
Meta
Title
Body
. . .
Unfortunately, I noticed quite repeated fields.
Here is the following code.
import tkinter as tk
from tkinter import ttk
tree_data = {
"tag": "html",
"children": [
{
"tag": "head",
"children": [
{
"tag": "meta",
"children": []
},
{
"tag": "title",
"children": []
}
]
},
{
"tag": "body",
"children": [
{
"tag": "ul",
"children": [
{
"tag": "div",
"children": []
},
{
"tag": "div",
"children": []
},
{
"tag": "div",
"children": []
}
]
},
{
"tag": "div",
"children": [
{
"tag": "ul",
"children": [
{
"tag": "li",
"children": []
},
{
"tag": "li",
"children": []
},
{
"tag": "li",
"children": []
}
]
},
{
"tag": "div",
"children": [
{
"tag": "h1",
"children": []
}
]
},
{
"tag": "div",
"children": [
{
"tag": "h2",
"children": []
}
]
}
]
},
{
"tag": "div",
"children": [
{
"tag": "div",
"children": [
{
"tag": "h1",
"children": []
},
{
"tag": "h1",
"children": []
},
{
"tag": "h2",
"children": []
}
]
}
]
},
{
"tag": "div",
"children": []
}
]
}
]
}
def parent_child(d, ind):
ind = 1
print("this:", ind , d['tag'], "children:", [c['tag'] for c in d['children']])
t = d['tag']
head_tag = tv.insert("", ind, text=f"{t}-{ind}", values=("",""))
for c in d['children']:
ind =1
tv.insert(head_tag, "end", text=c['tag'], values=("",""))
parent_child(c, ind)
root = tk.Tk()
label = tk.Label(root, text="Dom Management")
label.pack()
tv = ttk.Treeview(root, columns=['attribs', 'text'], selectmode='none')
tv.heading('#0', text='Tag')
# tv.heading('attribs', text='Attributes', anchor='center')
tv.heading('text', text='Text', anchor='e')
parent_child(dt, 0)
tv.pack(side=tk.TOP,fill=tk.X)
root.mainloop()
I was expecting to have a single HTML root with corresponding added children, unfortunately, This is the result below.
How can I readjust my code to have a perfect tree?
CodePudding user response:
First of all, I think you forgot to use the "tree_data" as the paramater to the function call at this line of code.
parent_child(tree_data, 0)
instead of
parent_child(dt, 0)
I also modified the JSON data so that every similar values of the "tag" keys has an identifier, it is just to help us differentiate easier, as follows:
tree_data = {
"tag": "html",
"children": [
{
"tag": "head",
"children": [
{
"tag": "meta",
"children": []
},
{
"tag": "title",
"children": []
}
]
},
{
"tag": "body",
"children": [
{
"tag": "ul0",
"children": [
{
"tag": "div0",
"children": []
},
{
"tag": "div1",
"children": []
},
{
"tag": "div2",
"children": []
}
]
},
{
"tag": "div3",
"children": [
{
"tag": "ul1",
"children": [
{
"tag": "li0",
"children": []
},
{
"tag": "li1",
"children": []
},
{
"tag": "li2",
"children": []
}
]
},
{
"tag": "div4",
"children": [
{
"tag": "h1-0",
"children": []
}
]
},
{
"tag": "div-5",
"children": [
{
"tag": "h2-0",
"children": []
}
]
}
]
},
{
"tag": "div6",
"children": [
{
"tag": "div7",
"children": [
{
"tag": "h1-1",
"children": []
},
{
"tag": "h1-2",
"children": []
},
{
"tag": "h2-3",
"children": []
}
]
}
]
},
{
"tag": "div-8",
"children": []
}
]
}
]
}
Referring to your question, from what I understand you would like to show the JSON data in the TreeView as so:
Referring to TkInter's documentation of the TreeView object and its "insert" method.
pathname insert parent index ?-id id? options...
Creates a new item. parent is the item ID of the parent item, or the empty string {} to create a new top-level item. index is an integer, or the value end, specifying where in the list of parent's children to insert the new item. If index is less than or equal to zero, the new node is inserted at the beginning; if index is greater than or equal to the current number of children, it is inserted at the end. If -id is specified, it is used as the item identifier; id must not already exist in the tree. Otherwise, a new unique identifier is generated.
Referring to your "parent_child" function, I noticed some things, that is:
On this line of code,
head_tag = tv.insert("", ind, text=f"{t}-{ind}", values=("",""))
First, you only refer to the automatically-generated identifier once.
Second, referring to the documentation, the second parameter of the insert function is to specify where in the list of parent's children to insert the new item at. Thus, for your case I think you would want to use the "end"
string for the second parameter, since you want to always add a new row object at a level on the last position.
Third, still referring to the documentation, if "id" parameter is not specified upon calling the insert function, a new identifier for the newly inserted row in the TreeView object will be returned.
However, in the loop below, you never refer your newly-created row object to anything. Thus, when the recursion happens it will not remember the object that was created before.
for c in d['children']:
ind =1
tv.insert(head_tag, "end", text=c['tag'], values=("",""))
parent_child(c, ind)
You would need to revise the logic of the recursion, because we want the recursion to remember the parent's row object identifier to its children. See the following solution that I made.
def parent_child(d, parent_id=None):
print("this:", parent_id, d['tag'], "children:", [c['tag'] for c in d['children']])
if parent_id is None:
# This line is only for the first call of the function
parent_id = tv.insert("", "end", text=d['tag'], values=("",""))
for c in d['children']:
# Here we create a new row object in the TreeView and pass its return value for recursion
# The return value will be used as the argument for the first parameter of this same line of code after recursion
parent_child(c, tv.insert(parent_id, "end", text=c['tag'], values=("","")))
# We no longer need to pass the ind parameter
parent_child(tree_data)