[python-win32] OpenOffice automation

Roach, Mark R. mrroach@uhg.net
16 May 2002 09:54:09 -0500


--=-7rU/FdBwTZXQtnUZUa7s
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Tue, 2002-05-14 at 13:33, Roach, Mark R. wrote:
> On Fri, 2002-05-10 at 02:20, Raymond Yee wrote:
> [snip]
> > 
> > So how do I report this as a bug?
> 
> Hello, Raymond. Did you ever report this? If not, I will try to do so

Sorry to reply to my own post, Raymond asked me to summarize the
solution that he and Mark Hammond worked out. As they said, flagging
(what Python believes to be) a property as a method should fix it. This
should resolve similar problems with other COM objects as well.

Mark came up with the attached replacement for
win32com\client\dynamic.py. here are his instructions

"""
After replacing your
existing file, you should be able to say:

ob = Dispatch("...")
ob._FlagAsMethod("method1", "method2", ...)

ob.method1() should then work correctly.
"""

Based on this fix, Raymond was able to run the following code, a
translation of the example code at
http://udk.openoffice.org/common/man/tutorial/office_automation.html

import win32com.client
objServiceManager = win32com.client.Dispatch("com.sun.star.ServiceManager")
objServiceManager._FlagAsMethod("CreateInstance")
objDesktop = objServiceManager.CreateInstance("com.sun.star.frame.Desktop")
objDesktop._FlagAsMethod("loadComponentFromURL")
args = []
objDocument = objDesktop.loadComponentFromURL("private:factory/swriter",
"_blank", 0, args)
objDocument._FlagAsMethod("GetText")
objText = objDocument.GetText()
objText._FlagAsMethod("createTextCursor","insertString")
objCursor = objText.createTextCursor()
objText.insertString(objCursor, "The first line in the newly created text
document.\n", 0)


Thanks to both of these guys

-Mark Roach

--=-7rU/FdBwTZXQtnUZUa7s
Content-Disposition: attachment; filename=dynamic.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=dynamic.py; charset=ISO-8859-1

"""Support for dynamic COM client support.

Introduction
 Dynamic COM client support is the ability to use a COM server without
 prior knowledge of the server.  This can be used to talk to almost all
 COM servers, including much of MS Office.
=20
 In general, you should not use this module directly - see below.
=20
Example
 >>> import win32com.client
 >>> xl =3D win32com.client.Dispatch("Excel.Application")
 # The line above invokes the functionality of this class.
 # xl is now an object we can use to talk to Excel.
 >>> xl.Visible =3D 1 # The Excel window becomes visible.

"""
import traceback
import string
import new

import pythoncom
import winerror
import build

from types import StringType, IntType, TupleType, ListType
from pywintypes import UnicodeType, IIDType

import win32com.client # Needed as code we eval() references it.
from win32com.client import NeedUnicodeConversions

debugging=3D0			# General debugging
debugging_attr=3D0	# Debugging dynamic attribute lookups.

LCID =3D 0x0

# These errors generally mean the property or method exists,
# but can't be used in this context - eg, property instead of a method, etc=
.
# Used to determine if we have a real error or not.
ERRORS_BAD_CONTEXT =3D [
	winerror.DISP_E_MEMBERNOTFOUND,
	winerror.DISP_E_BADPARAMCOUNT,
	winerror.DISP_E_PARAMNOTOPTIONAL,
	winerror.DISP_E_TYPEMISMATCH,
    winerror.E_INVALIDARG,
]

def debug_print(*args):
	if debugging:
		for arg in args:
			print arg,
		print

def debug_attr_print(*args):
	if debugging_attr:
		for arg in args:
			print arg,
		print

# get the dispatch type in use.
dispatchType =3D pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
iunkType =3D pythoncom.TypeIIDs[pythoncom.IID_IUnknown]
_StringOrUnicodeType=3D[StringType, UnicodeType]
_GoodDispatchType=3D[StringType,IIDType,UnicodeType]
_defaultDispatchItem=3Dbuild.DispatchItem

def _GetGoodDispatch(IDispatch, clsctx =3D pythoncom.CLSCTX_SERVER):
	if type(IDispatch) in _GoodDispatchType:
		try:
			IDispatch =3D pythoncom.connect(IDispatch)
		except pythoncom.ole_error:
			IDispatch =3D pythoncom.CoCreateInstance(IDispatch, None, clsctx, python=
com.IID_IDispatch)
	return IDispatch

def _GetGoodDispatchAndUserName(IDispatch,userName,clsctx):
	if userName is None:
		if type(IDispatch) in _StringOrUnicodeType:
			userName =3D IDispatch
		else:
			userName =3D "<unknown>"
	return (_GetGoodDispatch(IDispatch, clsctx), userName)

def Dispatch(IDispatch, userName =3D None, createClass =3D None, typeinfo =
=3D None, UnicodeToString=3DNeedUnicodeConversions, clsctx =3D pythoncom.CL=
SCTX_SERVER):
	IDispatch, userName =3D _GetGoodDispatchAndUserName(IDispatch,userName,cls=
ctx)
	if createClass is None:
		createClass =3D CDispatch
	lazydata =3D None
	try:
		if typeinfo is None:
			typeinfo =3D IDispatch.GetTypeInfo()
		try:
			#try for a typecomp
			typecomp =3D typeinfo.GetTypeComp()
			lazydata =3D typeinfo, typecomp
		except pythoncom.com_error:
			pass
	except pythoncom.com_error:
		typeinfo =3D None
	olerepr =3D MakeOleRepr(IDispatch, typeinfo, lazydata)
	return createClass(IDispatch, olerepr, userName,UnicodeToString, lazydata)

def MakeOleRepr(IDispatch, typeinfo, typecomp):
	olerepr =3D None
	if typeinfo is not None:
		try:
			attr =3D typeinfo.GetTypeAttr()
			# If the type info is a special DUAL interface, magically turn it into
			# a DISPATCH typeinfo.
			if attr[5] =3D=3D pythoncom.TKIND_INTERFACE and attr[11] & pythoncom.TYP=
EFLAG_FDUAL:
				# Get corresponding Disp interface;
				# -1 is a special value which does this for us.
				href =3D typeinfo.GetRefTypeOfImplType(-1);
				typeinfo =3D typeinfo.GetRefTypeInfo(href)
				attr =3D typeinfo.GetTypeAttr()
			if typecomp is None:
				olerepr =3D build.DispatchItem(typeinfo, attr, None, 0)
			else:
				olerepr =3D build.LazyDispatchItem(attr, None)
		except pythoncom.ole_error:
			pass
	if olerepr is None: olerepr =3D build.DispatchItem()
	return olerepr

def DumbDispatch(IDispatch, userName =3D None, createClass =3D None,Unicode=
ToString=3DNeedUnicodeConversions, clsctx=3Dpythoncom.CLSCTX_SERVER):
	"Dispatch with no type info"
	IDispatch, userName =3D _GetGoodDispatchAndUserName(IDispatch,userName,cls=
ctx)
	if createClass is None:
		createClass =3D CDispatch
	return createClass(IDispatch, build.DispatchItem(), userName,UnicodeToStri=
ng)

class CDispatch:
	def __init__(self, IDispatch, olerepr, userName =3D  None, UnicodeToString=
=3DNeedUnicodeConversions, lazydata =3D None):
		if userName is None: userName =3D "<unknown>"
		self.__dict__['_oleobj_'] =3D IDispatch
		self.__dict__['_username_'] =3D userName
		self.__dict__['_olerepr_'] =3D olerepr
		self.__dict__['_mapCachedItems_'] =3D {}
		self.__dict__['_builtMethods_'] =3D {}
		self.__dict__['_enum_'] =3D None
		self.__dict__['_unicode_to_string_'] =3D UnicodeToString
		self.__dict__['_lazydata_'] =3D lazydata

	def __call__(self, *args):
		"Provide 'default dispatch' COM functionality - allow instance to be call=
ed"
		if self._olerepr_.defaultDispatchName:
			invkind, dispid =3D self._find_dispatch_type_(self._olerepr_.defaultDisp=
atchName)
		else:
			invkind, dispid =3D pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPE=
RTYGET, pythoncom.DISPID_VALUE
		if invkind is not None:
			allArgs =3D (dispid,LCID,invkind,1) + args
			return self._get_good_object_(apply(self._oleobj_.Invoke,allArgs),self._=
olerepr_.defaultDispatchName,None)
		raise TypeError, "This dispatch object does not define a default method"

	def __nonzero__(self):
		return 1 # ie "if object:" should always be "true" - without this, __len_=
_ is tried.
		# _Possibly_ want to defer to __len__ if available, but Im not sure this =
is
		# desirable???

	def __repr__(self):
		return "<COMObject %s>" % (self._username_)

	def __str__(self):
		# __str__ is used when the user does "print object", so we gracefully
		# fall back to the __repr__ if the object has no default method.
		try:
			return str(self.__call__())
		except pythoncom.com_error, details:
			if details[0] not in ERRORS_BAD_CONTEXT:
				raise
			return self.__repr__()

	# Delegate comparison to the oleobjs, as they know how to do identity.
	def __cmp__(self, other):
		other =3D getattr(other, "_oleobj_", other)
		return cmp(self._oleobj_, other)

	def __int__(self):
		return int(self.__call__())

	def __len__(self):
		invkind, dispid =3D self._find_dispatch_type_("Count")
		if invkind:
			return self._oleobj_.Invoke(dispid, LCID, invkind, 1)
		raise TypeError, "This dispatch object does not define a Count method"

	def _NewEnum(self):
		invkind, dispid =3D self._find_dispatch_type_("_NewEnum")
		if invkind is None:
			return None
	=09
		enum =3D self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,=
(13, 10),())
		import util
		return util.WrapEnum(enum, None)

	def __getitem__(self, index): # syver modified
		# Improved __getitem__ courtesy Syver Enstad
		# Must check _NewEnum before Item, to ensure b/w compat.
		if isinstance(index, IntType):
			if self.__dict__['_enum_'] is None:
				self.__dict__['_enum_'] =3D self._NewEnum()
			if self.__dict__['_enum_'] is not None:
				return self._get_good_object_(self._enum_.__getitem__(index))
		# See if we have an "Item" method/property we can use (goes hand in hand =
