Microsoft Office是应用最广泛的办公软件,绝大多数公司的必备软件。其中的Excel操作直观方便,很多公司都有大量数据以Excel文件的格式保存。一个LotusNotes应用在很多情况下都会需要导入Excel文件,比如程序初始化的时候导入历史数据,批量导入配置数据,定期导入人工输入或从其他系统导出的Excel文件。导入Excel文件的功能,从原理上说并不复杂,就是读取Excel工作表中的一行数据,保存为一个Notes文档。应用面向对象的思想,加上良好的设计,可以写出一个具有很好通用性,使用方便的导入Excel文件的类ExcelImporter。
看一个调用这个类的实例:
Public Function Import Dim importer As New ExcelImporter() Call importer.Import("fmRecord") End Function
只需要创建一个ExcelImporter类的实例,然后调用Import()方法就完成了导入。唯一需要传入的参数是创建文档所用的表单名。文档中的字段和工作表列的对应关系这种元数据,可以不同的方式获得。一种方案是导入时指定一个视图,该视图的每一列的值都源自一个字段,而没有常数和公式等情况。工作表的列和视图列一一对应。Excel某一列的值就保存到视图对应列所关联的字段。第二种方案是将这种对应关系显式写在Excel中。一般工作表中的第一行是列标题,我们就在第二行里填写每一列对应的字段名。两相比较,创建和调整一个视图更为麻烦,还会消耗索引资源,所以ExcelImporter类采用了第二种方案。
有些情况下,Notes文档中的某些字段值不能简单复制工作表对应的单元格的数据,需要根据其他字段或者配置文档计算。这些应具体情况而变的代码,不能写在ExcelImporter类里,否则它就丧失了通用性。这种情况同样可以应用《33. 面向对象的LotusScript(六)之为自定义对象模拟事件》里面设计的事件机制来解决。为此,ExcelImporter类继承了EventPublisher类,调用它的代码注册一个函数,响应保存文档前触发的事件,在这个函数中可以完成对当前文档任意字段的任意计算。下面的例子中SaveRecord()函数通过调用ExcelImporter实例的多个方法,获的当前的工作表、行数和文档等信息,然后计算Description字段的值。LocalLib是这段代码所在的脚本库的名称。
Public Function Import Set importer=New ExcelImporter() Call importer.AddEventHandler("QuerySaveDoc", {Use"LocalLib":SaveRecord}) Call importer.Import("fmRecord") End Function Public Function SaveRecord() On Error GoTo EH Dim sheet As Variant Set sheet=importer.GetSheet() Dim rowNum As Integer rowNum=importer.GetRowNum() Dim doc As NotesDocument 'current imported document Set doc=importer.GetCurrentDoc() 'from code to desc Dim account As String, desc As Variant account=sheet.Cells(rowNum, 4).Value Call doc.Replaceitemvalue("Account", CStr(account)) desc=DBLookUp("vwAccount", account, 1, "") If Not IsEmpty(desc) Then Call doc.Replaceitemvalue("Description", desc) End If Exit Function EH: MsgBox GetErrorMsg() Exit Function End Function
下面给出这个ExcelImporter类的代码:
Class ExcelImporter As EventPublisher Private session As NotesSession Private ws As NotesUIWorkspace Private db As NotesDatabase Private doc As NotesDocument 'current imported document Private xlFileName As String Private xlApp As Variant Private xlWork As Variant Private xlSheet As Variant Private docForm As String Private colNum As Integer 'column numbers in the imported Excel file Private rowNum As Integer 'current row number in the worksheet Private ImportTime As String Public Debug As Boolean Sub New() Set session = New NotesSession Set ws = New NotesUIWorkspace Set db = session.CurrentDatabase colNum = 0 'import from the 3rd line. The first two lines are the column title and the corresponding field name. rowNum = 3 xlFileName = "" ImportTime = Format( Now(), "yyyy-mm-dd hh:mm:ss" ) 'The time used to mark the imported documents. End Sub 'The entry function. docForm is the form used to create documents. Public Function Import(docForm As String) As Integer If Not Debug Then On Error GoTo QuitApp Import = False Me.docForm = DocForm If Not CreateExcelObject() Then Exit Function Call GetColumns() Call ImportData() Import = True xlApp.Quit Print "导入完成" Exit Function QuitApp: MsgBox GetErrorMsg If Not (xlApp Is Nothing) Then xlApp.Quit Exit Function End Function 'Create an Excel COM object. Private Function CreateExcelObject() As Boolean CreateExcelObject = False Dim result As Variant result=ws.Openfiledialog(False, "Lotus Notes", "Excel files|*.xlsx|Excel 97-2003 files|*.xls") If IsEmpty(result) Then Exit Function me.xlFileName=result(0) Print "正在初始化 Excel对象..." Set xlApp = CreateObject("Excel.Application") If xlApp Is Nothing Then Error 6503,"创建 Excel.Application对象失败,请确认是否安装Excel。" xlApp.Visible = False Set xlWork = xlApp.Workbooks.Open( xlFileName ) Set xlSheet = xlWork.ActiveSheet CreateExcelObject = True End Function 'Calculate the numbers of the effective (non-empty) columns in the Excel worksheet. Private Function GetColumns While xlSheet.Cells(1, 1+colNum).Value><"" colNum=colNum+1 Wend End Function 'Import the data. 10 continual rows which are empty in the first column are treated as the end of the file. Private Function ImportData() Dim BlankRow As Integer BlankRow = 0 While (BlankRow < 10) Print "正在处理第 " & rowNum & "行" If xlSheet.Cells(rowNum,1).Value = "" Then BlankRow = BlankRow + 1 Else BlankRow = 0 Call ImportLine() End If rowNum = rowNum + 1 Wend End Function 'Import one line of data. Private Function ImportLine() Call OnEvent("QueryCreateDoc") If (Not EventResult) Then Exit Function Set doc = db.CreateDocument doc.Form = Me.docForm Dim Field As String, value As Variant Dim i As Integer For i = 1 To colNum 'value=xlSheet.Cells(rowNum,i).Value Call doc.ReplaceItemValue( xlSheet.Cells(2, i).Value, xlSheet.Cells(rowNum,i).Value ) Next doc.ImportTime = ImportTime 'Mark all the document with the preset time. Call doc.ComputeWithForm( False, False ) Call OnEvent("QuerySaveDoc") Call doc.Save( True, False ) End Function %REM Property method. Get the current row in the Excel worksheet. %END REM Public Function GetRowNum() As Integer GetRowNum=rowNum End Function %REM Property method. Get the current Excel worksheet. %END REM Public Function GetSheet() As Variant Set GetSheet=me.xlSheet End Function %REM Property method. Get the current document. %END REM Public Function GetCurrentDoc() As NotesDocument Set GetCurrentDoc=me.doc End Function End Class
可以看出,ExcelImporter类还模拟了QueryCreateDoc事件。因为有些时候,我们还要判断Excel工作表里某一行的数据是否有效,以决定是否创建一个文档。这时候就可以注册QueryCreateDoc事件的响应程序,当其返回真时才创建文档。
Call importer.AddEventHandler("QueryCreateDoc", {Use"LocalLib":EventResult=CheckRecord()})