# Viewer automation methods # Copyright(c) SPSS Inc, 2006 """Methods for performing certain operations on the SPSS Viewer (not the Draft Viewer) via OLE automation. These methods require an SPSS Viewer to be present in the process and, hence, do not work in distributed mode nor in xdrives mode. Using this class in XDrives mode will raise an exception. However, a freestanding Python module can use this module by creating the spssapp object with the standalone flag. E.g., app = viewer.spssapp(standalone=True). Note: Full synchronization of Viewer actions with syntax requires SPSS 14.0.1 or later. This module requires pythoncom and win32com.client modules, which can be downloaded from http://sourceforge.net/projects/pywin32. Be sure to get the version for the current version of Python. In order to use the PivotTable class, after installing these modules run makepy once on each of the SPSS type libraries, including at least SPSS Type Library and SPSS Pivot Table Type Library. Example usage: BEGIN PROGRAM. import spss, viewer spss.Submit("DESCRIPTIVES ALL") spssapp = viewer.spssapp() try: actualName=spssapp.SaveDesignatedOutput("c:/temp/myoutput.spo") except: print "Save attempt failed. Name:", actualName else: spssapp.CloseDesignatedOutput() END PROGRAM. """ __author__ = 'spss' __version__= '1.3.0' # history # 23-dec-2005 jkp force exception if no Viewer and GetDesignated # 04-jan-2006 jkp new class for creating pivot tables; automatic creation of designated Viewer if none # 17-oct-2006 jkp support for PDF export import os.path, sys, time, spssaux try: import pythoncom, win32com.client except: print "This module requires pythoncom and win32com.client modules, which can be downloaded from http://sourceforge.net/projects/pywin32/ Be sure to get the Python 2.4 version." raise class ViewerError(Exception): pass #possible export formats formats = ["html", "text", "Excel", "Word", "Ppt", "Pdf"] class spssapp(object): """This class provides some methods for controlling the SPSS Viewer. It can save, close, and export the Viewer contents. Its GetDesignatedOutput method can be used to access other automation methods for Viewer contents. Note: Although the spssapp handle could be used to apply automation methods for other objects than the Viewer, it will almost always be superior to use Python-based methods for this purpose. No guarantee is made that non-Viewer OLE methods will work. If you use automation methods on this object that are not built in to this class, call this classes reco method first in order to ensure that the state of SPSS is properly synchronized. To create an spssapp instance that can be used as an autoscript or as a freestanding process working with a separate SPSS instance, set standalone = True in the constructor. Viewer methods are only available when in local mode.""" def __init__(self, standalone=False): if not standalone: import spss if spss.PyInvokeSpss.IsXDriven(): raise ViewerError,"No Viewer exists in XDrives mode" self.standalone = standalone self.reco() self.session = win32com.client.gencache.EnsureDispatch("spss.Application") self.Alerts = self.session.Alerts def reco(self): """reinit com in case of new thread, which occurs with every new BEGIN PROGRAM, and issue backend synchronization request. COM Server connection may also have to be reestablished.""" # Try to synchronize frontend and backend activity (requires at least SPSS 14.0.1 to be certain) # This will fail if there is an open cursor to SPSS data, but in that case, there will normally # not be any pending Viewer activity. try: if not self.standalone: spss.Submit("_SYNC.") except: time.sleep(1) #poor man's sync. protects SPSS 14.0.0 in most cases self.COMrenew() def COMrenew(self): """Reinitialize COM thread and possibly reconnect to SPSS COM Server.""" pythoncom.CoInitialize() if not self.standalone: try: a = self.session.Alerts # see if COM Server connection still alive except: self.session = win32com.client.gencache.EnsureDispatch("spss.Application") def alerts(self,action="off"): "Suppress alerts, if action='off'; otherwise restore saved alert state as it was when off was issued" if action == "off": self.Alerts = self.session.Alerts self.session.Alerts = 0 else: self.session.Alerts = self.Alerts def GetDesignatedOutput(self): """Get a reference to the designated Viewer window. Ensures thread initialization and synchronization. If there is no designated Viewer, this will attempt to create one.""" self.reco() try: self.DesOut = self.session.GetDesignatedOutputDoc() # if there is no designated Viewer, create one if not self.DesOut.Visible: self.DesOut.Visible = True except: self.DesOut = None raise return self.DesOut def SaveDesignatedOutput(self, filespec): """Save the designated Viewer window as filespec. If filespec is the current name of the document, the name is modified to include a datetime stamp. The method returns the actual name used. In SPSS 15 or later, the OUTPUT SAVE command can be used intead of this method.""" self.GetDesignatedOutput() self.alerts(action="off") rv = None curViewerName = self.DesOut.GetDocumentPath().replace('\\', '/') filespec = filespec.replace('\\', '/') if os.path.abspath(curViewerName).lower() == os.path.abspath(filespec).lower(): base, ext = os.path.splitext(filespec) filespec = base + time.strftime("_%Y-%m-%d_%H-%M-%S") + ext try: self.DesOut.SaveAs(filespec) rv = filespec finally: self.alerts(action="on") if not rv: raise ViewerError, "Failed to Save Viewer As: " + filespec return rv def CloseDesignatedOutput(self): """Close the designated Viewer window and open (and designate) a new one. In SPSS 15 or later, the OUTPUT CLOSE * command can be used instead of this method.""" # !For technical reasons related to the architecture of SaxBasic and SPSS OLE interfaces # the designated window cannot simply be closed. It must first be relieved of # its designated status by opening a new output window. The new window automatically # becomes the designated output! w = self.GetDesignatedOutput() self.alerts(action="off") nw = self.session.NewOutputDoc() #get and automatically designate a new output window nw.Visible = True w.Close() # close the original one self.alerts(action="on") def ExportDesignatedOutput(self, filespec, visibleContents=True, format="html", graphics=True): """Export the contents of the designated Viewer to file filespec. visibleContents = True (default) exports all visible contents; if False, all contents are exported. format can be "html" (default), "text", "Excel", "Word", "Ppt", or "Pdf" Pdf requires at least SPSS 15. graphics = True (default) to include charts. Ignored where the format does not support it. """ try: fmt = formats.index(format) except: raise ValueError, "invalid export format" try: self.GetDesignatedOutput() self.alerts("off") self.DesOut.ExportDocument(visibleContents and 1 or 0, filespec, fmt, graphics) finally: self.alerts("on") def ExportOutputByType(self, filespec, format, objectTypes='all', separate=False): """Export the current designed Viewer contents to a file for selected object types. Return count of exported objects. filespec is the filename for the export. format is any of the supported export formats (see ExportDesignatedOutput). objectTypes is a list of object types to export or "all", which is the default. It can also be written as a single string. If the list contains 'all', then other listed types are NOT exported. For example, objectTypes == ['all', 'spsslog'] means export everything except logs. Common object type names are 'spsschart', 'spsslog','spsspivot','spsstext'. See code below for complete list. If separate is True, each item is exported to a separate file with a number appended to the name. In this case, it is generally best to omit the file extension in filespec. Other behavior is the same as for the OLE ExportDocument method. In particular, the filename may be modified. If the selected format does not support an object type, objects of that type will not be exported. For example, the Excel format does not support charts, so they will be excluded. Examples: viewer.ExportOutputByType(filespec="c:/temp/tables", format="Excel", objectTypes="spsspivot") produce an Excel file with all pivot tables viewer.ExportOutputByType(filespec="c:/temp/tables", format="Excel", objectTypes="spsspivot", separate=True) produce a set of Excel files, tables1.xls, tables2.xls,... each containing one table viewer.ExportOutputByType(filespec="c:/temp/output", format="html", objectTypes=["spsspivot", spsschart"]) produce an html file with all pivot tables and charts (each graphic image is a separate file) viewer.ExportOutputByType(filespec="c:/temp/output", format="Pdf", objectTypes="all spsslog") produce a pdf file with all output objects except logs. """ outputTypes={'spssunknown':0, 'spsschart':1, 'spsshead':2, 'spsslog':3, 'spssnote':4, 'spsspivot':5, 'spsstext':7, 'spsswarning':8, 'spsstitle':9, 'spssigraph':10, 'spsspagetitle':11, 'spssimap':12} outputTypesSet = set(outputTypes) fmt = formats.index(format) objectTypes = set(spssaux._listify(objectTypes)) if not objectTypes.issubset(outputTypesSet.union(set(['all']))): raise AttributeError, "Invalid object type specified" if 'all' in objectTypes: exportset = outputTypesSet - objectTypes else: exportset = objectTypes if len(exportset) == 0: raise AttributeError, "No object types to export" exportset = set(outputTypes[k] for k in exportset) #results in type numbers for object types to export try: out = self.GetDesignatedOutput() self.alerts("off") out.ClearSelection() items = out.Items itemcount = items.Count itemsexported = 0 for item in range(itemcount): theitem = items.GetItem(item) if theitem.SPSSType in exportset: theitem.Selected = True itemsexported += 1 if separate: self.DesOut.ExportDocument(2, filespec + str(itemsexported), fmt, True) out.ClearSelection() if not separate: self.DesOut.ExportDocument(2, filespec, fmt, True) #2 indicates export selected items finally: out.ClearSelection() self.alerts("on") return itemsexported def _promote(viewer, itemindex): """promote the item designated by itemindex in the current viewer as high as possible.""" viewer.ClearSelection() itemindex.Selected = True # not all locations are promotable initialLevel = itemindex.Level while itemindex.Level > 1: viewer.Promote() if itemindex.Level == initialLevel: break viewer.ClearSelection() class PivotTable(object): """This class provides methods for creating a new pivot table in the SPSS Viewer. These methods will not work in distributed mode, and these tables are not subject to manipulation by OMS.""" def __init__(self, outlinetitle,tabletitle="", caption="", rowdim="", rowlabels=[],coldim = "", collabels=[], cells=None, tablelook=""): """Create a pivot table structure where outlinetitle is the outline title for the table. tabletitle is the title for the table itself. If not specified, the outline title is used. rowdim and coldim are the optional respective dimension labels as strings. If empty, the dimension label is hidden. rowlabels is an optional list of strings to label the rows, collabels is an optional list of strings to label the columns. Every row and column must have a label if any values in that dimension do, but it may be empty. caption is an optional caption for the table. cells is a 1- or 2-dimensional sequence of values for the table cells that can be indexed as cells[i] or cells[i][j]. Cells must match the label structure if labels are present, and if a dimension is given, all preceding dimensions must also be given. If there are no labels for the row dimension, the number of rows in Cells is used. If there are no labels for the column dimension, the length of the first cells item is used In the absence of labels, if cells is one-dimensional, there will be one column (and many rows). If a labels list is None, that dimension will have only one element. tablelook is an optional string naming a tablelook to apply The table is not inserted into the Viewer until the insert method is called. Example: pt = viewer.PivotTable("table of data", rowlabels=['rowa', 'rowb'], collabels=['col1', 'coll2'], cells=[[1,2],[3,4]]) pt.insert(des) where des is the Designated Viewer. """ self.outlinetitle = str(outlinetitle) self.tabletitle=tabletitle self.caption = caption self.rowdim = str(rowdim) self.rowlabels = rowlabels self.coldim = str(coldim) self.collabels = collabels self.cells = cells self.tablelook = tablelook def insert(self, viewer, afteritem=None): """Insert the table object in the Viewer designated by viewer. If afteritem is None the table is inserted after the current item, otherwise, it indicates the index of the item after which the table is to be inserted. The index is automatically bounded by the number of items in the Viewer. The table is always inserted at the highest level in the outline except that if the chosen location cannot be promoted, the insertion happens at that level. The return value is the index of the inserted item.""" nrows, ncols = len(self.rowlabels), len(self.collabels) if nrows == 0: nrows = len(self.cells) if ncols == 0: if isinstance(self.cells[0], (list, tuple)): ncols = len(self.cells[0]) else: ncols = 1 if not self.tabletitle: self.tabletitle = self.outlinetitle if not afteritem is None: afteritem = min(afteritem, viewer.Items.Count) viewer.Items.GetItem(afteritem).Current = True index = viewer.InsertTable(self.outlinetitle, nrows, ncols, 1) ptindex = viewer.Items.GetItem(index) _promote(viewer, ptindex) pt = ptindex.ActivateTable() try: pmgr = pt.PivotManager() pt.TitleText = str(self.tabletitle) if (self.caption): pt.CaptionText = str(self.caption) a = pt.RowLabelArray() if not self.rowdim: a.HideLabelsInDimensionAt(0,0) else: a.SetValueAt(0,0,self.rowdim) for i in range(len(self.rowlabels)): a.SetValueAt(i,1,str(self.rowlabels[i])) a = pt.ColumnLabelArray() if not self.coldim: a.HideLabelsInDimensionAt(0,0) else: a.SetValueAt(0,0, self.coldim) if len(self.collabels) ==0: a.HideLabelsInDimensionAt(1,0) for i in range(len(self.collabels)): a.SetValueAt(1,i,str(self.collabels[i])) a = pt.DataCellArray() if ncols <=1: for i in range(nrows): a.SetValueAt(i,0, str(self.cells[i])) else: for i in range(nrows): for j in range(ncols): a.SetValueAt(i,j, str(self.cells[i][j])) if self.tablelook: pt.TableLook = self.tablelook pt.Autofit() finally: ptindex.Deactivate() return index # This is intended for the viewer module when bug 100290, a problem with the InsertTitle automation # method, is fixed. class ViewerText(object): """This class provides methods for creating a new text block in the SPSS Viewer. These methods will not work in distributed mode, and these items are not subject to manipulation by OMS. This class does not work with SPSS 14.0.1 or 14.0.0 because of a problem with the InsertTitle automation method. However, it does work with SPSS 14.0.2.""" def __init__(self, outlinetitle,text=""): """outlinetitle is the text to appear in the outline for this item. text is the text to be inserted.""" self.outlinetitle=outlinetitle self.text = text def insert(self, viewer, afteritem=None): """Insert the text object in the Viewer designated by viewer. If afteritem is None the item is inserted after the current item, otherwise, it indicates the index of the item after which the item is to be inserted. The index is automatically bounded by the number of items in the Viewer. The item is always inserted at the highest level in the outline except that if the chosen location cannot be promoted, the insertion happens at that level. The return value is the index of the inserted item.""" if not afteritem is None: afteritem = min(afteritem, viewer.Items.Count) viewer.Items.GetItem(afteritem).Current = True index = viewer.InsertTitle(str(self.outlinetitle), str(self.text)) itemindex = viewer.Items.GetItem(index) _promote(viewer, itemindex) act=itemindex.ActivateText() act.Text = self.text itemindex.Deactivate() return index