with Count() above!)
		invkind, dispid =3D self._find_dispatch_type_("Item")
		if invkind is not None:
			return self._get_good_object_(self._oleobj_.Invoke(dispid, LCID, invkind=
, 1, index))
		raise TypeError, "This object does not support enumeration"

	def __setitem__(self, index, *args):
		# XXX - todo - We should support calling Item() here too!
#		print "__setitem__ with", index, args
		if self._olerepr_.defaultDispatchName:
			invkind, dispid =3D self._find_dispatch_type_(self._olerepr_.defaultDisp=
atchName)
		else:
			invkind, dispid =3D pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_=
PROPERTYPUTREF, pythoncom.DISPID_VALUE
		if invkind is not None:
			allArgs =3D (dispid,LCID,invkind,0,index) + args
			return self._get_good_object_(apply(self._oleobj_.Invoke,allArgs),self._=
olerepr_.defaultDispatchName,None)
		raise TypeError, "This dispatch object does not define a default method"

	def _find_dispatch_type_(self, methodName):
		if self._olerepr_.mapFuncs.has_key(methodName):
			item =3D self._olerepr_.mapFuncs[methodName]
			return item.desc[4], item.dispid

		if self._olerepr_.propMapGet.has_key(methodName):
			item =3D self._olerepr_.propMapGet[methodName]
			return item.desc[4], item.dispid

		try:
			dispid =3D self._oleobj_.GetIDsOfNames(0,methodName)
		except:	### what error?
			return None, None
		return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid

	def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSI=
