1
2 """ xmltodict(): convert xml into tree of Python dicts.
3
4 This was copied and modified from John Bair's recipe at aspn.activestate.com:
5 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/149368
6 """
7 import os
8 import string
9 import locale
10 from xml.parsers import expat
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 code_linesep = "\n"
28 eol = os.linesep
29
30
32 """XML to Object"""
34 self.root = None
35 self.nodeStack = []
36 self.attsToSkip = []
37 self._inCode = False
38 self._mthdName = ""
39 self._mthdCode = ""
40 self._codeDict = None
41 self._inProp = False
42 self._propName = ""
43 self._propData = ""
44 self._propDict = None
45 self._currPropAtt = ""
46 self._currPropDict = None
47
48
50 """SAX start element even handler"""
51 if name == "code":
52
53 self._inCode = True
54 parent = self.nodeStack[-1]
55 if not parent.has_key("code"):
56 parent["code"] = {}
57 self._codeDict = parent["code"]
58
59 elif name == "properties":
60
61 self._inProp = True
62 self._propName = ""
63 self._propData = ""
64 parent = self.nodeStack[-1]
65 if not parent.has_key("properties"):
66 parent["properties"] = {}
67 self._propDict = parent["properties"]
68
69 else:
70 if self._inCode:
71 self._mthdName = name.encode()
72 elif self._inProp:
73 if self._propName:
74
75 self._currPropAtt = name.encode()
76 else:
77 self._propName = name.encode()
78 self._currPropDict = {}
79 self._currPropAtt = ""
80 else:
81 element = {"name": name.encode()}
82 if len(attributes) > 0:
83 for att in self.attsToSkip:
84 if attributes.has_key(att):
85 del attributes[att]
86 element["attributes"] = attributes
87
88
89 if len(self.nodeStack) > 0:
90 parent = self.nodeStack[-1]
91 if not parent.has_key("children"):
92 parent["children"] = []
93 parent["children"].append(element)
94 else:
95 self.root = element
96 self.nodeStack.append(element)
97
98
100 """SAX end element event handler"""
101 if self._inCode:
102 if name == "code":
103 self._inCode = False
104 self._codeDict = None
105 else:
106
107 mth = self._mthdCode.strip()
108 if not mth.endswith("\n"):
109 mth += "\n"
110 self._codeDict[self._mthdName] = mth
111 self._mthdName = ""
112 self._mthdCode = ""
113 elif self._inProp:
114 if name == "properties":
115 self._inProp = False
116 self._propDict = None
117 elif name == self._propName:
118
119 self._propDict[self._propName] = self._currPropDict
120 self._propName = ""
121 else:
122
123 self._currPropDict[self._currPropAtt] = self._propData
124 self._propData = self._currPropAtt = ""
125 else:
126 self.nodeStack = self.nodeStack[:-1]
127
128
130 """SAX character data event handler"""
131 if self._inCode or data.strip():
132 data = data.replace("<", "<")
133 data = data.encode()
134 if self._inCode:
135 if self._mthdCode:
136 self._mthdCode += data
137 else:
138 self._mthdCode = data
139 elif self._inProp:
140 self._propData += data
141 else:
142 element = self.nodeStack[-1]
143 if not element.has_key("cdata"):
144 element["cdata"] = ""
145 element["cdata"] += data
146
147
149
150 Parser = expat.ParserCreate()
151
152 Parser.StartElementHandler = self.StartElement
153 Parser.EndElementHandler = self.EndElement
154 Parser.CharacterDataHandler = self.CharacterData
155
156 ParserStatus = Parser.Parse(xml, 1)
157 return self.root
158
159
161 return self.Parse(open(filename,"r").read())
162
163
164 -def xmltodict(xml, attsToSkip=[], addCodeFile=False):
165 """Given an xml string or file, return a Python dictionary."""
166 parser = Xml2Obj()
167 parser.attsToSkip = attsToSkip
168 isPath = os.path.exists(xml)
169 errmsg = ""
170 if eol not in xml and isPath:
171
172 try:
173 ret = parser.ParseFromFile(xml)
174 except expat.ExpatError, e:
175 errmsg = _("The XML in '%s' is not well-formed and cannot be parsed: %s") % (xml, e)
176 else:
177
178 if not xml.strip().startswith("<?xml "):
179
180 errmsg = _("The file '%s' could not be found") % xml
181 else:
182 try:
183 ret = parser.Parse(xml)
184 except expat.ExpatError:
185 errmsg = _("An invalid XML string was encountered")
186 if errmsg:
187 raise dabo.dException.XmlException, errmsg
188 if addCodeFile and isPath:
189
190 codePth = "%s-code.py" % os.path.splitext(xml)[0]
191 if os.path.exists(codePth):
192 try:
193 codeDict = desUtil.parseCodeFile(open(codePth).read())
194 desUtil.addCodeToClassDict(ret, codeDict)
195 except StandardError, e:
196 print "Failed to parse code file:", e
197 return ret
198
199
200 -def escQuote(val, noEscape=False, noQuote=False):
201 """Add surrounding quotes to the string, and escape
202 any illegal XML characters.
203 """
204 if not isinstance(val, basestring):
205 val = str(val)
206 if not isinstance(val, unicode):
207 val = unicode(val, default_encoding)
208 if noQuote:
209 qt = ''
210 else:
211 qt = '"'
212 slsh = "\\"
213
214 if not noEscape:
215
216
217 val = val.replace("&", "&&")
218
219 val = val.replace('"', '"').replace("'", "'")
220
221 chars = []
222 for pos, char in enumerate(list(val)):
223 if ord(char) > 127:
224 chars.append("&#%s;" % ord(char))
225 else:
226 chars.append(char)
227 val = "".join(chars)
228 val = val.replace("<", "<").replace(">", ">")
229 return "%s%s%s" % (qt, val, qt)
230
231
232 -def dicttoxml(dct, level=0, header=None, linesep=None):
233 """Given a Python dictionary, return an xml string.
234
235 The dictionary must be in the format returned by dicttoxml(), with keys
236 on "attributes", "code", "cdata", "name", and "children".
237
238 Send your own XML header, otherwise a default one will be used.
239
240 The linesep argument is a dictionary, with keys on levels, allowing the
241 developer to add extra whitespace depending on the level.
242 """
243 att = ""
244 ret = ""
245
246 if dct.has_key("attributes"):
247 for key, val in dct["attributes"].items():
248
249 noEscape = key in ("sizerInfo",)
250 val = escQuote(val, noEscape)
251 att += " %s=%s" % (key, val)
252 ret += "%s<%s%s" % ("\t" * level, dct["name"], att)
253
254 if (not dct.has_key("cdata") and not dct.has_key("children")
255 and not dct.has_key("code") and not dct.has_key("properties")):
256 ret += " />%s" % eol
257 else:
258 ret += ">"
259 if dct.has_key("cdata"):
260 ret += "%s" % dct["cdata"].replace("<", "<")
261
262 if dct.has_key("code"):
263 if len(dct["code"].keys()):
264 ret += "%s%s<code>%s" % (eol, "\t" * (level+1), eol)
265 methodTab = "\t" * (level+2)
266 for mthd, cd in dct["code"].items():
267
268 cd = eol.join(cd.splitlines())
269
270
271 if not cd.endswith(eol):
272 cd += eol
273
274 ret += "%s<%s><![CDATA[%s%s]]>%s%s</%s>%s" % (methodTab,
275 mthd, eol, cd, eol,
276 methodTab, mthd, eol)
277 ret += "%s</code>%s" % ("\t" * (level+1), eol)
278
279 if dct.has_key("properties"):
280 if len(dct["properties"].keys()):
281 ret += "%s%s<properties>%s" % (eol, "\t" * (level+1), eol)
282 currTab = "\t" * (level+2)
283 for prop, val in dct["properties"].items():
284 ret += "%s<%s>%s" % (currTab, prop, eol)
285 for propItm, itmVal in val.items():
286 itmTab = "\t" * (level+3)
287 ret += "%s<%s>%s</%s>%s" % (itmTab, propItm, itmVal,
288 propItm, eol)
289 ret += "%s</%s>%s" % (currTab, prop, eol)
290 ret += "%s</properties>%s" % ("\t" * (level+1), eol)
291
292 if dct.has_key("children") and len(dct["children"]) > 0:
293 ret += eol
294 for child in dct["children"]:
295 ret += dicttoxml(child, level+1, linesep=linesep)
296 indnt = ""
297 if ret.endswith(eol):
298
299 indnt = ("\t" * level)
300 ret += "%s</%s>%s" % (indnt, dct["name"], eol)
301
302 if linesep:
303 ret += linesep.get(level, "")
304
305 if level == 0:
306 if header is None:
307 header = '<?xml version="1.0" encoding="%s" standalone="no"?>%s' \
308 % (default_encoding, eol)
309 ret = header + ret
310
311 return ret
312
313
315 """Given a dict containing a series of nested objects such as would
316 be created by restoring from a cdxml file, returns a dict with all classIDs
317 as keys, and a dict as the corresponding value. The dict value will have
318 keys for the attributes and/or code, depending on what was in the original
319 dict. The end result is to take a nested dict structure and return a flattened
320 dict with all objects at the top level.
321 """
322 if retDict is None:
323 retDict = {}
324 atts = cd.get("attributes", {})
325 props = cd.get("properties", {})
326 kids = cd.get("children", [])
327 code = cd.get("code", {})
328 classID = atts.get("classID", "")
329 classFile = resolvePath(atts.get("designerClass", ""))
330 superclass = resolvePath(atts.get("superclass", ""))
331 superclassID = atts.get("superclassID", "")
332 if superclassID and os.path.exists(superclass):
333
334 superCD = xmltodict(superclass, addCodeFile=True)
335 flattenClassDict(superCD, retDict)
336 if classID:
337 if os.path.exists(classFile):
338
339 classCD = xmltodict(classFile, addCodeFile=True)
340 classAtts = classCD.get("attributes", {})
341 classProps = classCD.get("properties", {})
342 classCode = classCD.get("code", {})
343 classKids = classCD.get("children", [])
344 currDict = retDict.get(classID, {})
345 retDict[classID] = {"attributes": classAtts, "code": classCode,
346 "properties": classProps}
347 retDict[classID].update(currDict)
348
349 for kid in classKids:
350 flattenClassDict(kid, retDict)
351 else:
352
353 currDict = retDict.get(classID, {})
354 retDict[classID] = {"attributes": atts, "code": code,
355 "properties": props}
356 retDict[classID].update(currDict)
357 if kids:
358 for kid in kids:
359 flattenClassDict(kid, retDict)
360 return retDict
361
362
364 """Called recursively on the class container structure, modifying
365 the attributes to incorporate superclass information. When the
366 'updateCode' parameter is True, superclass code is added to the
367 object's code
368 """
369 atts = src.get("attributes", {})
370 props = src.get("properties", {})
371 kids = src.get("children", [])
372 code = src.get("code", {})
373 classID = atts.get("classID", "")
374 if classID:
375 superInfo = super.get(classID, {"attributes": {}, "code": {}, "properties": {}})
376 src["attributes"] = superInfo["attributes"].copy()
377 src["attributes"].update(atts)
378 src["properties"] = superInfo.get("properties", {}).copy()
379 src["properties"].update(props)
380 if updateCode:
381 src["code"] = superInfo["code"].copy()
382 src["code"].update(code)
383 if kids:
384 for kid in kids:
385 addInheritedInfo(kid, super, updateCode)
386
387
388
389
390
391
392
393
394
395
396
397
398