Function Excel_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Excel_Services

    Description :   Handler program for all module related services.

    Notes       :   This service module relies upon the GemBox.Spreadsheet .NET library
                    (https://www.gemboxsoftware.com/spreadsheet)

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    Metadata    :

    History     :   (Date, Initials, Notes)
        11/18/17    dmb     Original programmer.
        06/19/18    dmb     Initialize the DocumentObject variable in the GetDocumentObject service.
        06/19/18    dmb     Make the GemBoxDLLPath and the License string dynamic based on the GetServer service.
        09/08/20    djs     Updated the GetDocumentObject, OpenDocument, and SaveDocument functions to support password
                            protected spreadsheets.
        09/16/20    djs     Created ClearCell service to remove any value or forumla in a given cell.
                            Updated SetCellValue service to clear the "Value" and "Formula" properties of the given
                            cell object before setting the value. This is necessary to do if a forumla exists in the
                            cell, otherwise the SetValue method of the cell object will fail to overwrite the formula.
        09/24/20    djs     Fixed a memory leak issue within the GetDocumentObject and SaveDocument services introduced
                            with the addition of password protected spreadsheet support code.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert REVDOTNETEQUATES

Equ Tab$                    to \09\
Equ CRLF$                   to \0D0A\
Equ LF$                     to \0A\
Equ Comma$                  to ','

GemBoxDLLPath   = 'C:\Program Files (x86)\GemBox Software\GemBox.Spreadsheet 4.3\Bin\net35\GemBox.Spreadsheet.dll'
License         = 'E0YV-XKIJ-WFKD-LMN7'
        
Common /ExcelServices/ SpreadsheetInfoObject@, DocumentObjects@, ExcelFileFactoryObjects@, DocumentPaths@, WorksheetsObjects@, WorksheetObjects@, SaveOptionsObjects@, WorksheetObjectsNames@, RowsObjects@, RowObjects@, CellsObjects@, CellObjectsKeyIDs@, CellObjects@

Declare function    Excel_Services, Memory_Services, SRP_List, SRP_FastArray, SRP_Array, Logging_Services
Declare function    Environment_Services
Declare subroutine  Excel_Services, Memory_Services, SRP_List, SRP_FastArray, Logging_Services

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\GaNWIP'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Excel Import Log.csv'
Headers     = 'Logging DTM' : @FM : 'Service' : @FM : 'Notes'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

If DocumentObjects@ EQ '' then
    DocumentObjects@         = SRP_List('Create')
    ExcelFileFactoryObjects@ = SRP_List('Create')
    DocumentPaths@           = SRP_List('Create')
    WorksheetsObjects@       = SRP_List('Create')
    SaveOptionsObjects@      = SRP_List('Create')
    WorksheetObjects@        = SRP_FastArray('Create')
    WorksheetObjectsNames@   = SRP_FastArray('Create')
    RowsObjects@             = SRP_FastArray('Create')
    RowObjects@              = SRP_FastArray('Create')
    CellsObjects@            = SRP_FastArray('Create')
    CellObjectsKeyIDs@       = SRP_List('Create')
    CellObjects@             = SRP_List('Create')
end

GoToService else
    Error_Services('Add', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
end

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//----------------------------------------------------------------------------------------------------------------------
// OpenDocument
//
// DocumentPath     - Path to where the Excel file is located.  - [Required]
// DocumentPassword - Password for protected Excel file.        - [Optional]
//
// Opens an excel document from the indicated path. If successful it returns an object handle.
//----------------------------------------------------------------------------------------------------------------------
Service OpenDocument(DocumentPath, DocumentPassword)

    DocumentObject  = ''

    If Documentpath NE '' then
        DocumentObject  = Excel_Services('GetDocumentObject', DocumentPath, DocumentPassword)
    end else
        Error_Services('Add', 'DocumentPath argument was missing in the ' : Service : ' service.')
    end

    Response    = DocumentObject

end service


//----------------------------------------------------------------------------------------------------------------------
// NewDocument
//
// Opens a new excel document. If successful it returns an object handle.
//----------------------------------------------------------------------------------------------------------------------
Service NewDocument()

    Response    = Excel_Services('GetDocumentObject')

end service


//----------------------------------------------------------------------------------------------------------------------
// SaveDocument
//
// Attempts save the indicated excel document to the indicated path. If DocumentPath is empty, an attempt will be made
// to identify the original path used to open this document. Supports password protected spreadsheets.
//----------------------------------------------------------------------------------------------------------------------
Service SaveDocument(DocumentObject, DocumentPath, DocumentPassword)

    If DocumentObject NE '' then
        If DocumentPath EQ '' then
            Index                 = SRP_List('Locate', DocumentObjects@, DocumentObject)
            DocumentPath          = SRP_List('GetAt', DocumentPaths@, Index)           
        end
        If DocumentPath NE '' then        
            If DocumentPassword NE '' then
                // This document has a password, use the XlsxSaveOptions class to pass in the password
                // via an XlsxSaveOptions object.               
                // Start the .NET engine.
                DocIndex              = SRP_List('Locate', DocumentObjects@, DocumentObject)        
                XlsxSaveOptionsObject = SRP_List('GetAt', SaveOptionsObjects@, DocIndex)
                DocParams             = DocumentPath:@FM:XlsxSaveOptionsObject
                ParamTypes            = 'System.String':@FM:'RevDotNet'
                rv                    = Send_Message.Net(DocumentObject, 'Save', DocParams, ParamTypes, 1)                        
            end else
                rv = Send_Message.Net(DocumentObject, 'Save', DocumentPath)
            end
            StatusCode = ''
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Error calling the Save method in the ' : Service : ' service. Status: ' : StatusCode)
            end
            Excel_Services('CloseDocument', DocumentObject)
        end else
            Error_Services('Add', 'Unable to get DocumentPath in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'DocumentObject argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// CloseDocument
//
// Attempts to close the indicated excel document. This frees up all .NET objects related to this document object and it
// removes all document object associated information tracked by the labelled common handles that the SRP List and
// SRP FastArray functions utilize.
//----------------------------------------------------------------------------------------------------------------------
Service CloseDocument(DocumentObject)

    If DocumentObject NE '' then
        DocObjIndex = SRP_List('Locate', DocumentObjects@, DocumentObject)
        If DocObjIndex GT 0 then
            WorksheetsObject    = SRP_List('GetAt', WorksheetsObjects@, DocObjIndex)
            WorksheetCount      = SRP_FastArray('Count', WorksheetObjects@, DocObjIndex, 0)
            If WorksheetCount GT 0 then
                For WorksheetNum = 1 to WorksheetCount
                    WorksheetObject = SRP_FastArray('Extract', WorksheetObjects@, DocObjIndex, WorksheetNum, 1)
                    Free_Class.Net(WorksheetObject)
                Next WorksheetCount
            end
            AllCellObjectKeyIDs = SRP_List('GetVariable', CellObjectsKeyIDs@, @FM)
            Convert '*' to @VM in AllCellObjectKeyIDs
            AllCellDocumentObjs = SRP_Array('Rotate', AllCellObjectKeyIDs, @FM, @VM)<1>
            Convert @VM to @FM in AllCellDocumentObjs
            NumCells    = DCount(AllCellDocumentObjs, @FM)
            For CellIndex = NumCells to 1 Step -1
                If AllCellDocumentObjs<CellIndex> EQ DocumentObject then
                    SRP_List('RemoveAt', CellObjectsKeyIDs@, CellIndex)
                    SRP_List('RemoveAt', CellObjects@, CellIndex)
                end
            Next CellIndex
            SRP_FastArray('Delete', CellsObjects@, DocObjIndex, 0, 0)
            SRP_FastArray('Delete', RowObjects@, DocObjIndex, 0, 0)
            SRP_FastArray('Delete', RowsObjects@, DocObjIndex, 0, 0)
            SRP_FastArray('Delete', WorksheetObjects@, DocObjIndex, 0, 0)
            SRP_FastArray('Delete', WorksheetObjectsNames@, DocObjIndex, 0, 0)           
            ExcelFileFactoryObject = SRP_List('GetAt', ExcelFileFactoryObjects@, DocObjIndex)
            XlsxSaveOptionsObject  = SRP_List('GetAt', SaveOptionsObjects@, DocObjIndex)
            Free_Class.Net(WorksheetsObject)
            Free_Class.Net(ExcelFileFactoryObject)
            Free_Class.Net(DocumentObject)
            Free_Class.Net(XlsxSaveOptionsObject)
            SRP_List('RemoveAt', WorksheetsObjects@, DocObjIndex)
            SRP_List('RemoveAt', DocumentPaths@, DocObjIndex)
            SRP_List('RemoveAt', ExcelFileFactoryObjects@, DocObjIndex)
            SRP_List('RemoveAt', DocumentObjects@, DocObjIndex)
            SRP_List('RemoveAt', SaveOptionsObjects@, DocObjIndex)
        end
    end else
        Error_Services('Add', 'DocumentObject argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// DocumentExists
//
// Returns a boolean result as to whether the indicated excel document exists.
//----------------------------------------------------------------------------------------------------------------------
Service DocumentExists(DocumentPath)

    Exists  = False$    ; // Assume false for now.

    If DocumentPath NE '' then
        DocumentInfo    = Dir(DocumentPath)
        If DocumentInfo EQ '' then
            Exists  = False$
        end else
            Exists  = True$
        end
    end else
        Error_Services('Add', 'DocumentPath argument was missing in the ' : Service : ' service.')
    end

    Response    = Exists

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWorksheet
//
// Returns an object handle to a worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service GetWorksheet(DocumentObject, WorksheetName, AddIfMissing)

    If AddIfMissing NE True$ then AddIfMissing = False$

    WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)

    If WorksheetObject EQ '' then
        If (DocumentObject NE '') AND (WorksheetName NE '') then
            If AddIfMissing then
                WorksheetObject = Excel_Services('AddWorksheet', DocumentObject, WorksheetName)
            end
        end else
            Error_Services('Add', 'DocumentObject or WorksheetName argument was missing in the ' : Service : ' service.')
        end
    end
    
    Response    = WorksheetObject

end service


//----------------------------------------------------------------------------------------------------------------------
// AddWorksheet
//
// Attempts to add a worksheet to the indicated excel document object. If successful, the object to the worksheet will
// be returned.
//----------------------------------------------------------------------------------------------------------------------
Service AddWorksheet(DocumentObject, WorksheetName)

    WorksheetObject = ''

    If (DocumentObject NE '') AND (WorksheetName NE '') then
        WorksheetsObject    = Excel_Services('GetWorksheetsObject', DocumentObject)
        If Error_Services('NoError') then
            rv      = Send_Message.Net(WorksheetsObject, 'Add', WorksheetName)
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Error calling the Add method in the ' : Service : ' service. Status: ' : StatusCode)
            end else
                WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)
            end
        end
    end else
        Error_Services('Add', 'DocumentObject or WorksheetName argument was missing in the ' : Service : ' service.')
    end

    Response    = WorksheetObject

end service


//----------------------------------------------------------------------------------------------------------------------
// WorksheetExists
//
// Returns a boolean result as to whether the indicated worksheet exists.
//----------------------------------------------------------------------------------------------------------------------
Service WorksheetExists(DocumentObject, WorksheetName)

    Exists  = False$    ; // Assume false for now.

    If (DocumentObject NE '') AND (WorksheetName NE '') then
        WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)
        If WorksheetObject EQ '' then
            Exists  = False$
        end else
            Exists  = True$
        end
    end else
        Error_Services('Add', 'DocumentObject or WorksheetName argument was missing in the ' : Service : ' service.')
    end

    Response    = Exists

end service


//----------------------------------------------------------------------------------------------------------------------
// GetNumRows
//
// 
//----------------------------------------------------------------------------------------------------------------------
Service GetNumRows(DocumentObject, WorksheetName)

    NumRows   = ''
    If (DocumentObject NE '') AND (WorksheetName NE '') then
        WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)
        If Error_Services('NoError') then
            NumRows   = Get_Property.Net(WorksheetObject, 'Rows.Count')
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Error sending message in the ' : Service : ' service. Status: ' : StatusCode)
            end
        end
    end else
        Error_Services('Add', 'DocumentObject or WorksheetName argument was missing in the ' : Service : ' service.')
    end

    Response    = NumRows

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCellValue
//
// Attempts to get the value in the indicated cell from the indicated worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service GetCellValue(DocumentObject, WorksheetName, CellColumn, CellRow)

    CellValue   = ''

    If (DocumentObject NE '') AND (WorksheetName NE '') AND (CellColumn NE '') AND (CellRow NE '') then
        CellObject  = Excel_Services('GetCellObject', DocumentObject, WorksheetName, CellColumn, CellRow)
        If Error_Services('NoError') then
            CellValue   = Get_Property.Net(CellObject, 'Value')
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Error getting Value property in the ' : Service : ' service. Status: ' : StatusCode)
            end
        end
    end else
        Error_Services('Add', 'DocumentObject, WorksheetName, CellColumn, or CellRow argument was missing in the ' : Service : ' service.')
    end

    Response    = CellValue