D, *args):
		result =3D apply(self._oleobj_.InvokeTypes, (dispid, LCID, wFlags, retTyp=
e, argTypes) + args)
		return self._get_good_object_(result, user, resultCLSID)

	def _wrap_dispatch_(self, ob, userName =3D None, returnCLSID =3D None, Uni=
codeToString =3D NeedUnicodeConversions):
		# Given a dispatch object, wrap it in a class
		return Dispatch(ob, userName, UnicodeToString=3DUnicodeToString)

	def _get_good_single_object_(self,ob,userName =3D None, ReturnCLSID=3DNone=
):
		if iunkType=3D=3Dtype(ob):
			try:
				ob =3D ob.QueryInterface(pythoncom.IID_IDispatch)
				# If this works, we then enter the "is dispatch" test below.
			except pythoncom.com_error:
				# It is an IUnknown, but not an IDispatch, so just let it through.
				pass
		if dispatchType=3D=3Dtype(ob):
			# make a new instance of (probably this) class.
			return self._wrap_dispatch_(ob, userName, ReturnCLSID)
		elif self._unicode_to_string_ and UnicodeType=3D=3Dtype(ob): =20
			return str(ob)
		else:
			return ob
	=09
	def _get_good_object_(self,ob,userName =3D None, ReturnCLSID=3DNone):
		"""Given an object (usually the retval from a method), make it a good obj=
ect to return.
		   Basically checks if it is a COM object, and wraps it up.
		   Also handles the fact that a retval may be a tuple of retvals"""
		if ob is None: # Quick exit!
			return None
		elif type(ob)=3D=3DTupleType:
			return tuple(map(lambda o, s=3Dself, oun=3DuserName, rc=3DReturnCLSID: s=
._get_good_single_object_(o, oun, rc),  ob))
		else:
			return self._get_good_single_object_(ob)
	=09
	def _make_method_(self, name):
		"Make a method object - Assumes in olerepr funcmap"
		methodName =3D build.MakePublicAttributeName(name) # translate keywords e=
