pyWinAuto: c:\.projects\py_pywinauto\pywinauto\XMLHelpers.py
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021"""Module containing operations for reading and writing dialogs as XML
0022"""
0023
0024__revision__ = "$Revision: 606 $"
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034try:
0035
0036 from xml.etree.ElementTree import Element, SubElement, ElementTree
0037 from xml.etree.cElementTree import Element, SubElement, ElementTree
0038except ImportError:
0039 from elementtree.ElementTree import Element, SubElement, ElementTree
0040 from cElementTree import Element, SubElement, ElementTree
0041
0042import ctypes
0043import re
0044import PIL.Image
0045import controls
0046
0047
0048
0049from win32structures import LOGFONTW, RECT
0050
0051class XMLParsingError(RuntimeError):
0052 "Wrap parsing Exceptions"
0053 pass
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070def _SetNodeProps(element, name, value):
0071 "Set the properties of the node based on the type of object"
0072
0073
0074 if isinstance(value, ctypes.Structure):
0075
0076
0077 struct_elem = SubElement(element, name)
0078
0079 cls_name = value.__class__.__name__
0080 struct_elem.set("__type__", "%s" % cls_name)
0081
0082
0083 for prop_name in value._fields_:
0084 prop_name = prop_name[0]
0085 item_val = getattr(value, prop_name)
0086
0087 if isinstance(item_val, (int, long)):
0088 prop_name += "_LONG"
0089 item_val = unicode(item_val)
0090
0091 struct_elem.set(prop_name, _EscapeSpecials(item_val))
0092
0093 elif isinstance(value, PIL.Image.Image):
0094 try:
0095
0096
0097
0098 if value.size[0] * value.size[1] > (5000*5000):
0099 raise MemoryError
0100
0101 image_data = value.tostring().encode("bz2").encode("base64")
0102 _SetNodeProps(
0103 element,
0104 name + "_IMG",
0105 {
0106 "mode": value.mode,
0107 "size_x":value.size[0],
0108 "size_y":value.size[1],
0109 "data":image_data
0110 })
0111
0112
0113
0114 except (SystemError, MemoryError):
0115 pass
0116
0117
0118 elif isinstance(value, (list, tuple)):
0119
0120
0121 listelem = SubElement(element, name + "_LIST")
0122
0123 for i, attrval in enumerate(value):
0124 _SetNodeProps(listelem, "%s_%05d"%(name, i), attrval)
0125
0126 elif isinstance(value, dict):
0127 dict_elem = SubElement(element, name)
0128
0129 for item_name, val in value.items():
0130 _SetNodeProps(dict_elem, item_name, val)
0131
0132 else:
0133 if isinstance(value, bool):
0134 value = long(value)
0135
0136 if isinstance(value, (int, long)):
0137 name += "_LONG"
0138
0139 element.set(name, _EscapeSpecials(value))
0140
0141
0142
0143def WriteDialogToFile(filename, props):
0144 """Write the props to the file
0145
0146 props can be either a dialog of a dictionary
0147 """
0148
0149
0150 try:
0151 props[0].keys()
0152 except (TypeError, AttributeError):
0153 props = controls.GetDialogPropsFromHandle(props)
0154
0155
0156 root = Element("DIALOG")
0157 root.set("_version_", "2.0")
0158 for ctrl in props:
0159 ctrlelem = SubElement(root, "CONTROL")
0160 for name, value in sorted(ctrl.items()):
0161 _SetNodeProps(ctrlelem, name, value)
0162
0163
0164 tree = ElementTree(root)
0165 tree.write(filename, encoding="utf-8")
0166
0167
0168
0169
0170def _EscapeSpecials(string):
0171 "Ensure that some characters are escaped before writing to XML"
0172
0173
0174 string = unicode(string)
0175
0176
0177 string = string.replace('\\', r'\\')
0178
0179
0180 for i in range(0, 32):
0181 string = string.replace(unichr(i), "\\%02d"%i)
0182
0183 return string
0184
0185
0186
0187def _UnEscapeSpecials(string):
0188 "Replace escaped characters with real character"
0189
0190
0191 for i in range(0, 32):
0192 string = string.replace("\\%02d"%i, unichr(i))
0193
0194
0195 string = string.replace(r'\\', '\\')
0196
0197 return unicode(string)
0198
0199
0200
0201
0202def _XMLToStruct(element, struct_type = None):
0203 """Convert an ElementTree to a ctypes Struct
0204
0205 If struct_type is not specified then element['__type__']
0206 will be used for the ctypes struct type"""
0207
0208
0209
0210 try:
0211 attribs = element.attrib
0212 except AttributeError:
0213 attribs = element
0214
0215
0216 if not struct_type:
0217
0218 struct = globals()[attribs["__type__"]]()
0219 else:
0220
0221 struct = globals()[struct_type]()
0222
0223
0224 struct_attribs = dict([(at.upper(), at) for at in dir(struct)])
0225
0226
0227 for prop_name in attribs:
0228
0229
0230 val = attribs[prop_name]
0231
0232
0233 if prop_name.endswith("_LONG"):
0234
0235 val = long(val)
0236 prop_name = prop_name[:-5]
0237
0238
0239 elif isinstance(val, basestring):
0240
0241 val = unicode(val)
0242
0243
0244
0245 if prop_name.upper() in struct_attribs:
0246 prop_name = struct_attribs[prop_name.upper()]
0247
0248
0249 setattr(struct, prop_name, val)
0250
0251
0252 return struct
0253
0254
0255
0256
0257def _OLD_XMLToTitles(element):
0258 "For OLD XML files convert the titles as a list"
0259
0260 title_names = element.keys()
0261
0262
0263 title_names.sort()
0264
0265
0266 titles = []
0267 for name in title_names:
0268 val = element[name]
0269 val = val.replace('\\n', '\n')
0270 val = val.replace('\\x12', '\x12')
0271 val = val.replace('\\\\', '\\')
0272
0273 titles.append(unicode(val))
0274
0275 return titles
0276
0277
0278
0279
0280
0281
0282def _ExtractProperties(properties, prop_name, prop_value):
0283 """Hmmm - confusing - can't remember exactly how
0284 all these similar functions call each other"""
0285
0286
0287
0288 prop_name, reqd_index = _SplitNumber(prop_name)
0289
0290
0291
0292
0293
0294 if reqd_index == None:
0295
0296 if prop_name in properties:
0297
0298 try:
0299 properties[prop_name].append(prop_value)
0300
0301
0302
0303
0304 except AttributeError:
0305 new_val = [properties[prop_name], prop_value]
0306 properties[prop_name] = new_val
0307
0308
0309 else:
0310 properties[prop_name] = prop_value
0311
0312
0313 else:
0314
0315
0316 properties.setdefault(prop_name, [])
0317
0318
0319 while 1:
0320 if len(properties[prop_name]) <= reqd_index:
0321 properties[prop_name].append('')
0322 else:
0323 break
0324
0325
0326 properties[prop_name][reqd_index] = prop_value
0327
0328
0329
0330def _GetAttributes(element):
0331 "Get the attributes from an element"
0332
0333 properties = {}
0334
0335
0336 for attrib_name, val in element.attrib.items():
0337
0338
0339 if attrib_name.endswith("_LONG"):
0340 val = long(val)
0341 attrib_name = attrib_name[:-5]
0342
0343 else:
0344
0345 val = _UnEscapeSpecials(val)
0346
0347 _ExtractProperties(properties, attrib_name, val)
0348
0349 return properties
0350
0351
0352
0353number = re.compile(r"^(.*)_(\d{5})$")
0354def _SplitNumber(prop_name):
0355 """Return (string, number) for a prop_name in the format string_number
0356
0357 The number part has to be 5 digits long
0358 None is returned if there is no _number part
0359
0360 e.g.
0361 >>> _SplitNumber("NoNumber")
0362 ('NoNumber', None)
0363 >>> _SplitNumber("Anumber_00003")
0364 ('Anumber', 3)
0365 >>> _SplitNumber("notEnoughDigits_0003")
0366 ('notEnoughDigits_0003', None)
0367 """
0368 found = number.search(prop_name)
0369
0370 if not found:
0371 return prop_name, None
0372
0373 return found.group(1), int(found.group(2))
0374
0375
0376
0377
0378def _ReadXMLStructure(control_element):
0379 """Convert an element into nested Python objects
0380
0381 The values will be returned in a dictionary as following:
0382
0383 - the attributes will be items of the dictionary
0384 for each subelement
0385
0386 + if it has a __type__ attribute then it is converted to a
0387 ctypes structure
0388 + if the element tag ends with _IMG then it is converted to
0389 a PIL image
0390
0391 - If there are elements with the same name or attributes with
0392 ordering e.g. texts_00001, texts_00002 they will be put into a
0393 list (in the correct order)
0394 """
0395
0396
0397 properties = _GetAttributes(control_element)
0398
0399 for elem in control_element:
0400
0401 if "__type__" in elem.attrib:
0402
0403
0404
0405 propval = _XMLToStruct(elem)
0406
0407 elif elem.tag.endswith("_IMG"):
0408 elem.tag = elem.tag[:-4]
0409
0410
0411 img = _GetAttributes(elem)
0412 data = img['data'].decode('base64').decode('bz2')
0413
0414 propval = PIL.Image.fromstring(
0415 img['mode'],
0416 (img['size_x'], img['size_y']),
0417 data)
0418
0419 elif elem.tag.endswith("_LIST"):
0420
0421
0422 elem.tag = elem.tag[:-5]
0423
0424
0425 propval = _ReadXMLStructure(elem)
0426
0427
0428
0429 if propval == {}:
0430 propval = list()
0431
0432
0433 else:
0434 propval = propval[elem.tag]
0435
0436 else:
0437 propval = _ReadXMLStructure(elem)
0438
0439 _ExtractProperties(properties, elem.tag, propval)
0440
0441 return properties
0442
0443
0444
0445
0446
0447def ReadPropertiesFromFile(filename):
0448 """Return an list of controls from XML file filename"""
0449
0450
0451 parsed = ElementTree().parse(filename)
0452
0453
0454 props = _ReadXMLStructure(parsed)['CONTROL']
0455 if not isinstance(props, list):
0456 props = [props]
0457
0458
0459
0460 if not parsed.attrib.has_key("_version_"):
0461
0462
0463 for ctrl_prop in props:
0464
0465 ctrl_prop['Fonts'] = [_XMLToStruct(ctrl_prop['FONT'], "LOGFONTW"), ]
0466
0467 ctrl_prop['Rectangle'] = _XMLToStruct(ctrl_prop["RECTANGLE"], "RECT")
0469
0470 ctrl_prop['ClientRects'] = [
0471 _XMLToStruct(ctrl_prop["CLIENTRECT"], "RECT"),]
0472
0473 ctrl_prop['Texts'] = _OLD_XMLToTitles(ctrl_prop["TITLES"])
0474
0475 ctrl_prop['Class'] = ctrl_prop['CLASS']
0476 ctrl_prop['ContextHelpID'] = ctrl_prop['HELPID']
0477 ctrl_prop['ControlID'] = ctrl_prop['CTRLID']
0478 ctrl_prop['ExStyle'] = ctrl_prop['EXSTYLE']
0479 ctrl_prop['FriendlyClassName'] = ctrl_prop['FRIENDLYCLASS']
0480 ctrl_prop['IsUnicode'] = ctrl_prop['ISUNICODE']
0481 ctrl_prop['IsVisible'] = ctrl_prop['ISVISIBLE']
0482 ctrl_prop['Style'] = ctrl_prop['STYLE']
0483 ctrl_prop['UserData'] = ctrl_prop['USERDATA']
0484
0485 for prop_name in [
0486 'CLASS',
0487 'CLIENTRECT',
0488 'CTRLID',
0489 'EXSTYLE',
0490 'FONT',
0491 'FRIENDLYCLASS',
0492 'HELPID',
0493 'ISUNICODE',
0494 'ISVISIBLE',
0495 'RECTANGLE',
0496 'STYLE',
0497 'TITLES',
0498 'USERDATA',
0499 ]:
0500 del(ctrl_prop[prop_name])
0501
0502 return props