I wrote some search function, though it’s is far from being satisfactory, because:
-
This bug doesn’t yet allow to exclude “going in loops” for walking through the hierarchy.
- Some objects that ought to have iterators doesn’t have them. Luckily, so far I only stumbled upon one such object:
XCellRange
, it allows to iterate with getCellByPosition()
, but not with iter(myCellRange)
in python. Currently, searchLimited()
function simply does not descend into these.
Still, I was able to implement something limited, and some minutes ago from me writing this I managed to successfully apply searchLimited()
for finding something I couldn’t figure out from searching the internet (which is: “given existing PageFields PivotTable filter, how to modify rows it filters out”). That makes me think, the code is worth putting out at the current state. When you absolutely desperate at finding the hierarchy, it may well save your day
Repo for the code is here. The current version is at the end of this post, and consists of 3 functions:
-
getmembers_uno
: basically, modified version of vanilla getmembers()
from inspect
module. I had to modify it because accessing some properties in UNO results in exceptions.
-
isiter
: tests whether given object is iterable
-
searchLimited
a depth-first search function with various knobs to alter its behavior. And, oh, believe me, you will want to do it, because occasionally you may stumble upon UNO objects with so many properties that dir()
function gonna hang for dozens of minutes, basically, not doing anything useful for you.
For that reason btw I left a print('TRACE:…')
function, so you can see whether it’s progressing, or got stuck and where; then interrupt it, and use ignore()
hook to ignore the field where it got stuck (or simply to ignore fields where, you’re sure, not gonna be anything useful).
Here’s modified snip from the session where it helped me:
>>> ignore = lambda s: 'Links' in s or 'Picture' in s or 'MasterPage' in s
>>> searchLimited(pilotTable, 'Publisher', 5, lambda p: isinstance(p, str), ignore)
…[lots of TRACE output snipped]
'FOUND: <TOPLEVEL>.DataPilotFields.<iter 9>.Items.<iter 3>.Name'
What this says is that I can find the Publisher
name if I evaluate pilotTable.DataPilotFields.getByIndex(9).Items.getByIndex(3).Name
. (and if anyone interested how to use that to filter PivotTable: altering ….getByIndex(3).IsHidden
will do it)
Code:
import uno
from inspect import *
# this is a modified version of inspect.getmembers(). It was modified to not fail on
# uno exceptions that sometimes happen during access attempts
# And while on it: ignore uno.ByteSequence by default.
# returns: [(String, a)]
def getmembers_uno(object, predicate=lambda obj: not isinstance(obj, uno.ByteSequence)):
"""Return all members of an object as (name, value) pairs sorted by name.
Optionally, only return members that satisfy a given predicate."""
if isclass(object):
mro = (object,) + getmro(object)
else:
mro = ()
results = []
processed = set()
names = dir(object)
# :dd any DynamicClassAttributes to the list of names if object is a class;
# this may result in duplicate entries if, for example, a virtual
# attribute with the same name as a DynamicClassAttribute exists
try:
for base in object.__bases__:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute):
names.append(k)
except AttributeError:
pass
for key in names:
# First try to get the value via getattr. Some descriptors don't
# like calling their __get__ (see bug #1785), so fall back to
# looking in the __dict__.
try:
value = getattr(object, key)
# handle the duplicate key
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
# could be a (currently) missing slot member, or a buggy
# __dir__; discard and move on
continue
except uno.getClass("com.sun.star.uno.RuntimeException"):
continue # ignore: inspect.RuntimeException: Getting from this property is not supported
except Exception:
continue # ignore: everything, we don't care
if not predicate or predicate(value):
results.append((key, value))
processed.add(key)
results.sort(key=lambda pair: pair[0])
return results
def isiter(maybe_iterable):
try:
iter(maybe_iterable)
return True
except TypeError:
return False
# nLevels: number of levels it allowed to descend
# predicate: types to use, except iterables
# pIgnore: String -> Bool, accepts property name, may be used to drive search.
def searchLimited(unoObject, valToSearch, nLevels,
predicate, pIgnore, path = '<TOPLEVEL>'):
def try_property(property_name, val):
if property_name.startswith('__') or pIgnore(property_name):
return None # ignore private stuff
print('TRACE: ' + path + '.' + property_name)
if val == valToSearch:
return 'FOUND: ' + path + '.' + property_name
if nLevels - 1 != 0 and isiter(val):
index = 0
for item in val:
ret = searchLimited(item, valToSearch, nLevels - 1, predicate, pIgnore,
path + '.' + property_name + '.<iter ' + str(index) + '>')
if (ret != None):
return ret
index += 1
for (property_name, val) in getmembers_uno(unoObject, lambda p: isiter(p) or predicate(p)):
ret = try_property(property_name, val)
if (ret != None):
return ret
if nLevels - 1 != 0 and isiter(unoObject):
index = 0
for item in unoObject:
ret = searchLimited(item, valToSearch, nLevels - 1, predicate, pIgnore,
path + '.<iter ' + str(index) + '>')
if (ret != None):
return ret
index += 1
return None