Posts: 211
Threads: 93
Joined: Aug 2018
Nov-21-2020, 12:48 AM
(This post was last modified: Nov-21-2020, 12:49 AM by Winfried.)
Hello,
I'm using the xmltodict module to handle an XML file like a dictionary.
Does Python prevent modifying a dictionary? Is there a work-around?
import xmltodict
with open("input.xml") as f:
xml = f.read()
doc = xmltodict.parse(xml)
for key in doc['kml']['Document']['Placemark']:
if "LineString" in key:
print("Found LS")
#BUG TypeError: 'str' object does not support item assignment
key['Style'] = "blah" Thank you.
Posts: 1,583
Threads: 3
Joined: Mar 2020
We know doc is a dictionary. We don't know what key is, since we can't see your data (but it appears to be a string).
Depending on the XML, the contents of doc['kml']['Document']['Placemark'] might be other datastructures that you can modify or just strings which you can't. Can you show the relevant section of the xml file?
Posts: 211
Threads: 93
Joined: Aug 2018
Nov-21-2020, 01:37 AM
(This post was last modified: Nov-21-2020, 01:37 AM by Winfried.)
Yes, it's a string:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Document>
<name>Some name</name>
<Placemark>
<name>Some other name</name>
<LineString>
<coordinates>3.880455,43.605057
3.880504,43.605063
3.922701,43.596438
3.923081,43.595929
3.923408,43.595535
</coordinates>
</LineString>
</Placemark>
</Document>
</kml>
If I find a LineString key (section), I'd like to add a new key in the dictionary at the same level.
Posts: 1,583
Threads: 3
Joined: Mar 2020
If the key will be named "LineString", why not just look for it directly rather than looping over things?
The key ("LineString") is the name of the entry. You can't modify the key, you modify the container with that key.
The entry here is doc['kml']['Document']['Placemark']['LineString']. You can read that item or modify it.
import xmltodict
with open("note.xml") as f:
xml = f.read()
doc = xmltodict.parse(xml)
for key in doc['kml']['Document']['Placemark']:
if "LineString" in key:
print("Found LineString")
print("Info in linestring is:")
print(doc['kml']['Document']['Placemark'][key]) Output: Found LineString
Info in linestring is:
OrderedDict([('coordinates', '3.880455,43.605057\n3.880504,43.605063\n3.922701,43.596438\n3.923081,43.595929\n3.923408,43.595535')])
Posts: 211
Threads: 93
Joined: Aug 2018
Nov-21-2020, 12:55 PM
(This post was last modified: Nov-21-2020, 12:55 PM by Winfried.)
Thanks, but the problem is not finding if a key named LineString lives in the dictionary, but adding a new key in that section:
for key in doc['kml']['Document']['Placemark']:
if "LineString" in key:
print("Found LS")
#TypeError: 'str' object does not support item assignment
key['Style'] = "blah"
I need to loop because there might be more than one Placemark section (I simplified the data to show what they look like).
Posts: 211
Threads: 93
Joined: Aug 2018
Found it
for key in doc['kml']['Document']['Placemark']:
if "LineString" in key:
print("Found LS")
doc['kml']['Document']['Placemark']['Style'] = "blah" Thanks for the help.
Posts: 211
Threads: 93
Joined: Aug 2018
Nov-21-2020, 01:08 PM
(This post was last modified: Nov-21-2020, 01:09 PM by Winfried.)
Arg: Doesn't work if the file contains other Placemark sections, that must be ignored since they don't have a LineString item:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Document>
<name>Some place</name>
<Placemark>
<name>Some track</name>
<LineString>
<coordinates>3.880455,43.605057
3.880504,43.605063
3.922701,43.596438
3.923081,43.595929
3.923408,43.595535
</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Some location</name>
<Point>
<coordinates>2.3285601,48.8395985</coordinates>
</Point>
</Placemark> </Document>
</kml>
============
Traceback (most recent call last):
File "C:\Debug.dic.py", line 29, in <module>
doc['kml']['Document']['Placemark']['Style'] = LS
TypeError: list indices must be integers or slices, not str
Should I create a second dictionary, that I can modify at will, fill it with the data parsed by xmltodic, and use that second dictionary as output?
Posts: 1,583
Threads: 3
Joined: Mar 2020
You can do that, but it won't change the behavior. If you have duplicate items, they can't be dictionary keys, because dictionary keys must be unique. So xmltodict will make them list entries. Let's try to see what's happening here
Output: OrderedDict([('kml', OrderedDict([('@xmlns', 'http://earth.google.com/kml/2.0'), ('Document', OrderedDict([('name', 'Some place'), ('Placemark', [OrderedDict([('name', 'Some track'), ('LineString', OrderedDict([('coordinates', '3.880455,43.605057\n3.880504,43.605063\n3.922701,43.596438\n3.923081,43.595929\n3.923408,43.595535')]))]), OrderedDict([('name', 'Some location'), ('Point', OrderedDict([('coordinates', '2.3285601,48.8395985')]))])])]))]))])
Ugly, so lets hack a pprettyprint...
Output: OrderedDict([('kml',
OrderedDict([('@xmlns', 'http://earth.google.com/kml/2.0'),
('Document',
OrderedDict([('name', 'Some place'),
('Placemark',
[OrderedDict([('name', 'Some track'),
('LineString',
OrderedDict([('coordinates',
'3.880455,43.605057\n'
'3.880504,43.605063\n'
'3.922701,43.596438\n'
'3.923081,43.595929\n'
'3.923408,43.595535')]))]),
OrderedDict([('name',
'Some location'),
('Point',
OrderedDict([('coordinates',
'2.3285601,48.8395985')]))])])]))]))])
What's happening here is that 'kml' and 'Document' are keys whose values are other dictionaries. But 'Placemark' (because it is duplicated) is not. Instead it has a value that is a list, and each of the elements are dictionaries. Those inside-the-list dictionaries are for each of the repetitions.
Because the contents of PlaceMark is a list, you can't index into with with "Style" here. The data structure that xmltodict gives you may change based on later data. Only one Placemark, you get a dict. 2 Placemarks, you get a list of dicts.
So the contents within this one are doc['kml']['Document']['Placemark'][0]. You can add your "Style" key there.
|