end service


//----------------------------------------------------------------------------------------------------------------------
// SetCellValue
//
// Attempts to set the value in the indicated cell from the indicated worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service SetCellValue(DocumentObject, WorksheetName, CellColumn, CellRow, CellValue)

    If (DocumentObject NE '') AND (WorksheetName NE '') AND (CellColumn NE '') AND (CellRow NE '') AND (CellValue NE '') then
        CellObject  = Excel_Services('GetCellObject', DocumentObject, WorksheetName, CellColumn, CellRow)
        // Clear the cell before writing to it. If a forumla exists in the cell, SetValue will fail.
        rv = Set_Property.Net(CellObject, 'Value', "")
        rv = Set_Property.Net(CellObject, 'Formula', "")
        If Error_Services('NoError') then
            If Num(CellValue) then
                If Index(CellValue, '.', 1) then
                    CellType    = 'System.Double'
                end else
                    CellType    = 'System.Int32'
                end
            end else
                CellType    = 'System.String'
            end
            rv  = Send_Message.Net(CellObject, 'SetValue', CellValue, CellType)
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Error getting Value property in the ' : Service : ' service. Status: ' : StatusCode)
            end
        end
    end else
        Error_Services('Add', 'DocumentObject, WorksheetName, CellColumn, CellRow, or CellValue argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ClearCell
//
// Attempts to clear the data in the indicated cell from the indicated worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service ClearCell(DocumentObject, WorksheetName, CellColumn, CellRow, ClearOptions)
    
    If (DocumentObject NE '') AND (WorksheetName NE '') AND (CellColumn NE '') AND (CellRow NE '') AND (ClearOptions NE '') then
        CellObject = Excel_Services('GetCellObject', DocumentObject, WorksheetName, CellColumn, CellRow)
        rv         = Set_Property.Net(CellObject, 'Value', "")
        rv         = Set_Property.Net(CellObject, 'Formula', "")
    end else
        Error_Services('Add', 'DocumentObject, WorksheetName, CellColumn, CellRow, or CellValue argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetDocumentObject
//
// DocumentPath     - Path to where the log file is located. - [Optional]
// DocumentPassword - Password to Excel Spreadsheet          - [Optional]
//
// Returns an object handle to an excel document. If DocumentPath is empty then an object to a new excel document
// is created.
//----------------------------------------------------------------------------------------------------------------------
Service GetDocumentObject(DocumentPath, DocumentPassword)

    DocumentObject        = ''
    XlsxSaveOptionsObject = ''
    // Start the .NET engine.
    DotNetObject          = StartDotNet('')
    If Get_Status(StatusCode) then
        Error_Services('Add', 'Unable to create DotNetObject in the ' : Service : ' service. Status: ' : StatusCode)
    end else
        // Load a .NET assembly. This creates the system object (DotNetObject).
        rv  = Set_Property.Net(DotNetObject, 'AssemblyName', GemBoxDLLPath)
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Unable to load the GemBox .NET assembly in the ' : Service : ' service. Status: ' : StatusCode)
        end else
            // Create a Spreadsheet handle/class directly from the system object. This is necessary to activate the product license.
            If SpreadsheetInfoObject@ EQ '' then
                SpreadsheetInfoObject@  = Create_Class.Net(DotNetObject, 'GemBox.Spreadsheet.SpreadsheetInfo', 0)
                If Get_Status(StatusCode) then
                    Error_Services('Add', 'Unable to create SpreadsheetInfoObject in the ' : Service : ' service. Status: ' : StatusCode)
                end else
                    // Set the product license.
                    rv  = Send_Message.Net(SpreadsheetInfoObject@, 'SetLicense', License)
                    If Get_Status(StatusCode) then
                        Error_Services('Add', 'Unable to set license. Status: ' : StatusCode)
                    end
                end
            end
            If Error_Services('NoError') then
                // Create an Excel factory file handle/class directly from the system object.
                ExcelFileFactoryObject  = Create_Class.Net(DotNetObject, 'GemBox.Spreadsheet.ExcelFile', 0)
                If Get_Status(StatusCode) then
                    Error_Services('Add', 'Unable to create ExcelFileFactoryObject in the ' : Service : ' service. Status: ' : StatusCode)
                end else                    
                    If DocumentPath NE '' then
                        // This is is an existing document, use the Load method to create a document object.
                        If DocumentPassword NE '' then
                            // This document has a password, use the XlsxLoadOptions class to pass in the password
                            // via an XlsxLoadOptions object.                        
                            XlsxLoadOptionsObject = Create_Class.Net(DotNetObject, 'GemBox.Spreadsheet.XlsxLoadOptions')
                            rv                    = Send_Message.Net(XlsxLoadOptionsObject, 'set_Password', DocumentPassword, 'System.String', 1)
                            XlsxSaveOptionsObject = Create_Class.Net(DotNetObject, 'GemBox.Spreadsheet.XlsxSaveOptions')
                            rv                    = Send_Message.Net(XlsxSaveOptionsObject, 'set_Password', DocumentPassword, 'System.String', 1)  
                            If Get_Status(StatusCode) then
                                Error_Services('Add', 'Unable to create XlsxLoadOptionsObject in the ' : Service : ' service. Status: ' : StatusCode)
                            end else
                                DocParams       = DocumentPath:@FM:XlsxLoadOptionsObject
                                ParamTypes      = 'System.String':@FM:'RevDotNet'
                                DocumentObject  = Send_Message.Net(ExcelFileFactoryObject, 'Load', DocParams, ParamTypes, 1)
                                If Get_Status(StatusCode) then
                                    Error_Services('Add', 'Unable to create DocumentObject in the ' : Service : ' service. Status: ' : StatusCode)
                                end
                            end
                            Free_Class.Net(XlsxLoadOptionsObject)                           
                        end else
                            DocumentObject  = Send_Message.Net(ExcelFileFactoryObject, 'Load', DocumentPath, 'System.String', 1)
                            If Get_Status(StatusCode) then
                                Error_Services('Add', 'Unable to create DocumentObject in the ' : Service : ' service. Status: ' : StatusCode)
                            end
                        end
                    end else
                        // This this is a new document, the document object will be the excel file factory object.
                        DocumentObject  = ExcelFileFactoryObject
                    end
                end
            end
        end
    end

    If Error_Services('NoError') then
        DocObjIndex = SRP_List('Add', DocumentObjects@, DocumentObject)
        SRP_List('SetAt', ExcelFileFactoryObjects@, DocObjIndex, ExcelFileFactoryObject)
        SRP_List('SetAt', DocumentPaths@, DocObjIndex, DocumentPath)
        SRP_List('SetAt', SaveOptionsObjects@, DocObjIndex, XlsxSaveOptionsObject)
    end else
        If Assigned(ExcelFileFactoryObject) then Free_Class.Net(ExcelFileFactoryObject)
        If Assigned(DocumentObject) then Free_Class.Net(DocumentObject)
        If Assigned(XlsxSaveOptionsObject) then Free_Class.Net(XlsxSaveOptionsObject)
    end

    Response    = DocumentObject

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWorksheetsObject
//
// Returns the worksheets (plural) object for the indicated excel document object.
//----------------------------------------------------------------------------------------------------------------------
Service GetWorksheetsObject(DocumentObject)

    WorksheetsObject    = ''
    DocObjIndex         = SRP_List('Locate', DocumentObjects@, DocumentObject)
    If DocObjIndex GT 0 then
        WorksheetsObject    = SRP_List('GetAt', WorksheetsObjects@, DocObjIndex)
        If WorksheetsObject EQ '' then
            WorksheetsObject    = Get_Property.Net(DocumentObject, 'Worksheets', 1)
            If Get_Status(StatusCode) then
                Error_Services('Add', 'Unable to create WorksheetsObject in the ' : Service : ' service. Status: ' : StatusCode)
            end else
                SRP_List('SetAt', WorksheetsObjects@, DocObjIndex, WorksheetsObject)
            end
        end
    end else
        Error_Services('Add', 'DocumentObject is missing or does not appear to be valid in the ' : Service : ' service.')
    end

    Response    = WorksheetsObject

end service


//----------------------------------------------------------------------------------------------------------------------
// GetWorksheetObject
//
// Returns an object handle to a worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service GetWorksheetObject(DocumentObject, WorksheetName)

    WorksheetObject = ''
    DocObjIndex     = SRP_List('Locate', DocumentObjects@, DocumentObject)
    If DocObjIndex GT 0 then
        If Worksheetname NE '' then
            WSObjsIndex = 0
            SRP_FastArray('Match', WorksheetObjectsNames@, WorksheetName, DocObjIndex : '', WSObjsIndex, 0, 'MatchAll')
            If WSObjsIndex GT 0 then
                WorksheetObject = SRP_FastArray('Extract', WorksheetObjects@, DocObjIndex, WSObjsIndex, 0)
            end
            If WorksheetObject EQ '' then
                WorksheetsObject    = Excel_Services('GetWorksheetsObject', DocumentObject)
                If Error_Services('NoError') then
                    WorksheetCount      = Get_Property.Net(WorksheetsObject, 'Count')
                    If Get_Status(StatusCode) then
                        Error_Services('Add', 'Error getting WorksheetCount property in the ' : Service : ' service. Status: ' : StatusCode)
                    end else
                        WorksheetCount -= 1 ; // This uses zero-based indexing so the first worksheet is 0 rather than 1.
                        For WorksheetNum = 0 to WorksheetCount
                            CurrentWorksheetObject  = Get_Property.Net(WorksheetsObject, 'Item[' : WorksheetNum : ']', 1)
                            If Get_Status(StatusCode) then
                                Error_Services('Add', 'Unable to create CurrentWorksheetObject in the ' : Service : ' service. Status: ' : StatusCode)
                            end else
                                Name    = Get_Property.Net(CurrentWorksheetObject, 'Name')
                                If Get_Status(StatusCode) then
                                    Error_Services('Add', 'Error getting Name property in the ' : Service : ' service. Status: ' : StatusCode)
                                end else
                                    If WorksheetName EQ Name then
                                        WorksheetObject = CurrentWorksheetObject
                                    end
                                    WSObjsIndex = 0
                                    SRP_FastArray('Match', WorksheetObjectsNames@, Name, DocObjIndex : '', WSObjsIndex, 0, 'MatchAll')
                                    If WSObjsIndex EQ 0 then
                                        // Cache the known worksheet objects and associated names for quicker retrieval later.
                                        SRP_FastArray('Insert', WorksheetObjects@, DocObjIndex, -1, 1, CurrentWorksheetObject)
                                        SRP_FastArray('Insert', WorksheetObjectsNames@, DocObjIndex, -1, 1, Name)
                                    end
                                end
                            end
                        While Error_Services('NoError')
                        Next WorksheetCount
                        If WorksheetObject EQ '' then
                            Error_Services('Add', 'Unable to find worksheet ' : WorksheetName : ' in the current excel document in the ' : Service : ' service.')
                        end
                    end
                end
            end
        end else
            Error_Services('Add', 'WorksheetName argument was missing in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'DocumentObject is missing or does not appear to be valid in the ' : Service : ' service.')
    end
   
    Response    = WorksheetObject

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCellObject
//
// Attempts to get the value in the indicated cell from the indicated worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service GetCellObject(DocumentObject, WorksheetName, CellColumn, CellRow)

    CellObject  = ''

    DocObjIndex     = SRP_List('Locate', DocumentObjects@, DocumentObject)
    If DocObjIndex GT 0 then
        If (WorksheetName NE '') AND (CellColumn NE '') AND (CellRow NE '') then
            OrigCellColumn  = CellColumn
            OrigCellRow     = CellRow
            If Not(Num(CellColumn)) then
                // CellColumn is presented in alpha format. Convert to number.
                Convert @Lower_Case to @Upper_Case in CellColumn
                NumColumn       = 0
                LenCellColumn   = Len(CellColumn)
                For Int = 1 to LenCellColumn
                    NumColumn   = NumColumn * 26
                    Char        = CellColumn[Int, 1]
                    NumColumn  += (Seq(Char) - Seq('A')) + 1
                Next Int
                Transfer NumColumn to CellColumn
            end
            CellColumn -= 1 ; // Adjust or 0-based indexing.
            CellRow    -= 1 ; // Adjust or 0-based indexing.
            WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)
            
            If Error_Services('NoError') then
                WSObjsIndex = 0
                SRP_FastArray('Match', WorksheetObjectsNames@, WorksheetName, DocObjIndex : '', WSObjsIndex, 0, 'MatchAll')
                If WSObjsIndex GT 0 then
                    // There is enough information to create the flat Key ID that links to this CellObject if it has
                    // already been created. Look for it now before moving forward.
                    CellObjectKeyID = DocumentObject : '*' : WorksheetName : '*' : OrigCellColumn : '*' : OrigCellRow
                    CellObjIndex    = SRP_List('Locate', CellObjectsKeyIDs@, CellObjectKeyID)
                    If CellObjIndex GT 0 then
                        CellObject  = SRP_List('GetAt', CellObjects@, CellObjIndex)
                    end
                end
            end
            
            If Error_Services('NoError') AND (CellObject EQ '') then
                // The RowsObject has a 1-to-1 relationship with the WorksheetObject. See if it already exists at the
                // same index in WorksheetObjects (WSObjsIndex) that the current WorksheetObject is located in.
                
                RowsObject  = SRP_FastArray('Extract', RowsObjects@, DocObjIndex, WSObjsIndex, 0)
                If RowsObject EQ '' then
                    RowsObject  = Get_Property.Net(WorksheetObject, 'Rows', 1)
                    If Get_Status(StatusCode) then
                        Error_Services('Add', 'Unable to create the Rows object in the ' : Service : ' service. Status: ' : StatusCode)
                    end else
                        SRP_FastArray('Replace', RowsObjects@, DocObjIndex, WSObjsIndex, 0, RowsObject)
                    end
                end

                If Error_Services('NoError') then
                    // The RowObject has a 1-to-Many relationship with the RowsObject. So the same DocumentObject index
                    // (DocObjIndex) and WorksheetObjects index (WSObjsIndex) will be used. The cell row will be used
                    // as the third index to find the RowObject.
                    
                    RowObject   = SRP_FastArray('Extract', RowObjects@, DocObjIndex, WSObjsIndex, OrigCellRow)
                    If RowObject EQ '' then
                        RowObject   = Get_Property.Net(RowsObject, 'Item[' : CellRow : ']', 1)
                        If Get_Status(StatusCode) then
                            Error_Services('Add', 'Unable to create the Item object (Rows) in the ' : Service : ' service. Status: ' : StatusCode)
                        end else
                            SRP_FastArray('Replace', RowObjects@, DocObjIndex, WSObjsIndex, OrigCellRow, RowObject)
                        end
                    end
                    
                    If Error_Services('NoError') then
                        // The CellsObject has a 1-to-1 relationship with the RowObject. Use the same indexes to see if
                        // it already exists.
                        
                        CellsObject = SRP_FastArray('Extract', CellsObjects@, DocObjIndex, WSObjsIndex, OrigCellRow)
                        If CellsObject EQ '' then
                            CellsObject = Get_Property.Net(RowObject, 'Cells', 1)
                            If Get_Status(StatusCode) then
                                Error_Services('Add', 'Unable to create the Cells object in the ' : Service : ' service. Status: ' : StatusCode)
                            end else
                                SRP_FastArray('Replace', CellsObjects@, DocObjIndex, WSObjsIndex, OrigCellRow, CellsObject)
                            end
                        end
                        
                        If Error_Services('NoError') then
                            // The CellObject has a 1-to-Many relationship with the CellsObject. Since this extends the
                            // hierarchical relationship to the fourth tier (relatively to the document object, the
                            // SRP_FastArray function cannot be used. Therefore, a multi-part Key ID will be created
                            // and this will be stored in a simple list. This list will contain the index to another
                            // list which stores the cell objects.              
                            CellObject  = Get_Property.Net(CellsObject, 'Item[' : CellColumn : ']', 1)
                            If Get_Status(StatusCode) then                                                           
                                Error_Services('Add', 'Unable to create the Item object (Cells) in the ' : Service : ' service. Status: ' : StatusCode)                               
                            end else                               
                                CellObjectKeyID = DocumentObject : '*' : WorksheetName : '*' : OrigCellColumn : '*' : OrigCellRow
                                SRP_List('Add', CellObjectsKeyIDs@, CellObjectKeyID)
                                SRP_List('Add', CellObjects@, CellObject)
                            end                            
                        end
                    end 
                end
            end
        end else
            Error_Services('Add', 'WorksheetName, CellColumn, or CellRow argument was missing in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'DocumentObject is missing or does not appear to be valid in the ' : Service : ' service.')
    end

    Response    = CellObject

end service


//----------------------------------------------------------------------------------------------------------------------
// GetCellRangeObject
//
// Attempts to get a cell range object from the indicated worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service GetCellRangeObject(DocumentObject, WorksheetName, CellRangeBegin, CellRangeEnd)
    
    WorksheetObject = Excel_Services('GetWorksheetObject', DocumentObject, WorksheetName)
    CellsObject     = Get_Property.Net(WorksheetObject, 'Cells', 1)
    If Get_Status(StatusCode) then
        Error_Services('Add', 'Unable to retrieve the Cells object in the ' : Service : ' service. Status: ' : StatusCode)             
    end else
        Params          = CellRangeBegin:@FM:CellRangeEnd
        ParamTypes      = 'System.String':@FM:'System.String'
        CellRangeObject = Send_Message.Net(CellsObject, 'GetSubrange', Params , ParamTypes, 1)
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Unable to create the CellRange object in the ' : Service : ' service. Status: ' : StatusCode)         
        end
        Response = CellRangeObject    
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// CopyCellRange
//
// Attempts to copy a range of cells to another location in the same worksheet.
//----------------------------------------------------------------------------------------------------------------------
Service CopyCellRange(DocumentObject, WorksheetName, CellRangeBegin, CellRangeEnd, CopyToCell)
    
    CellRangeObject = Excel_Services('GetCellRangeObject', DocumentObject, WorksheetName, CellRangeBegin, CellRangeEnd)
    If Error_Services('NoError') then
        rv = Send_Message.Net(CellRangeObject, 'CopyTo', CopyToCell, 'System.String', 0)
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Unable to copy cell range in the ' : Service : ' service. Status: ' : StatusCode)      
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// DestroyAllObjects
//
// Destroys all .NET objects that were created. Note: this destroys other.NET objects that may have been created in
// other BASIC+ routines. Use this service when a quick clean slate is needed.
//----------------------------------------------------------------------------------------------------------------------
Service DestroyAllObjects()

    Free_Class.Net()
    SRP_List('Release', DocumentObjects@)
    SRP_List('Release', ExcelFileFactoryObjects@)
    SRP_List('Release', DocumentPaths@)
    SRP_List('Release', WorksheetsObjects@)
    SRP_FastArray('Release', WorksheetObjects@)
    SRP_FastArray('Release', WorksheetObjectsNames@)
    SRP_FastArray('Release', RowsObjects@)
    SRP_FastArray('Release', RowObjects@)
    SRP_FastArray('Release', CellsObjects@)
    SRP_List('Release', CellObjectsKeyIDs@)
    SRP_List('Release', CellObjects@)
    FreeCommon 'EXCELSERVICES'
    Memory_Services('ReleaseHashTable')

end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


