123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- """Exception and error handling.
- This contains the core exceptions that the implementations should raise
- as well as the IActiveScriptError interface code.
-
- """
- import sys, traceback
- from win32com.axscript import axscript
- import winerror
- import win32com.server.exception
- import win32com.server.util
- import pythoncom
- import re
- debugging = 0
- def FormatForAX(text):
- """Format a string suitable for an AX Host
- """
- # Replace all " with ', so it works OK in HTML (ie, ASP)
- return ExpandTabs(AddCR(text))
- def ExpandTabs(text):
- return re.sub('\t',' ', text)
- def AddCR(text):
- return re.sub('\n','\r\n',text)
- class IActiveScriptError:
- """An implementation of IActiveScriptError
-
- The ActiveX Scripting host calls this client whenever we report
- an exception to it. This interface provides the exception details
- for the host to report to the user.
- """
- _com_interfaces_ = [axscript.IID_IActiveScriptError]
- _public_methods_ = ["GetSourceLineText","GetSourcePosition","GetExceptionInfo"]
- def _query_interface_(self, iid):
- print("IActiveScriptError QI - unknown IID", iid)
- return 0
- def _SetExceptionInfo(self, exc):
- self.exception = exc
- def GetSourceLineText(self):
- return self.exception.linetext
- def GetSourcePosition(self):
- ctx = self.exception.sourceContext
- # Zero based in the debugger (but our columns are too!)
- return ctx, self.exception.lineno + self.exception.startLineNo-1, self.exception.colno
- def GetExceptionInfo(self):
- return self.exception
- class AXScriptException(win32com.server.exception.COMException):
- """A class used as a COM exception.
-
- Note this has attributes which conform to the standard attributes
- for COM exceptions, plus a few others specific to our IActiveScriptError
- object.
- """
- def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback):
- # set properties base class shares via base ctor...
- win32com.server.exception.COMException.__init__( self, \
- description = "Unknown Exception", \
- scode = winerror.DISP_E_EXCEPTION, \
- source = "Python ActiveX Scripting Engine",
- )
-
- # And my other values...
- if codeBlock is None:
- self.sourceContext = 0
- self.startLineNo = 0
- else:
- self.sourceContext = codeBlock.sourceContextCookie
- self.startLineNo = codeBlock.startLineNumber
- self.linetext = ""
- self.__BuildFromException(site, exc_type, exc_value, exc_traceback)
- def __BuildFromException(self, site, type , value, tb):
- if debugging:
- import linecache
- linecache.clearcache()
- try:
- if issubclass(type, SyntaxError):
- self._BuildFromSyntaxError(site, value, tb)
- else:
- self._BuildFromOther(site, type, value, tb)
- except: # Error extracting traceback info!!!
- traceback.print_exc()
- # re-raise.
- raise
- def _BuildFromSyntaxError(self, site, exc, tb):
- value = exc.args
- # All syntax errors should have a message as element 0
- try:
- msg = value[0]
- except:
- msg = "Unknown Error (%s)" % (value,)
- try:
- (filename, lineno, offset, line) = value[1]
- # Some of these may be None, which upsets us!
- if offset is None:
- offset = 0
- if line is None:
- line = ""
- except:
- msg = "Unknown"
- lineno = 0
- offset = 0
- line = "Unknown"
- self.description=FormatForAX(msg)
- self.lineno = lineno
- self.colno = offset - 1
- self.linetext = ExpandTabs(line.rstrip())
- def _BuildFromOther(self, site, exc_type, value, tb):
- self.colno = -1
- self.lineno = 0
- if debugging: # Full traceback if debugging.
- list=traceback.format_exception(exc_type, value, tb)
- self.description = ExpandTabs(''.join(list))
- return
- # Run down the traceback list, looking for the first "<Script..>"
- # Hide traceback above this. In addition, keep going down
- # looking for a "_*_" attribute, and below hide these also.
- hide_names = ["r_import","r_reload","r_open"] # hide from these functions down in the traceback.
- depth = None
- tb_top = tb
- while tb_top:
- filename, lineno, name, line = self.ExtractTracebackInfo(tb_top, site)
- if filename[:7]=="<Script":
- break
- tb_top = tb_top.tb_next
- format_items = []
- if tb_top: # found one.
- depth = 0
- tb_look = tb_top
- # Look down for our bottom
- while tb_look:
- filename, lineno, name, line = self.ExtractTracebackInfo(tb_look, site)
- if name in hide_names:
- break
- # We can report a line-number, but not a filename. Therefore,
- # we return the last line-number we find in one of our script
- # blocks.
- if filename.startswith("<Script"):
- self.lineno = lineno
- self.linetext = line
- format_items.append((filename, lineno, name, line))
- depth = depth + 1
- tb_look = tb_look.tb_next
- else:
- depth = None
- tb_top = tb
-
- bits = ['Traceback (most recent call last):\n']
- bits.extend(traceback.format_list(format_items))
- if exc_type==pythoncom.com_error:
- desc = "%s (0x%x)" % (value.strerror, value.hresult)
- if value.hresult==winerror.DISP_E_EXCEPTION and value.excepinfo and value.excepinfo[2]:
- desc = value.excepinfo[2]
- bits.append("COM Error: "+desc)
- else:
- bits.extend(traceback.format_exception_only(exc_type, value))
- # XXX - this utf8 encoding seems bogus. From well before py3k,
- # we had the comment:
- # > all items in the list are utf8 courtesy of Python magically
- # > converting unicode to utf8 before compilation.
- # but that is likely just confusion from early unicode days;
- # Python isn't doing it, pywin32 probably was, so 'mbcs' would
- # be the default encoding. We should never hit this these days
- # anyway, but on py3k, we *never* will, and str objects there
- # don't have a decode method...
- if sys.version_info < (3,):
- for i in range(len(bits)):
- if type(bits[i]) is str:
- #assert type(bits[i]) is str, type(bits[i])
- bits[i] = bits[i].decode('utf8')
- self.description = ExpandTabs(''.join(bits))
- # Clear tracebacks etc.
- tb = tb_top = tb_look = None
- def ExtractTracebackInfo(self, tb, site):
- import linecache
- f = tb.tb_frame
- lineno = tb.tb_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- line = linecache.getline(filename, lineno)
- if not line:
- try:
- codeBlock = site.scriptCodeBlocks[filename]
- except KeyError:
- codeBlock = None
- if codeBlock:
- # Note: 'line' will now be unicode.
- line = codeBlock.GetLineNo(lineno)
- if line:
- line = line.strip()
- else:
- line = None
- return filename, lineno, name, line
- def __repr__(self):
- return "AXScriptException Object with description:" + self.description
- def ProcessAXScriptException(scriptingSite, debugManager, exceptionInstance):
- """General function to handle any exception in AX code
-
- This function creates an instance of our IActiveScriptError interface, and
- gives it to the host, along with out exception class. The host will
- likely call back on the IActiveScriptError interface to get the source text
- and other information not normally in COM exceptions.
- """
- # traceback.print_exc()
- instance = IActiveScriptError()
- instance._SetExceptionInfo(exceptionInstance)
- gateway = win32com.server.util.wrap(instance, axscript.IID_IActiveScriptError)
- if debugManager:
- fCallOnError = debugManager.HandleRuntimeError()
- if not fCallOnError:
- return None
-
- try:
- result = scriptingSite.OnScriptError(gateway)
- except pythoncom.com_error as details:
- print("**OnScriptError failed:", details)
- print("Exception description:'%s'" % (repr(exceptionInstance.description)))
- print("Exception text:'%s'" % (repr(exceptionInstance.linetext)))
- result = winerror.S_FALSE
- if result==winerror.S_OK:
- # If the above returns NOERROR, it is assumed the error has been
- # correctly registered and the value SCRIPT_E_REPORTED is returned.
- ret = win32com.server.exception.COMException(scode=axscript.SCRIPT_E_REPORTED)
- return ret
- else:
- # The error is taken to be unreported and is propagated up the call stack
- # via the IDispatch::Invoke's EXCEPINFO parameter (hr returned is DISP_E_EXCEPTION.
- return exceptionInstance
|