I'm trying to use a TreeCtrl to represent a folder structure. For each folder I need to know it's absolute path and name. I'm currently doing something like this:
self.root = self.tree.AddRoot(project.name)
self.tree.SetPyData(self.root, None)
self.root.path = root
---- other code -----
childItem = self.tree.AppendItem(self.root, child.name)
childItem.path = self.root.path + "/" + child.name
But now on an event I will need to get the path string. So far my approach that fails is:
self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree)
----- other code -------
def OnItemExpanded(self, evt):
selected = evt.GetItem()
print selected.path
Now this fails because: AttributeError: 'TreeItemId' object has no attribute 'path' . From what I understand here the event only gives me a Id to a Item from the tree and not the actual Item that resulted from the "childItem = self.tree.AppendItem(self.root, child.name)" ? If that is the case how can I get to that item ?
regards,
Bogdan
What is the .path property? Is this something you are creating or an actual member of the TreeItemId object (this is the object returned from the "AppendItem" method)? I do not see any docs on it.
If you want to store arbitrary data in the child items use SetPyData/GetPyData methods.
childItem = self.tree.AppendItem(self.root, child.name)
self.tree.SetPyData(childItem, ["hi", "i" , "am", "a", "python", "object"])
Then in your handler:
def OnItemExpanded(self, event):
item = event.GetItem()
if item:
pyObj = self.tree.GetPyData(item)
Related
Using lxml library I have objectified some elements (sample code below)
config = objectify.Element("config")
gui = objectify.Element("gui")
color = objectify.Element("color")
gui.append(color)
config.append(gui)
config.gui.color.active = "red"
config.gui.color.readonly = "black"
config.gui.color.match = "grey"
the result is the following structure
config
config.gui
config.gui.color
config.gui.color.active
config.gui.color.readonly
config.gui.color.match
I can get a full path for each of the objects
for element in config.iter():
print(element.getroottree().getpath(element))
The path elements are separated by slash but that is not a problem. I do not know how can I get only the parent part of the path so I can use setattr to change the value of given element
For example for element
config.gui.color.active
I would like to enter the command
setattr(config.gui.color, 'active', 'something')
But have no idea how get the "parent" part of full path.
You can get the parent of an element using the getparent function.
for element in config.iter():
print("path:", element.getroottree().getpath(element))
if element.getparent() is not None:
print("parent-path:", element.getroottree().getpath(element.getparent()))
You could also just remove the last part of the element path itself.
for element in config.iter():
path = element.getroottree().getpath(element)
print("path:", path)
parts = path.split("/")
parent_path = "/".join(parts[:-1])
print("parent-path:", parent_path)
I'm using the following code as a portion of a larger program that does some error checking on a Digital Cinema Package and tries to check the validity of the XML file that lists the asses on the DCP. ANyway, this is all still very much in its infancy and I'm hoping to learn more python as a result of it.
import xml.etree.ElementTree as etree
import sys
class Parser(object):
def __init__(self, file_name):
self.file_name = file_name
def display(self, rename_this_list):
tree = etree.parse(self.file_name)
for node in tree.getiterator():
for element in rename_this_list:
if element in node.tag:
uuid=(node.text)
#uuid = [s.strip('urn:') for s in uuid]
print(uuid)
fname = sys.argv[1]
key_search_words = ['KeyId']
instance = Parser(fname)
instance.display(key_search_words)
when I try to store the output so that each line is a list it doesn't format the way that I would expect. Minus the urn: I'd like to be storing each line with uuid: and the following info as an element of a list.
urn:uuid:9851b0f6-4790-0d4c-a69d-ea8abdedd03d
urn:uuid:8317e8f3-1597-494d-9ed8-08a751ff8615
urn:uuid:5d9b228d-7120-344c-aefc-840cdd32bbfc
urn:uuid:1e32ccb2-ab0b-9d43-b879-1c12840c178b
urn:uuid:44d04416-676a-2e4f-8995-165de8cab78d
urn:uuid:906da0c1-b0cb-4541-b8a9-86476583cdc4
urn:uuid:0fe2d73a-ebe3-9844-b3de-4517c63c4b90
urn:uuid:862fa79a-18c7-9245-a172-486541bef0c0
urn:uuid:aa2f1a88-7a55-894d-bc19-42afca589766
urn:uuid:59d6eeff-cd56-6245-9f13-951554466626
urn:uuid:14a13b1a-76ba-764c-97d0-9900f58af53e
urn:uuid:ccdbe0ae-1c3f-224c-b450-947f43bbd640
urn:uuid:dcd37f10-b042-8e44-bef0-89bda2174842
urn:uuid:9dd7103e-7e5a-a840-a15f-f7d7fe699203
If you need a list, then you can try this.
def display(self, rename_this_list):
listOfNodes = []
tree = etree.parse(self.file_name)
for node in tree.getiterator():
for element in rename_this_list:
if element in node.tag:
# append text of element to the list
# without first four characters which are "urn:"
listOfNodes.append(node.text[4:])
print str(listOfNodes)
return listOfNodes
Remember that keys of a dictionary have to be unique, in a dictionary you can't have two items with keys "uuid", if you want a dictionary then you can only have one dictionary with one key "uuid" and a list of all those numbers as values.
import collections
class Parser(object):
def __init__(self, file_name):
self.file_name = file_name, self.res = collections.defaultdict(list)
def display(self, rename_this_list):
tree = etree.parse(self.file_name)
for node in tree.getiterator():
for element in rename_this_list:
if element in node.tag:
uuid = node.text
key, value = uuid[4:].split(':')
self.res[key].append(value)
Can this satisfy your need? I don't know the details of your data so if anything wrong please tell. I think the result should be like this:
{'uuid':['9851b0f6-4790-0d4c-a69d-ea8abdedd03d','ccdbe0ae-1c3f-224c-b450-947f43bbd640',...]}
I've got a gtk.TextView that I'd like to add markup-like text to. I know this can be achieved through the use of gtk.TextTag which you can create with similar properties as a pango markup string. I noticed there is no easy way to just say set_markup to a gtk.TextBuffer much like you can with multiple other widgets. Instead you have to create a TextTag, give it properties, and then insert it into the TextBuffer's TagTable specifying the iters that the tag applies to.
I'd ideally like to create a function that can convert a pango markup string into a TextTag to get the same effect. But gtk doesn't appear to have that functionality built-in.
I've noticed that you can use pango.parse_markup() on a marked up string and it will create a pango.AttributeList which contains information regarding the properties set on the string and the indices that they occur at. But there are slight differences in each type of attribute that make it difficult to generalize for every case. Is there a better way to go about this? Or is pango markup just not meant to be converted into gtk.TextTag's?
I finally worked out my own solution to this problem. I created a function that parses the markup string (using pango.parse_markup). Through reading the documentation and python introspection, I was able to work out how to take pango.Attribute and turn convert it into properties that a GtkTextTag can use.
Here's the function:
def parse_markup_string(string):
'''
Parses the string and returns a MarkupProps instance
'''
#The 'value' of an attribute...for some reason the same attribute is called several different things...
attr_values = ('value', 'ink_rect', 'logical_rect', 'desc', 'color')
#Get the AttributeList and text
attr_list, text, accel = pango.parse_markup( string )
attr_iter = attr_list.get_iterator()
#Create the converter
props = MarkupProps()
props.text = text
val = True
while val:
attrs = attr_iter.get_attrs()
for attr in attrs:
name = attr.type
start = attr.start_index
end = attr.end_index
name = pango.AttrType(name).value_nick
value = None
#Figure out which 'value' attribute to use...there's only one per pango.Attribute
for attr_value in attr_values:
if hasattr( attr, attr_value ):
value = getattr( attr, attr_value )
break
#There are some irregularities...'font_desc' of the pango.Attribute
#should be mapped to the 'font' property of a GtkTextTag
if name == 'font_desc':
name = 'font'
props.add( name, value, start, end )
val = attr_iter.next()
return props
This function creates a MarkupProps() object that has the ability to generate GtkTextTags along with the index in the text to apply them to.
Here's the object:
class MarkupProps():
'''
Stores properties that contain indices and appropriate values for that property.
Includes an iterator that generates GtkTextTags with the start and end indices to
apply them to
'''
def __init__(self):
'''
properties = ( {
'properties': {'foreground': 'green', 'background': 'red'}
'start': 0,
'end': 3
},
{
'properties': {'font': 'Lucida Sans 10'},
'start': 1,
'end':2,
},
)
'''
self.properties = []#Sequence containing all the properties, and values, organized by like start and end indices
self.text = ""#The raw text without any markup
def add( self, label, value, start, end ):
'''
Add a property to MarkupProps. If the start and end indices are already in
a property dictionary, then add the property:value entry into
that property, otherwise create a new one
'''
for prop in self.properties:
if prop['start'] == start and prop['end'] == end:
prop['properties'].update({label:value})
else:
new_prop = {
'properties': {label:value},
'start': start,
'end':end,
}
self.properties.append( new_prop )
def __iter__(self):
'''
Creates a GtkTextTag for each dict of properties
Yields (TextTag, start, end)
'''
for prop in self.properties:
tag = gtk.TextTag()
tag.set_properties( **prop['properties'] )
yield (tag, prop['start'], prop['end'])
So with this function and the MarkupProps object, I am able to, given a pango markup string, breakdown the string into it's properties, and text form, and then convert that into GtkTextTags.
Haven't followed GTK+ development, maybe they added something lately, but see these bugs: #59390 and #505478. Since they are not closed, likely nothing is done.
I have a project where I need to change the selection of a tree widget in code. This needs to be done after I clear out the tree and populate it again.
I'm trying to mark the appropriate item as "selected" while I'm adding them. This works for root level nodes. But for child nodes, it doesn't. I need to store the QTreeWidgetItem in another variable and mark it as selected after the tree has been completely populated. Why does this happen?
This does not work:
def refreshTree(self):
treeObj.clear()
for item in items:
temp = QTreeWidgetItem(0)
for key, val in item.subitems().items():
childTemp = QTreeWidgetItem(0)
...setup text, font, etc...
if(condition1):
childTemp.setSelected(True)
temp.addChild(childTemp)
if(!condition1 and condition2):
temp.setSelected(True)
treeObj.addToplevelItem(temp)
This does:
def refreshTree(self):
treeObj.clear()
for item in items:
temp = QTreeWidgetItem(0)
for key, val in item.subitems().items():
childTemp = QTreeWidgetItem(0)
...setup text, font, etc...
if(condition1):
selTemp = childTemp
temp.addChild(childTemp)
if(!condition1 and condition2):
temp.setSelected(True)
elif(selTemp):
selTemp.setSelected(True)
treeObj.addToplevelItem(temp)
It is not specified in the documentation, but setSelected does nothing if the item hasn't been added to a view yet:
inline void QTreeWidgetItem::setSelected(bool aselect)
{ if (view) view->setItemSelected(this, aselect); }
So, you should either
pass treeObj or temp in the constructor of your QTreeWidgetItem to make them part of the view from the start
or call addChild/addTopLevelItem before calling setSelected (or other functions like setExpanded...).
I don't know why your second code was even working.
I have an XML document in which I want to search for some elements and if they match some criteria
I would like to delete them
However, I cannot seem to be able to access the parent of the element so that I can delete it
file = open('test.xml', "r")
elem = ElementTree.parse(file)
namespace = "{http://somens}"
props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
type = prop.attrib.get('type', None)
if type == 'json':
value = json.loads(prop.attrib['value'])
if value['name'] == 'Page1.Button1':
#here I need to access the parent of prop
# in order to delete the prop
Is there a way I can do this?
Thanks
You can remove child elements with the according remove method. To remove an element you have to call its parents remove method. Unfortunately Element does not provide a reference to its parents, so it is up to you to keep track of parent/child relations (which speaks against your use of elem.findall())
A proposed solution could look like this:
root = elem.getroot()
for child in root:
if child.name != "prop":
continue
if True:# TODO: do your check here!
root.remove(child)
PS: don't use prop.attrib.get(), use prop.get(), as explained here.
You could use xpath to select an Element's parent.
file = open('test.xml', "r")
elem = ElementTree.parse(file)
namespace = "{http://somens}"
props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
type = prop.get('type', None)
if type == 'json':
value = json.loads(prop.attrib['value'])
if value['name'] == 'Page1.Button1':
# Get parent and remove this prop
parent = prop.find("..")
parent.remove(prop)
http://docs.python.org/2/library/xml.etree.elementtree.html#supported-xpath-syntax
Except if you try that it doesn't work: http://elmpowered.skawaii.net/?p=74
So instead you have to:
file = open('test.xml', "r")
elem = ElementTree.parse(file)
namespace = "{http://somens}"
search = './/{0}prop'.format(namespace)
# Use xpath to get all parents of props
prop_parents = elem.findall(search + '/..')
for parent in prop_parents:
# Still have to find and iterate through child props
for prop in parent.findall(search):
type = prop.get('type', None)
if type == 'json':
value = json.loads(prop.attrib['value'])
if value['name'] == 'Page1.Button1':
parent.remove(prop)
It is two searches and a nested loop. The inner search is only on Elements known to contain props as first children, but that may not mean much depending on your schema.
I know this is an old thread but this kept popping up while I was trying to figure out a similar task. I did not like the accepted answer for two reasons:
1) It doesn't handle multiple nested levels of tags.
2) It will break if multiple xml tags are deleted in the same level one-after-another. Since each element is an index of Element._children you shouldn't delete while forward iterating.
I think a better more versatile solution is this:
import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)
root = tree.getroot()
def iterator(parents, nested=False):
for child in reversed(parents):
if nested:
if len(child) >= 1:
iterator(child)
if True: # Add your entire condition here
parents.remove(child)
iterator(root, nested=True)
For the OP, this should work - but I don't have the data you're working with to test if it's perfect.
import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)
namespace = "{http://somens}"
props = tree.findall('.//{0}prop'.format(namespace))
def iterator(parents, nested=False):
for child in reversed(parents):
if nested:
if len(child) >= 1:
iterator(child)
if prop.attrib.get('type') == 'json':
value = json.loads(prop.attrib['value'])
if value['name'] == 'Page1.Button1':
parents.remove(child)
iterator(props, nested=True)
A solution using lxml module
from lxml import etree
root = ET.fromstring(xml_str)
for e in root.findall('.//{http://some.name.space}node'):
parent = e.getparent()
for child in parent.find('./{http://some.name.space}node'):
try:
parent.remove(child)
except ValueError:
pass
Using the fact that every child must have a parent, I'm going to simplify #kitsu.eb's example. f using the findall command to get the children and parents, their indices will be equivalent.
file = open('test.xml', "r")
elem = ElementTree.parse(file)
namespace = "{http://somens}"
search = './/{0}prop'.format(namespace)
# Use xpath to get all parents of props
prop_parents = elem.findall(search + '/..')
props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
type = prop.attrib.get('type', None)
if type == 'json':
value = json.loads(prop.attrib['value'])
if value['name'] == 'Page1.Button1':
#use the index of the current child to find
#its parent and remove the child
prop_parents[props.index[prop]].remove(prop)
I also used XPath for this issue, but in a different way:
root = elem.getroot()
elementName = "YourElement"
#this will find all the parents of the elements with elementName
for elementParent in root.findall(".//{}/..".format(elementName)):
#this will find all the elements under the parent, and remove them
for element in elementParent.findall("{}".format(elementName)):
elementParent.remove(element)
I like to use an XPath expression for this kind of filtering. Unless I know otherwise, such an expression must be applied at the root level, which means I can't just get a parent and apply the same expression on that parent. However, it seems to me that there is a nice and flexible solution that should work with any supported XPath, as long as none of the sought nodes is the root. It goes something like this:
root = elem.getroot()
# Find all nodes matching the filter string (flt)
nodes = root.findall(flt)
while len(nodes):
# As long as there are nodes, there should be parents
# Get the first of all parents to the found nodes
parent = root.findall(flt+'/..')[0]
# Use this parent to remove the first node
parent.remove(nodes[0])
# Find all remaining nodes
nodes = root.findall(flt)
I would like only to add a comment on the accepted answer, but my lack of reputation doesn't allow me to. I wanted to add that it is important to add .findall("*")to the iterator to avoid issues, as stated in the documentation:
Note that concurrent modification while iterating can lead to problems, just like when iterating and modifying Python lists or dicts. Therefore, the example first collects all matching elements with root.findall(), and only then iterates over the list of matches.
Therefore, in the accepted answer the iteration should be for child in root.findal("*"):instead of for child in root:. Not doing so made my code skip some elements from the list.