diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba index 0f3d6c5d9f67..d0f557a01fcc 100644 --- a/wizards/source/scriptforge/SF_Array.xba +++ b/wizards/source/scriptforge/SF_Array.xba @@ -2254,8 +2254,7 @@ Public Function Reduce(Optional ByVal Module As Variant _ ''' The script must be written in Basic. ''' Reduce() returns the value returned by the last iteration. ''' Module: a Basic object referring either to a module, or to an instance of a Basic class. -''' Method: the method to execute, as a string -''' When Module is a ScriptForge object, Method may also be a property (get). +''' Method: the method to execute, as a string. The method must have two arguments. ''' Params: a 1D array where each item represents the argument to pass for a given iteration. ''' InitialValue: when present, the first argument of the first iteration. ''' Return: @@ -3334,4 +3333,4 @@ Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer End Function ' ScriptForge.SF_Array._ValCompare REM ================================================= END OF SCRIPTFORGE.SF_ARRAY - + \ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Root.xba b/wizards/source/scriptforge/SF_Root.xba index 5e2f0591592d..6446aa0f04f4 100644 --- a/wizards/source/scriptforge/SF_Root.xba +++ b/wizards/source/scriptforge/SF_Root.xba @@ -71,6 +71,7 @@ Private CharacterClass As Object ' com.sun.star.i18n.CharacterClassificati Private FileAccess As Object ' com.sun.star.ucb.SimpleFileAccess Private FilterFactory As Object ' com.sun.star.document.FilterFactory Private URLTransformer As Object ' com.sun.star.util.URLTransformer +Private Invocation As Object ' com.sun.star.script.Invocation Private Introspection As Object ' com.sun.star.beans.Introspection Private BrowseNodeFactory As Object ' com.sun.star.script.browse.BrowseNodeFactory Private DatabaseContext As Object ' com.sun.star.sdb.DatabaseContext @@ -142,6 +143,7 @@ Private Sub Class_Initialize() Set FileAccess = Nothing Set FilterFactory = Nothing Set URLTransformer = Nothing + Set Invocation = Nothing Set Introspection = Nothing FileSystemNaming = "ANY" PythonHelper = "ScriptForgeHelper.py" @@ -659,12 +661,12 @@ Try: & "%4: An identifier\n" _ & "%5: A string" _ ) - ' SF_Session.ExecuteBasicScript + ' SF_Session.ExecuteBasicScript/ExecutePythonScript .AddText( Context := "SCRIPTEXEC" _ - , MsgId := "An exception occurred during the execution of the Basic script.\n" _ + , MsgId := "An exception occurred during the execution of the script.\n" _ & "Cause: %3\n" _ & "« %1 » = %2" _ - , Comment := "SF_Session.ExecuteBasicScript error message\n" _ + , Comment := "SF_Session.ExecuteBasicScript/ExecutePythonScript error message\n" _ & "%1: An identifier\n" _ & "%2: A string\n" _ & "%3: A (long) string" _ diff --git a/wizards/source/scriptforge/SF_Session.xba b/wizards/source/scriptforge/SF_Session.xba index 842436811405..4216f32edf06 100644 --- a/wizards/source/scriptforge/SF_Session.xba +++ b/wizards/source/scriptforge/SF_Session.xba @@ -314,6 +314,7 @@ Try: ' Execute script If Not IsNull(oScript) Then + On Local Error GoTo CatchExec vReturn = oScript.Invoke(pvArgs(), Array(), Array()) ' Remove surrounding array when single returned value If IsArray(vReturn) Then @@ -327,90 +328,11 @@ Finally: Exit Function Catch: GoTo Finally +CatchExec: + SF_Exception.RaiseFatal(SCRIPTEXECERROR, "Script", Script, Error$) + GoTo Finally End Function ' ScriptForge.SF_Session.ExecutePythonScript -REM ----------------------------------------------------------------------------- -Public Function GetRangeFromCalc(Optional ByVal FileName As Variant _ - , Optional ByVal Range As Variant _ - ) As Variant -''' Extract the content of a range of adjacent cells stored in a Calc file. -''' The file may be open or closed. If the file is open in the actual LibreOffice -''' session, its actual content will be read. Otherwise the content will be -''' read from the file on disk. -''' Inspired by https://bugs.documentfoundation.org/show_bug.cgi?id=148040 -''' Args: -''' FileName: the full file name, given in FileSystem.FileNaming notation. -''' The file must exist. -''' Range: either -''' "sheet.cells", where "sheet" is the mandatory sheet name, eventually -''' surrounded with single quotes when it contains special characters, -''' and "cells" is the single cell or the range of cells -''' to be considered, in A1 notation with or without dollar signs -''' a defined name at document-global level. -''' Current selection shortcuts ("~") are forbidden in this context. -''' Returns: -''' Either a scalar, or a 1D array when the range is located in a single row or column, -''' or a 2D array. -''' The returned values are either strings or numbers. No dates, booleans, ... -''' To convert doubles to dates, use the CDate builtin function. -''' Empty cells are returned as empty strings. -''' Exceptions: -''' UNKNOWNFILEERROR The Calc file does not exist -''' CALCADDRESSERROR Invalid Calc range -''' Example: -''' value = session.GetFromFileName("C:\myFile.ods", "Sheet1.A1:J10") - -Dim vReturn As Variant ' Returned value -Dim oCalc As Object ' Give access to the com.sun.star.sheet.FunctionAccess service -Dim sArgument As String ' The argument given to the "Indirect" Calc function - -Const cstThisSub = "Session.GetRangeFromCalc" -Const cstSubArgs = "FileName, Range" - - If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch - vReturn = Empty - -Check: - If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then - If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally - If Not SF_Utils._Validate(Range, "Range", V_STRING) Then GoTo Finally - End If - - If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchNotExists - -Try: - ' The execution results from the com.sun.star.sheet.FunctionAccess service - Set oCalc = SF_Utils._GetUNOService("FunctionAccess") - - ' Error trapping, execution, error reset - If SF_Utils._ErrorHandling() Then On Local Error GoTo CatchCall - sArgument = "'" & SF_FileSystem._ConvertToUrl(FileName) & "'#" & Range - vReturn = oCalc.callFunction("INDIRECT", Array(sArgument)) - If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch - - ' Transform the arrays of subarrays => 2D arrays, only in Basic - Select Case SF_Array.CountDims(vReturn) - Case -1, 0, 2 - ' Do nothing: scalar or empty array or 2D array (should not happen) - Case 1 - If IsArray(vReturn(0)) And Not _SF_.TriggeredByPython Then vReturn = SF_Array.ConvertFromDataArray(vReturn, IsRange := True) - Case Else - End Select - -Finally: - GetRangeFromCalc = vReturn - SF_Utils._ExitFunction(cstThisSub) - Exit Function -Catch: - GoTo Finally -CatchNotExists: - SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) - GoTo Finally -CatchCall: - ScriptForge.SF_Exception.RaiseFatal(CALCADDRESSERROR, "Range", Range, "FileName", FileName) - GoTo Finally -End Function ' ScriptForge.SF_Session.GetRangeFromCalc - REM ----------------------------------------------------------------------------- Public Function GetPDFExportOptions() As Variant ''' Return the actual values of the PDF export options @@ -499,6 +421,173 @@ Catch: GoTo Finally End Function ' ScriptForge.SF_Session.GetProperty +REM ----------------------------------------------------------------------------- +Public Function GetPropertyByName(Optional ByRef BasicObject As Variant _ + , Optional ByVal PropertyName As Variant _ + , Optional ByVal Arg As Variant _ + ) As Variant +''' Return the actual value of the given property related to the given ScriptForge/Basic object +''' Args: +''' BasicObject: a ScriptForge Basic module or a ScriptForge Basic class instance +''' PropertyName: the name of the property as a string +''' Arg: some properties have 1 argument +''' Returns: +''' The actual value of the property or Null/None if the method failed. +''' Exceptions +''' ARGUMENTERROR The argument does not pass the validation rules + +Dim vProperty As Variant ' Return value + +Const cstThisSub = "Session.GetPropertyByName" +Const cstSubArgs = "BasicObject, PropertyName, [Arg]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vProperty = Null + +Check: + If IsMissing(Arg) Then Arg = Empty + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + If Not SF_Utils._Validate(BasicObject, "BasicObject", V_OBJECT, , , "ANY") Then GoTo Catch + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, BasicObject.Properties()) Then GoTo Catch + +Try: + With BasicObject + If IsEmpty(Arg) Then + vProperty = .GetProperty(PropertyName) + Else + vProperty = .GetProperty(PropertyName, Arg) + End If + End With + +Finally: + GetPropertyByName = vProperty + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.GetPropertyByName + +REM ----------------------------------------------------------------------------- +Public Function GetRangeFromCalc(Optional ByVal FileName As Variant _ + , Optional ByVal Range As Variant _ + ) As Variant +''' Extract the content of a range of adjacent cells stored in a Calc file. +''' The file may be open or closed. If the file is open in the actual LibreOffice +''' session, its actual content will be read. Otherwise the content will be +''' read from the file on disk. +''' Inspired by https://bugs.documentfoundation.org/show_bug.cgi?id=148040 +''' Args: +''' FileName: the full file name, given in FileSystem.FileNaming notation. +''' The file must exist. +''' Range: either +''' "sheet.cells", where "sheet" is the mandatory sheet name, eventually +''' surrounded with single quotes when it contains special characters, +''' and "cells" is the single cell or the range of cells +''' to be considered, in A1 notation with or without dollar signs +''' a defined name at document-global level. +''' Current selection shortcuts ("~") are forbidden in this context. +''' Returns: +''' Either a scalar, or a 1D array when the range is located in a single row or column, +''' or a 2D array. +''' The returned values are either strings or numbers. No dates, booleans, ... +''' To convert doubles to dates, use the CDate builtin function. +''' Empty cells are returned as empty strings. +''' Exceptions: +''' UNKNOWNFILEERROR The Calc file does not exist +''' CALCADDRESSERROR Invalid Calc range +''' Example: +''' value = session.GetFromFileName("C:\myFile.ods", "Sheet1.A1:J10") + +Dim vReturn As Variant ' Returned value +Dim oCalc As Object ' Give access to the com.sun.star.sheet.FunctionAccess service +Dim sArgument As String ' The argument given to the "Indirect" Calc function + +Const cstThisSub = "Session.GetRangeFromCalc" +Const cstSubArgs = "FileName, Range" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vReturn = Empty + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Range, "Range", V_STRING) Then GoTo Finally + End If + + If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchNotExists + +Try: + ' The execution results from the com.sun.star.sheet.FunctionAccess service + Set oCalc = SF_Utils._GetUNOService("FunctionAccess") + + ' Error trapping, execution, error reset + If SF_Utils._ErrorHandling() Then On Local Error GoTo CatchCall + sArgument = "'" & SF_FileSystem._ConvertToUrl(FileName) & "'#" & Range + vReturn = oCalc.callFunction("INDIRECT", Array(sArgument)) + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + ' Transform the arrays of subarrays => 2D arrays, only in Basic + Select Case SF_Array.CountDims(vReturn) + Case -1, 0, 2 + ' Do nothing: scalar or empty array or 2D array (should not happen) + Case 1 + If IsArray(vReturn(0)) And Not _SF_.TriggeredByPython Then vReturn = SF_Array.ConvertFromDataArray(vReturn, IsRange := True) + Case Else + End Select + +Finally: + GetRangeFromCalc = vReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +CatchCall: + ScriptForge.SF_Exception.RaiseFatal(CALCADDRESSERROR, "Range", Range, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_Session.GetRangeFromCalc + +REM ----------------------------------------------------------------------------- +Public Function GetUnoProperty(Optional ByRef UnoObject As Variant _ + , Optional ByVal PropertyName As Variant _ + ) As Variant +''' Return the actual value of the given property related to the given UNO object +''' Args: +''' UnoObject: the UNO object to consider +''' PropertyName: the exact name of the property as a string +''' Returns: +''' The actual value of the property or Null/None if the method failed to find the property or to catch its value. + +Dim vProperty As Variant ' Return value +Dim oInvocation As Object ' com.sun.star.script.Invocation +Dim oIntroObject As Object ' Alias of UNO object for introspection + +Const cstThisSub = "Session.GetUnoProperty" +Const cstSubArgs = "UnoObject, PropertyName" + + vProperty = Null + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + If Not SF_Utils._Validate(UnoObject, "UnoObject", V_OBJECT) Then GoTo Catch + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, SF_Session.UnoProperties(UnoObject)) Then GoTo Catch + +Try: + Set oInvocation = SF_Utils._GetUNOService("Invocation") + Set oIntroObject = oInvocation.createInstanceWithArguments(Array(UnoObject)) + vProperty = oIntroObject.getValue(PropertyName) + +Finally: + GetUnoProperty = vProperty + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.GetUnoProperty + REM ----------------------------------------------------------------------------- Public Function HasUnoMethod(Optional ByRef UnoObject As Variant _ , Optional ByVal MethodName As Variant _ diff --git a/wizards/source/scriptforge/SF_Utils.xba b/wizards/source/scriptforge/SF_Utils.xba index 7da0499c5398..93ebeaaa0d2a 100644 --- a/wizards/source/scriptforge/SF_Utils.xba +++ b/wizards/source/scriptforge/SF_Utils.xba @@ -18,7 +18,7 @@ Option Private Module REM ===================================================================== GLOBALS -Global _SF_ As Variant ' SF_Root (Basic) object) +Global _SF_ As Variant ' SF_Root (Basic) class singleton ''' ScriptForge version Const SF_Version = "26.8" @@ -413,6 +413,11 @@ Dim oDefaultContext As Object Set .Introspection = CreateUnoService("com.sun.star.beans.Introspection") End If Set _GetUNOService = .Introspection + Case "Invocation" + If IsEmpty(.Invocation) Or IsNull(.Invocation) Then + Set .Invocation = CreateUnoService("com.sun.star.script.Invocation") + End If + Set _GetUNOService = .Invocation Case "LocaleData" If IsEmpty(.LocaleData) Or IsNull(.LocaleData) Then Set .LocaleData = CreateUnoService("com.sun.star.i18n.LocaleData") @@ -746,7 +751,7 @@ Public Function _Validate(Optional ByRef pvArgument As Variant _ ''' pvTypes: array of allowed VarTypes ''' pvValues: array of allowed values ''' pvCaseSensitive: when True the comparison between strings is done case-sensitively -''' pvObjectType: mandatory Basic class +''' pvObjectType: ScriptForge class, may be "ANY" ''' Return: True if validation OK ''' Otherwise an error is raised ''' Exceptions: @@ -786,10 +791,9 @@ Try: End If ' Check instance types If bValid And Len(pvObjectType) > 0 And iVarType = V_OBJECT Then - 'Set oArgument = pvArgument Set oObjectDescriptor = SF_Utils._VarTypeObj(pvArgument) bValid = ( oObjectDescriptor.iVarType = V_SFOBJECT ) - If bValid Then bValid = ( oObjectDescriptor.sObjectType = pvObjectType ) + If bValid And pvObjectType <> "ANY" Then bValid = ( oObjectDescriptor.sObjectType = pvObjectType ) End If End If @@ -1102,10 +1106,12 @@ Try: If .sObjectType = cstBasicClass Then bUno = False ' Try if the Basic object has an ObjectType property - If Left(oValue.Name, 3) = "SF_" Then + On Local Error GoTo NotSF 'If Left(oValue.Name, 3) = "SF_" Then .sObjectType = oValue.ObjectType .sServiceName = oValue.ServiceName - End If + 'End If + NotSF: + On Local Error GoTo 0 End If ' Derive the return value from the object type Select Case True diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py index f5bc30403ce4..60fc20a09e08 100644 --- a/wizards/source/scriptforge/python/scriptforge.py +++ b/wizards/source/scriptforge/python/scriptforge.py @@ -380,7 +380,7 @@ class ScriptForge(object, metaclass = _Singleton): pass elif returntuple[cstClass] == cls.objDICT: dico = CreateScriptService('ScriptForge.Dictionary') - if not isinstance(returnvalue, uno.ByteSequence): # if array not empty + if len(returnvalue) > 0: # if array not empty dico.ImportFromPropertyValues(returnvalue, overwrite = True) return dico else: @@ -393,10 +393,10 @@ class ScriptForge(object, metaclass = _Singleton): if subcls is not None: return subcls(returnvalue, returntuple[cstType], returntuple[cstClass], returntuple[cstName]) elif returntuple[cstVarType] >= cls.V_ARRAY: - # Intercept empty array - if isinstance(returnvalue, uno.ByteSequence): - return () - if flag & SFServices.flgDateRet == SFServices.flgDateRet: # Bits comparison + # Ignore empty array + if len(returnvalue) == 0: + pass + elif flag & SFServices.flgDateRet == SFServices.flgDateRet: # Bits comparison # Intercept all UNO dates in the 1D or 2D array if isinstance(returnvalue[0], tuple): # tuple of tuples arr = [] @@ -647,7 +647,7 @@ class SFServices(object): """ if self.serviceimplementation == 'basic': # Conventionally properties starting with X (and only them) may return a UNO object - calltype = self.vbGet + (self.flgUno if propertyname[0] == 'X' else 0) + calltype = self.vbGet + self.flgDateRet + (self.flgUno if propertyname[0] == 'X' else 0) if arg is None: return self.EXEC(self.objectreference, calltype, propertyname) else: # There are a few cases (Calc ...) where GetProperty accepts an argument @@ -1575,11 +1575,28 @@ class SFScriptForge: def ExecutePythonScript(cls, scope = '', script = '', *args): return cls.SIMPLEEXEC(scope + '#' + script, *args) + def GetPDFExportOptions(self): + return self.ExecMethod(self.vbMethod, 'GetPDFExportOptions') + + def GetPropertyByName(self, basicobject, propertyname, arg = None): + if isinstance(basicobject, SFServices) is False: + return None + if basicobject.serviceimplementation == 'basic': + # Conventionally properties starting with X (and only them) may return a UNO object + calltype = (self.vbMethod + self.flgObject + self.flgDateRet + + (self.flgUno if propertyname[0] == 'X' else 0)) + if arg is None: + return self.ExecMethod(calltype, 'GetPropertyByName', basicobject.objectreference, propertyname) + else: # There are a few cases (Calc ...) where GetProperty accepts an argument + return self.ExecMethod(calltype, 'GetPropertyByName', + basicobject.objectreference, propertyname, arg) + return None + def GetRangeFromCalc(self, filename, range): return self.ExecMethod(self.vbMethod, 'GetRangeFromCalc', filename, range) - def GetPDFExportOptions(self): - return self.ExecMethod(self.vbMethod, 'GetPDFExportOptions') + def GetUnoProperty(self, unoobject, propertyname): + return self.ExecMethod(self.vbMethod, 'GetUnoProperty', unoobject, propertyname) def HasUnoMethod(self, unoobject, methodname): return self.ExecMethod(self.vbMethod, 'HasUnoMethod', unoobject, methodname) diff --git a/wizards/source/scriptforge/python/scriptforge.pyi b/wizards/source/scriptforge/python/scriptforge.pyi index ab3c504259e5..1bb02c149afb 100644 --- a/wizards/source/scriptforge/python/scriptforge.pyi +++ b/wizards/source/scriptforge/python/scriptforge.pyi @@ -1869,30 +1869,6 @@ class SFScriptForge: """ ... - def GetRangeFromCalc(self, filename: FILE, range: RANGE): - """ - Extract the content of a range of adjacent cells stored in a Calc file. The file may be open - or closed. If the file is open in the actual LibreOffice session, its actual content will be read. - Otherwise, the content will be read from the file on disk. - Args - ``filename``: the full file name, given in ``FileSystem.FileNaming`` notation. - - ``range``: either - - "sheet.cells", where "sheet" is the mandatory sheet name, eventually surrounded with - single quotes when it contains special characters, and "cells" is the single cell - or the range of cells to be considered, in A1 notation with or without dollar signs - - - a defined name at document-global level. - Note - Current selection shortcuts ("~") are forbidden in this context. - Returns - Either a scalar, or a tuple when the range is located in a single row or column, - or a tuple of tuples. The returned values are either strings or numbers. No dates, booleans, ... - To convert doubles to dates, use the ``basic.CDate()`` method. - Empty cells are returned as empty strings. - """ - ... - def GetPDFExportOptions(self) -> DICTIONARY: """ Returns the current PDF export settings defined in the ``PDF Options`` dialog, which can be accessed @@ -1903,6 +1879,64 @@ class SFScriptForge: """ ... + def GetPropertyByName(self, + basicobject: SERVICE, + propertyname: str, + arg: Any = ..., + ) -> Any: + """ + Return the actual value of the given property related to the given ``ScriptForge`` service instance. + Args + ``basicobject``: an instance of a class implemented in the Basic world. + + ``propertyname``: the name of the property, as a ProperCased string. + + ``arg``: some properties require an argument. + Returns + The actual value of the property. + """ + ... + + def GetRangeFromCalc(self, filename: FILE, range: RANGE): + """ + Extract the content of a range of adjacent cells stored in a Calc file. The file may be open + or closed. If the file is open in the actual LibreOffice session, its actual content will be read. + Otherwise, the content will be read from the file on disk. + Args + ``filename``: the full file name, given in ``FileSystem.FileNaming`` notation. + + ``range``: either + - "sheet.cells", where "sheet" is the mandatory sheet name, eventually surrounded with + single quotes when it contains special characters, and "cells" is the single cell + or the range of cells to be considered, in A1 notation with or without dollar signs + + - a defined name at document-global level. + Note + Current selection shortcuts ("~") are forbidden in this context. + Returns + Either a scalar, or a tuple when the range is located in a single row or column, + or a tuple of tuples. The returned values are either strings or numbers. No dates, booleans, ... + To convert doubles to dates, use the ``basic.CDate()`` method. + Empty cells are returned as empty strings. + """ + ... + + def GetUnoProperty(self, + unoobject: UNO, + propertyname: str, + ) -> Any: + """ + Return the actual value of the given property related to the given ``UNO`` object. + Args + ``unoobject``: the.considered ``UNO`` object. + + ``propertyname``: the name of the property, as a string. + Returns + The actual value of the property or None if the method failed to find the property + or to catch its value. + """ + ... + def HasUnoMethod(self, unoobject: UNO, methodname: str) -> bool: """ Returns ``True`` if a UNO object contains the given method. diff --git a/wizards/source/scriptforge/script.xlb b/wizards/source/scriptforge/script.xlb index e4ac88f29e9e..7c5f29231c27 100644 --- a/wizards/source/scriptforge/script.xlb +++ b/wizards/source/scriptforge/script.xlb @@ -1,24 +1,24 @@ + + + + + + + + + + + + + + - - - - - - - - - - - - - - \ No newline at end of file