tc.
		methodCodeList =3D self._olerepr_.MakeFuncMethod(self._olerepr_.mapFuncs[=
name], methodName,0)
		methodCode =3D string.join(methodCodeList,"\n")
		try:
#			print "Method code for %s is:\n" % self._username_, methodCode
#			self._print_details_()
			codeObject =3D compile(methodCode, "<COMObject %s>" % self._username_,"e=
xec")
			# Exec the code object
			tempNameSpace =3D {}
			exec codeObject in globals(), tempNameSpace # self.__dict__, self.__dict=
__
			name =3D methodName
			# Save the function in map.
			fn =3D self._builtMethods_[name] =3D tempNameSpace[name]
			newMeth =3D new.instancemethod(fn, self, self.__class__)
			return newMeth
		except:
			debug_print("Error building OLE definition for code ", methodCode)
			traceback.print_exc()
		return None
	=09
	def _Release_(self):
		"""Cleanup object - like a close - to force cleanup when you dont=20
		   want to rely on Python's reference counting."""
		for childCont in self._mapCachedItems_.values():
			childCont._Release_()
		self._mapCachedItems_ =3D {}
		if self._oleobj_:
			self._oleobj_.Release()
			self.__dict__['_oleobj_'] =3D None
		if self._olerepr_:
			self.__dict__['_olerepr_'] =3D None
		self._enum_ =3D None

	def _proc_(self, name, *args):
		"""Call the named method as a procedure, rather than function.
		   Mainly used by Word.Basic, which whinges about such things."""
		try:
			item =3D self._olerepr_.mapFuncs[name]
			dispId =3D item.dispid
			return self._get_good_object_(apply( self._oleobj_.Invoke, (dispId, LCID=
, item.desc[4], 0 ) + (args) ))
		except KeyError:
			raise AttributeError, name
	=09
	def _print_details_(self):
		"Debug routine - dumps what it knows about an object."
		print "AxDispatch container",self._username_
		try:
			print "Methods:"
			for method in self._olerepr_.mapFuncs.keys():
				print "\t", method
			print "Props:"
			for prop, entry in self._olerepr_.propMap.items():
				print "\t%s =3D 0x%x - %s" % (prop, entry.dispid, `entry`)
			print "Get Props:"
			for prop, entry in self._olerepr_.propMapGet.items():
				print "\t%s =3D 0x%x - %s" % (prop, entry.dispid, `entry`)
			print "Put Props:"
			for prop, entry in self._olerepr_.propMapPut.items():
				print "\t%s =3D 0x%x - %s" % (prop, entry.dispid, `entry`)
		except:
			traceback.print_exc()

	def __LazyMap__(self, attr):
		try:
			if self._LazyAddAttr_(attr):
				debug_attr_print("%s.__LazyMap__(%s) added something" % (self._username=
_,attr))
				return 1
		except AttributeError:
			return 0

	# Using the typecomp, lazily create a new attribute definition.
	def _LazyAddAttr_(self,attr):
		if self._lazydata_ is None: return 0
		res =3D 0
		i =3D 0
		typeinfo, typecomp =3D self._lazydata_
		olerepr =3D self._olerepr_
		try:
			x,t =3D typecomp.Bind(attr,i)
			if x=3D=3D1:	#it's a FUNCDESC
				r =3D olerepr._AddFunc_(typeinfo,t,0)
			elif x=3D=3D2:	#it's a VARDESC
				r =3D olerepr._AddVar_(typeinfo,t,0)
			else:		#not found or TYPEDESC/IMPLICITAPP
				r=3DNone

			if not r is None:
				key, map =3D r[0],r[1]
				item =3D map[key]
				if map=3D=3Dolerepr.propMapPut:
					olerepr._propMapPutCheck_(key,item)
				elif map=3D=3Dolerepr.propMapGet:
					olerepr._propMapGetCheck_(key,item)
				res =3D 1
		except:
			pass
		return res

	def _FlagAsMethod(self, *methodNames):
		"""Flag these attribute names as being methods.
		Some objects do not correctly differentiate methods and
		properties, leading to problems when calling these methods.

		Specifically, trying to say: ob.SomeFunc()
		may yield an exception "None object is not callable"
		In this case, an attempt to fetch the *property*has worked
		and returned None, rather than indicating it is really a method.
		Calling: ob._FlagAsMethod("SomeFunc")
		should then allow this to work.
		"""
		for name in methodNames:
			details =3D build.MapEntry(self.__AttrToID__(name), (name,))
			self._olerepr_.mapFuncs[name] =3D details

	def __AttrToID__(self,attr):
			debug_attr_print("Calling GetIDsOfNames for property %s in Dispatch cont=
ainer %s" % (attr, self._username_))
			return self._oleobj_.GetIDsOfNames(0,attr)

	def __getattr__(self, attr):
		if attr[0]=3D=3D'_' and attr[-1]=3D=3D'_': # Fast-track.
			raise AttributeError, attr
		# If a known method, create new instance and return.
		try:
			return new.instancemethod(self._builtMethods_[attr], self, self.__class_=
_)
		except KeyError:
			pass
		# XXX - Note that we current are case sensitive in the method.
		#debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr=
,self._username_))
		# First check if it is in the method map.  Note that an actual method
		# must not yet exist, (otherwise we would not be here).  This
		# means we create the actual method object - which also means
		# this code will never be asked for that method name again.
		if self._olerepr_.mapFuncs.has_key(attr):
			return self._make_method_(attr)

		# Delegate to property maps/cached items
		retEntry =3D None
		if self._olerepr_ and self._oleobj_:
			# first check general property map, then specific "put" map.
			if self._olerepr_.propMap.has_key(attr):
				retEntry =3D self._olerepr_.propMap[attr]
			if retEntry is None and self._olerepr_.propMapGet.has_key(attr):
				retEntry =3D self._olerepr_.propMapGet[attr]
			# Not found so far - See what COM says.
			if retEntry is None:
				try:
					if self.__LazyMap__(attr):
						if self._olerepr_.mapFuncs.has_key(attr): return self._make_method_(a=
ttr)
						if self._olerepr_.propMap.has_key(attr):
							retEntry =3D self._olerepr_.propMap[attr]
						if retEntry is None and self._olerepr_.propMapGet.has_key(attr):
							retEntry =3D self._olerepr_.propMapGet[attr]
					if retEntry is None:
						retEntry =3D build.MapEntry(self.__AttrToID__(attr), (attr,))
				except pythoncom.ole_error:
					pass # No prop by that name - retEntry remains None.

		if not retEntry is None: # see if in my cache
			try:
				ret =3D self._mapCachedItems_[retEntry.dispid]
				debug_attr_print ("Cached items has attribute!", ret)
				return ret
			except (KeyError, AttributeError):
				debug_attr_print("Attribute %s not in cache" % attr)

		# If we are still here, and have a retEntry, get the OLE item
		if not retEntry is None:
			debug_attr_print("Getting property Id 0x%x from OLE object" % retEntry.d=
ispid)
			try:
				ret =3D self._oleobj_.Invoke(retEntry.dispid,0,pythoncom.DISPATCH_PROPE=
RTYGET,1)
			except pythoncom.com_error, details:
				if details[0] in ERRORS_BAD_CONTEXT:
					# May be a method.
					self._olerepr_.mapFuncs[attr] =3D retEntry
					return self._make_method_(attr)
				raise pythoncom.com_error, details
			self._olerepr_.propMap[attr] =3D retEntry
			debug_attr_print("OLE returned ", ret)
			return self._get_good_object_(ret)

		# no where else to look.
		raise AttributeError, "%s.%s" % (self._username_, attr)

	def __setattr__(self, attr, value):
		if self.__dict__.has_key(attr): # Fast-track - if already in our dict, ju=
