1""" pydevd_vars deals with variables: 2 resolution/conversion to XML. 3""" 4import math 5import pickle 6 7from _pydev_bundle.pydev_imports import quote 8from _pydev_imps._pydev_saved_modules import thread 9from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, xrange, NUMPY_NUMERIC_TYPES, NUMPY_FLOATING_POINT_TYPES 10from _pydevd_bundle.pydevd_custom_frames import get_custom_frame 11from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml 12 13try: 14 from StringIO import StringIO 15except ImportError: 16 from io import StringIO 17import sys # @Reimport 18 19try: 20 from collections import OrderedDict 21except: 22 OrderedDict = dict 23 24from _pydev_imps._pydev_saved_modules import threading 25import traceback 26from _pydevd_bundle import pydevd_save_locals 27from _pydev_bundle.pydev_imports import Exec, execfile 28from _pydevd_bundle.pydevd_utils import to_string, VariableWithOffset 29 30SENTINEL_VALUE = [] 31DEFAULT_DF_FORMAT = "s" 32 33# ------------------------------------------------------------------------------------------------------ class for errors 34 35class VariableError(RuntimeError): pass 36 37 38class FrameNotFoundError(RuntimeError): pass 39 40 41def _iter_frames(initialFrame): 42 '''NO-YIELD VERSION: Iterates through all the frames starting at the specified frame (which will be the first returned item)''' 43 # cannot use yield 44 frames = [] 45 46 while initialFrame is not None: 47 frames.append(initialFrame) 48 initialFrame = initialFrame.f_back 49 50 return frames 51 52 53def dump_frames(thread_id): 54 sys.stdout.write('dumping frames\n') 55 if thread_id != get_current_thread_id(threading.currentThread()): 56 raise VariableError("find_frame: must execute on same thread") 57 58 curFrame = get_frame() 59 for frame in _iter_frames(curFrame): 60 sys.stdout.write('%s\n' % pickle.dumps(frame)) 61 62 63# =============================================================================== 64# AdditionalFramesContainer 65# =============================================================================== 66class AdditionalFramesContainer: 67 lock = thread.allocate_lock() 68 additional_frames = {} # dict of dicts 69 70 71def add_additional_frame_by_id(thread_id, frames_by_id): 72 AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id 73 74 75addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility 76 77 78def remove_additional_frame_by_id(thread_id): 79 del AdditionalFramesContainer.additional_frames[thread_id] 80 81 82removeAdditionalFrameById = remove_additional_frame_by_id # Backward compatibility 83 84 85def has_additional_frames_by_id(thread_id): 86 return thread_id in AdditionalFramesContainer.additional_frames 87 88 89def get_additional_frames_by_id(thread_id): 90 return AdditionalFramesContainer.additional_frames.get(thread_id) 91 92 93def find_frame(thread_id, frame_id): 94 """ returns a frame on the thread that has a given frame_id """ 95 try: 96 curr_thread_id = get_current_thread_id(threading.currentThread()) 97 if thread_id != curr_thread_id: 98 try: 99 return get_custom_frame(thread_id, frame_id) # I.e.: thread_id could be a stackless frame id + thread_id. 100 except: 101 pass 102 103 raise VariableError("find_frame: must execute on same thread (%s != %s)" % (thread_id, curr_thread_id)) 104 105 lookingFor = int(frame_id) 106 107 if AdditionalFramesContainer.additional_frames: 108 if thread_id in AdditionalFramesContainer.additional_frames: 109 frame = AdditionalFramesContainer.additional_frames[thread_id].get(lookingFor) 110 111 if frame is not None: 112 return frame 113 114 curFrame = get_frame() 115 if frame_id == "*": 116 return curFrame # any frame is specified with "*" 117 118 frameFound = None 119 120 for frame in _iter_frames(curFrame): 121 if lookingFor == id(frame): 122 frameFound = frame 123 del frame 124 break 125 126 del frame 127 128 # Important: python can hold a reference to the frame from the current context 129 # if an exception is raised, so, if we don't explicitly add those deletes 130 # we might have those variables living much more than we'd want to. 131 132 # I.e.: sys.exc_info holding reference to frame that raises exception (so, other places 133 # need to call sys.exc_clear()) 134 del curFrame 135 136 if frameFound is None: 137 msgFrames = '' 138 i = 0 139 140 for frame in _iter_frames(get_frame()): 141 i += 1 142 msgFrames += str(id(frame)) 143 if i % 5 == 0: 144 msgFrames += '\n' 145 else: 146 msgFrames += ' - ' 147 148# Note: commented this error message out (it may commonly happen 149# if a message asking for a frame is issued while a thread is paused 150# but the thread starts running before the message is actually 151# handled). 152# Leaving code to uncomment during tests. 153# err_msg = '''find_frame: frame not found. 154# Looking for thread_id:%s, frame_id:%s 155# Current thread_id:%s, available frames: 156# %s\n 157# ''' % (thread_id, lookingFor, curr_thread_id, msgFrames) 158# 159# sys.stderr.write(err_msg) 160 return None 161 162 return frameFound 163 except: 164 import traceback 165 traceback.print_exc() 166 return None 167 168 169def getVariable(thread_id, frame_id, scope, attrs): 170 """ 171 returns the value of a variable 172 173 :scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME 174 175 BY_ID means we'll traverse the list of all objects alive to get the object. 176 177 :attrs: after reaching the proper scope, we have to get the attributes until we find 178 the proper location (i.e.: obj\tattr1\tattr2). 179 180 :note: when BY_ID is used, the frame_id is considered the id of the object to find and 181 not the frame (as we don't care about the frame in this case). 182 """ 183 if scope == 'BY_ID': 184 if thread_id != get_current_thread_id(threading.currentThread()): 185 raise VariableError("getVariable: must execute on same thread") 186 187 try: 188 import gc 189 objects = gc.get_objects() 190 except: 191 pass # Not all python variants have it. 192 else: 193 frame_id = int(frame_id) 194 for var in objects: 195 if id(var) == frame_id: 196 if attrs is not None: 197 attrList = attrs.split('\t') 198 for k in attrList: 199 _type, _typeName, resolver = get_type(var) 200 var = resolver.resolve(var, k) 201 202 return var 203 204 # If it didn't return previously, we coudn't find it by id (i.e.: alrceady garbage collected). 205 sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,)) 206 return None 207 208 frame = find_frame(thread_id, frame_id) 209 if frame is None: 210 return {} 211 212 if attrs is not None: 213 attrList = attrs.split('\t') 214 else: 215 attrList = [] 216 217 for attr in attrList: 218 attr.replace("@_@TAB_CHAR@_@", '\t') 219 220 if scope == 'EXPRESSION': 221 for count in xrange(len(attrList)): 222 if count == 0: 223 # An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression 224 var = evaluate_expression(thread_id, frame_id, attrList[count], False) 225 else: 226 _type, _typeName, resolver = get_type(var) 227 var = resolver.resolve(var, attrList[count]) 228 else: 229 if scope == "GLOBAL": 230 var = frame.f_globals 231 del attrList[0] # globals are special, and they get a single dummy unused attribute 232 else: 233 # in a frame access both locals and globals as Python does 234 var = {} 235 var.update(frame.f_globals) 236 var.update(frame.f_locals) 237 238 for k in attrList: 239 _type, _typeName, resolver = get_type(var) 240 var = resolver.resolve(var, k) 241 242 return var 243 244 245def get_offset(attrs): 246 """ 247 Extract offset from the given attributes. 248 249 :param attrs: The string of a compound variable fields split by tabs. 250 If an offset is given, it must go the first element. 251 :return: The value of offset if given or 0. 252 """ 253 offset = 0 254 if attrs is not None: 255 try: 256 offset = int(attrs.split('\t')[0]) 257 except ValueError: 258 pass 259 return offset 260 261 262def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs): 263 """ 264 Resolve compound variable in debugger scopes by its name and attributes 265 266 :param thread_id: id of the variable's thread 267 :param frame_id: id of the variable's frame 268 :param scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME 269 :param attrs: after reaching the proper scope, we have to get the attributes until we find 270 the proper location (i.e.: obj\tattr1\tattr2) 271 :return: a dictionary of variables's fields 272 273 :note: PyCharm supports progressive loading of large collections and uses the `attrs` 274 parameter to pass the offset, e.g. 300\t\\obj\tattr1\tattr2 should return 275 the value of attr2 starting from the 300th element. This hack makes it possible 276 to add the support of progressive loading without extending of the protocol. 277 """ 278 offset = get_offset(attrs) 279 280 orig_attrs, attrs = attrs, attrs.split('\t', 1)[1] if offset else attrs 281 282 var = getVariable(thread_id, frame_id, scope, attrs) 283 284 try: 285 _type, _typeName, resolver = get_type(var) 286 return _typeName, resolver.get_dictionary(VariableWithOffset(var, offset) if offset else var) 287 except: 288 sys.stderr.write('Error evaluating: thread_id: %s\nframe_id: %s\nscope: %s\nattrs: %s\n' % ( 289 thread_id, frame_id, scope, orig_attrs,)) 290 traceback.print_exc() 291 292 293def resolve_var_object(var, attrs): 294 """ 295 Resolve variable's attribute 296 297 :param var: an object of variable 298 :param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2) 299 :return: a value of resolved variable's attribute 300 """ 301 if attrs is not None: 302 attr_list = attrs.split('\t') 303 else: 304 attr_list = [] 305 for k in attr_list: 306 type, _typeName, resolver = get_type(var) 307 var = resolver.resolve(var, k) 308 return var 309 310 311def resolve_compound_var_object_fields(var, attrs): 312 """ 313 Resolve compound variable by its object and attributes 314 315 :param var: an object of variable 316 :param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2) 317 :return: a dictionary of variables's fields 318 """ 319 offset = get_offset(attrs) 320 321 attrs = attrs.split('\t', 1)[1] if offset else attrs 322 323 attr_list = attrs.split('\t') 324 325 for k in attr_list: 326 type, _typeName, resolver = get_type(var) 327 var = resolver.resolve(var, k) 328 329 try: 330 type, _typeName, resolver = get_type(var) 331 return resolver.get_dictionary(VariableWithOffset(var, offset) if offset else var) 332 except: 333 traceback.print_exc() 334 335 336def custom_operation(thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name): 337 """ 338 We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var. 339 340 code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed. 341 operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint) 342 """ 343 expressionValue = getVariable(thread_id, frame_id, scope, attrs) 344 345 try: 346 namespace = {'__name__': '<custom_operation>'} 347 if style == "EXECFILE": 348 namespace['__file__'] = code_or_file 349 execfile(code_or_file, namespace, namespace) 350 else: # style == EXEC 351 namespace['__file__'] = '<customOperationCode>' 352 Exec(code_or_file, namespace, namespace) 353 354 return str(namespace[operation_fn_name](expressionValue)) 355 except: 356 traceback.print_exc() 357 358 359def eval_in_context(expression, globals, locals): 360 result = None 361 try: 362 result = eval(expression, globals, locals) 363 except Exception: 364 s = StringIO() 365 traceback.print_exc(file=s) 366 result = s.getvalue() 367 368 try: 369 try: 370 etype, value, tb = sys.exc_info() 371 result = value 372 finally: 373 etype = value = tb = None 374 except: 375 pass 376 377 result = ExceptionOnEvaluate(result) 378 379 # Ok, we have the initial error message, but let's see if we're dealing with a name mangling error... 380 try: 381 if '__' in expression: 382 # Try to handle '__' name mangling... 383 split = expression.split('.') 384 curr = locals.get(split[0]) 385 for entry in split[1:]: 386 if entry.startswith('__') and not hasattr(curr, entry): 387 entry = '_%s%s' % (curr.__class__.__name__, entry) 388 curr = getattr(curr, entry) 389 390 result = curr 391 except: 392 pass 393 return result 394 395 396def evaluate_expression(thread_id, frame_id, expression, doExec): 397 '''returns the result of the evaluated expression 398 @param doExec: determines if we should do an exec or an eval 399 ''' 400 frame = find_frame(thread_id, frame_id) 401 if frame is None: 402 return 403 404 # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329 405 # (Names not resolved in generator expression in method) 406 # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html 407 updated_globals = {} 408 updated_globals.update(frame.f_globals) 409 updated_globals.update(frame.f_locals) # locals later because it has precedence over the actual globals 410 411 try: 412 expression = str(expression.replace('@LINE@', '\n')) 413 414 if doExec: 415 try: 416 # try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and 417 # it will have whatever the user actually did) 418 compiled = compile(expression, '<string>', 'eval') 419 except: 420 Exec(expression, updated_globals, frame.f_locals) 421 pydevd_save_locals.save_locals(frame) 422 else: 423 result = eval(compiled, updated_globals, frame.f_locals) 424 if result is not None: # Only print if it's not None (as python does) 425 sys.stdout.write('%s\n' % (result,)) 426 return 427 428 else: 429 return eval_in_context(expression, updated_globals, frame.f_locals) 430 finally: 431 # Should not be kept alive if an exception happens and this frame is kept in the stack. 432 del updated_globals 433 del frame 434 435 436def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SENTINEL_VALUE): 437 '''Changes some attribute in a given frame. 438 ''' 439 frame = find_frame(thread_id, frame_id) 440 if frame is None: 441 return 442 443 try: 444 expression = expression.replace('@LINE@', '\n') 445 446 if dbg.plugin and value is SENTINEL_VALUE: 447 result = dbg.plugin.change_variable(frame, attr, expression) 448 if result: 449 return result 450 451 if value is SENTINEL_VALUE: 452 # It is possible to have variables with names like '.0', ',,,foo', etc in scope by setting them with 453 # `sys._getframe().f_locals`. In particular, the '.0' variable name is used to denote the list iterator when we stop in 454 # list comprehension expressions. This variable evaluates to 0. by `eval`, which is not what we want and this is the main 455 # reason we have to check if the expression exists in the global and local scopes before trying to evaluate it. 456 value = frame.f_locals.get(expression) or frame.f_globals.get(expression) or eval(expression, frame.f_globals, frame.f_locals) 457 458 if attr[:7] == "Globals": 459 attr = attr[8:] 460 if attr in frame.f_globals: 461 frame.f_globals[attr] = value 462 return frame.f_globals[attr] 463 else: 464 if pydevd_save_locals.is_save_locals_available(): 465 frame.f_locals[attr] = value 466 pydevd_save_locals.save_locals(frame) 467 return frame.f_locals[attr] 468 469 # default way (only works for changing it in the topmost frame) 470 result = value 471 Exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals) 472 return result 473 474 except Exception: 475 traceback.print_exc() 476 477 478MAXIMUM_ARRAY_SIZE = 100 479 480 481def array_to_xml(array, name, roffset, coffset, rows, cols, format): 482 array, xml, r, c, f = array_to_meta_xml(array, name, format) 483 format = '%' + f 484 if rows == -1 and cols == -1: 485 rows = r 486 cols = c 487 488 rows = min(rows, MAXIMUM_ARRAY_SIZE) 489 cols = min(cols, MAXIMUM_ARRAY_SIZE) 490 491 # there is no obvious rule for slicing (at least 5 choices) 492 if len(array) == 1 and (rows > 1 or cols > 1): 493 array = array[0] 494 if array.size > len(array): 495 array = array[roffset:, coffset:] 496 rows = min(rows, len(array)) 497 cols = min(cols, len(array[0])) 498 if len(array) == 1: 499 array = array[0] 500 elif array.size == len(array): 501 if roffset == 0 and rows == 1: 502 array = array[coffset:] 503 cols = min(cols, len(array)) 504 elif coffset == 0 and cols == 1: 505 array = array[roffset:] 506 rows = min(rows, len(array)) 507 508 def get_value(row, col): 509 value = array 510 if rows == 1 or cols == 1: 511 if rows == 1 and cols == 1: 512 value = array[0] 513 else: 514 value = array[(col if rows == 1 else row)] 515 if "ndarray" in str(type(value)): 516 value = value[0] 517 else: 518 value = array[row][col] 519 return value 520 xml += array_data_to_xml(rows, cols, lambda r: (get_value(r, c) for c in range(cols)), format) 521 return xml 522 523 524class ExceedingArrayDimensionsException(Exception): 525 pass 526 527 528def array_to_meta_xml(array, name, format): 529 type = array.dtype.kind 530 slice = name 531 l = len(array.shape) 532 533 # initial load, compute slice 534 if format == '%': 535 if l > 2: 536 slice += '[0]' * (l - 2) 537 for r in range(l - 2): 538 array = array[0] 539 if type == 'f': 540 format = '.5f' 541 elif type == 'i' or type == 'u': 542 format = 'd' 543 else: 544 format = 's' 545 else: 546 format = format.replace('%', '') 547 548 l = len(array.shape) 549 reslice = "" 550 if l > 2: 551 raise ExceedingArrayDimensionsException() 552 elif l == 1: 553 # special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim 554 # http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns 555 # explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1 556 # we use kind of a hack - get information about memory from C_CONTIGUOUS 557 is_row = array.flags['C_CONTIGUOUS'] 558 559 if is_row: 560 rows = 1 561 cols = len(array) 562 if cols < len(array): 563 reslice = '[0:%s]' % (cols) 564 array = array[0:cols] 565 else: 566 cols = 1 567 rows = len(array) 568 if rows < len(array): 569 reslice = '[0:%s]' % (rows) 570 array = array[0:rows] 571 elif l == 2: 572 rows = array.shape[-2] 573 cols = array.shape[-1] 574 if cols < array.shape[-1] or rows < array.shape[-2]: 575 reslice = '[0:%s, 0:%s]' % (rows, cols) 576 array = array[0:rows, 0:cols] 577 578 # avoid slice duplication 579 if not slice.endswith(reslice): 580 slice += reslice 581 582 bounds = (0, 0) 583 if type in NUMPY_NUMERIC_TYPES: 584 bounds = (array.min(), array.max()) 585 return array, slice_to_xml(slice, rows, cols, format, type, bounds), rows, cols, format 586 587 588def get_column_formatter_by_type(initial_format, column_type): 589 if column_type in NUMPY_NUMERIC_TYPES and initial_format: 590 if column_type in NUMPY_FLOATING_POINT_TYPES and initial_format.strip() == DEFAULT_DF_FORMAT: 591 # use custom formatting for floats when default formatting is set 592 return array_default_format(column_type) 593 return initial_format 594 else: 595 return array_default_format(column_type) 596 597 598def get_formatted_row_elements(row, iat, dim, cols, format, dtypes): 599 for c in range(cols): 600 val = iat[row, c] if dim > 1 else iat[row] 601 col_formatter = get_column_formatter_by_type(format, dtypes[c]) 602 try: 603 yield ("%" + col_formatter) % val 604 except TypeError: 605 yield ("%" + DEFAULT_DF_FORMAT) % val 606 607 608def array_default_format(type): 609 if type == 'f': 610 return '.5f' 611 elif type == 'i' or type == 'u': 612 return 'd' 613 else: 614 return 's' 615 616 617def get_label(label): 618 return str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) 619 620 621def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): 622 """ 623 :type df: pandas.core.frame.DataFrame 624 :type name: str 625 :type coffset: int 626 :type roffset: int 627 :type rows: int 628 :type cols: int 629 :type format: str 630 631 632 """ 633 dim = len(df.axes) 634 num_rows = df.shape[0] 635 num_cols = df.shape[1] if dim > 1 else 1 636 format = format.replace('%', '') 637 638 if not format: 639 if num_rows > 0 and num_cols == 1: # series or data frame with one column 640 try: 641 kind = df.dtype.kind 642 except AttributeError: 643 try: 644 kind = df.dtypes[0].kind 645 except (IndexError, KeyError): 646 kind = 'O' 647 format = array_default_format(kind) 648 else: 649 format = array_default_format(DEFAULT_DF_FORMAT) 650 651 xml = slice_to_xml(name, num_rows, num_cols, format, "", (0, 0)) 652 653 if (rows, cols) == (-1, -1): 654 rows, cols = num_rows, num_cols 655 656 rows = min(rows, MAXIMUM_ARRAY_SIZE) 657 cols = min(cols, MAXIMUM_ARRAY_SIZE, num_cols) 658 # need to precompute column bounds here before slicing! 659 col_bounds = [None] * cols 660 dtypes = [None] * cols 661 if dim > 1: 662 for col in range(cols): 663 dtype = df.dtypes.iloc[coffset + col].kind 664 dtypes[col] = dtype 665 if dtype in NUMPY_NUMERIC_TYPES: 666 cvalues = df.iloc[:, coffset + col] 667 bounds = (cvalues.min(), cvalues.max()) 668 else: 669 bounds = (0, 0) 670 col_bounds[col] = bounds 671 else: 672 dtype = df.dtype.kind 673 dtypes[0] = dtype 674 col_bounds[0] = (df.min(), df.max()) if dtype in NUMPY_NUMERIC_TYPES else (0, 0) 675 676 df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] if dim > 1 else df.iloc[roffset: roffset + rows] 677 rows = df.shape[0] 678 cols = df.shape[1] if dim > 1 else 1 679 680 def col_to_format(c): 681 return get_column_formatter_by_type(format, dtypes[c]) 682 683 iat = df.iat if dim == 1 or len(df.columns.unique()) == len(df.columns) else df.iloc 684 685 def formatted_row_elements(row): 686 return get_formatted_row_elements(row, iat, dim, cols, format, dtypes) 687 688 xml += header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim) 689 xml += array_data_to_xml(rows, cols, formatted_row_elements, format) 690 return xml 691 692 693def array_data_to_xml(rows, cols, get_row, format): 694 xml = "<arraydata rows=\"%s\" cols=\"%s\"/>\n" % (rows, cols) 695 for row in range(rows): 696 xml += "<row index=\"%s\"/>\n" % to_string(row) 697 for value in get_row(row): 698 xml += var_to_xml(value, '', format=format) 699 return xml 700 701 702def slice_to_xml(slice, rows, cols, format, type, bounds): 703 return '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"%s\" type=\"%s\" max=\"%s\" min=\"%s\"/>' % \ 704 (slice, rows, cols, quote(format), type, bounds[1], bounds[0]) 705 706 707def header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim): 708 xml = "<headerdata rows=\"%s\" cols=\"%s\">\n" % (rows, cols) 709 for col in range(cols): 710 col_label = quote(get_label(df.axes[1].values[col]) if dim > 1 else str(col)) 711 bounds = col_bounds[col] 712 col_format = "%" + col_to_format(col) 713 xml += '<colheader index=\"%s\" label=\"%s\" type=\"%s\" format=\"%s\" max=\"%s\" min=\"%s\" />\n' % \ 714 (str(col), col_label, dtypes[col], col_to_format(col), col_format % bounds[1], col_format % bounds[0]) 715 for row in range(rows): 716 xml += "<rowheader index=\"%s\" label = \"%s\"/>\n" % (str(row), get_label(df.axes[0].values[row])) 717 xml += "</headerdata>\n" 718 return xml 719 720 721def is_able_to_format_number(format): 722 try: 723 format % math.pi 724 except Exception: 725 return False 726 return True 727 728 729TYPE_TO_XML_CONVERTERS = { 730 "ndarray": array_to_xml, 731 "DataFrame": dataframe_to_xml, 732 "Series": dataframe_to_xml, 733 "GeoDataFrame": dataframe_to_xml, 734 "GeoSeries": dataframe_to_xml 735} 736 737 738def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): 739 _, type_name, _ = get_type(array) 740 format = format if is_able_to_format_number(format) else '%' 741 if type_name in TYPE_TO_XML_CONVERTERS: 742 return "<xml>%s</xml>" % TYPE_TO_XML_CONVERTERS[type_name](array, name, roffset, coffset, rows, cols, format) 743 else: 744 raise VariableError("type %s not supported" % type_name) 745 746