ExcelWorksheetExporter and Importer using OpenXML Libraries
Introduction
The purpose of this document is both to guide and outline the functionality of using the OpenXML Libraries to perform Imports and Exports.
Prerequisites
Depending on the target .NET Framework to be used, there are some configuration prerequisites to be performed beforehand. These configuration prerequisites are primarily to be performed within the “SmartComponentLibrary” folder.
Configuration for .NET 4.8 Framework
The configuration for using the .NET 4.8 Framework is ensuring that the following .NET 4.8 Framework Libraries are in place:
Assemblies/Consultingwerk
Consultingwerk.Utilities.Support.dll
Assemblies/Microsoft
DocumentFormat.OpenXml.dll
DocumentFormat.OpenXml.Features.dll
DocumentFormat.OpenXml.Framework.dll
DocumentFormat.OpenXml.Linq.dll
Configuration for .NET8
The configuration for using the .NET8 is ensuring that the following .NET 8.0 Libraries are moved/located in correct place.
All the .NET8 Libraries within AssembliesNetCore should reside within the root of the ‘project’ (i.e ‘SmartComponentLibrary/AssembliesNetCore’ - “Completely separate from the current ‘Assemblies’ folder”).
Consultingwerk.Utilities.Support.dll
DocumentFormat.OpenXml.dll
DocumentFormat.OpenXml.Features.dll
DocumentFormat.OpenXml.Framework.dll
DocumentFormat.OpenXml.Linq.dll
Then ensure that the project ‘startup’ parameter “-clrnetcore” is set accordingly.
Note: .NET Core does not support sub-folders in the -assemblies folder
Base Classes
ExcelUtility
The class ExcelUtility is ‘inherited’ by both ExcelWorksheetExporter and ExcelWorksheetImporter. The class itself contains the following methods:
CloseSpreadsheetDocument
@param: poSpreadsheetDocument The reference to the Excel SpreadsheetDocument
GetCellValue
@param: poWorkbookPart The reference to the Excel WorkbookPart
@param: poCell Cell
@return: Cell Value
GetRowCell
@param: poRow The reference to the Excel Row
@param: pcCellReference The cell reference
@return: Cell Object
GetWorkbookPart
@param: oSpreadsheetDocument The reference to the Excel SpreadsheetDocument
@return: WorkbookPart Object
GetWorksheetSheetData
@param: poWorkbookPart The reference to the Excel WorkbookPart
@return: SheetData Object
GetWorksheetPart
@param: poWorkbookPart The reference to the Excel WorkbookPart
@return: WorksheetPart Object
InsertCellValue
@param: poWorksheetPart WorksheetPart
@param: pcContent Character The content to insert
@param: piRowIndex Integer The row to insert into
@param: pcCol Character The column to insert into
OpenSpreadsheetDocument
@param: pcFileName The file name of the Excel file
@param: plEditable Logical indicating if the document should be opened in update mode
@return: SpreadsheetDocument Object
SaveWorkbook
@param: poWorkbookPart The reference to the Excel WorkbookPart
SaveSpreadsheetDocument
@param: poSpreadsheetDocument The reference to the Excel SpreadsheetDocument
SetExcelCellValues
@param: poWorksheetPart WorksheetPart
@param: piCount Integer The row to insert into
@param: poValues Object[] The values to insert
The section below shall expand on the aforementioned methods, how they are used and what purpose they serve.
OpenSpreadsheetDocument
The method OpenSpreadsheetDocument opens an existing spreadsheet in either update or read-only mode. The premise here is that the relevant ‘spreadsheet’ exists.
GetWorkbookPart
The method GetWorkbookPart accesses and returns the WorkbookPart element of the spreadsheet.
GetWorksheetPart
The method GetWorksheetPart accesses and returns the WorksheetPart element of the WorkbookPart.
GetWorksheetSheetData
The method GetWorksheetSheetData accesses and returns the SheetData element of the WorkbookPart.
GetRowCell
The method GetRowCell is used to retrieve a Cell Object from a specified Row & Cell Reference. This achieved by calling into the ExcelHelper Library method ‘GetRowCell”.
GetCellValue
The method GetCellValue is used to retrieve a specific Cell Object value.
SetExcelCellValues
The method SetExcelCellValues is used to set a Row’s cell values for a given Worksheet. The assignment of the individual cells is performed by calling into ‘InsertCellValue’.
InsertCellValue
The method InsertCellValue is used to populate the cells on a Spreadsheet Row. Each cell of the relevant row is extracted as a Cell Object from calling into the ExcelHelper Library method ‘InsertCellInWorksheet”. The Cell Objects attributes “CellValue” and “DataType” are then populated.
SaveWorkbook
The method SaveWorkbook is used to ‘save’ the Workbook element.
SaveSpreadsheetDocument
The method SaveSpreadsheetDocument is used to ‘save’ the spreadsheet and then dispose of the spreadsheet memory element. This method is used for when the Spreadsheet has been opened in ‘update mode’. The internal aspect of the method calls the ‘Save’ and ‘Close’ attributes the spreadsheet memory element.
CloseSpreadsheetDocument
The method CloseSpreadsheetDocument is used to dispose of the spreadsheet memory element. This method is used for when the Spreadsheet has been opened in read mode’. The internal aspect of the method is only calling the ‘Close’ attribute the spreadsheet memory element.
ExcelWorksheetExporter
The original “ExcelWorksheetExporter” was constructed to use “Microsoft.Office.Interop.Excel” for performing the Exporting of data to Excel. The details below outline the changes that were required to use the ‘OpenXML Libraries’.
The structure of “ExcelWorksheetExporter” currently depends on using an existing Excel Spreadsheet Template, which is then copied and updated. Hence the reason that the code uses the ‘OpenSpreadsheetDocument’ method and uses the second parameter to indicate whether or not the Spreadsheet is updateable.
In addition, an Interface for the ExcelWorksheetExporter has been introduced.
The conversion of “ExcelWorksheetExporter” required the following changes:
Using Statements
Original | New |
using Consultingwerk.Util.*. using Consultingwerk.Utilities.Excel.*. using Consultingwerk.Utilities.Support.*. using Microsoft.Office.Interop.Excel. using Progress.Lang.*. | using Consultingwerk.*. using Consultingwerk.Util.*. using Consultingwerk.Utilities.Excel.*. using DocumentFormat.OpenXml.*. using DocumentFormat.OpenXml.Packaging.*. using DocumentFormat.OpenXml.Spreadsheet.*. using Progress.Lang.*. |
Methods
Only two methods were required to be modified.
ExportExcel
The ExportExcel method is the main method and entry point into Exporting Data to Excel. The two tables below show the original code and the updated code.
Original Code |
method public void ExportExcel (poExportWorksheetArguments as ExportWorksheetArguments, phTempTable as handle):
define variable oExcel as Microsoft.Office.Interop.Excel.Application no-undo . define variable oWorkbook as Workbook no-undo . define variable oWorksheet as Worksheet no-undo . define variable iStartRow as INTEGER no-undo . define variable cFileName as CHARACTER no-undo .
assign cFileName = FileHelper:FindFile (poExportWorksheetArguments:FileName, TRUE) iStartRow = poExportWorksheetArguments:StartRow .
oExcel = new Microsoft.Office.Interop.Excel.ApplicationClass () . oWorkbook = OpenWorkbook (oExcel, cFileName) . oWorksheet = this-object:GetWorksheet (oWorkbook, poExportWorksheetArguments:WorksheetName, poExportWorksheetArguments:WorksheetIndex) .
assign oWorksheet:EnableCalculation = false oWorksheet:EnableFormatConditionsCalculation = false oWorksheet:EnableOutlining = false . this-object:ExportRows (oWorksheet, poExportWorksheetArguments, phTempTable, iStartRow) . oWorksheet:Calculate () . oWorkbook:Save () . finally: if valid-object (oWorkbook) then oWorkbook:Saved = true . if valid-object (oExcel) then oExcel:Quit() . end finally. end method. |
New Code |
method public void ExportExcel (poExportWorksheetArguments as ExportWorksheetArguments, phTempTable as handle): define variable oSpreadsheetDocument as SpreadsheetDocument no-undo. define variable oWorkbookPart as WorkbookPart no-undo. define variable oWorksheetPart as WorksheetPart no-undo. define variable iStartRow as integer no-undo . define variable cFileName as character no-undo .
assign cFileName = FileHelper:FindFile (poExportWorksheetArguments:FileName, TRUE) iStartRow = poExportWorksheetArguments:StartRow .
oSpreadsheetDocument = this-object:OpenSpreadsheetDocument (cFileName, true). oWorkbookPart = this-object:GetWorkbookPart (oSpreadsheetDocument). oWorksheetPart = this-object:GetWorksheetPart(oWorkbookPart).
this-object:ExportRows (oWorksheetPart, poExportWorksheetArguments, phTempTable, iStartRow) .
this-object:SaveWorkbook(oWorkbookPart). this-object:SaveSpreadsheetDocument(oSpreadsheetDocument).
finally: GarbageCollectorHelper:DeleteObject(oWorkbookPart). GarbageCollectorHelper:DeleteObject(oWorksheetPart). GarbageCollectorHelper:DeleteObject(oSpreadsheetDocument). end finally. end method. |
The code highlighted in “red” within the original code is what has been replaced, and the code highlighted in “green” within the new code shows what the code has been replaced with.
ExportRows
The changes required for this method were relatively simple, as outlined below.
The first method parameter changed from
“poWorksheet AS Worksheet”
to
poWorksheetPart as WorksheetPart
All occurrences of
ExcelHelper:SetExcelCellValues (poWorksheet, iCount, oValues).
replaced with
this-object:SetExcelCellValues (poWorksheetPart, iCount, oValues).
(the SetExcelCellValues is contained within the class ExcelUtility).
ExcelWorksheetImporter
The original “ExcelWorksheetImporter” was constructed to use “Microsoft.Office.Interop.Excel” for performing the Importing of data from Excel. The details below outline the changes that were required to use the ‘OpenXML Libraries’.
In addition, an Interface for the ExcelWorksheetImporter has been introduced.
Using Statements
Original | New |
using Consultingwerk.Utilities.Excel.*. using Microsoft.Office.Interop.Excel. using Progress.Lang.*. | using Consultingwerk.*. using Consultingwerk.Util.*. using Consultingwerk.Utilities.Excel.*. using DocumentFormat.OpenXml.*. using DocumentFormat.OpenXml.Packaging.*. using DocumentFormat.OpenXml.Spreadsheet.*. using Progress.Lang.*. |
Methods
Only two methods were required to be modified and the creation of a new method. The modified methods are:
Import
ImportRows
The new method is:
ExtractRowValues
Import
The “Import” method is the main method and entry point into Importing Data from Excel. The two tables below show the original code and the updated code.
Original Code |
method public void Import (poImportWorksheetArguments as ImportWorksheetArguments, OUTPUT TABLE-handle phTempTable):
define variable oExcel as Microsoft.Office.Interop.Excel.Application no-undo . define variable oWorkbook as Workbook no-undo . define variable oWorksheet as Worksheet no-undo . define variable cFileName as CHARACTER no-undo.
assign cFileName = poImportWorksheetArguments:FileName .
oExcel = new Microsoft.Office.Interop.Excel.ApplicationClass () . oWorkbook = OpenWorkbook (oExcel, cFileName) . oWorksheet = this-object:GetWorksheet (oWorkbook, poImportWorksheetArguments:WorksheetName, poImportWorksheetArguments:WorksheetIndex) .
this-object:ImportRows (oWorksheet, poImportWorksheetArguments, output table-handle phTempTable by-reference) . finally: oExcel:Quit() . end finally. end method. |
New Code |
method public void Import (poImportWorksheetArguments as ImportWorksheetArguments, OUTPUT TABLE-HANDLE phTempTable): define variable oSpreadsheetDocument as SpreadsheetDocument no-undo. define variable oWorkbookPart as WorkbookPart no-undo. define variable oSheetData as SheetData no-undo. define variable cFileName as character no-undo. assign cFileName = poImportWorksheetArguments:FileName . oSpreadsheetDocument = this-object:OpenSpreadsheetDocument (cFileName, false). oWorkbookPart = this-object:GetWorkbookPart (oSpreadsheetDocument). oSheetData = this-object:GetWorksheetSheetData(oWorkbookPart).
this-object:ImportRows (oWorkbookPart, oSheetData, poImportWorksheetArguments, output table-handle phTempTable by-reference) . finally: this-object:CloseSpreadsheetDocument(oSpreadsheetDocument). GarbageCollectorHelper:DeleteObject(oSheetData). GarbageCollectorHelper:DeleteObject(oWorkbookPart). GarbageCollectorHelper:DeleteObject(oSpreadsheetDocument). end finally. end method. |
The code highlighted in “red” within the original code highlights what has been replaced, and the code highlighted in “green” within the new code shows what the code has been replaced with.
Notice that here the call to “OpenSpreadsheetDocument” passes the second parameter as “false” meaning that we are opening the Spreadsheet as ‘Read-Only’. For the ‘ImportRows’ we are passing in the ‘SheetData’ as an extra parameter.
ImportRows
The “ImportRows” method is responsible for reading through the Excel Data and importing the Row values into a Temp Table. The two tables below show the original code and the updated code.
Original Code |
method protected void ImportRows (poWorksheet as Worksheet, poImportWorksheetArgs as ImportWorksheetArguments, output table-handle phTempTable): define variable ifrom as integer no-undo. define variable ito as integer no-undo. define variable iRow as integer no-undo. define variable iCell as integer no-undo. define variable iNumFields as integer no-undo. define variable hBuffer as handle no-undo. define variable lEmptyRow as logical no-undo. define variable iEmptyRows as integer no-undo initial 0 . define variable oValues as "System.Object[]":U no-undo .
assign hBuffer = phTempTable:default-buffer-handle iNumFields = hBuffer:num-fields ifrom = poImportWorksheetArgs:SkipRowsFromtop + 1 ito = poWorksheet:Rows:Count. oValues = {Consultingwerk/new-array.i System.Object iNumFields} . do iRow = ifrom to ito: assign lEmptyRow = TRUE . Consultingwerk.Utilities.Support.ExcelHelper:GetExcelCellValues (poWorksheet, iRow, input-output oValues) . emptyrowloop: do iCell = 1 to iNumFields: if string (oValues:GetValue (iCell - 1)) > "":U then do: assign lEmptyRow = false . leave emptyrowloop . end. end.
if lEmptyRow then iEmptyRows = iEmptyRows + 1 .
if iEmptyRows >= poImportWorksheetArgs:StopAfterEmptyRows then return . hBuffer:buffer-create () . do iCell = 1 to iNumFields on error undo, throw: hBuffer:buffer-field (iCell):buffer-value = unbox (oValues:GetValue (iCell - 1)) . catch err as Progress.Lang.error: undo, throw new ExcelImporterror (err, hBuffer:buffer-field (iCell):NAME, string (unbox (oValues:GetValue (iCell - 1))), iRow, iCell). end catch. end. hBuffer:buffer-release () . this-object:RowsImported = this-object:RowsImported + 1 . if poImportWorksheetArgs:RaiseProgressChangedAfter <> ? then if this-object:RowsImported MODULO poImportWorksheetArgs:RaiseProgressChangedAfter = 0 then onProgressChanged (Consultingwerk.EventArgs:Empty) .
if poImportWorksheetArgs<> ? and this-object:RowsImported >= poImportWorksheetArgs:ImportRows then return . end.
finally: if poImportWorksheetArgs:RaiseProgressChangedAfter <> ? then onProgressChanged (Consultingwerk.EventArgs:Empty) . onCompleted (Consultingwerk.EventArgs:Empty) . end finally. end method. |
New Code |
method protected void ImportRows (poWorkbookPart as WorkbookPart, poSheetData as SheetData, poImportWorksheetArgs as ImportWorksheetArguments, output table-handle phTempTable): define variable oRowEnumerator as System.Collections.IEnumerator no-undo . define variable iRowIndex as integer no-undo. define variable iFrom as integer no-undo. define variable iCount as integer no-undo. define variable iVar as integer no-undo. define variable iCell as integer no-undo. define variable iNumFields as integer no-undo. define variable hBuffer as handle no-undo. define variable lEmptyRow as logical no-undo. define variable iEmptyRows as integer no-undo initial 0 . define variable oValues as "System.Object[]":U no-undo .
assign hBuffer = phTempTable:default-buffer-handle iNumFields = hBuffer:num-fields iFrom = poImportWorksheetArgs:SkipRowsFromTop + 1 iVar = 64 ColumnReferences = "".
oValues = {Consultingwerk/new-array.i System.Object iNumFields} . do iCount = 1 to iNumFields: iVar += 1. ColumnReferences = ColumnReferences + chr(iVar) + ",". end. ColumnReferences = right-trim(ColumnReferences, ","). oRowEnumerator = poSheetData:Descendants():GetEnumerator(). do while oRowEnumerator:MoveNext() on error undo, throw: if oRowEnumerator:Current:GetClass():TypeName = get-class(Row):TypeName then do: assign lEmptyRow = true. iRowIndex = UInt32Value:ToUInt32(cast(oRowEnumerator:Current, DocumentFormat.OpenXml.Spreadsheet.Row):RowIndex). if iRowIndex ge iFrom then do: ExtractRowValues(poWorkbookPart, cast(oRowEnumerator:Current, DocumentFormat.OpenXml.Spreadsheet.Row), input-output oValues). |