st make the assignment.
			# XXX - should maybe check method map - if someone assigns to a method,
			# it could mean something special (not sure what, tho!)
			self.__dict__[attr] =3D value
			return
		# Allow property assignment.
		debug_attr_print("SetAttr called for %s.%s=3D%s on DispatchContainer" % (=
self._username_, attr, `value`))
		if self._olerepr_:
			# Check the "general" property map.
			if self._olerepr_.propMap.has_key(attr):
				self._oleobj_.Invoke(self._olerepr_.propMap[attr].dispid, 0, pythoncom.=
DISPATCH_PROPERTYPUT, 0, value)
				return
			# Check the specific "put" map.
			if self._olerepr_.propMapPut.has_key(attr):
				self._oleobj_.Invoke(self._olerepr_.propMapPut[attr].dispid, 0, pythonc=
om.DISPATCH_PROPERTYPUT, 0, value)
				return

		# Try the OLE Object
		if self._oleobj_:
			if self.__LazyMap__(attr):
				# Check the "general" property map.
				if self._olerepr_.propMap.has_key(attr):
					self._oleobj_.Invoke(self._olerepr_.propMap[attr].dispid, 0, pythoncom=
.DISPATCH_PROPERTYPUT, 0, value)
					return
				# Check the specific "put" map.
				if self._olerepr_.propMapPut.has_key(attr):
					self._oleobj_.Invoke(self._olerepr_.propMapPut[attr].dispid, 0, python=
com.DISPATCH_PROPERTYPUT, 0, value)
					return
			try:
				entry =3D build.MapEntry(self.__AttrToID__(attr),(attr,))
			except pythoncom.com_error:
				# No attribute of that name
				entry =3D None
			if entry is not None:
				try:
					self._oleobj_.Invoke(entry.dispid, 0, pythoncom.DISPATCH_PROPERTYPUT, =
0, value)
					self._olerepr_.propMap[attr] =3D entry
					debug_attr_print("__setattr__ property %s (id=3D0x%x) in Dispatch cont=
ainer %s" % (attr, entry.dispid, self._username_))
					return
				except pythoncom.com_error:
					pass
		raise AttributeError, "Property '%s.%s' can not be set." % (self._usernam=
e_, attr)

--=-7rU/FdBwTZXQtnUZUa7s--