diff options
author | Jean-Pierre Ledure <jp@ledure.be> | 2020-11-05 15:55:39 +0100 |
---|---|---|
committer | Jean-Pierre Ledure <jp@ledure.be> | 2020-11-05 15:55:39 +0100 |
commit | 09c1bee1f91315fd7901af1804e028f6574228a6 (patch) | |
tree | 1ed2b93132dffc8cd73c57943874e59164e67233 /wizards | |
parent | e2f66f5ba5d813af97bc4fb5f28cea9d737e25e9 (diff) |
ScriptForge - core library
Additional "LibreOffice Macros & Dialogs" library
Change-Id: I7380cf3f9ee56b73cfcf7b9e33d0cf50ecb40429
Diffstat (limited to 'wizards')
21 files changed, 16362 insertions, 0 deletions
diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba new file mode 100644 index 000000000000..914f42269867 --- /dev/null +++ b/wizards/source/scriptforge/SF_Array.xba @@ -0,0 +1,2549 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Array +''' ======== +''' Singleton class implementing the "ScriptForge.Array" service +''' Implemented as a usual Basic module +''' Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected +''' With the noticeable exception of the CountDims method (>2 dims allowed) +''' The first argument of almost every method is the array to consider +''' It is always passed by reference and left unchanged +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const ARRAYSEQUENCEERROR = "ARRAYSEQUENCEERROR" ' Incoherent arguments +Const ARRAYINSERTERROR = "ARRAYINSERTERROR" ' Matrix and vector have incompatible sizes +Const ARRAYINDEX1ERROR = "ARRAYINDEX1ERROR" ' Given index does not fit in array bounds +Const ARRAYINDEX2ERROR = "ARRAYINDEX2ERROR" ' Given indexes do not fit in array bounds +Const CSVPARSINGERROR = "CSVPARSINGERROR" ' Parsing error detected while parsing a csv file +Const CSVOVERFLOWWARNING = "CSVOVERFLOWWARNING" ' Array becoming too big, import process of csv file is interrupted + +REM ============================================================ MODULE CONSTANTS + +Const MAXREPR = 50 ' Maximum length to represent an array in the console + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_Array Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_Array" +End Property ' ScriptForge.SF_Array.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.Array" +End Property ' ScriptForge.SF_Array.ServiceName + +REM ============================================================== PUBLIC METHODS + +REM ----------------------------------------------------------------------------- +Public Function Append(Optional ByRef Array_1D As Variant _ + , ParamArray pvArgs() As Variant _ + ) As Variant +''' Append at the end of the input array the items listed as arguments +''' Arguments are appended blindly +''' each of them might be a scalar of any type or a subarray +''' Args +''' Array_1D: the pre-existing array, may be empty +''' pvArgs: a list of items to append to Array_1D +''' Return: +''' the new extended array. Its LBound is identical to that of Array_1D +''' Examples: +''' SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5) + +Dim vAppend As Variant ' Return value +Dim lNbArgs As Long ' Number of elements to append +Dim lMax As Long ' UBound of input array +Dim i As Long +Const cstThisSub = "Array.Append" +Const cstSubArgs = "Array_1D, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vAppend = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + lMax = UBound(Array_1D) + lNbArgs = UBound(pvArgs) + 1 ' pvArgs is always zero-based + If lMax < LBound(Array_1D) Then ' Initial array is empty + If lNbArgs > 0 Then + ReDim vAppend(0 To lNbArgs - 1) + End If + Else + vAppend() = Array_1D() + If lNbArgs > 0 Then + ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs) + End If + End If + For i = 1 To lNbArgs + vAppend(lMax + i) = pvArgs(i - 1) + Next i + +Finally: + Append = vAppend() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Append + +REM ----------------------------------------------------------------------------- +Public Function AppendColumn(Optional ByRef Array_2D As Variant _ + , Optional ByRef Column As Variant _ + ) As Variant +''' AppendColumn appends to the right side of a 2D array a new Column +''' Args +''' Array_2D: the pre-existing array, may be empty +''' If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array +''' Column: a 1D array with as many items as there are rows in Array_2D +''' Returns: +''' the new extended array. Its LBounds are identical to that of Array_2D +''' Exceptions: +''' ARRAYINSERTERROR +''' Examples: +''' SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6)) +''' x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i + +Dim vAppendColumn As Variant ' Return value +Dim iDims As Integer ' Dimensions of Array_2D +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim lMin As Long ' LBound of Column array +Dim lMax As Long ' UBound of Column array +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.AppendColumn" +Const cstSubArgs = "Array_2D, Column" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vAppendColumn = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D") Then GoTo Finally 'Initial check: not missing and array + If Not SF_Utils._ValidateArray(Column, "Column", 1) Then GoTo Finally + End If + iDims = SF_Array.CountDims(Array_2D) + If iDims > 2 Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally '2nd check to manage error + End If + +Try: + lMin = LBound(Column) + lMax = UBound(Column) + + ' Compute future dimensions of output array + Select Case iDims + Case 0 : lMin1 = lMin : lMax1 = lMax + lMin2 = 0 : lMax2 = -1 + Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = 0 : lMax2 = 0 + Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + End Select + If iDims > 0 And lMax - lMin <> lMax1 - lMin1 Then GoTo CatchColumn + ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1) + + ' Copy input array to output array + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i) + Next j + Next i + ' Copy new Column + For i = lMin1 To lMax1 + vAppendColumn(i, lMax2 + 1) = Column(i) + Next i + +Finally: + AppendColumn = vAppendColumn() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchColumn: + SF_Exception.RaiseFatal(ARRAYINSERTERROR, "Column", SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR)) + GoTo Finally +End Function ' ScriptForge.SF_Array.AppendColumn + +REM ----------------------------------------------------------------------------- +Public Function AppendRow(Optional ByRef Array_2D As Variant _ + , Optional ByRef Row As Variant _ + ) As Variant +''' AppendRow appends below a 2D array a new row +''' Args +''' Array_2D: the pre-existing array, may be empty +''' If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array +''' Row: a 1D array with as many items as there are columns in Array_2D +''' Returns: +''' the new extended array. Its LBounds are identical to that of Array_2D +''' Exceptions: +''' ARRAYINSERTERROR +''' Examples: +''' SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6)) +''' x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i + +Dim vAppendRow As Variant ' Return value +Dim iDims As Integer ' Dimensions of Array_2D +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim lMin As Long ' LBound of row array +Dim lMax As Long ' UBound of row array +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.AppendRow" +Const cstSubArgs = "Array_2D, Row" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vAppendRow = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D") Then GoTo Finally 'Initial check: not missing and array + If Not SF_Utils._ValidateArray(Row, "Row", 1) Then GoTo Finally + End If + iDims = SF_Array.CountDims(Array_2D) + If iDims > 2 Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally '2nd check to manage error + End If + +Try: + lMin = LBound(Row) + lMax = UBound(Row) + + ' Compute future dimensions of output array + Select Case iDims + Case 0 : lMin1 = 0 : lMax1 = -1 + lMin2 = lMin : lMax2 = lMax + Case 1 : lMin1 = 0 : lMax1 = 0 + lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1) + Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + End Select + If iDims > 0 And lMax - lMin <> lMax2 - lMin2 Then GoTo CatchRow + ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2) + + ' Copy input array to output array + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j) + Next j + Next i + ' Copy new row + For j = lMin2 To lMax2 + vAppendRow(lMax1 + 1, j) = Row(j) + Next j + +Finally: + AppendRow = vAppendRow() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchRow: + SF_Exception.RaiseFatal(ARRAYINSERTERROR, "Row", SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR)) + GoTo Finally +End Function ' ScriptForge.SF_Array.AppendRow + +REM ----------------------------------------------------------------------------- +Public Function Contains(Optional ByRef Array_1D As Variant _ + , Optional ByVal ToFind As Variant _ + , Optional ByVal CaseSensitive As Variant _ + , Optional ByVal SortOrder As Variant _ + ) As Boolean +''' Check if a 1D array contains the ToFind number, string or date +''' The comparison between strings can be done case-sensitive or not +''' If the array is sorted then +''' the array must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' a binary search is done +''' Otherwise the array is scanned from top. Null or Empty items are simply ignored +''' Args: +''' Array_1D: the array to scan +''' ToFind: a number, a date or a string to find +''' CaseSensitive: Only for string comparisons, default = False +''' SortOrder: "ASC", "DESC" or "" (= not sorted, default) +''' Return: True when found +''' Result is unpredictable when array is announced sorted and is in reality not +''' Examples: +''' SF_Array.Contains(Array("A","B","c","D"), "C", SortOrder := "ASC") returns True +''' SF_Array.Contains(Array("A","B","c","D"), "C", CaseSensitive := True) returns False + +Dim bContains As Boolean ' Return value +Dim iToFindType As Integer ' VarType of ToFind +Const cstThisSub = "Array.Contains" +Const cstSubArgs = "Array_1D, ToFind, [CaseSensitive=False], [SortOrder=""""|""ASC""|""DESC""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + bContains = False + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC", "DESC", "")) Then GoTo Finally + If Not SF_Utils._Validate(ToFind, "ToFind", Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally + iToFindType = SF_Utils._VarTypeExt(ToFind) + If SortOrder <> "" Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1, iToFindType) Then GoTo Finally + Else + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)(0) + +Finally: + Contains = bContains + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Contains + +REM ----------------------------------------------------------------------------- +Public Function ConvertToDictionary(Optional ByRef Array_2D As Variant) As Variant +''' Store the content of a 2-columns array into a dictionary +''' Key found in 1st column, Item found in 2nd +''' Args: +''' Array_2D: 1st column must contain exclusively non zero-length strings +''' 1st column may not be sorted +''' Returns: +''' a ScriptForge dictionary object +''' Examples: +''' + +Dim oDict As Variant ' Return value +Dim i As Long +Const cstThisSub = "Dictionary.ConvertToArray" +Const cstSubArgs = "Array_2D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2, V_STRING, True) Then GoTo Finally + End If + +Try: + Set oDict = SF_Services.CreateScriptService("Dictionary") + For i = LBound(Array_2D, 1) To UBound(Array_2D, 1) + oDict.Add(Array_2D(i, 0), Array_2D(i, 1)) + Next i + + ConvertToDictionary = oDict + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.ConvertToDictionary + +REM ----------------------------------------------------------------------------- +Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer +''' Count the number of dimensions of an array - may be > 2 +''' Args: +''' Array_ND: the array to be examined +''' Return: the number of dimensions: -1 = not array, 0 = unitialized array, else >= 1 +''' Examples: +''' Dim a(1 To 10, -3 To 12, 5) +''' CountDims(a) returns 3 + +Dim iDims As Integer ' Return value +Dim lMax As Long ' Storage for UBound of each dimension +Const cstThisSub = "Array.CountDims" +Const cstSubArgs = "Array_ND" + +Check: + iDims = -1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If IsMissing(Array_ND) Then ' To have missing exception processed + If Not SF_Utils._ValidateArray(Array_ND, "Array_ND") Then GoTo Finally + End If + End If + +Try: + On Local Error Goto ErrHandler + ' Loop, increasing the dimension index (i) until an error occurs. + ' An error will occur when i exceeds the number of dimensions in the array. Returns i - 1. + iDims = 0 + If Not IsArray(Array_ND) Then + Else + Do + iDims = iDims + 1 + lMax = UBound(Array_ND, iDims) + Loop Until (Err <> 0) + End If + + ErrHandler: + On Local Error GoTo 0 + + iDims = iDims - 1 + If iDims = 1 Then + If LBound(Array_ND, 1) > UBound(Array_ND, 1) Then iDims = 0 + End If + +Finally: + CountDims = iDims + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Array.CountDims + +REM ----------------------------------------------------------------------------- +Public Function Difference(Optional ByRef Array1_1D As Variant _ + , Optional ByRef Array2_1D As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Build a set being the Difference of the two input arrays, i.e. items are contained in 1st array and NOT in 2nd +''' both input arrays must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' The comparison between strings is case sensitive or not +''' Args: +''' Array1_1D: a 1st input array +''' Array2_1D: a 2nd input array +''' CaseSensitive: default = False +''' Returns: a zero-based array containing unique items from the 1st array not present in the 2nd +''' The output array is sorted in ascending order +''' Examples: +''' SF_Array.Difference(Array("A", "C", "A", "b", "B"), Array("C", "Z", "b"), True) returns ("A", "B") + +Dim vDifference() As Variant ' Return value +Dim vSorted() As Variant ' The 2nd input array after sort +Dim iType As Integer ' VarType of elements in input arrays +Dim lMin1 As Long ' LBound of 1st input array +Dim lMax1 As Long ' UBound of 1st input array +Dim lMin2 As Long ' LBound of 2nd input array +Dim lMax2 As Long ' UBound of 2nd input array +Dim lSize As Long ' Number of Difference items +Dim vItem As Variant ' One single item in the array +Dim i As Long +Const cstThisSub = "Array.Difference" +Const cstSubArgs = "Array1_1D, Array2_1D, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vDifference = Array() + +Check: + If IsMissing(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array1_1D, "Array1_1D", 1, 0, True) Then GoTo Finally + iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D))) + If Not SF_Utils._ValidateArray(Array2_1D, "Array2_1D", 1, iType, True) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D) + lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D) + + ' If 1st array is empty, do nothing + If lMax1 < lMin1 Then + ElseIf lMax2 < lMin2 Then ' only 2nd array is empty + vUnion = SF_Array.Unique(Array1_1D, CaseSensitive) + Else + + ' First sort the 2nd array + vSorted = SF_Array.Sort(Array2_1D, "ASC", CaseSensitive) + + ' Resize the output array to the size of the 1st array + ReDim vDifference(0 To (lMax1 - lMin1)) + lSize = -1 + + ' Fill vDifference one by one with items present only in 1st set + For i = lMin1 To lMax1 + vItem = Array1_1D(i) + If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, "ASC") Then + lSize = lSize + 1 + vDifference(lSize) = vItem + End If + Next i + + ' Remove unfilled entries and duplicates + If lSize >= 0 Then + ReDim Preserve vDifference(0 To lSize) + vDifference() = SF_Array.Unique(vDifference, CaseSensitive) + Else + vDifference = Array() + End If + End If + +Finally: + Difference = vDifference() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Difference + +REM ----------------------------------------------------------------------------- +Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _ + , Optional ByVal FileName As Variant _ + , Optional ByVal Encoding As Variant _ + ) As Boolean +''' Write all items of the array sequentially to a text file +''' If the file exists already, it will be overwritten without warning +''' Args: +''' Array_1D: the array to export +''' FileName: the full name (path + file) in SF_FileSystem.FileNaming notation +''' Encoding: The character set that should be used +''' Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that LibreOffice does not implement all existing sets +''' Default = UTF-8 +''' Returns: +''' True if successful +''' Examples: +''' SF_Array.ExportToTextFile(Array("A","B","C","D"), "C:\Temp\A short file.txt") + +Dim bExport As Boolean ' Return value +Dim oFile As Object ' Output file handler +Dim sLine As String ' A single line +Const cstThisSub = "Array.ExportToTextFile" +Const cstSubArgs = "Array_1D, FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExport = False + +Check: + If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = "UTF-8" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1, V_STRING, True) Then GoTo Finally + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Encoding, "Encoding", V_STRING) Then GoTo Finally + End If + +Try: + Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding) + If Not IsNull(oFile) Then + With oFile + For Each sLine In Array_1D + .WriteLine(sLine) + Next sLine + .CloseFile() + End With + End If + + bExport = True + +Finally: + If Not IsNull(oFile) Then Set oFile = oFile.Dispose() + ExportToTextFile = bExport + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.ExportToTextFile + +REM ----------------------------------------------------------------------------- +Public Function ExtractColumn(Optional ByRef Array_2D As Variant _ + , Optional ByVal ColumnIndex As Variant _ + ) As Variant +''' ExtractColumn extracts from a 2D array a specific column +''' Args +''' Array_2D: the array from which to extract +''' ColumnIndex: the column to extract - must be in the interval [LBound, UBound] +''' Returns: +''' the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D +''' Exceptions: +''' ARRAYINDEX1ERROR +''' Examples: +''' |1, 2, 3| +''' SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9) +''' |7, 8, 9| + +Dim vExtractColumn As Variant ' Return value +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound1 of input array +Dim lMax2 As Long ' UBound1 of input array +Dim i As Long +Const cstThisSub = "Array.ExtractColumn" +Const cstSubArgs = "Array_2D, ColumnIndex" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vExtractColumn = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + If Not SF_Utils._Validate(ColumnIndex, "ColumnIndex", V_NUMERIC) Then GoTo Finally + End If + +Try: + ' Compute future dimensions of output array + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + If ColumnIndex < lMin2 Or ColumnIndex > lMax2 Then GoTo CatchIndex + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + ReDim vExtractColumn(lMin1 To lMax1) + + ' Copy Column of input array to output array + For i = lMin1 To lMax1 + vExtractColumn(i) = Array_2D(i, ColumnIndex) + Next i + +Finally: + ExtractColumn = vExtractColumn() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchIndex: + SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, "ColumnIndex", SF_Array._Repr(Array_2D), ColumnIndex) + GoTo Finally +End Function ' ScriptForge.SF_Array.ExtractColumn + +REM ----------------------------------------------------------------------------- +Public Function ExtractRow(Optional ByRef Array_2D As Variant _ + , Optional ByVal RowIndex As Variant _ + ) As Variant +''' ExtractRow extracts from a 2D array a specific row +''' Args +''' Array_2D: the array from which to extract +''' RowIndex: the row to extract - must be in the interval [LBound, UBound] +''' Returns: +''' the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D +''' Exceptions: +''' ARRAYINDEX1ERROR +''' Examples: +''' |1, 2, 3| +''' SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9) +''' |7, 8, 9| + +Dim vExtractRow As Variant ' Return value +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound1 of input array +Dim lMax2 As Long ' UBound1 of input array +Dim i As Long +Const cstThisSub = "Array.ExtractRow" +Const cstSubArgs = "Array_2D, RowIndex" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vExtractRow = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + If Not SF_Utils._Validate(RowIndex, "RowIndex", V_NUMERIC) Then GoTo Finally + End If + +Try: + ' Compute future dimensions of output array + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + If RowIndex < lMin1 Or RowIndex > lMax1 Then GoTo CatchIndex + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + ReDim vExtractRow(lMin2 To lMax2) + + ' Copy row of input array to output array + For i = lMin2 To lMax2 + vExtractRow(i) = Array_2D(RowIndex, i) + Next i + +Finally: + ExtractRow = vExtractRow() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchIndex: + SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, "RowIndex", SF_Array._Repr(Array_2D), RowIndex) + GoTo Finally +End Function ' ScriptForge.SF_Array.ExtractRow + +REM ----------------------------------------------------------------------------- +Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant +''' Stack all items and all items in subarrays into one array without subarrays +''' Args +''' Array_1D: the pre-existing array, may be empty +''' Return: +''' The new flattened array. Its LBound is identical to that of Array_1D +''' If one of the subarrays has a number of dimensions > 1 Then that subarray is left unchanged +''' Examples: +''' SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5) + +Dim vFlatten As Variant ' Return value +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim lIndex As Long ' Index in output array +Dim vItem As Variant ' Array single item +Dim iDims As Integer ' Array number of dimensions +Dim lEmpty As Long ' Number of empty subarrays +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.Flatten" +Const cstSubArgs = "Array_1D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vFlatten = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + If UBound(Array_1D) >= LBound(Array_1D) Then + lMin = LBound(Array_1D) : lMax = UBound(Array_1D) + ReDim vFlatten(lMin To lMax) ' Initial minimal sizing + lEmpty = 0 + lIndex = lMin - 1 + For i = lMin To lMax + vItem = Array_1D(i) + If IsArray(vItem) Then + iDims = SF_Array.CountDims(vItem) + Select Case iDims + Case 0 ' Empty arrays are ignored + lEmpty = lEmpty + 1 + Case 1 ' Only 1D subarrays are flattened + ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem)) + For j = LBound(vItem) To UBound(vItem) + lIndex = lIndex + 1 + vFlatten(lIndex) = vItem(j) + Next j + Case > 1 ' Other arrays are left unchanged + lIndex = lIndex + 1 + vFlatten(lIndex) = vItem + End Select + Else + lIndex = lIndex + 1 + vFlatten(lIndex) = vItem + End If + Next i + End If + ' Reduce size of output if Array_1D is populated with some empty arrays + If lEmpty > 0 Then + If lIndex - lEmpty < lMin Then + vFlatten = Array() + Else + ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty) + End If + End If + +Finally: + Flatten = vFlatten() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Flatten + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Array.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _ + , Optional ByVal Delimiter As Variant _ + , Optional ByVal DateFormat As Variant _ + ) As Variant +''' Import the data contained in a comma-separated values (CSV) file +''' The comma may be replaced by any character +''' Each line in the file contains a full record +''' Line splitting is not allowed) +''' However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them +''' A special mechanism is implemented to load dates +''' The applicable CSV format is described in https://tools.ietf.org/html/rfc4180 +''' Args: +''' FileName: the name of the text file containing the data expressed as given by the current FileNaming +''' property of the SF_FileSystem service. Default = both URL format or native format +''' Delimiter: Default = ",". Other usual options are ";" and the tab character +''' DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY +''' The dash (-) may be replaced by a dot (.), a slash (/) or a space +''' Other date formats will be ignored +''' If "" (default), dates will be considered as strings +''' Returns: +''' A 2D-array with each row corresponding with a single record read in the file +''' and each column corresponding with a field of the record +''' No check is made about the coherence of the field types across columns +''' A best guess will be made to identify numeric and date types +''' If a line contains less or more fields than the first line in the file, +''' an exception will be raised. Empty lines however are simply ignored +''' If the size of the file exceeds the number of items limit, a warning is raised +''' and the array is truncated +''' Exceptions: +''' CSVPARSINGERROR Given file is not formatted as a csv file +''' CSVOVERFLOWWARNING Maximum number of allowed items exceeded + +Dim vArray As Variant ' Returned array +Dim lCol As Long ' Index of last column of vArray +Dim lRow As Long ' Index of current row of vArray +Dim lFileSize As Long ' Number of records found in the file +Dim vCsv As Object ' CSV file handler +Dim sLine As String ' Last read line +Dim vLine As Variant ' Array of fields of last read line +Dim sItem As String ' Individual item in the file +Dim vItem As Variant ' Individual item in the output array +Dim iPosition As Integer ' Date position in individual item +Dim iYear As Integer, iMonth As Integer, iDay As Integer + ' Date components +Dim i As Long +Const cstItemsLimit = 250000 ' Maximum number of admitted items +Const cstThisSub = "Array.ImportFromCSVFile" +Const cstSubArgs = "FileName, [Delimiter="",""], [DateFormat=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vArray = Array() + +Check: + If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = "," + If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Delimiter, "Delimiter", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(DateFormat, "DateFormat", V_STRING) Then GoTo Finally + End If + If Len(Delimiter) = 0 Then Delimiter = "," + +Try: + ' Counts the lines present in the file to size the final array + ' Very beneficial for large files, better than multiple ReDims + ' Small overhead for small files + lFileSize = SF_FileSystem._CountTextLines(FileName, False) + If lFileSize <= 0 Then GoTo Finally + + ' Reread file line by line + Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading) + If IsNull(vCsv) Then GoTo Finally ' Open error + lRow = -1 + With vCsv + Do While Not .AtEndOfStream + sLine = .ReadLine() + If Len(sLine) > 0 Then ' Ignore empty lines + If InStr(sLine, """") > 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) ' Simple split when relevant + lRow = lRow + 1 + If lRow = 0 Then ' Initial sizing of output array + lCol = UBound(vLine) + ReDim vArray(0 To lFileSize - 1, 0 To lCol) + ElseIf UBound(vLine) <> lCol Then + GoTo CatchCSVFormat + End If + ' Check type and copy all items of the line + For i = 0 To lCol + If Left(vLine(i), 1) = """" Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) ' Unquote only when useful + ' Interprete the individual line item + Select Case True + Case IsNumeric(sItem) + If InStr(sItem, ".") + InStr(1, sItem, "e", 1) > 0 Then vItem = Val(sItem) Else vItem = CLng(sItem) + Case DateFormat <> "" And Len(sItem) = Len(DateFormat) + If SF_String.IsADate(sItem, DateFormat) Then + iPosition = InStr(DateFormat, "YYYY") : iYear = CInt(Mid(sItem, iPosition, 4)) + iPosition = InStr(DateFormat, "MM") : iMonth = CInt(Mid(sItem, iPosition, 2)) + iPosition = InStr(DateFormat, "DD") : iDay = CInt(Mid(sItem, iPosition, 2)) + vItem = DateSerial(iYear, iMonth, iDay) + Else + vItem = sItem + End If + Case Else : vItem = sItem + End Select + vArray(lRow, i) = vItem + Next i + End If + ' Provision to avoid very large arrays and their sometimes erratic behaviour + If (lRow + 2) * (lCol + 1) > cstItemsLimit Then + ReDim Preserve vArray(0 To lRow, 0 To lCol) + GoTo CatchOverflow + End If + Loop + End With + +Finally: + If Not IsNull(vCsv) Then + vCsv.CloseFile() + Set vCsv = vCsv.Dispose() + End If + ImportFromCSVFile = vArray + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchCSVFormat: + SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine) + GoTo Finally +CatchOverflow: + 'TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub) + 'MsgBox "TOO MUCH LINES !!" + GoTo Finally +End Function ' ScriptForge.SF_Array.ImportFromCSVFile + +REM ----------------------------------------------------------------------------- +Public Function IndexOf(Optional ByRef Array_1D As Variant _ + , Optional ByVal ToFind As Variant _ + , Optional ByVal CaseSensitive As Variant _ + , Optional ByVal SortOrder As Variant _ + ) As Long +''' Finds in a 1D array the ToFind number, string or date +''' ToFind must exist within the array. +''' The comparison between strings can be done case-sensitively or not +''' If the array is sorted then +''' the array must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' a binary search is done +''' Otherwise the array is scanned from top. Null or Empty items are simply ignored +''' Args: +''' Array_1D: the array to scan +''' ToFind: a number, a date or a string to find +''' CaseSensitive: Only for string comparisons, default = False +''' SortOrder: "ASC", "DESC" or "" (= not sorted, default) +''' Return: the index of the found item, LBound - 1 if not found +''' Result is unpredictable when array is announced sorted and is in reality not +''' Examples: +''' SF_Array.IndexOf(Array("A","B","c","D"), "C", SortOrder := "ASC") returns 2 +''' SF_Array.IndexOf(Array("A","B","c","D"), "C", CaseSensitive := True) returns -1 + +Dim vFindItem() As Variant ' 2-items array (0) = True if found, (1) = Index where found +Dim lIndex As Long ' Return value +Dim iToFindType As Integer ' VarType of ToFind +Const cstThisSub = "Array.IndexOf" +Const cstSubArgs = "Array_1D, ToFind, [CaseSensitive=False], [SortOrder=""""|""ASC""|""DESC""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + lIndex = -1 + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC", "DESC", "")) Then GoTo Finally + If Not SF_Utils._Validate(ToFind, "ToFind", Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally + iToFindType = SF_Utils._VarTypeExt(ToFind) + If SortOrder <> "" Then + If Not SF_Utils._ValidateArray(Array_1D, "Array", 1, iToFindType) Then GoTo Finally + Else + If Not SF_Utils._ValidateArray(Array_1D, "Array", 1) Then GoTo Finally + End If + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder) + If vFindItem(0) = True Then lIndex = vFindItem(1) Else lIndex = LBound(Array_1D) - 1 + +Finally: + IndexOf = lIndex + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.IndexOf + +REM ----------------------------------------------------------------------------- +Public Function Insert(Optional ByRef Array_1D As Variant _ + , Optional ByVal Before As Variant _ + , ParamArray pvArgs() As Variant _ + ) As Variant +''' Insert before the index Before of the input array the items listed as arguments +''' Arguments are inserted blindly +''' each of them might be a scalar of any type or a subarray +''' Args +''' Array_1D: the pre-existing array, may be empty +''' Before: the index before which to insert; must be in the interval [LBound, UBound + 1] +''' pvArgs: a list of items to Insert inside Array_1D +''' Returns: +''' the new rxtended array. Its LBound is identical to that of Array_1D +''' Exceptions: +''' ARRAYINSERTERROR +''' Examples: +''' SF_Array.Insert(Array(1, 2, 3), 2, 4, 5) returns (1, 2, 4, 5, 3) + +Dim vInsert As Variant ' Return value +Dim lNbArgs As Long ' Number of elements to Insert +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim i As Long +Const cstThisSub = "Array.Insert" +Const cstSubArgs = "Array_1D, Before, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vInsert = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + If Not SF_Utils._Validate(Before, "Before", V_NUMERIC) Then GoTo Finally + If Before < LBound(Array_1D) Or Before > UBound(Array_1D) + 1 Then GoTo CatchArgument + End If + +Try: + lNbArgs = UBound(pvArgs) + 1 ' pvArgs is always zero-based + lMin = LBound(Array_1D) ' = LBound(vInsert) + lMax = UBound(Array_1D) ' <> UBound(vInsert) + If lNbArgs > 0 Then + ReDim vInsert(lMin To lMax + lNbArgs) + For i = lMin To UBound(vInsert) + If i < Before Then + vInsert(i) = Array_1D(i) + ElseIf i < Before + lNbArgs Then + vInsert(i) = pvArgs(i - Before) + Else + vInsert(i) = Array_1D(i - lNbArgs) + End If + Next i + Else + vInsert() = Array_1D() + End If + +Finally: + Insert = vInsert() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchArgument: + 'TODO SF_Exception.RaiseFatal(ARRAYINSERTERROR, cstThisSub) + MsgBox "INVALID ARGUMENT VALUE !!" + GoTo Finally +End Function ' ScriptForge.SF_Array.Insert + +REM ----------------------------------------------------------------------------- +Public Function InsertSorted(Optional ByRef Array_1D As Variant _ + , Optional ByVal Item As Variant _ + , Optional ByVal SortOrder As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Insert in a sorted array a new item on its place +''' the array must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' Args: +''' Array_1D: the array to sort +''' Item: the scalar value to insert, same type as the existing array items +''' SortOrder: "ASC" (default) or "DESC" +''' CaseSensitive: Default = False +''' Returns: the extended sorted array with same LBound as input array +''' Examples: +''' InsertSorted(Array("A", "C", "a", "b"), "B", CaseSensitive := True) returns ("A", "B", "C", "a", "b") + +Dim vSorted() As Variant ' Return value +Dim iType As Integer ' VarType of elements in input array +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim lIndex As Long ' Place where to insert new item +Const cstThisSub = "Array.InsertSorted" +Const cstSubArgs = "Array_1D, Item, [SortOrder=""ASC""|""DESC""], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSorted = Array() + +Check: + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "ASC" + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1, 0) Then GoTo Finally + If LBound(Array_1D) <= UBound(Array_1D) Then + iType = SF_Utils._VarTypeExt(Array_1D(LBound(Array_1D))) + If Not SF_Utils._Validate(Item, "Item", iType) Then GoTo Finally + Else + If Not SF_Utils._Validate(Item, "Item", Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally + End If + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC","DESC")) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lMax = UBound(Array_1D) + lIndex = SF_Array._FindItem(Array_1D, Item, CaseSensitive, SortOrder)(1) + vSorted = SF_Array.Insert(Array_1D, lIndex, Item) + +Finally: + InsertSorted = vSorted() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.InsertSorted + +REM ----------------------------------------------------------------------------- +Public Function Intersection(Optional ByRef Array1_1D As Variant _ + , Optional ByRef Array2_1D As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Build a set being the intersection of the two input arrays, i.e. items are contained in both arrays +''' both input arrays must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' The comparison between strings is case sensitive or not +''' Args: +''' Array1_1D: a 1st input array +''' Array2_1D: a 2nd input array +''' CaseSensitive: default = False +''' Returns: a zero-based array containing unique items stored in both input arrays +''' The output array is sorted in ascending order +''' Examples: +''' Intersection(Array("A", "C", "A", "b", "B"), Array("C", "Z", "b"), True) returns ("C", "b") + +Dim vIntersection() As Variant ' Return value +Dim vSorted() As Variant ' The shortest input array after sort +Dim iType As Integer ' VarType of elements in input arrays +Dim lMin1 As Long ' LBound of 1st input array +Dim lMax1 As Long ' UBound of 1st input array +Dim lMin2 As Long ' LBound of 2nd input array +Dim lMax2 As Long ' UBound of 2nd input array +Dim lMin As Long ' LBound of unsorted array +Dim lMax As Long ' UBound of unsorted array +Dim iShortest As Integer ' 1 or 2 depending on shortest input array +Dim lSize As Long ' Number of Intersection items +Dim vItem As Variant ' One single item in the array +Dim i As Long +Const cstThisSub = "Array.Intersection" +Const cstSubArgs = "Array1_1D, Array2_1D, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vIntersection = Array() + +Check: + If IsMissing(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array1_1D, "Array1_1D", 1, 0, True) Then GoTo Finally + iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D))) + If Not SF_Utils._ValidateArray(Array2_1D, "Array2_1D", 1, iType, True) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D) + lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D) + + ' If one of both arrays is empty, do nothing + If lMax1 >= lMin1 And lMax2 >= lMin2 Then + + ' First sort the shortest array + If lMax1 - lMin1 <= lMax2 - lMin2 Then + iShortest = 1 + vSorted = SF_Array.Sort(Array1_1D, "ASC", CaseSensitive) + lMin = lMin2 : lMax = lMax2 ' Bounds of unsorted array + Else + iShortest = 2 + vSorted = SF_Array.Sort(Array2_1D, "ASC", CaseSensitive) + lMin = lMin1 : lMax = lMax1 ' Bounds of unsorted array + End If + + ' Resize the output array to the size of the shortest array + ReDim vIntersection(0 To (lMax - lMin)) + lSize = -1 + + ' Fill vIntersection one by one only with items present in both sets + For i = lMin To lMax + If iShortest = 1 Then vItem = Array2_1D(i) Else vItem = Array1_1D(i) ' Pick in unsorted array + If SF_Array.Contains(vSorted, vItem, CaseSensitive, "ASC") Then + lSize = lSize + 1 + vIntersection(lSize) = vItem + End If + Next i + + ' Remove unfilled entries and duplicates + If lSize >= 0 Then + ReDim Preserve vIntersection(0 To lSize) + vIntersection() = SF_Array.Unique(vIntersection, CaseSensitive) + Else + vIntersection = Array() + End If + End If + +Finally: + Intersection = vIntersection() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Intersection + +REM ----------------------------------------------------------------------------- +Public Function Join2D(Optional ByRef Array_2D As Variant _ + , Optional ByVal ColumnDelimiter As Variant _ + , Optional ByVal RowDelimiter As Variant _ + , Optional ByVal Quote As Variant _ + ) As String +''' Join a two-dimensional array with two delimiters, one for columns, one for rows +''' Args: +''' Array_2D: each item must be either a String, a number, a Date or a Boolean +''' ColumnDelimiter: delimits each column (default = Tab/Chr(9)) +''' RowDelimiter: delimits each row (default = LineFeed/Chr(10)) +''' Quote: if True, protect strings with double quotes (default = False) +''' Return: +''' A string after conversion of numbers and dates +''' Invalid items are replaced by a zero-length string +''' Examples: +''' | 1, 2, "A", [2020-02-29], 5 | +''' SF_Array.Join_2D( | 6, 7, "this is a string", 9, 10 | , ",", "/") +''' ' "1,2,A,2020-02-29 00:00:00,5/6,7,this is a string,9,10" + +Dim sJoin As String ' The return value +Dim sItem As String ' The string representation of a single item +Dim vItem As Variant ' Single item +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.Join2D" +Const cstSubArgs = "Array_2D, [ColumnDelimiter=Chr(9)], [RowDelimiter=Chr(10)], [Quote=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sJoin = "" + +Check: + If IsMissing(ColumnDelimiter) Or IsEmpty(ColumnDelimiter) Then ColumnDelimiter = Chr(9) + If IsMissing(RowDelimiter) Or IsEmpty(RowDelimiter) Then RowDelimiter = Chr(10) + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + If Not SF_Utils._Validate(ColumnDelimiter, "ColumnDelimiter", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(RowDelimiter, "RowDelimiter", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Quote, "Quote", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + If lMin1 <= lMax1 Then + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + vItem = Array_2D(i, j) + Select Case SF_Utils._VarTypeExt(vItem) + Case V_STRING : If Quote Then sItem = SF_String.Quote(vItem) Else sItem = vItem + Case V_NUMERIC, V_DATE : sItem = SF_Utils._Repr(vItem) + Case V_BOOLEAN : sItem = Iif(vItem, "True", "False") 'TODO: L10N + Case Else : sItem = "" + End Select + sJoin = sJoin & sItem & Iif(j < lMax2, ColumnDelimiter, "") + Next j + sJoin = sJoin & Iif(i < lMax1, RowDelimiter, "") + Next i + End If + +Finally: + Join2D = sJoin + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Join2D + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Array service as an array + + Methods = Array( _ + "Append" _ + , "AppendColumn" _ + , "AppendRow" _ + , "Contains" _ + , "ConvertToDictionary" _ + , "CountDims" _ + , "Difference" _ + , "ExportToTextFile" _ + , "ExtractColumn" _ + , "ExtractRow" _ + , "Flatten" _ + , "ImportFromCSVFile" _ + , "IndexOf" _ + , "Insert" _ + , "InsertSorted" _ + , "Intersection" _ + , "Join2D" _ + , "Prepend" _ + , "PrependColumn" _ + , "PrependRow" _ + , "RangeInit" _ + , "Reverse" _ + , "Shuffle" _ + , "Sort" _ + , "SortColumns" _ + , "SortRows" _ + , "Transpose" _ + , "TrimArray" _ + , "Union" _ + , "Unique" _ + ) + +End Function ' ScriptForge.SF_Array.Methods + +REM ----------------------------------------------------------------------------- +Public Function Prepend(Optional ByRef Array_1D As Variant _ + , ParamArray pvArgs() As Variant _ + ) As Variant +''' Prepend at the beginning of the input array the items listed as arguments +''' Arguments are Prepended blindly +''' each of them might be a scalar of any type or a subarray +''' Args +''' Array_1D: the pre-existing array, may be empty +''' pvArgs: a list of items to Prepend to Array_1D +''' Return: the new rxtended array. Its LBound is identical to that of Array_1D +''' Examples: +''' SF_Array.Prepend(Array(1, 2, 3), 4, 5) returns (4, 5, 1, 2, 3) + +Dim vPrepend As Variant ' Return value +Dim lNbArgs As Long ' Number of elements to Prepend +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim i As Long +Const cstThisSub = "Array.Prepend" +Const cstSubArgs = "Array_1D, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vPrepend = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + lNbArgs = UBound(pvArgs) + 1 ' pvArgs is always zero-based + lMin = LBound(Array_1D) ' = LBound(vPrepend) + lMax = UBound(Array_1D) ' <> UBound(vPrepend) + If lMax < LBound(Array_1D) And lNbArgs > 0 Then ' Initial array is empty + ReDim vPrepend(0 To lNbArgs - 1) + Else + ReDim vPrepend(lMin To lMax + lNbArgs) + End If + For i = lMin To UBound(vPrepend) + If i < lMin + lNbArgs Then vPrepend(i) = pvArgs(i - lMin) Else vPrepend(i) = Array_1D(i - lNbArgs) + Next i + +Finally: + Prepend = vPrepend + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Prepend + +REM ----------------------------------------------------------------------------- +Public Function PrependColumn(Optional ByRef Array_2D As Variant _ + , Optional ByRef Column As Variant _ + ) As Variant +''' PrependColumn prepends to the left side of a 2D array a new Column +''' Args +''' Array_2D: the pre-existing array, may be empty +''' If the array has 1 dimension, it is considered as the last Column of the resulting 2D array +''' Column: a 1D array with as many items as there are rows in Array_2D +''' Returns: +''' the new rxtended array. Its LBounds are identical to that of Array_2D +''' Exceptions: +''' ARRAYINSERTERROR +''' Examples: +''' SF_Array.PrependColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 1), (5, 2), (6, 3)) +''' x = SF_Array.PrependColumn(Array(), Array(1, 2, 3)) => ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i + +Dim vPrependColumn As Variant ' Return value +Dim iDims As Integer ' Dimensions of Array_2D +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim lMin As Long ' LBound of Column array +Dim lMax As Long ' UBound of Column array +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.PrependColumn" +Const cstSubArgs = "Array_2D, Column" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vPrependColumn = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D") Then GoTo Finally 'Initial check: not missing and array + If Not SF_Utils._ValidateArray(Column, "Column", 1) Then GoTo Finally + End If + iDims = SF_Array.CountDims(Array_2D) + If iDims > 2 Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally '2nd check to manage error + End If + +Try: + lMin = LBound(Column) + lMax = UBound(Column) + + ' Compute future dimensions of output array + Select Case iDims + Case 0 : lMin1 = lMin : lMax1 = lMax + lMin2 = 0 : lMax2 = -1 + Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = 0 : lMax2 = 0 + Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + End Select + If iDims > 0 And lMax - lMin <> lMax1 - lMin1 Then GoTo CatchColumn + ReDim vPrependColumn(lMin1 To lMax1, lMin2 To lMax2 + 1) + + ' Copy input array to output array + For i = lMin1 To lMax1 + For j = lMin2 + 1 To lMax2 + 1 + If iDims = 2 Then vPrependColumn(i, j) = Array_2D(i, j - 1) Else vPrependColumn(i, j) = Array_2D(i) + Next j + Next i + ' Copy new Column + For i = lMin1 To lMax1 + vPrependColumn(i, lMin2) = Column(i) + Next i + +Finally: + PrependColumn = vPrependColumn() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchColumn: + SF_Exception.RaiseFatal(ARRAYINSERTERROR, "Column", SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR)) + GoTo Finally +End Function ' ScriptForge.SF_Array.PrependColumn + +REM ----------------------------------------------------------------------------- +Public Function PrependRow(Optional ByRef Array_2D As Variant _ + , Optional ByRef Row As Variant _ + ) As Variant +''' PrependRow prepends on top of a 2D array a new row +''' Args +''' Array_2D: the pre-existing array, may be empty +''' If the array has 1 dimension, it is considered as the last row of the resulting 2D array +''' Row: a 1D array with as many items as there are columns in Array_2D +''' Returns: +''' the new rxtended array. Its LBounds are identical to that of Array_2D +''' Exceptions: +''' ARRAYINSERTERROR +''' Examples: +''' SF_Array.PrependRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 5, 6), (1, 2, 3)) +''' x = SF_Array.PrependColumn(Array(), Array(1, 2, 3) => ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i + +Dim vPrependRow As Variant ' Return value +Dim iDims As Integer ' Dimensions of Array_2D +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim lMin As Long ' LBound of row array +Dim lMax As Long ' UBound of row array +Dim i As Long +Dim j As Long +Const cstThisSub = "Array.PrependRow" +Const cstSubArgs = "Array_2D, Row" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vPrependRow = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D") Then GoTo Finally 'Initial check: not missing and array + If Not SF_Utils._ValidateArray(Row, "Row", 1) Then GoTo Finally + End If + iDims = SF_Array.CountDims(Array_2D) + If iDims > 2 Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally '2nd check to manage error + End If + +Try: + lMin = LBound(Row) + lMax = UBound(Row) + + ' Compute future dimensions of output array + Select Case iDims + Case 0 : lMin1 = 0 : lMax1 = -1 + lMin2 = lMin : lMax2 = lMax + Case 1 : lMin1 = 0 : lMax1 = 0 + lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1) + Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + End Select + If iDims > 0 And lMax - lMin <> lMax2 - lMin2 Then GoTo CatchRow + ReDim vPrependRow(lMin1 To lMax1 + 1, lMin2 To lMax2) + + ' Copy input array to output array + For i = lMin1 + 1 To lMax1 + 1 + For j = lMin2 To lMax2 + If iDims = 2 Then vPrependRow(i, j) = Array_2D(i - 1, j) Else vPrependRow(i, j) = Array_2D(j) + Next j + Next i + ' Copy new row + For j = lMin2 To lMax2 + vPrependRow(lMin1, j) = Row(j) + Next j + +Finally: + PrependRow = vPrependRow() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchRow: + SF_Exception.RaiseFatal(ARRAYINSERTERROR, "Row", SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR)) + GoTo Finally +End Function ' ScriptForge.SF_Array.PrependRow + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties as an array + + Properties = Array( _ + ) + +End Function ' ScriptForge.SF_Array.Properties + +REM ----------------------------------------------------------------------------- +Public Function RangeInit(Optional ByVal From As Variant _ + , Optional ByVal UpTo As Variant _ + , Optional ByVal ByStep As Variant _ + ) As Variant +''' Initialize a new zero-based array with numeric values +''' Args: all numeric +''' From: value of first item +''' UpTo: last item should not exceed UpTo +''' ByStep: difference between 2 successive items +''' Return: the new array +''' Exceptions: +''' ARRAYSEQUENCEERROR Wrong arguments, f.i. UpTo < From with ByStep > 0 +''' Examples: +''' SF_Array.RangeInit(10, 1, -1) returns (10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + +Dim lIndex As Long ' Index of array +Dim lSize As Long ' UBound of resulting array +Dim vCurrentItem As Variant ' Last stored item +Dim vArray() ' The return value +Const cstThisSub = "Array.RangeInit" +Const cstSubArgs = "From, UpTo, [ByStep = 1]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vArray = Array() + +Check: + If IsMissing(ByStep) Or IsEmpty(ByStep) Then ByStep = 1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(From, "From", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(UpTo, "UpTo", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(ByStep, "ByStep", V_NUMERIC) Then GoTo Finally + End If + If (From < UpTo And ByStep <= 0) Or (From > UpTo And ByStep >= 0) Then GoTo CatchSequence + +Try: + lSize = CLng(Abs((UpTo - From) / ByStep)) + ReDim vArray(0 To lSize) + For lIndex = 0 To lSize + vArray(lIndex) = From + lIndex * ByStep + Next lIndex + +Finally: + RangeInit = vArray + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchSequence: + SF_Exception.RaiseFatal(ARRAYSEQUENCEERROR, From, UpTo, ByStep) + GoTo Finally +End Function ' ScriptForge.SF_Array.RangeInit + +REM ----------------------------------------------------------------------------- +Public Function Reverse(Optional ByRef Array_1D As Variant) As Variant +''' Return the reversed 1D input array +''' Args: +''' Array_1D: the array to reverse +''' Returns: the reversed array +''' Examples: +''' SF_Array.Reverse(Array(1, 2, 3, 4)) returns (4, 3, 2, 1) + +Dim vReverse() As Variant ' Return value +Dim lHalf As Long ' Middle of array +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim i As Long, j As Long +Const cstThisSub = "Array.Reverse" +Const cstSubArgs = "Array_1D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vReverse = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lMax = UBound(Array_1D) + ReDim vReverse(lMin To lMax) + lHalf = Int((lMax + lMin) / 2) + j = lMax + For i = lMin To lHalf + vReverse(i) = Array_1D(j) + vReverse(j) = Array_1D(i) + j = j - 1 + Next i + ' Odd number of items + If IsEmpty(vReverse(lHalf + 1)) Then vReverse(lHalf + 1) = Array_1D(lHalf + 1) + +Finally: + Reverse = vReverse() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Reverse + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Array.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.SetProperty + +REM ----------------------------------------------------------------------------- +Public Function Shuffle(Optional ByRef Array_1D As Variant) As Variant +''' Returns a random permutation of a 1D array +''' https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle +''' Args: +''' Array_1D: the array to shuffle +''' Returns: the shuffled array + +Dim vShuffle() As Variant ' Return value +Dim vSwapValue As Variant ' Intermediate value during swap +Dim lMin As Long ' LBound of Array_1D +Dim lCurrentIndex As Long ' Decremented from UBount to LBound +Dim lRandomIndex As Long ' Random between LBound and lCurrentIndex +Dim i As Long +Const cstThisSub = "Array.Shuffle" +Const cstSubArgs = "Array_1D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vShuffle = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lCurrentIndex = UBound(array_1D) + ' Initialize the output array + ReDim vShuffle(lMin To lCurrentIndex) + For i = lMin To lCurrentIndex + vShuffle(i) = Array_1D(i) + Next i + ' Now ... shuffle ! + Do While lCurrentIndex > lMin + lRandomIndex = Int(Rnd * (lCurrentIndex - lMin)) + lMin + vSwapValue = vShuffle(lCurrentIndex) + vShuffle(lCurrentIndex) = vShuffle(lRandomIndex) + vShuffle(lRandomIndex) = vSwapValue + lCurrentIndex = lCurrentIndex - 1 + Loop + +Finally: + Shuffle = vShuffle() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Shuffle + +REM ----------------------------------------------------------------------------- +Public Function Slice(Optional ByRef Array_1D As Variant _ + , Optional ByVal From As Variant _ + , Optional ByVal UpTo As Variant _ + ) As Variant +''' Returns a subset of a 1D array +''' Args: +''' Array_1D: the array to slice +''' From: the lower index of the subarray to extract (included) +''' UpTo: the upper index of the subarray to extract (included). Default = the last item of Array_1D +''' Returns: +''' The selected subarray with the same LBound as the input array. +''' If UpTo < From then the returned array is empty +''' Exceptions: +''' ARRAYINDEX2ERROR Wrong values for From and/or UpTo +''' Example: +''' SF_Array.Slice(Array(1, 2, 3, 4, 5), 1, 3) returns (2, 3, 4) + +Dim vSlice() As Variant ' Return value +Dim lMin As Long ' LBound of Array_1D +Dim lIndex As Long ' Current index in output array +Dim i As Long +Const cstThisSub = "Array.Slice" +Const cstSubArgs = "Array_1D, From, [UpTo = UBound(Array_1D)]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSlice = Array() + +Check: + If IsMissing(UpTo) Or IsEmpty(UpTo) Then UpTo = -1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + If Not SF_Utils._Validate(From, "From", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(UpTo, "UpTo", V_NUMERIC) Then GoTo Finally + End If + If UpTo = -1 Then UpTo = UBound(Array_1D) + If From < LBound(Array_1D) Or From > UBound(Array_1D) _ + Or From > UpTo Or UpTo > UBound(Array_1D) Then GoTo CatchIndex + +Try: + If UpTo >= From Then + lMin = LBound(Array_1D) + ' Initialize the output array + ReDim vSlice(lMin To lMin + UpTo - From) + lIndex = lMin - 1 + For i = From To UpTo + lIndex = lIndex + 1 + vSlice(lIndex) = Array_1D(i) + Next i + End If + +Finally: + Slice = vSlice() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchIndex: + SF_Exception.RaiseFatal(ARRAYINDEX2ERROR, SF_Array._Repr(Array_1D), From, UpTo) + GoTo Finally +End Function ' ScriptForge.SF_Array.Slice + +REM ----------------------------------------------------------------------------- +Public Function Sort(Optional ByRef Array_1D As Variant _ + , Optional ByVal SortOrder As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Sort a 1D array in ascending or descending order. String comparisons can be case-sensitive or not +''' Args: +''' Array_1D: the array to sort +''' must be filled homogeneously by either strings, dates or numbers +''' Null and Empty values are allowed +''' SortOrder: "ASC" (default) or "DESC" +''' CaseSensitive: Default = False +''' Returns: the sorted array +''' Examples: +''' Sort(Array("a", "A", "b", "B", "C"), CaseSensitive := True) returns ("A", "B", "C", "a", "b") + +Dim vSort() As Variant ' Return value +Dim vIndexes() As Variant ' Indexes of sorted items +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim i As Long +Const cstThisSub = "Array.Sort" +Const cstSubArgs = "Array_1D, [SortOrder=""""|""ASC""|""DESC""], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSort = Array() + +Check: + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "ASC" + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1, 0) Then GoTo Finally + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC","DESC")) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lMax = UBound(Array_1D) + vIndexes() = SF_Array._HeapSort(Array_1D, ( SortOrder = "ASC" ), CaseSensitive) + + ' Load output array + ReDim vSort(lMin To lMax) + For i = lMin To lMax + vSort(i) = Array_1D(vIndexes(i)) + Next i + +Finally: + Sort = vSort() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Sort + +REM ----------------------------------------------------------------------------- +Public Function SortColumns(Optional ByRef Array_2D As Variant _ + , Optional ByVal RowIndex As Variant _ + , Optional ByVal SortOrder As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Returns a permutation of the columns of a 2D array, sorted on the values of a given row +''' Args: +''' Array_2D: the input array +''' RowIndex: the index of the row to sort the columns on +''' the row must be filled homogeneously by either strings, dates or numbers +''' Null and Empty values are allowed +''' SortOrder: "ASC" (default) or "DESC" +''' CaseSensitive: Default = False +''' Returns: +''' the array with permuted columns, LBounds and UBounds are unchanged +''' Exceptions: +''' ARRAYINDEXERROR +''' Examples: +''' | 5, 7, 3 | | 7, 5, 3 | +''' SF_Array.SortColumns( | 1, 9, 5 |, 2, "ASC") returns | 9, 1, 5 | +''' | 6, 1, 8 | | 1, 6, 8 | + +Dim vSort() As Variant ' Return value +Dim vRow() As Variant ' The row on which to sort the array +Dim vIndexes() As Variant ' Indexes of sorted row +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim i As Long, j As Long +Const cstThisSub = "Array.SortColumn" +Const cstSubArgs = "Array_2D, RowIndex, [SortOrder=""""|""ASC""|""DESC""], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSort = Array() + +Check: + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "ASC" + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + If Not SF_Utils._Validate(RowIndex, "RowIndex", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC","DESC")) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + If RowIndex < lMin1 Or RowIndex > lMax1 Then GoTo CatchIndex + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + + ' Extract and sort the RowIndex-th row + vRow = SF_Array.ExtractRow(Array_2D, RowIndex) + If Not SF_Utils._ValidateArray(vRow, "Row #" & CStr(RowIndex), 1, 0) Then GoTo Finally + vIndexes() = SF_Array._HeapSort(vRow, ( SortOrder = "ASC" ), CaseSensitive) + + ' Load output array + ReDim vSort(lMin1 To lMax1, lMin2 To lMax2) + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + vSort(i, j) = Array_2D(i, vIndexes(j)) + Next j + Next i + +Finally: + SortColumns = vSort() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchIndex: + 'TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub) + MsgBox "INVALID INDEX VALUE !!" + GoTo Finally +End Function ' ScriptForge.SF_Array.SortColumns + +REM ----------------------------------------------------------------------------- +Public Function SortRows(Optional ByRef Array_2D As Variant _ + , Optional ByVal ColumnIndex As Variant _ + , Optional ByVal SortOrder As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Returns a permutation of the rows of a 2D array, sorted on the values of a given column +''' Args: +''' Array_2D: the input array +''' ColumnIndex: the index of the column to sort the rows on +''' the column must be filled homogeneously by either strings, dates or numbers +''' Null and Empty values are allowed +''' SortOrder: "ASC" (default) or "DESC" +''' CaseSensitive: Default = False +''' Returns: +''' the array with permuted Rows, LBounds and UBounds are unchanged +''' Exceptions: +''' ARRAYINDEXERROR +''' Examples: +''' | 5, 7, 3 | | 1, 9, 5 | +''' SF_Array.SortRows( | 1, 9, 5 |, 0, "ASC") returns | 5, 7, 3 | +''' | 6, 1, 8 | | 6, 1, 8 | + +Dim vSort() As Variant ' Return value +Dim vCol() As Variant ' The column on which to sort the array +Dim vIndexes() As Variant ' Indexes of sorted row +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim i As Long, j As Long +Const cstThisSub = "Array.SortRow" +Const cstSubArgs = "Array_2D, ColumnIndex, [SortOrder=""""|""ASC""|""DESC""], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSort = Array() + +Check: + If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = "ASC" + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + If Not SF_Utils._Validate(ColumnIndex, "ColumnIndex", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(SortOrder, "SortOrder", V_STRING, Array("ASC","DESC")) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + If ColumnIndex < lMin2 Or ColumnIndex > lMax2 Then GoTo CatchIndex + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + + ' Extract and sort the ColumnIndex-th column + vCol = SF_Array.ExtractColumn(Array_2D, ColumnIndex) + If Not SF_Utils._ValidateArray(vCol, "Column #" & CStr(ColumnIndex), 1, 0) Then GoTo Finally + vIndexes() = SF_Array._HeapSort(vCol, ( SortOrder = "ASC" ), CaseSensitive) + + ' Load output array + ReDim vSort(lMin1 To lMax1, lMin2 To lMax2) + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + vSort(i, j) = Array_2D(vIndexes(i), j) + Next j + Next i + +Finally: + SortRows = vSort() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchIndex: + 'TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub) + MsgBox "INVALID INDEX VALUE !!" + GoTo Finally +End Function ' ScriptForge.SF_Array.SortRows + +REM ----------------------------------------------------------------------------- +Public Function Transpose(Optional ByRef Array_2D As Variant) As Variant +''' Swaps rows and columns in a 2D array +''' Args: +''' Array_2D: the array to transpose +''' Returns: +''' The transposed array +''' Examples: +''' | 1, 2 | | 1, 3, 5 | +''' SF_Array.Transpose( | 3, 4 | ) returns | 2, 4, 6 | +''' | 5, 6 | + +Dim vTranspose As Variant ' Return value +Dim lIndex As Long ' vTranspose index +Dim lMin1 As Long ' LBound1 of input array +Dim lMax1 As Long ' UBound1 of input array +Dim lMin2 As Long ' LBound2 of input array +Dim lMax2 As Long ' UBound2 of input array +Dim i As Long, j As Long +Const cstThisSub = "Array.Transpose" +Const cstSubArgs = "Array_2D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vTranspose = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_2D, "Array_2D", 2) Then GoTo Finally + End If + +Try: + ' Resize the output array + lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1) + lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2) + If lMin1 <= lMax1 Then + ReDim vTranspose(lMin2 To lMax2, lMin1 To lMax1) + End If + + ' Transpose items + For i = lMin1 To lMax1 + For j = lMin2 To lMax2 + vTranspose(j, i) = Array_2D(i, j) + Next j + Next i + +Finally: + Transpose = vTranspose + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Transpose + +REM ----------------------------------------------------------------------------- +Public Function TrimArray(Optional ByRef Array_1D As Variant) As Variant +''' Remove from a 1D array all Null, Empty and zero-length entries +''' Strings are trimmed as well +''' Args: +''' Array_1D: the array to scan +''' Return: The trimmed array +''' Examples: +''' SF_Array.TrimArray(Array("A","B",Null," D ")) returns ("A","B","D") + +Dim vTrimArray As Variant ' Return value +Dim lIndex As Long ' vTrimArray index +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim vItem As Variant ' Single array item +Dim i As Long +Const cstThisSub = "Array.TrimArray" +Const cstSubArgs = "Array_1D" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vTrimArray = Array() + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lMax = UBound(Array_1D) + If lMin <= lMax Then + ReDim vTrimArray(lMin To lMax) + End If + lIndex = lMin - 1 + + ' Load only valid items from Array_1D to vTrimArray + For i = lMin To lMax + vItem = Array_1D(i) + Select Case VarType(vItem) + Case V_EMPTY + Case V_NULL : vItem = Empty + Case V_STRING + vItem = Trim(vItem) + If Len(vItem) = 0 Then vItem = Empty + Case Else + End Select + If Not IsEmpty(vItem) Then + lIndex = lIndex + 1 + vTrimArray(lIndex) = vItem + End If + Next i + + 'Keep valid entries + If lMin <= lIndex Then + ReDim Preserve vTrimArray(lMin To lIndex) + Else + vTrimArray = Array() + End If + +Finally: + TrimArray = vTrimArray + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.TrimArray + +REM ----------------------------------------------------------------------------- +Public Function Union(Optional ByRef Array1_1D As Variant _ + , Optional ByRef Array2_1D As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Build a set being the Union of the two input arrays, i.e. items are contained in any of both arrays +''' both input arrays must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' The comparison between strings is case sensitive or not +''' Args: +''' Array1_1D: a 1st input array +''' Array2_1D: a 2nd input array +''' CaseSensitive: default = False +''' Returns: a zero-based array containing unique items stored in any of both input arrays +''' The output array is sorted in ascending order +''' Examples: +''' SF_Array.Union(Array("A", "C", "A", "b", "B"), Array("C", "Z", "b"), True) returns ("A", "B", "C", "Z", "b") + +Dim vUnion() As Variant ' Return value +Dim iType As Integer ' VarType of elements in input arrays +Dim lMin1 As Long ' LBound of 1st input array +Dim lMax1 As Long ' UBound of 1st input array +Dim lMin2 As Long ' LBound of 2nd input array +Dim lMax2 As Long ' UBound of 2nd input array +Dim lSize As Long ' Number of Union items +Dim i As Long +Const cstThisSub = "Array.Union" +Const cstSubArgs = "Array1_1D, Array2_1D, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vUnion = Array() + +Check: + If IsMissing(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array1_1D, "Array1_1D", 1, 0, True) Then GoTo Finally + iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D))) + If Not SF_Utils._ValidateArray(Array2_1D, "Array2_1D", 1, iType, True) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D) + lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D) + + ' If both arrays are empty, do nothing + If lMax1 < lMin1 And lMax2 < lMin2 Then + ElseIf lMax1 < lMin1 Then ' only 1st array is empty + vUnion = SF_Array.Unique(Array2_1D, CaseSensitive) + ElseIf lMax2 < lMin2 Then ' only 2nd array is empty + vUnion = SF_Array.Unique(Array1_1D, CaseSensitive) + Else + + ' Build union of both arrays + ReDim vUnion(0 To (lMax1 - lMin1) + (lMax2 - lMin2) + 1) + lSize = -1 + + ' Fill vUnion one by one only with items present in any set + For i = lMin1 To lMax1 + lSize = lSize + 1 + vUnion(lSize) = Array1_1D(i) + Next i + For i = lMin2 To lMax2 + lSize = lSize + 1 + vUnion(lSize) = Array2_1D(i) + Next i + + ' Remove duplicates + vUnion() = SF_Array.Unique(vUnion, CaseSensitive) + End If + +Finally: + Union = vUnion() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Union + +REM ----------------------------------------------------------------------------- +Public Function Unique(Optional ByRef Array_1D As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Variant +''' Build a set of unique values derived from the input array +''' the input array must be filled homogeneously, i.e. all items must be of the same type +''' Empty and Null items are forbidden +''' The comparison between strings is case sensitive or not +''' Args: +''' Array_1D: the input array with potential duplicates +''' CaseSensitive: default = False +''' Returns: the array without duplicates with same LBound as input array +''' The output array is sorted in ascending order +''' Examples: +''' Unique(Array("A", "C", "A", "b", "B"), True) returns ("A", "B", "C", "b") + +Dim vUnique() As Variant ' Return value +Dim vSorted() As Variant ' The input array after sort +Dim lMin As Long ' LBound of input array +Dim lMax As Long ' UBound of input array +Dim lUnique As Long ' Number of unique items +Dim vIndex As Variant ' Output of _FindItem() method +Dim vItem As Variant ' One single item in the array +Dim i As Long +Const cstThisSub = "Array.Unique" +Const cstSubArgs = "Array_1D, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vUnique = Array() + +Check: + If IsMissing(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateArray(Array_1D, "Array_1D", 1, 0, True) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lMin = LBound(Array_1D) + lMax = UBound(Array_1D) + If lMax >= lMin Then + ' First sort the array + vSorted = SF_Array.Sort(Array_1D, "ASC", CaseSensitive) + ReDim vUnique(lMin To lMax) + lUnique = lMin + ' Fill vUnique one by one ignoring duplicates + For i = lMin To lMax + vItem = vSorted(i) + If i = lMin Then + vUnique(i) = vItem + Else + If SF_Array._ValCompare(vItem, vSorted(i - 1), CaseSensitive) = 0 Then ' Ignore item + Else + lUnique = lUnique + 1 + vUnique(lUnique) = vItem + End If + End If + Next i + ' Remove unfilled entries + ReDim Preserve vUnique(lMin To lUnique) + End If + +Finally: + Unique = vUnique() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Array.Unique + +REM ============================================================= PRIVATE METHODS + +REM ----------------------------------------------------------------------------- +Public Function _FindItem(ByRef pvArray_1D As Variant _ + , ByVal pvToFind As Variant _ + , ByVal pbCaseSensitive As Boolean _ + , ByVal psSortOrder As String _ + ) As Variant +''' Check if a 1D array contains the ToFind number, string or date and return its index +''' The comparison between strings can be done case-sensitively or not +''' If the array is sorted then a binary search is done +''' Otherwise the array is scanned from top. Null or Empty items are simply ignored +''' Args: +''' pvArray_1D: the array to scan +''' pvToFind: a number, a date or a string to find +''' pbCaseSensitive: Only for string comparisons, default = False +''' psSortOrder: "ASC", "DESC" or "" (= not sorted, default) +''' Return: a (0:1) array +''' (0) = True when found +''' (1) = if found: index of item +''' if not found: if sorted, index of next item in the array (might be = UBound + 1) +''' if not sorted, meaningless +''' Result is unpredictable when array is announced sorted and is in reality not +''' Called by Contains, IndexOf and InsertSorted. Also called by SF_Dictionary + +Dim bContains As Boolean ' True if match found +Dim iToFindType As Integer ' VarType of pvToFind +Dim lTop As Long, lBottom As Long ' Interval in scope of binary search +Dim lIndex As Long ' Index used in search +Dim iCompare As Integer ' Output of _ValCompare function +Dim lLoops As Long ' Count binary searches +Dim lMaxLoops As Long ' Max number of loops during binary search: to avoid infinite loops if array not sorted +Dim vFound(1) As Variant ' Returned array (Contains, Index) + + bContains = False + + If LBound(pvArray_1D) > UBound(pvArray_1D) Then ' Empty array, do nothing + Else + ' Search sequentially + If Len(psSortOrder) = 0 Then + For lIndex = LBound(pvArray_1D) To UBound(pvArray_1D) + bContains = ( SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) = 0 ) + If bContains Then Exit For + Next lIndex + Else + ' Binary search + If psSortOrder = "ASC" Then + lTop = UBound(pvArray_1D) + lBottom = lBound(pvArray_1D) + Else + lBottom = UBound(pvArray_1D) + lTop = lBound(pvArray_1D) + End If + lLoops = 0 + lMaxLoops = CLng((Log(UBound(pvArray_1D) - LBound(pvArray_1D) + 1.0) / Log(2.0))) + 1 + Do + lLoops = lLoops + 1 + lIndex = (lTop + lBottom) / 2 + iCompare = SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) + Select Case True + Case iCompare = 0 : bContains = True + Case iCompare < 0 And psSortOrder = "ASC" + lTop = lIndex - 1 + Case iCompare > 0 And psSortOrder = "DESC" + lBottom = lIndex - 1 + Case iCompare > 0 And psSortOrder = "ASC" + lBottom = lIndex + 1 + Case iCompare < 0 And psSortOrder = "DESC" + lTop = lIndex + 1 + End Select + Loop Until ( bContains ) Or ( lBottom > lTop And psSortOrder = "ASC" ) Or (lBottom < lTop And psSortOrder = "DESC" ) Or lLoops > lMaxLoops + ' Flag first next non-matching element + If Not bContains Then lIndex = Iif(psSortOrder = "ASC", lBottom, lTop) + End If + End If + + ' Build output array + vFound(0) = bContains + vFound(1) = lIndex + _FindItem = vFound + +End Function ' ScriptForge.SF_Array._FindItem + +REM ----------------------------------------------------------------------------- +Private Function _HeapSort(ByRef pvArray As Variant _ + , Optional ByVal pbAscending As Boolean _ + , Optional ByVal pbCaseSensitive As Boolean _ + ) As Variant +''' Sort an array: items are presumed all strings, all dates or all numeric +''' Null or Empty are allowed and are considered smaller than other items +''' https://en.wikipedia.org/wiki/Heapsort +''' http://www.vbforums.com/showthread.php?473677-VB6-Sorting-algorithms-(sort-array-sorting-arrays)&p=2909250#post2909250 +''' HeapSort preferred to QuickSort because not recursive (this routine returns an array of indexes !!) +''' Args: +''' pvArray: a 1D array +''' pbAscending: default = True +''' pbCaseSensitive: default = False +''' Returns +''' An array of Longs of same dimensions as the input array listing the indexes of the sorted items +''' An empty array if the sort failed +''' Examples: +''' _HeapSort(Array(4, 2, 6, 1) returns (3, 1, 0, 2) + +Dim vIndexes As Variant ' Return value +Dim i As Long +Dim lMin As Long, lMax As Long ' Array bounds +Dim lSwap As Long ' For index swaps + + If IsMissing(pbAscending) Then pbAscending = True + If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False + vIndexes = Array() + lMin = LBound(pvArray, 1) + lMax = UBound(pvArray, 1) + + ' Initialize output array + ReDim vIndexes(lMin To lMax) + For i = lMin To lMax + vIndexes(i) = i + Next i + + ' Initial heapify + For i = (lMax + lMin) \ 2 To lMin Step -1 + SF_Array._HeapSort1(pvArray, vIndexes, i, lMin, lMax, pbCaseSensitive) + Next i + ' Next heapifies + For i = lMax To lMin + 1 Step -1 + ' Only indexes as swapped, not the array items themselves + lSwap = vIndexes(i) + vIndexes(i) = vIndexes(lMin) + vIndexes(lMin) = lSwap + SF_Array._HeapSort1(pvArray, vIndexes, lMin, lMin, i - 1, pbCaseSensitive) + Next i + + If pbAscending Then _HeapSort = vIndexes() Else _HeapSort = SF_Array.Reverse(vIndexes()) + +End Function ' ScriptForge.SF_Array._HeapSort + +REM ----------------------------------------------------------------------------- +Private Sub _HeapSort1(ByRef pvArray As Variant _ + , ByRef pvIndexes As Variant _ + , ByVal plIndex As Long _ + , ByVal plMin As Long _ + , ByVal plMax As Long _ + , ByVal pbCaseSensitive As Boolean _ + ) +''' Sub called by _HeapSort only + + Dim lLeaf As Long + Dim lSwap As Long + + Do + lLeaf = plIndex + plIndex - (plMin - 1) + Select Case lLeaf + Case Is > plMax: Exit Do + Case Is < plMax + If SF_Array._ValCompare(pvArray(pvIndexes(lLeaf + 1)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) > 0 Then lLeaf = lLeaf + 1 + End Select + If SF_Array._ValCompare(pvArray(pvIndexes(plIndex)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) > 0 Then Exit Do + ' Only indexes as swapped, not the array items themselves + lSwap = pvIndexes(plIndex) + pvIndexes(plIndex) = pvIndexes(lLeaf) + pvIndexes(lLeaf) = lSwap + plIndex = lLeaf + Loop + +End Sub ' ScriptForge.SF_Array._HeapSort1 + +REM ----------------------------------------------------------------------------- +Private Function _Repr(ByRef pvArray As Variant) As String +''' Convert array to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' pvArray: the array to convert, individual items may be of any type, including arrays +''' Return: +''' "[ARRAY] (L:U[, L:U]...)" if # of Dims > 1 +''' "[ARRAY] (L:U) (item1,item2, ...)" if 1D array + +Dim iDims As Integer ' Number of dimensions of the array +Dim sArray As String ' Return value +Dim i As Long +Const cstArrayEmpty = "[ARRAY] ()" +Const cstArray = "[ARRAY]" +Const cstMaxLength = 50 ' Maximum length for items +Const cstSeparator = ", " + + _Repr = "" + iDims = SF_Array.CountDims(pvArray) + + Select Case iDims + Case -1 : Exit Function ' Not an array + Case 0 : sArray = cstArrayEmpty + Case Else + sArray = cstArray + For i = 1 To iDims + sArray = sArray & Iif(i = 1, " (", ", ") & CStr(LBound(pvArray, i)) & ":" & CStr(UBound(pvArray, i)) + Next i + sArray = sArray & ")" + ' List individual items of 1D arrays + If iDims = 1 Then + sArray = sArray & " (" + For i = LBound(pvArray) To UBound(pvArray) + sArray = sArray & SF_Utils._Repr(pvArray(i), cstMaxLength) & cstSeparator ' Recursive call + Next i + sArray = Left(sArray, Len(sArray) - Len(cstSeparator)) ' Suppress last comma + sArray = sArray & ")" + End If + End Select + + _Repr = sArray + +End Function ' ScriptForge.SF_Array._Repr + +REM ----------------------------------------------------------------------------- +Public Function _StaticType(ByRef pvArray As Variant) As Integer +''' If array is static, return its type +''' Args: +''' pvArray: array to examine +''' Return: +''' array type, -1 if not identified +''' All numeric types are aggregated into V_NUMERIC + +Dim iArrayType As Integer ' VarType of array +Dim iType As Integer ' VarType of items + + iArrayType = VarType(pvArray) + iType = iArrayType - V_ARRAY + Select Case iType + Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL, V_BOOLEAN + _StaticType = V_NUMERIC + Case V_STRING, V_DATE + _StaticType = iType + Case Else + _StaticType = -1 + End Select + +End Function ' ScriptForge.SF_Utils._StaticType + +REM ----------------------------------------------------------------------------- +Private Function _ValCompare(ByVal pvValue1 As Variant _ + , pvValue2 As Variant _ + , Optional ByVal pbCaseSensitive As Boolean _ + ) As Integer +''' Compare 2 values : equality, greater than or smaller than +''' Args: +''' pvValue1 and pvValue2: values to compare. pvValues must be String, Number, Date, Empty or Null +''' By convention: Empty < Null < string, number or date +''' pbCaseSensitive: ignored when not String comparison +''' Return: -1 when pvValue1 < pvValue2 +''' +1 when pvValue1 > pvValue2 +''' 0 when pvValue1 = pvValue2 +''' -2 when comparison is nonsense + +Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer + + If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False + iVarType1 = SF_Utils._VarTypeExt(pvValue1) + iVarType2 = SF_Utils._VarTypeExt(pvValue2) + + iCompare = -2 + If iVarType1 = V_OBJECT Or iVarType1 = V_BYTE Or iVarType1 >= V_ARRAY Then ' Nonsense + ElseIf iVarType2 = V_OBJECT Or iVarType2 = V_BYTE Or iVarType2 >= V_ARRAY Then ' Nonsense + ElseIf iVarType1 = V_STRING And iVarType2 = V_STRING Then + iCompare = StrComp(pvValue1, pvValue2, Iif(pbCaseSensitive, 1, 0)) + ElseIf iVarType1 = V_NULL Or iVarType1 = V_EMPTY Or iVarType2 = V_NULL Or iVarType2 = V_EMPTY Then + Select Case True + Case pvValue1 = pvValue2 : iCompare = 0 + Case iVarType1 = V_NULL And iVarType2 = V_EMPTY : iCompare = +1 + Case iVarType1 = V_EMPTY And iVarType2 = V_NULL : iCompare = -1 + Case iVarType1 = V_NULL Or iVarType1 = V_EMPTY : iCompare = -1 + Case iVarType2 = V_NULL Or iVarType2 = V_EMPTY : iCompare = +1 + End Select + ElseIf iVarType1 = iVarType2 Then + Select Case True + Case pvValue1 < pvValue2 : iCompare = -1 + Case pvValue1 = pvValue2 : iCompare = 0 + Case pvValue1 > pvValue2 : iCompare = +1 + End Select + End If + + _ValCompare = iCompare + +End Function ' ScriptForge.SF_Array._ValCompare + +REM ================================================= END OF SCRIPTFORGE.SF_ARRAY +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Dictionary.xba b/wizards/source/scriptforge/SF_Dictionary.xba new file mode 100644 index 000000000000..e84db342fd5b --- /dev/null +++ b/wizards/source/scriptforge/SF_Dictionary.xba @@ -0,0 +1,952 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Dictionary" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Dictionary +''' ============= +''' Class for management of dictionaries +''' A dictionary is a collection of key-item pairs +''' The key is a not case-sensitive string +''' Items may be of any type +''' Keys, items can be retrieved, counted, etc. +''' +''' The implementation is based on +''' - one collection mapping keys and entries in the array +''' - one 1-column array: key + data +''' +''' Why a Dictionay class beside the builtin Collection class ? +''' A standard Basic collection does not support the retrieval of the keys +''' Additionally it may contain only simple data (strings, numbers, ...) +''' +''' Service instanciation example: +''' Dim myDict As Variant +''' myDict = CreateScriptService("Dictionary") ' Once per dictionary +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const DUPLICATEKEYERROR = "DUPLICATEKEYERROR" ' Key exists already +Const UNKNOWNKEYERROR = "UNKNOWNKEYERROR" ' Key not found +Const INVALIDKEYERROR = "INVALIDKEYERROR" ' Key contains only spaces + +REM ============================================================= PRIVATE MEMBERS + +' Defines an entry in the MapItems array +Type ItemMap + Key As String + Value As Variant +End Type + +Private [Me] As Object +Private [_Parent] As Object +Private ObjectType As String ' Must be "DICTIONARY" +Private ServiceName As String +Private MapKeys As Variant ' To retain the original keys +Private MapItems As Variant ' Array of ItemMaps +Private _MapSize As Long ' Total number of entries in the dictionary +Private _MapRemoved As Long ' Number of inactive entries in the dictionary + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "DICTIONARY" + ServiceName = "ScriptForge.Dictionary" + Set MapKeys = New Collection + Set MapItems = Array() + _MapSize = 0 + _MapRemoved = 0 +End Sub ' ScriptForge.SF_Dictionary Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + Call Class_Initialize() +End Sub ' ScriptForge.SF_Dictionary Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + RemoveAll() + Set Dispose = Nothing +End Function ' ScriptForge.SF_Dictionary Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get Count() As Long +''' Actual number of entries in the dictionary +''' Example: +''' myDict.Count + + Count = _PropertyGet("Count") + +End Property ' ScriptForge.SF_Dictionary.Count + +REM ----------------------------------------------------------------------------- +Public Function Item(Optional ByVal Key As Variant) As Variant +''' Return the value of the item related to Key +''' Args: +''' Key: the key value (string) +''' Returns: +''' Empty if not found, otherwise the found value +''' Example: +''' myDict.Item("ThisKey") +''' NB: defined as a function to not disrupt the Basic IDE debugger + + Item = _PropertyGet("Item", Key) + +End Function ' ScriptForge.SF_Dictionary.Item + +REM ----------------------------------------------------------------------------- +Property Get Items() as Variant +''' Return the list of Items as a 1D array +''' The Items and Keys properties return their respective contents in the same order +''' The order is however not necessarily identical to the creation sequence +''' Returns: +''' The array is empty if the dictionary is empty +''' Examples +''' a = myDict.Items +''' For Each b In a ... + + Items = _PropertyGet("Items") + +End Property ' ScriptForge.SF_Dictionary.Items + +REM ----------------------------------------------------------------------------- +Property Get Keys() as Variant +''' Return the list of keys as a 1D array +''' The Keys and Items properties return their respective contents in the same order +''' The order is however not necessarily identical to the creation sequence +''' Returns: +''' The array is empty if the dictionary is empty +''' Examples +''' a = myDict.Keys +''' For each b In a ... + + Keys = _PropertyGet("Keys") + +End Property ' ScriptForge.SF_Dictionary.Keys + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function Add(Optional ByVal Key As Variant _ + , Optional ByVal Item As Variant _ + ) As Boolean +''' Add a new key-item pair into the dictionary +''' Args: +''' Key: must not yet exist in the dictionary +''' Item: any value, including an array, a Basic object, a UNO object, ... +''' Returns: True if successful +''' Exceptions: +''' DUPLICATEKEYERROR: such a key exists already +''' INVALIDKEYERROR: zero-length string or only spaces +''' Examples: +''' myDict.Add("NewKey", NewValue) + +Dim oItemMap As ItemMap ' New entry in the MapItems array +Const cstThisSub = "Dictionary.Add" +Const cstSubArgs = "Key, Item" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Add = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Key, "Key", V_STRING) Then GoTo Catch + If IsArray(Item) Then + If Not SF_Utils._ValidateArray(Item, "Item") Then GoTo Catch + Else + If Not SF_Utils._Validate(Item, "Item") Then GoTo Catch + End If + End If + If Key = Space(Len(Key)) Then GoTo CatchInvalid + If Exists(Key) Then GoTo CatchDuplicate + +Try: + _MapSize = _MapSize + 1 + MapKeys.Add(_MapSize, Key) + oItemMap.Key = Key + oItemMap.Value = Item + ReDim Preserve MapItems(1 To _MapSize) + MapItems(_MapSize) = oItemMap + Add = True + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchDuplicate: + SF_Exception.RaiseFatal(DUPLICATEKEYERROR, "Key", Key) + GoTo Finally +CatchInvalid: + SF_Exception.RaiseFatal(INVALIDKEYERROR, "Key") + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.Add + +REM ----------------------------------------------------------------------------- +Public Function ConvertToArray() As Variant +''' Store the content of the dictionary in a 2-columns array: +''' Key stored in 1st column, Item stored in 2nd +''' Args: +''' Returns: +''' a zero-based 2D array(0:Count - 1, 0:1) +''' an empty array if the dictionary is empty + +Dim vArray As Variant ' Return value +Dim sKey As String ' Tempry key +Dim vKeys As Variant ' Array of keys +Dim lCount As Long ' Counter +Const cstThisSub = "Dictionary.ConvertToArray" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vArray = Array() + If Count = 0 Then + Else + ReDim vArray(0 To Count - 1, 0 To 1) + lCount = -1 + vKeys = Keys + For Each sKey in vKeys + lCount = lCount + 1 + vArray(lCount, 0) = sKey + vArray(lCount, 1) = Item(sKey) + Next sKey + End If + +Finally: + ConvertToArray = vArray() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ConvertToArray + +REM ----------------------------------------------------------------------------- +Public Function ConvertToJson(ByVal Optional Indent As Variant) As Variant +''' Convert the content of the dictionary to a JSON string +''' JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON +''' Limitations +''' Allowed item types: String, Boolean, numbers, Null and Empty +''' Arrays containing above types are allowed +''' Dates are converted into strings (not within arrays) +''' Other types are converted to their string representation (cfr. SF_String.Represent) +''' Args: +''' Indent: +''' If indent is a non-negative integer or string, then JSON array elements and object members will be pretty-printed with that indent level. +''' An indent level <= 0 will only insert newlines. +''' "", (the default) selects the most compact representation. +''' Using a positive integer indent indents that many spaces per level. +''' If indent is a string (such as Chr(9)), that string is used to indent each level. +''' Returns: +''' the JSON string +''' Example: +''' myDict.Add("p0", 12.5) +''' myDict.Add("p1", "a string àé""ê") +''' myDict.Add("p2", DateSerial(2020,9,28)) +''' myDict.Add("p3", True) +''' myDict.Add("p4", Array(1,2,3)) +''' MsgBox a.ConvertToJson() ' {"p0": 12.5, "p1": "a string \u00e0\u00e9\"\u00ea", "p2": "2020-09-28", "p3": true, "p4": [1, 2, 3]} + +Dim sJson As String ' Return value +Dim vArray As Variant ' Array of property values +Dim oPropertyValue As Object ' com.sun.star.beans.PropertyValue +Dim sKey As String ' Tempry key +Dim vKeys As Variant ' Array of keys +Dim vItem As Variant ' Tempry item +Dim iVarType As Integer ' Extended VarType +Dim lCount As Long ' Counter +Dim vIndent As Variant ' Python alias of Indent +Const cstPyHelper = "$" & "_SF_Dictionary__ConvertToJson" + +Const cstThisSub = "Dictionary.ConvertToJson" +Const cstSubArgs = "[Indent=Null]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Indent) Or IsEmpty(INDENT) Then Indent = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Indent, "Indent", Array(V_STRING, V_NUMERIC)) Then GoTo Finally + End If + sJson = "" + +Try: + vArray = Array() + If Count = 0 Then + Else + ReDim vArray(0 To Count - 1) + lCount = -1 + vKeys = Keys + For Each sKey in vKeys + ' Check item type + vItem = Item(sKey) + iVarType = SF_Utils._VarTypeExt(vItem) + Select Case iVarType + Case V_STRING, V_BOOLEAN, V_NUMERIC, V_NULL, V_EMPTY + Case V_DATE + vItem = SF_Utils._CDateToIso(vItem) + Case >= V_ARRAY + Case Else + vItem = SF_Utils._Repr(vItem) + End Select + ' Build in each array entry a (Name, Value) pair + Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, vItem) + lCount = lCount + 1 + Set vArray(lCount) = oPropertyValue + Next sKey + End If + + 'Pass array to Python script for the JSON conversion + With ScriptForge.SF_Session + vIndent = Indent + If VarType(Indent) = V_STRING Then + If Len(Indent) = 0 Then vIndent = Null + End If + sJson = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper, vArray, vIndent) + End With + +Finally: + ConvertToJson = sJson + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ConvertToJson + +REM ----------------------------------------------------------------------------- +Public Function ConvertToPropertyValues() As Variant +''' Store the content of the dictionary in an array of PropertyValues +''' Key stored in Name, Item stored in Value +''' Args: +''' Returns: +''' a zero-based 1D array(0:Count - 1). Each entry is a com.sun.star.beans.PropertyValue +''' Name: the key in the dictionary +''' Value: +''' Dates are converted to UNO dates +''' Empty arrays are replaced by Null +''' an empty array if the dictionary is empty + +Dim vArray As Variant ' Return value +Dim oPropertyValue As Object ' com.sun.star.beans.PropertyValue +Dim sKey As String ' Tempry key +Dim vKeys As Variant ' Array of keys +Dim lCount As Long ' Counter +Const cstThisSub = "Dictionary.ConvertToPropertyValues" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vArray = Array() + If Count = 0 Then + Else + ReDim vArray(0 To Count - 1) + lCount = -1 + vKeys = Keys + For Each sKey in vKeys + ' Build in each array entry a (Name, Value) pair + Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, Item(sKey)) + lCount = lCount + 1 + Set vArray(lCount) = oPropertyValue + Next sKey + End If + +Finally: + ConvertToPropertyValues = vArray() + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ConvertToPropertyValues + +REM ----------------------------------------------------------------------------- +Public Function Exists(Optional ByVal Key As Variant) As Boolean +''' Determine if a key exists in the dictionary +''' Args: +''' Key: the key value (string) +''' Returns: True if key exists +''' Examples: +''' If myDict.Exists("SomeKey") Then ' don't add again + +Dim vItem As Variant ' Item part in MapKeys +Const cstThisSub = "Dictionary.Exists" +Const cstSubArgs = "Key" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Exists = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Key, "Key", V_STRING) Then GoTo Catch + End If + +Try: + ' Dirty but preferred to go through whole collection + On Local Error GoTo NotFound + vItem = MapKeys(Key) + NotFound: + Exists = ( Not ( Err = 5 ) And vItem > 0 ) + On Local Error GoTo 0 + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.Exists + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByVal Key As Variant _ + ) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Key: mandatory if PropertyName = "Item", ignored otherwise +''' Returns: +''' The actual value of the property +''' Exceptions: +''' ARGUMENTERROR The property does not exist +''' Examples: +''' myDict.GetProperty("Count") + +Const cstThisSub = "Dictionary.GetProperty" +Const cstSubArgs = "PropertyName, [Key]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If IsMissing(Key) Or IsEmpty(Key) Then Key = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName, Key) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function ImportFromJson(Optional ByVal InputStr As Variant _ + , Optional Byval Overwrite As Variant _ + ) As Boolean +''' Adds the content of a Json string into the current dictionary +''' JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON +''' Limitations +''' The JSON string may contain numbers, strings, booleans, null values and arrays containing those types +''' It must not contain JSON objects, i.e. sub-dictionaries +''' An attempt is made to convert strings to dates if they fit one of next patterns: +''' YYYY-MM-DD, HH:MM:SS or YYYY-MM-DD HH:MM:SS +''' Args: +''' InputStr: the json string to import +''' Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten +''' Default = False +''' Returns: +''' True if successful +''' Exceptions: +''' DUPLICATEKEYERROR: such a key exists already +''' INVALIDKEYERROR: zero-length string or only spaces +''' Example: +''' Dim s As String +''' s = "{'firstName': 'John','lastName': 'Smith','isAlive': true,'age': 66, 'birth': '1954-09-28 20:15:00'" _ +''' & ",'address': {'streetAddress': '21 2nd Street','city': 'New York','state': 'NY','postalCode': '10021-3100'}" _ +''' & ",'phoneNumbers': [{'type': 'home','number': '212 555-1234'},{'type': 'office','number': '646 555-4567'}]" _ +''' & ",'children': ['Q','M','G','T'],'spouse': null}" +''' s = Replace(s, "'", """") +''' myDict.ImportFromJson(s, OverWrite := True) +''' ' The (sub)-dictionaries "adress" and "phoneNumbers(0) and (1) are reduced to Empty + +Dim bImport As Boolean ' Return value +Dim vArray As Variant ' JSON string converted to array +Dim vArrayEntry As Variant ' A single entry in vArray +Dim vKey As Variant ' Tempry key +Dim vItem As Variant ' Tempry item +Dim bExists As Boolean ' True when an entry exists +Dim dDate As Date ' String converted to Date +Const cstPyHelper = "$" & "_SF_Dictionary__ImportFromJson" + +Const cstThisSub = "Dictionary.ImportFromJson" +Const cstSubArgs = "InputStr, [Overwrite=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bImport = False + +Check: + If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Overwrite, "Overwrite", V_BOOLEAN) Then GoYo Finally + End If + +Try: + With ScriptForge.SF_Session + vArray = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper, InputStr) + End With + If Not IsArray(vArray) Then GoTo Finally ' Conversion error or nothing to do + + ' vArray = Array of subarrays = 2D DataArray (cfr. Calc) + For Each vArrayEntry In vArray + vKey = vArrayEntry(0) + If VarType(vKey) = V_STRING Then ' Else skip + vItem = vArrayEntry(1) + If Overwrite Then bExists = Exists(vKey) Else bExists = False + ' When the item matches a date pattern, convert it to a date + If VarType(vItem) = V_STRING Then + dDate = SF_Utils._CStrToDate(vItem) + If dDate > -1 Then vItem = dDate + End If + If bExists Then + ReplaceItem(vKey, vItem) + Else + Add(vKey, vItem) ' Key controls are done in Add + End If + End If + Next vArrayEntry + + bImport = True + +Finally: + ImportFromJson = bImport + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ImportFromJson + +REM ----------------------------------------------------------------------------- +Public Function ImportFromPropertyValues(Optional ByVal PropertyValues As Variant _ + , Optional Byval Overwrite As Variant _ + ) As Boolean +''' Adds the content of an array of PropertyValues into the current dictionary +''' Names contain Keys, Values contain Items +''' UNO dates are replaced by Basic dates +''' Args: +''' PropertyValues: a zero-based 1D array. Each entry is a com.sun.star.beans.PropertyValue +''' Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten +''' Default = False +''' Returns: +''' True if successful +''' Exceptions: +''' DUPLICATEKEYERROR: such a key exists already +''' INVALIDKEYERROR: zero-length string or only spaces + +Dim bImport As Boolean ' Return value +Dim oPropertyValue As Object ' com.sun.star.beans.PropertyValue +Dim vItem As Variant ' Tempry item +Dim sObjectType As String ' UNO object type of dates +Dim bExists As Boolean ' True when an entry exists +Const cstThisSub = "Dictionary.ImportFromPropertyValues" +Const cstSubArgs = "PropertyValues, [Overwrite=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bImport = False + +Check: + If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If IsArray(PropertyValues) Then + If Not SF_Utils._ValidateArray(PropertyValues, "PropertyValues", 1, V_OBJECT, True) Then GoTo Finally + Else + If Not SF_Utils._Validate(PropertyValues, "PropertyValues", V_OBJECT) Then GoTo Finally + End If + If Not SF_Utils._Validate(Overwrite, "Overwrite", V_BOOLEAN) Then GoYo Finally + End If + +Try: + If Not IsArray(PropertyValues) Then PropertyValues = Array(PropertyValues) + With oPropertyValue + For Each oPropertyValue In PropertyValues + If Overwrite Then bExists = Exists(.Name) Else bExists = False + If SF_Session.UnoObjectType(oPropertyValue) = "com.sun.star.beans.PropertyValue" Then + If IsUnoStruct(.Value) Then + sObjectType = SF_Session.UnoObjectType(.Value) + Select Case sObjectType + Case "com.sun.star.util.DateTime" : vItem = CDateFromUnoDateTime(.Value) + Case "com.sun.star.util.Date" : vItem = CDateFromUnoDate(.Value) + Case "com.sun.star.util.Time" : vItem = CDateFromUnoTime(.Value) + Case Else : vItem = .Value + End Select + Else + vItem = .Value + End If + If bExists Then + ReplaceItem(.Name, vItem) + Else + Add(.Name, vItem) ' Key controls are done in Add + End If + End If + Next oPropertyValue + End With + bImport = True + +Finally: + ImportFromPropertyValues = bImport + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ImportFromPropertyValues + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list or methods of the Dictionary class as an array + + Methods = Array( _ + "Add" _ + , "ConvertToArray" _ + , "ConvertToJson" _ + , "ConvertToPropertyValues" _ + , "Exists" _ + , "ImportFromJson" _ + , "ImportFromPropertyValues" _ + , "Remove" _ + , "RemoveAll" _ + , "ReplaceItem" _ + , "ReplaceKey" _ + ) + +End Function ' ScriptForge.SF_Dictionary.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Dictionary class as an array + + Properties = Array( _ + "Count" _ + , "Item" _ + , "Items" _ + , "Keys" _ + ) + +End Function ' ScriptForge.SF_Dictionary.Properties + +REM ----------------------------------------------------------------------------- +Public Function Remove(Optional ByVal Key As Variant) As Boolean +''' Remove an existing dictionary entry based on its key +''' Args: +''' Key: must exist in the dictionary +''' Returns: True if successful +''' Exceptions: +''' UNKNOWNKEYERROR: the key does not exist +''' Examples: +''' myDict.Remove("OldKey") + +Dim lIndex As Long ' To remove entry in the MapItems array +Const cstThisSub = "Dictionary.Remove" +Const cstSubArgs = "Key" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Remove = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Key, "Key", V_STRING) Then GoTo Catch + End If + If Not Exists(Key) Then GoTo CatchUnknown + +Try: + lIndex = MapKeys.Item(Key) + MapKeys.Remove(Key) + Erase MapItems(lIndex) ' Is now Empty + _MapRemoved = _MapRemoved + 1 + Remove = True + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchUnknown: + SF_Exception.RaiseFatal(UNKNOWNKEYERROR, "Key", Key) + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.Remove + +REM ----------------------------------------------------------------------------- +Public Function RemoveAll() As Boolean +''' Remove all the entries from the dictionary +''' Args: +''' Returns: True if successful +''' Examples: +''' myDict.RemoveAll() + +Dim vKeys As Variant ' Array of keys +Dim sColl As String ' A collection key in MapKeys +Const cstThisSub = "Dictionary.RemoveAll" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + RemoveAll = False + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vKeys = Keys + For Each sColl In vKeys + MapKeys.Remove(sColl) + Next sColl + Erase MapKeys + Erase MapItems + ' Make dictionary ready to receive new entries + Call Class_Initialize() + RemoveAll = True + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.RemoveAll + +REM ----------------------------------------------------------------------------- +Public Function ReplaceItem(Optional ByVal Key As Variant _ + , Optional ByVal Value As Variant _ + ) As Boolean +''' Replace the item value +''' Args: +''' Key: must exist in the dictionary +''' Returns: True if successful +''' Exceptions: +''' UNKNOWNKEYERROR: the old key does not exist +''' Examples: +''' myDict.ReplaceItem("Key", NewValue) + +Dim oItemMap As ItemMap ' Content to update in the MapItems array +Dim lIndex As Long ' Entry in the MapItems array +Const cstThisSub = "Dictionary.ReplaceItem" +Const cstSubArgs = "Key, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + ReplaceItem = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Key, "Key", V_STRING) Then GoTo Catch + If Not SF_Utils._Validate(Value, "Value") Then GoTo Catch + End If + If Not Exists(Key) Then GoTo CatchUnknown + +Try: + ' Find entry in MapItems and update it with the new value + lIndex = MapKeys.Item(Key) + oItemMap = MapItems(lIndex) + oItemMap.Value = Value + ReplaceItem = True + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchUnknown: + SF_Exception.RaiseFatal(UNKNOWNKEYERROR, "Key", Key) + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ReplaceItem + +REM ----------------------------------------------------------------------------- +Public Function ReplaceKey(Optional ByVal Key As Variant _ + , Optional ByVal Value As Variant _ + ) As Boolean +''' Replace existing key +''' Args: +''' Key: must exist in the dictionary +''' Value: must not exist in the dictionary +''' Returns: True if successful +''' Exceptions: +''' UNKNOWNKEYERROR: the old key does not exist +''' DUPLICATEKEYERROR: the new key exists +''' Examples: +''' myDict.ReplaceKey("OldKey", "NewKey") + +Dim oItemMap As ItemMap ' Content to update in the MapItems array +Dim lIndex As Long ' Entry in the MapItems array +Const cstThisSub = "Dictionary.ReplaceKey" +Const cstSubArgs = "Key, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + ReplaceKey = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Key, "Key", V_STRING) Then GoTo Catch + If Not SF_Utils._Validate(Value, "Value", V_STRING) Then GoTo Catch + End If + If Not Exists(Key) Then GoTo CatchUnknown + If Value = Space(Len(Value)) Then GoTo CatchInvalid + If Exists(Value) Then GoTo CatchDuplicate + +Try: + ' Remove the Key entry and create a new one in MapKeys + With MapKeys + lIndex = .Item(Key) + .Remove(Key) + .Add(lIndex, Value) + End With + oItemMap = MapItems(lIndex) + oItemMap.Key = Value + ReplaceKey = True + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchUnknown: + SF_Exception.RaiseFatal(UNKNOWNKEYERROR, "Key", Key) + GoTo Finally +CatchDuplicate: + SF_Exception.RaiseFatal(DUPLICATEKEYERROR, "Value", Value) + GoTo Finally +CatchInvalid: + SF_Exception.RaiseFatal(INVALIDKEYERROR, "Key") + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.ReplaceKey + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Dictionary.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary.SetProperty + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String _ + , Optional pvKey As Variant _ + ) +''' Return the named property +''' Args: +''' psProperty: the name of the property +''' pvKey: the key to retrieve, numeric or string + +Dim vItemMap As Variant ' Entry in the MapItems array +Dim vArray As Variant ' To get Keys or Values +Dim i As Long +Dim cstThisSub As String +Dim cstSubArgs As String + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + cstThisSub = "SF_Dictionary.get" & psProperty + If IsMissing(pvKey) Then cstSubArgs = "" Else cstSubArgs = "[Key]" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + Select Case UCase(psProperty) + Case UCase("Count") + _PropertyGet = _MapSize - _MapRemoved + Case UCase("Item") + If Not SF_Utils._Validate(pvKey, "Key", V_STRING) Then GoTo Catch + If Exists(pvKey) Then _PropertyGet = MapItems(MapKeys(pvKey)).Value Else _PropertyGet = Empty + Case UCase("Keys"), UCase("Items") + vArray = Array() + If _MapSize - _MapRemoved - 1 >= 0 Then + ReDim vArray(0 To (_MapSize - _MapRemoved - 1)) + i = -1 + For each vItemMap In MapItems() + If Not IsEmpty(vItemMap) Then + i = i + 1 + If UCase(psProperty) = "KEYS" Then vArray(i) = vItemMap.Key Else vArray(i) = vItemMap.Value + End If + Next vItemMap + End If + _PropertyGet = vArray + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Dictionary._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the Dictionary instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[Dictionary] (key1:value1, key2:value2, ...) + +Dim sDict As String ' Return value +Dim vKeys As Variant ' Array of keys +Dim sKey As String ' Tempry key +Dim vItem As Variant ' Tempry item +Const cstDictEmpty = "[Dictionary] ()" +Const cstDict = "[Dictionary]" +Const cstMaxLength = 50 ' Maximum length for items +Const cstSeparator = ", " + + _Repr = "" + + If Count = 0 Then + sDict = cstDictEmpty + Else + sDict = cstDict & " (" + vKeys = Keys + For Each sKey in vKeys + vItem = Item(sKey) + sDict = sDict & sKey & ":" & SF_Utils._Repr(vItem, cstMaxLength) & cstSeparator + Next sKey + sDict = Left(sDict, Len(sDict) - Len(cstSeparator)) & ")" ' Suppress last comma + End If + + _Repr = sDict + +End Function ' ScriptForge.SF_Dictionary._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_DICTIONARY +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Exception.xba b/wizards/source/scriptforge/SF_Exception.xba new file mode 100644 index 000000000000..09e930a9ba90 --- /dev/null +++ b/wizards/source/scriptforge/SF_Exception.xba @@ -0,0 +1,1107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Exception" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' Exception (aka SF_Exception) +''' ========= +''' Generic singleton class for Basic code debugging and error handling +''' +''' Errors may be generated by +''' the Basic run-time error detection +''' in the ScriptForge code => RaiseAbort() +''' in a user code => Raise() +''' an error detection implemented +''' in the ScriptForge code => RaiseFatal() +''' in a user code => Raise() or RaiseWarning() +''' +''' When a run-time error occurs, the properties of the Exception object are filled +''' with information that uniquely identifies the error and information that can be used to handle it +''' The SF_Exception object is in this context similar to the VBA Err object +''' See https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/err-object +''' The Number property identifies the error: it can be a numeric value or a string +''' Numeric values up to 2000 are considered Basic run-time errors +''' +''' The "console" logs events, actual variable values, errors, ... It is an easy mean +''' to debug Basic programs especially when the IDE is not usable, f.i. in Calc user defined functions +''' or during control events processing +''' => DebugPrint() +''' +''' The usual behaviour of the application when an error occurs is: +''' 1. Log the error in the console +''' 2, Inform the user about the error with either a standard or a customized message +''' 3. Optionally, stop the execution of the current macro +''' +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +' SF_Utils +Const MISSINGARGERROR = "MISSINGARGERROR" +Const ARGUMENTERROR = "ARGUMENTERROR" +Const ARRAYERROR = "ARRAYERROR" +Const FILEERROR = "FILEERROR" + +' SF_Array +Const ARRAYSEQUENCEERROR = "ARRAYSEQUENCEERROR" +Const ARRAYINSERTERROR = "ARRAYINSERTERROR" +Const ARRAYINDEX1ERROR = "ARRAYINDEX1ERROR" +Const ARRAYINDEX2ERROR = "ARRAYINDEX2ERROR" +Const CSVPARSINGERROR = "CSVPARSINGERROR" +Const CSVOVERFLOWWARNING = "CSVOVERFLOWWARNING" + +' SF_Dictionary +Const DUPLICATEKEYERROR = "DUPLICATEKEYERROR" +Const UNKNOWNKEYERROR = "UNKNOWNKEYERROR" +Const INVALIDKEYERROR = "INVALIDKEYERROR" + +' SF_FileSystem +Const UNKNOWNFILEERROR = "UNKNOWNFILEERROR" +Const UNKNOWNFOLDERERROR = "UNKNOWNFOLDERERROR" +Const NOTAFILEERROR = "NOTAFILEERROR" +Const NOTAFOLDERERROR = "NOTAFOLDERERROR" +Const OVERWRITEERROR = "OVERWRITEERROR" +Const READONLYERROR = "READONLYERROR" +Const NOFILEMATCHERROR = "NOFILEMATCHFOUND" +Const FOLDERCREATIONERROR = "FOLDERCREATIONERROR" + +' SF_Services +Const UNKNOWNSERVICEERROR = "UNKNOWNSERVICEERROR" +Const SERVICESNOTLOADEDERROR = "SERVICESNOTLOADEDERROR" + +' SF_Session +Const CALCFUNCERROR = "CALCFUNCERROR" +Const NOSCRIPTERROR = "NOSCRIPTERROR" +Const SCRIPTEXECERROR = "SCRIPTEXECERROR" +Const WRONGEMAILERROR = "WRONGEMAILERROR" +Const SENDMAILERROR = "SENDMAILERROR" + +' SF_TextStream +Const FILENOTOPENERROR = "FILENOTOPENERROR" +Const FILEOPENMODEERROR = "FILEOPENMODEERROR" + +' SF_UI +Const DOCUMENTERROR = "DOCUMENTERROR" +Const DOCUMENTCREATIONERROR = "DOCUMENTCREATIONERROR" +Const DOCUMENTOPENERROR = "DOCUMENTOPENERROR" +Const BASEDOCUMENTOPENERROR = "BASEDOCUMENTOPENERROR" + +' SF_Document +Const DOCUMENTDEADERROR = "DOCUMENTDEADERROR" +Const DOCUMENTSAVEERROR = "DOCUMENTSAVEERROR" +Const DOCUMENTSAVEASERROR = "DOCUMENTSAVEASERROR" +Const DOCUMENTREADONLYERROR = "DOCUMENTREADONLYERROR" +Const DBCONNECTERROR = "DBCONNECTERROR" + +' SF_Calc +Const CALCADDRESSERROR = "CALCADDRESSERROR" +Const DUPLICATESHEETERROR = "DUPLICATESHEETERROR" +Const OFFSETADDRESSERROR = "OFFSETADDRESSERROR" + +' SF_Dialog +Const DIALOGNOTFOUNDERROR = "DIALOGNOTFOUNDERROR" +Const DIALOGDEADERROR = "DIALOGDEADERROR" +Const CONTROLTYPEERROR = "CONTROLTYPEERROR" +Const TEXTFIELDERROR = "TEXTFIELDERROR" + +' SF_Database +Const DBREADONLYERROR = "DBREADONLYERROR" +Const SQLSYNTAXERROR = "SQLSYNTAXERROR" + +REM ============================================================= PRIVATE MEMBERS + +' User defined errors +Private _Number As Variant ' Error number/code (Integer or String) +Private _Source As Variant ' Where the error occurred: a module, a Sub/Function, ... +Private _Description As String ' The error message + +' System run-time errors +Private _SysNumber As Long ' Alias of Err +Private _SysSource As Long ' Alias of Erl +Private _SysDescription As String ' Alias of Error$ + +REM ============================================================ MODULE CONSTANTS + +Const RUNTIMEERRORS = 2000 ' Upper limit of Basic run-time errors +Const CONSOLENAME = "ConsoleLines" ' Name of control in the console dialog + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_Exception Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get Description() As Variant +''' Returns the description of the last error that has occurred +''' Example: +''' myException.Description + Description = _PropertyGet("Description") +End Property ' ScriptForge.SF_Exception.Description (get) + +REM ----------------------------------------------------------------------------- +Property Let Description(ByVal pvDescription As Variant) +''' Set the description of the last error that has occurred +''' Example: +''' myException.Description = "Not smart to divide by zero" + _PropertySet "Description", pvDescription +End Property ' ScriptForge.SF_Exception.Description (let) + +REM ----------------------------------------------------------------------------- +Property Get Number() As Variant +''' Returns the code of the last error that has occurred +''' Example: +''' myException.Number + Number = _PropertyGet("Number") +End Property ' ScriptForge.SF_Exception.Number (get) + +REM ----------------------------------------------------------------------------- +Property Let Number(ByVal pvNumber As Variant) +''' Set the code of the last error that has occurred +''' Example: +''' myException.Number = 11 ' Division by 0 + _PropertySet "Number", pvNumber +End Property ' ScriptForge.SF_Exception.Number (let) + +REM ----------------------------------------------------------------------------- +Property Get Source() As Variant +''' Returns the location of the last error that has occurred +''' Example: +''' myException.Source + Source = _PropertyGet("Source") +End Property ' ScriptForge.SF_Exception.Source (get) + +REM ----------------------------------------------------------------------------- +Property Let Source(ByVal pvSource As Variant) +''' Set the location of the last error that has occurred +''' Example: +''' myException.Source = 123 ' Line # 123. Source may also be a string + _PropertySet "Source", pvSource +End Property ' ScriptForge.SF_Exception.Source (let) + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_Exception" +End Property ' ScriptForge.SF_String.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.Exception" +End Property ' ScriptForge.SF_Exception.ServiceName + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Sub Clear() +''' Reset the current error status and clear the SF_Exception object +''' Args: +''' Examples: +''' On Local Error GoTo Catch +''' ' ... +''' Catch: +''' SF_Exception.Clear() ' Deny the error + +Const cstThisSub = "Exception.Clear" +Const cstSubArgs = "" + +Check: + +Try: + With SF_Exception + ._Number = Empty + ._Source = Empty + ._Description = "" + ._SysNumber = 0 + ._SysSource = 0 + ._SysDescription = "" + End With + +Finally: + On Error GoTo 0 + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Exception.Clear + +REM ----------------------------------------------------------------------------- +Public Sub Console(Optional ByVal Modal As Variant) +''' Display the console messages in a modal or non-modal dialog +''' If the dialog is already active, when non-modal, it is brought to front +''' Args: +''' Modal: Boolean. Default = True +''' Example: +''' SF_Exception.Console() + +Dim bConsoleActive As Boolean ' When True, dialog is active +Dim sClose As String ' Caption of the close buttons +Dim oModalBtn As Object ' Modal close button +Dim oNonModalBtn As Object ' Non modal close button +Const cstThisSub = "Exception.Console" +Const cstSubArgs = "[Modal=True]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Finally ' Never interrupt processing + +Check: + If IsMissing(Modal) Or IsEmpty(Modal) Then Modal = True + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Modal, "Modal", V_BOOLEAN) Then GoTo Finally + End If + +Try: + With _SF_ + bConsoleActive = False + If Not IsNull(.ConsoleDialog) Then bConsoleActive = .ConsoleDialog._IsStillAlive(False) ' False to not raise an error + If bConsoleActive Then + ' Bring to front + .ConsoleDialog.Activate() + Else + ' Initialize dialog and fill with actual data + ' The dual modes (modal and non-modal) require to have 2 close buttons o/w only 1 is visible + ' - a usual OK button + ' - a Default button triggering the Close action + Set .ConsoleDialog = CreateScriptService("SFDialogs.Dialog", "GlobalScope", "ScriptForge", "dlgConsole") + ' Setup labels and visibility + sClose = .Interface.GetText("CLOSEBUTTON") + Set oModalBtn = .ConsoleDialog.Controls("CloseModalButton") + Set oNonModalBtn = .ConsoleDialog.Controls("CloseNonModalButton") + If Modal Then oModalBtn.Caption = sClose Else oNonModalBtn.Caption = sClose + oModalBtn.Visible = Modal + oNonModalBtn.Visible = CBool(Not Modal) + ' Load console lines + _ConsoleRefresh() + .ConsoleDialog.Execute(Modal) + ' Terminate the modal dialog + If Modal Then + Set .ConsoleControl = .ConsoleControl.Dispose() + Set .ConsoleDialog = .ConsoleDialog.Dispose() + End If + End If + End With + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +End Sub ' ScriptForge.SF_Exception.Console + +REM ----------------------------------------------------------------------------- +Public Sub ConsoleClear(Optional ByVal Keep) +''' Clear the console keeping an optional number of recent messages +''' Args: +''' Keep: the number of messages to keep +''' If Keep is bigger than the the number of messages stored in the console, +''' the console is not cleared +''' Example: +''' SF_Exception.ConsoleClear(5) + +Dim lConsole As Long ' UBound of ConsoleLines +Const cstThisSub = "Exception.ConsoleClear" +Const cstSubArgs = "[Keep=0]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Finally ' Never interrupt processing + +Check: + If IsMissing(Keep) Or IsEmpty(Keep) Then Keep = 0 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Keep, "Keep", V_NUMERIC) Then GoTo Finally + End If + +Try: + With _SF_ + If Keep <= 0 Then + .ConsoleLines = Array() + Else + lConsole = UBound(.ConsoleLines) + If Keep < lConsole + 1 Then .ConsoleLines = SF_Array.Slice(.ConsoleLines, lConsole - Keep + 1) + End If + End With + + ' If active, the console dialog needs to be refreshed + _ConsoleRefresh() + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +End Sub ' ScriptForge.SF_Exception.ConsoleClear + +REM ----------------------------------------------------------------------------- +Public Function ConsoleToFile(Optional ByVal FileName As Variant) As Boolean +''' Export the content of the console to a text file +''' If the file exists and the console is not empty, it is overwritten without warning +''' Args: +''' FileName: the complete file name to export to. It it exists, it will be overwritten without warning +''' Returns: +''' True if the file could be created +''' Examples: +''' SF_Exception.ConsoleToFile("myFile.txt") + +Dim bExport As Boolean ' Return value +Dim oFile As Object ' Output file handler +Dim sLine As String ' A single line +Const cstThisSub = "Exception.ConsoleToFile" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExport = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + +Try: + + If UBound(_SF_.ConsoleLines) > -1 Then + Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True) + If Not IsNull(oFile) Then + With oFile + For Each sLine In _SF_.ConsoleLines + .WriteLine(sLine) + Next sLine + .CloseFile() + End With + End If + bExport = True + End If + +Finally: + If Not IsNull(oFile) Then Set oFile = oFile.Dispose() + ConsoleToFile = bExport + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Exception.ConsoleToFile + +REM ----------------------------------------------------------------------------- +Public Sub DebugPrint(ParamArray pvArgs() As Variant) +''' Print the list of arguments in a readable form in the console +''' Arguments are separated by a TAB character (simulated by spaces) +''' The maximum length of each individual argument = 1024 characters +''' Args: +''' Any number of arguments of any type +''' Examples: +''' SF_Exception.DebugPrint(a, Array(1, 2, 3), , "line1" & Chr(10) & "Line2", DateSerial(2020, 04, 09)) + +Dim sOutput As String ' Line to write in console +Dim sArg As String ' Single argument +Dim sMainSub As String ' Temporary storage for main function +Dim i As Integer +Const cstTab = 4 +Const cstMaxLength = 1024 +Const cstThisSub = "Exception.DebugPrint" +Const cstSubArgs = "Arg0, [Arg1, ...]" + + If SF_Utils._ErrorHandling() Then On Local Error Goto Finally ' Never interrupt processing + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) +Try: + ' Build new console line + sOutput = "" + For i = 0 To UBound(pvArgs) + sArg = Iif(i = 0, "", SF_String.sfTAB) & SF_Utils._Repr(pvArgs(i), cstMaxLength) 'Do not use SF_String.Represent() + sOutput = sOutput & sArg + Next i + + ' Add to actual console + _SF_._AddToConsole(SF_String.ExpandTabs(sOutput, cstTab)) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +End Sub ' ScriptForge.SF_Exception.DebugPrint + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' If the property does not exist, returns Null +''' Exceptions +''' ARGUMENTERROR The property does not exist +''' Examples: +''' myException.GetProperty("MyProperty") + +Const cstThisSub = "Exception.GetProperty" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Exception.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Exception service as an array + + Methods = Array( _ + "Clear" _ + , "Console" _ + , "ConsoleClear" _ + , "ConsoleToFile" _ + , "DebugPrint" _ + , "Raise" _ + , "RaiseAbort" _ + , "RaiseFatal" _ + , "RaiseWarning" _ + ) + +End Function ' ScriptForge.SF_Exception.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Timer class as an array + + Properties = Array( _ + "Description" _ + , "Number" _ + , "Source" _ + ) + +End Function ' ScriptForge.SF_Exception.Properties + +REM ----------------------------------------------------------------------------- +Public Sub Raise(Optional ByVal Number As Variant _ + , Optional ByVal Source As Variant _ + , Optional ByVal Description As Variant _ + ) +''' Generate a run-time error. An error message is displayed to the user and logged +''' in the console. The execution is STOPPED +''' Args: +''' Number: the error number, may be numeric or string +''' If numeric and <= 2000, it is considered a LibreOffice Basic run-time error (default = Err) +''' Source: the line where the error occurred (default = Erl) or any string describing the location of the error +''' Description: the error message to log in the console and to display to the user +''' Examples: +''' On Local Error GoTo Catch +''' ' ... +''' Catch: +''' SF_Exception.Raise() ' Standard behaviour +''' SF_Exception.Raise(11) ' Force division by zero +''' SF_Exception.Raise("MYAPPERROR", "myFunction", "Application error") +''' SF_Exception.Raise(,, "To divide by zero is not a good idea !") + +Dim sMessage As String ' Error message to log and to display +Dim L10N As Object ' Alias to Interface +Const cstThisSub = "Exception.Raise" +Const cstSubArgs = "[Number=Err], [Source=Erl], [Description]" + + ' Save Err, Erl, .. values before any On Error ... statement + SF_Exception._CaptureSystemError() + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Number) Or IsEmpty(Number) Then Number = -1 + If IsMissing(Source) Or IsEmpty(Source) Then Source = -1 + If IsMissing(Description) Or IsEmpty(Description) Then Description = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Number, "Number", Array(V_STRING, V_NUMERIC)) Then GoTo Finally + If Not SF_Utils._Validate(Source, "Source", Array(V_STRING, V_NUMERIC)) Then GoTo Finally + If Not SF_Utils._Validate(Description, "Description", V_STRING) Then GoTo Finally + End If + +Try: + With SF_Exception + If Number >= 0 Then .Number = Number + If VarType(Source) = V_STRING Then + If Len(Source) > 0 Then .Source = Source + ElseIf Source >= 0 Then ' -1 = Default => no change + .Source = Source + End If + If Len(Description) > 0 Then .Description = Description + + ' Log and display + Set L10N = _SF_.Interface + sMessage = L10N.GetText("LONGERRORDESC", .Number, .Source, .Description) + .DebugPrint(sMessage) + If _SF_.DisplayEnabled Then MsgBox L10N.GetText("ERRORNUMBER", .Number) _ + & SF_String.sfNewLine & L10N.GetText("ERRORLOCATION", .Source) _ + & SF_String.sfNewLine & .Description _ + , MB_OK + MB_ICONSTOP _ + , L10N.GetText("ERRORNUMBER", .Number) + .Clear() + End With + +Finally: + SF_Utils._ExitFunction(cstThisSub) + If _SF_.StopWhenError Then + _SF_._StackReset() + Stop + End If + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Exception.Raise + +REM ----------------------------------------------------------------------------- +Public Sub RaiseAbort(Optional ByVal Source As Variant) +''' Manage a run-time error that occurred inside the ScriptForge piece of software itself. +''' The event is logged. +''' The execution is STOPPED +''' For INTERNAL USE only +''' Args: +''' Source: the line where the error occurred + +Dim sLocation As String ' Common header in error messages: location of error +Dim vLocation As Variant ' Splitted array (library, module, method) +Dim sMessage As String ' Error message to log and to display +Dim L10N As Object ' Alias to Interface +Const cstTabSize = 4 +Const cstThisSub = "Exception.RaiseAbort" +Const cstSubArgs = "[Source=Erl]" + + ' Save Err, Erl, .. values before any On Error ... statement + SF_Exception._CaptureSystemError() + On Local Error Resume Next + +Check: + If IsMissing(Source) Or IsEmpty(Source) Then Source = "" + +Try: + With SF_Exception + + ' Prepare message header + Set L10N = _SF_.Interface + If Len(_SF_.MainFunction) > 0 Then ' MainFunction = [Library.]Module.Method + vLocation = Split(_SF_.MainFunction, ".") + If UBound(vLocation) < 2 Then vLocation = SF_Array.Prepend(vLocation, "ScriptForge") + sLocation = L10N.GetText("VALIDATESOURCE", vLocation(0), vLocation(1), vLocation(2)) & "\n\n\n" + Else + sLocation = "" + End If + + ' Log and display + Set L10N = _SF_.Interface + sMessage = L10N.GetText("LONGERRORDESC", .Number, .Source, .Description) + .DebugPrint(sMessage) + If _SF_.DisplayEnabled Then + sMessage = sLocation _ + & L10N.GetText("INTERNALERROR") _ + & L10N.GetText("ERRORLOCATION", Source & "/" & .Source) & SF_String.sfNewLine & .Description _ + & "\n" & "\n" & "\n" & L10N.GetText("STOPEXECUTION") + MsgBox SF_String.ExpandTabs(SF_String.Unescape(sMessage), cstTabSize) _ + , MB_OK + MB_ICONSTOP _ + , L10N.GetText("ERRORNUMBER", .Number) + End If + + .Clear() + End With + +Finally: + _SF_._StackReset() + If _SF_.StopWhenError Then Stop + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Exception.RaiseAbort + +REM ----------------------------------------------------------------------------- +Public Sub RaiseFatal(Optional ByVal ErrorCode As Variant _ + , ParamArray pvArgs _ + ) +''' Generate a run-time error caused by an anomaly in a user script detected by ScriptForge +''' The message is logged in the console. The execution is STOPPED +''' For INTERNAL USE only +''' Args: +''' ErrorCode: as a string, the unique identifier of the error +''' pvArgs: the arguments to insert in the error message + +Dim sLocation As String ' Common header in error messages: location of error +Dim vLocation As Variant ' Splitted array (library, module, method) +Dim sMessage As String ' Message to log and display +Dim L10N As Object ' Alias of Interface +Dim sAlt As String ' Alternative error messages +Const cstTabSize = 4 +Const cstThisSub = "Exception.RaiseFatal" +Const cstSubArgs = "ErrorCode, [Arg0[, Arg1 ...]]" +Const cstStop = "⏻" ' Chr(9211) + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(ErrorCode) Or IsEmpty(ErrorCode) Then ErrorCode = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(ErrorCode, "ErrorCode", V_STRING) Then GoTo Finally + End If + +Try: + Set L10N = _SF_.Interface + ' Location header common to all error messages + If Len(_SF_.MainFunction) > 0 Then ' MainFunction = [Library.]Module.Method + vLocation = Split(_SF_.MainFunction, ".") + If UBound(vLocation) < 2 Then vLocation = SF_Array.Prepend(vLocation, "ScriptForge") + sLocation = L10N.GetText("VALIDATESOURCE", vLocation(0), vLocation(1), vLocation(2)) _ + & "\n" & L10N.GetText("VALIDATEARGS", _SF_.MainFunctionArgs) + Else + sLocation = "" + End If + + With L10N + Select Case UCase(ErrorCode) + Case MISSINGARGERROR ' SF_Utils._Validate(Name) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("VALIDATEMISSING", pvArgs(0)) + Case ARGUMENTERROR ' SF_Utils._Validate(Value, Name, Types, Values, Regex, Class) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(1)) _ + & "\n" & "\n" & .GetText("VALIDATIONRULES") + If Len(pvArgs(2)) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATETYPES", pvArgs(1), pvArgs(2)) + If Len(pvArgs(3)) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATEVALUES", pvArgs(1), pvArgs(3)) + If Len(pvArgs(4)) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATEREGEX", pvArgs(1), pvArgs(4)) + If Len(pvArgs(5)) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATECLASS", pvArgs(1), pvArgs(5)) + sMessage = sMessage & "\n" & "\n" & .GetText("VALIDATEACTUAL", pvArgs(1), pvArgs(0)) + Case ARRAYERROR ' SF_Utils._ValidateArray(Value, Name, Dimensions, Types, NotNull) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(1)) _ + & "\n" & "\n" & .GetText("VALIDATIONRULES") _ + & "\n" & .GetText("VALIDATEARRAY", pvArgs(1)) + If pvArgs(2) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATEDIMS", pvArgs(1), pvArgs(2)) + If Len(pvArgs(3)) > 0 Then sMessage = sMessage & "\n" & .GetText("VALIDATEALLTYPES", pvArgs(1), pvArgs(3)) + If pvArgs(4) Then sMessage = sMessage & "\n" & .GetText("VALIDATENOTNULL", pvArgs(1)) + sMessage = sMessage & "\n" & "\n" & .GetText("VALIDATEACTUAL", pvArgs(1), pvArgs(0)) + Case FILEERROR ' SF_Utils._ValidateFile(Value, Name, WildCards) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(1)) _ + & "\n" & "\n" & .GetText("VALIDATIONRULES") _ + & "\n" & "\n" & .GetText("VALIDATEFILE", pvArgs(1)) + sAlt = "VALIDATEFILE" & SF_FileSystem.FileNaming + sMessage = sMessage & "\n" & .GetText(sAlt, pvArgs(1)) + If pvArgs(2) Then sMessage = sMessage & "\n" & .GetText("VALIDATEWILDCARD", pvArgs(1)) + sMessage = sMessage & "\n" & "\n" & .GetText("VALIDATEACTUAL", pvArgs(1), pvArgs(0)) + Case ARRAYSEQUENCEERROR ' SF_Array.RangeInit(From, UpTo, ByStep) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("ARRAYSEQUENCE", pvArgs(0), pvArgs(1), pvArgs(2)) + Case ARRAYINSERTERROR ' SF_Array.AppendColumn/Row/PrependColumn/Row(VectorName, Array_2D, Vector) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("ARRAYINSERT", pvArgs(0), pvArgs(1), pvArgs(2)) + Case ARRAYINDEX1ERROR ' SF_Array.ExtractColumn/Row(IndexName, Array_2D, Index) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("ARRAYINDEX1", pvArgs(0), pvArgs(1), pvArgs(2)) + Case ARRAYINDEX2ERROR ' SF_Array.Slice(From, UpTo) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("ARRAYINDEX2", pvArgs(0), pvArgs(1), pvArgs(2)) + Case CSVPARSINGERROR ' SF_Array.ImportFromCSVFile(FileName, LineNumber, Line) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("CSVPARSING", pvArgs(0), pvArgs(1), pvArgs(2)) + Case DUPLICATEKEYERROR ' SF_Dictionary.Add/ReplaceKey("Key", Key) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("DUPLICATEKEY", pvArgs(0), pvArgs(1)) + Case UNKNOWNKEYERROR ' SF_Dictionary.Remove/ReplaceItem/ReplaceKey("Key", Key) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("UNKNOWNKEY", pvArgs(0), pvArgs(1)) + Case INVALIDKEYERROR ' SF_Dictionary.Add/ReplaceKey(Key) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("INVALIDKEY") + Case UNKNOWNFILEERROR ' SF_FileSystem.CopyFile/MoveFile/DeleteFile/CreateScriptService("L10N")(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("UNKNOWNFILE", pvArgs(0), pvArgs(1)) + Case UNKNOWNFOLDERERROR ' SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("UNKNOWNFOLDER", pvArgs(0), pvArgs(1)) + Case NOTAFILEERROR ' SF_FileSystem.CopyFiler/MoveFile/DeleteFile(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("NOTAFILE", pvArgs(0), pvArgs(1)) + Case NOTAFOLDERERROR ' SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("NOTAFOLDER", pvArgs(0), pvArgs(1)) + Case OVERWRITEERROR ' SF_FileSystem.Copy+Move/File+Folder/CreateTextFile/OpenTextFile(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("OVERWRITE", pvArgs(0), pvArgs(1)) + Case READONLYERROR ' SF_FileSystem.Copy+Move+Delete/File+Folder(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("READONLY", pvArgs(0), pvArgs(1)) + Case NOFILEMATCHERROR ' SF_FileSystem.Copy+Move+Delete/File+Folder(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("NOFILEMATCH", pvArgs(0), pvArgs(1)) + Case FOLDERCREATIONERROR ' SF_FileSystem.CreateFolder(ArgName, Filename) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("FOLDERCREATION", pvArgs(0), pvArgs(1)) + Case UNKNOWNSERVICEERROR ' SF_Services.CreateScriptService(ArgName, Value, Library, Service) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("UNKNOWNSERVICE", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case SERVICESNOTLOADEDERROR ' SF_Services.CreateScriptService(ArgName, Value, Library) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("SERVICESNOTLOADED", pvArgs(0), pvArgs(1), pvArgs(2)) + Case CALCFUNCERROR ' SF_Session.ExecuteCalcFunction(CalcFunction) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", "CalcFunction") _ + & "\n" & "\n" & .GetText("CALCFUNC", pvArgs(0)) + Case NOSCRIPTERROR ' SF_Session._GetScript(Language, "Scope", Scope, "Script", Script) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", "Script") _ + & "\n" & "\n" & .GetText("NOSCRIPT", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4)) + Case SCRIPTEXECERROR ' SF_Session.ExecuteBasicScript("Script", Script, Cause) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("SCRIPTEXEC", pvArgs(0), pvArgs(1), pvArgs(2)) + Case WRONGEMAILERROR ' SF_Session.SendMail(Arg, Email) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("WRONGEMAIL", pvArgs(1)) + Case SENDMAILERROR ' SF_Session.SendMail() + sMessage = sLocation _ + & "\n" & "\n" & .GetText("SENDMAIL") + Case FILENOTOPENERROR ' SF_TextStream._IsFileOpen(FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("FILENOTOPEN", pvArgs(0)) + Case FILEOPENMODEERROR ' SF_TextStream._IsFileOpen(FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("FILEOPENMODE", pvArgs(0), pvArgs(1)) + Case DOCUMENTERROR ' SF_UI.GetDocument(ArgName, WindowName) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("DOCUMENT", pvArgs(0), pvArgs(1)) + Case DOCUMENTCREATIONERROR ' SF_UI.Create(Arg1Name, DocumentType, Arg2Name, TemplateFile) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTCREATION", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case DOCUMENTOPENERROR ' SF_UI.OpenDocument(Arg1Name, FileName, Arg2Name, Password, Arg3Name, FilterName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTOPEN", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4), pvArgs(5)) + Case BASEDOCUMENTOPENERROR ' SF_UI.OpenBaseDocument(Arg1Name, FileName, Arg2Name, RegistrationName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("BASEDOCUMENTOPEN", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case DOCUMENTDEADERROR ' SF_Document._IsStillAlive(FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTDEAD", pvArgs(0)) + Case DOCUMENTSAVEERROR ' SF_Document.Save(Arg1Name, FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTSAVE", pvArgs(0), pvArgs(1)) + Case DOCUMENTSAVEASERROR ' SF_Document.SaveAs(Arg1Name, FileName, Arg2, Overwrite, Arg3, FilterName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTSAVEAS", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4), pvArgs(5)) + Case DOCUMENTREADONLYERROR ' SF_Document.update property("Document", FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DOCUMENTREADONLY", pvArgs(0), pvArgs(1)) + Case DBCONNECTERROR ' SF_Base.GetDatabase("User", User, "Password", Password, FileName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DBCONNECT", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4)) + Case CALCADDRESSERROR ' SF_Calc._ParseAddress(Address, "Range"/"Sheet", Scope, Document) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("CALCADDRESS" & Iif(pvArgs(0) = "Sheet", "1", "2"), pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case DUPLICATESHEETERROR ' SF_Calc.InsertSheet(arg, SheetName, Document) + sMessage = sLocation _ + & "\n" & "\n" & "\n" & .GetText("VALIDATEERROR", pvArgs(0)) _ + & "\n" & "\n" & .GetText("DUPLICATESHEET", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case OFFSETADDRESSERROR ' SF_Calc.RangeOffset("range", Range, "Rows", Rows, "Columns", Columns, "Height", Height, "Width", Width, "Document, Document) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("OFFSETADDRESS", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4) _ + , pvArgs(5), pvArgs(6), pvArgs(7), pvArgs(8), pvArgs(9), pvArgs(10), pvArgs(11)) + Case DIALOGNOTFOUNDERROR ' SF_Dialog._NewDialog(Service, DialogName, WindowName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DIALOGNOTFOUND", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4) _ + , pvArgs(5), pvArgs(6), pvArgs(7)) + Case DIALOGDEADERROR ' SF_Dialog._IsStillAlive(DialogName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DIALOGDEAD", pvArgs(0)) + Case CONTROLTYPEERROR ' SF_DialogControl._SetProperty(ControlName, DialogName, ControlType, Property) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("CONTROLTYPE", pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3)) + Case TEXTFIELDERROR ' SF_DialogControl.WriteLine(ControlName, DialogName) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("TEXTFIELD", pvArgs(0), pvArgs(1)) + Case DBREADONLYERROR ' SF_Database.RunSql() + sMessage = sLocation _ + & "\n" & "\n" & .GetText("DBREADONLY", vLocation(2)) + Case SQLSYNTAXERROR ' SF_Database._ExecuteSql(SQL) + sMessage = sLocation _ + & "\n" & "\n" & .GetText("SQLSYNTAX", pvArgs(0)) + Case Else + End Select + End With + + ' Log fatal event + _SF_._AddToConsole(sMessage) + + ' Display fatal event, if relevant (default) + If _SF_.DisplayEnabled Then + If _SF_.StopWhenError Then sMessage = sMessage & "\n" & "\n" & "\n" & L10N.GetText("STOPEXECUTION") + MsgBox SF_String.ExpandTabs(SF_String.Unescape(sMessage), cstTabSize) _ + , MB_OK + MB_ICONEXCLAMATION _ + , L10N.GetText("ERRORNUMBER", ErrorCode) + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + _SF_._StackReset() + If _SF_.StopWhenError Then Stop + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Exception.RaiseFatal + +REM ----------------------------------------------------------------------------- +Public Sub RaiseWarning(Optional ByVal Number As Variant _ + , Optional ByVal Source As Variant _ + , Optional ByVal Description As Variant _ + ) +''' Generate a run-time error. An error message is displayed to the user and logged +''' in the console. The execution is NOT STOPPED +''' Args: +''' Number: the error number, may be numeric or string +''' If numeric and <= 2000, it is considered a LibreOffice Basic run-time error (default = Err) +''' Source: the line where the error occurred (default = Erl) or any string describing the location of the error +''' Description: the error message to log in the console and to display to the user +''' Returns: +''' True if successful. Anyway, the execution continues +''' Examples: +''' On Local Error GoTo Catch +''' ' ... +''' Catch: +''' SF_Exception.RaiseWarning() ' Standard behaviour +''' SF_Exception.RaiseWarning(11) ' Force division by zero +''' SF_Exception.RaiseWarning("MYAPPERROR", "myFunction", "Application error") +''' SF_Exception.RaiseWarning(,, "To divide by zero is not a good idea !") + +Dim bStop As Boolean ' Alias for stop switch +Const cstThisSub = "Exception.RaiseWarning" +Const cstSubArgs = "[Number=Err], [Source=Erl], [Description]" + + ' Save Err, Erl, .. values before any On Error ... statement + SF_Exception._CaptureSystemError() + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Number) Or IsEmpty(Number) Then Number = -1 + If IsMissing(Source) Or IsEmpty(Source) Then Source = -1 + If IsMissing(Description) Or IsEmpty(Description) Then Description = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Number, "Number", Array(V_STRING, V_NUMERIC, V_EMPTY)) Then GoTo Finally + If Not SF_Utils._Validate(Source, "Source", Array(V_STRING, V_NUMERIC, V_EMPTY)) Then GoTo Finally + If Not SF_Utils._Validate(Description, "Description", V_STRING) Then GoTo Finally + End If + +Try: + bStop = _SF_.StopWhenError ' Store current value to reset it before leaving the Sub + _SF_.StopWhenError = False + SF_Exception.Raise(Number, Source, Description) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + _SF_.StopWhenError = bStop + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Exception.RaiseWarning + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Exception.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + SetProperty = _PropertySet(PropertyName, Value) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Exception.SetProperty + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Sub _CaptureSystemError() +''' Store system error status in system error properties +''' Called at each invocation of an error management property or method +''' Reset by SF_Exception.Clear() + + If Err > 0 And _SysNumber = 0 Then + _SysNumber = Err + _SysSource = Erl + _SysDescription = Error$ + End If + +End Sub ' ScriptForge.SF_Exception._CaptureSystemError + +REM ----------------------------------------------------------------------------- +Public Sub _CloseConsole(Optional ByRef poEvent As Object) +''' Close the console when opened in non-modal mode +''' Triggered by the CloseNonModalButton from the dlgConsole dialog + + On Local Error GoTo Finally + +Try: + With _SF_ + If Not IsNull(.ConsoleDialog) Then + If .ConsoleDialog._IsStillAlive(False) Then ' False to not raise an error + Set .ConsoleControl = .ConsoleControl.Dispose() + Set .ConsoleDialog = .ConsoleDialog.Dispose() + End If + End If + End With + +Finally: + Exit Sub +End Sub ' ScriptForge.SF_Exception._CloseConsole + +REM ----------------------------------------------------------------------------- +Private Sub _ConsoleRefresh() +''' Reload the content of the console in the dialog +''' Needed when console first loaded or when totally or partially cleared + + With _SF_ + ' Do nothing if console inactive + If IsNull(.ConsoleDialog) Then GoTo Finally + If Not .ConsoleDialog._IsStillAlive(False) Then ' False to not generate an error when dead + Set .ConsoleControl = .ConsoleControl.Dispose() + Set .ConsoleDialog = Nothing + GoTo Finally + End If + ' Store the relevant text in the control + If IsNull(.ConsoleControl) Then Set .ConsoleControl = .ConsoleDialog.Controls(CONSOLENAME) + .ConsoleControl.Value = "" + If UBound(.ConsoleLines) >= 0 Then .ConsoleControl.WriteLine(Join(.ConsoleLines, SF_String.sfNEWLINE)) + End With + +Finally: + Exit Sub +End Sub ' ScriptForge.SF_Exception._ConsoleRefresh + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant +''' Return the value of the named property +''' Args: +''' psProperty: the name of the property + +Dim cstThisSub As String +Const cstSubArgs = "" + + cstThisSub = "SF_Exception.get" & psProperty + + SF_Exception._CaptureSystemError() + + Select Case psProperty + Case "Description" + If _Description = "" Then _PropertyGet = _SysDescription Else _PropertyGet = _Description + Case "Number" + If IsEmpty(_Number) Then _PropertyGet = _SysNumber Else _PropertyGet = _Number + Case "Source" + If IsEmpty(_Source) Then _PropertyGet = _SysSource Else _PropertyGet = _Source + Case Else + _PropertyGet = Null + End Select + +Finally: + Exit Function +End Function ' ScriptForge.SF_Exception._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _PropertySet(Optional ByVal psProperty As String _ + , Optional ByVal pvValue As Variant _ + ) As Boolean +''' Set a new value to the named property +''' Applicable only to user defined errors +''' Args: +''' psProperty: the name of the property +''' pvValue: the new value + +Dim cstThisSub As String +Const cstSubArgs = "" + + cstThisSub = "SF_Exception.set" & psProperty + _PropertySet = False + + SF_Exception._CaptureSystemError() + + ' Argument validation must be manual to preserve system error status + ' If wrong VarType then property set is ignored + Select Case psProperty + Case "Description" + If VarType(pvValue) = V_STRING Then _Description = pvValue + Case "Number" + Select Case SF_Utils._VarTypeExt(pvValue) + Case V_STRING + _Number = pvValue + Case V_NUMERIC + _Number = CLng(pvValue) + If _Number <= RUNTIMEERRORS And Len(_Description) = 0 Then _Description = Error(_Number) + Case V_EMPTY + _Number = Empty + Case Else + End Select + Case "Source" + Select Case SF_Utils._VarTypeExt(pvValue) + Case V_STRING + _Source = pvValue + Case V_NUMERIC + _Source = CLng(pvValue) + Case Else + End Select + Case Else + End Select + + _PropertySet = True + +Finally: + Exit Function +End Function ' ScriptForge.SF_Exception._PropertySet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the Exception instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[Exception]: A readable string" + + _Repr = "[Exception]: " & _Number & " (" & _Description & ")" + +End Function ' ScriptForge.SF_Exception._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_EXCEPTION +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_FileSystem.xba b/wizards/source/scriptforge/SF_FileSystem.xba new file mode 100644 index 000000000000..a0d124da3b5b --- /dev/null +++ b/wizards/source/scriptforge/SF_FileSystem.xba @@ -0,0 +1,2084 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_FileSystem" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_FileSystem +''' ============= +''' Class implementing the file system service +''' for common file and folder handling routines +''' Including copy and move of files and folders, with or without wildcards +''' The design choices are largely inspired by +''' https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/filesystemobject-object +''' The File and Folder classes have been found redundant with the current class and have not been implemented +''' The implementation is mainly based on the XSimpleFileAccess UNO interface +''' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1ucb_1_1XSimpleFileAccess.html +''' +''' Subclasses: +''' SF_TextStream +''' +''' Definitions: +''' File and folder names may be expressed either in the (preferable because portable) URL form +''' or in the more usual operating system notation (e.g. C:\... for Windows) +''' The notation, both for arguments and for returned values +''' is determined by the FileNaming property: either "URL" (default) or "SYS" +''' +''' FileName: the full name of the file including the path without any ending path separator +''' FolderName: the full name of the folder including the path and the ending path separator +''' Name: the last component of the File- or FolderName including its extension +''' BaseName: the last component of the File- or FolderName without its extension +''' NamePattern: any of the above names containing wildcards in its last component +''' Admitted wildcards are: the "?" represents any single character +''' the "*" represents zero, one, or multiple characters +''' +''' Service invocation example: +''' Dim FSO As Variant +''' Set FSO = CreateScriptService("FileSystem") +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const UNKNOWNFILEERROR = "UNKNOWNFILEERROR" ' Source file does not exist +Const UNKNOWNFOLDERERROR = "UNKNOWNFOLDERERROR" ' Source folder or Destination folder does not exist +Const NOTAFILEERROR = "NOTAFILEERROR" ' Destination is a folder, not a file +Const NOTAFOLDERERROR = "NOTAFOLDERERROR" ' Destination is a file, not a folder +Const OVERWRITEERROR = "OVERWRITEERROR" ' Destination can not be overwritten +Const READONLYERROR = "READONLYERROR" ' Destination has its read-only attribute set +Const NOFILEMATCHERROR = "NOFILEMATCHFOUND" ' No file matches Source containing wildcards +Const FOLDERCREATIONERROR = "FOLDERCREATIONERROR" ' FolderName is an existing folder or file + +REM ============================================================ MODULE CONSTANTS + +''' TextStream open modes +Const cstForReading = 1 +Const cstForWriting = 2 +Const cstForAppending = 8 + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_FileSystem Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get ConfigFolder() As String +''' Return the configuration folder of LibreOffice + +Const cstThisSub = "FileSystem.getConfigFolder" + + SF_Utils._EnterFunction(cstThisSub) + ConfigFolder = SF_FileSystem._GetConfigFolder("user") + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.ConfigFolder + +REM ----------------------------------------------------------------------------- +Property Get ExtensionsFolder() As String +''' Return the folder containing the installed extensions + +Dim oMacro As Object ' /singletons/com.sun.star.util.theMacroExpander +Const cstThisSub = "FileSystem.getExtensionsFolder" + + SF_Utils._EnterFunction(cstThisSub) + Set oMacro = SF_Utils._GetUNOService("MacroExpander") + ExtensionsFolder = SF_FileSystem._ConvertFromUrl(oMacro.ExpandMacros("$UNO_USER_PACKAGES_CACHE") & "/") + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.ExtensionsFolder + +REM ----------------------------------------------------------------------------- +Property Get FileNaming() As Variant +''' Return the current files and folder notation, either "ANY", "URL" or "SYS" +''' "ANY": methods receive either URL or native file names, but always return URL file names +''' "URL": methods expect URL arguments and return URL strings (when relevant) +''' "SYS": idem but operating system notation + +Const cstThisSub = "FileSystem.getFileNaming" + SF_Utils._EnterFunction(cstThisSub) + FileNaming = _SF_.FileSystemNaming + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.FileNaming (get) + +REM ----------------------------------------------------------------------------- +Property Let FileNaming(ByVal pvNotation As Variant) +''' Set the files and folders notation: "ANY", "URL" or "SYS" + +Const cstThisSub = "FileSystem.setFileNaming" + SF_Utils._EnterFunction(cstThisSub) + If VarType(pvNotation) = V_STRING Then + Select Case UCase(pvNotation) + Case "ANY", "URL", "SYS" : _SF_.FileSystemNaming = UCase(pvNotation) + Case Else ' Unchanged + End Select + End If + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.FileNaming (let) + +REM ----------------------------------------------------------------------------- +Property Get ForAppending As Integer +''' Convenient constant (see documentation) + ForAppending = cstForAppending +End Property ' ScriptForge.SF_FileSystem.ForAppending + +REM ----------------------------------------------------------------------------- +Property Get ForReading As Integer +''' Convenient constant (see documentation) + ForReading = cstForReading +End Property ' ScriptForge.SF_FileSystem.ForReading + +REM ----------------------------------------------------------------------------- +Property Get ForWriting As Integer +''' Convenient constant (see documentation) + ForWriting = cstForWriting +End Property ' ScriptForge.SF_FileSystem.ForWriting + +REM ----------------------------------------------------------------------------- +Property Get HomeFolder() As String +''' Return the user home folder + +Const cstThisSub = "FileSystem.getHomeFolder" + + SF_Utils._EnterFunction(cstThisSub) + HomeFolder = SF_FileSystem._GetConfigFolder("home") + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.HomeFolder + +REM ----------------------------------------------------------------------------- +Property Get InstallFolder() As String +''' Return the installation folder of LibreOffice + +Const cstThisSub = "FileSystem.getInstallFolder" + + SF_Utils._EnterFunction(cstThisSub) + InstallFolder = SF_FileSystem._GetConfigFolder("inst") + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.InstallFolder + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_FileSystem" +End Property ' ScriptForge.SF_FileSystem.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.FileSystem" +End Property ' ScriptForge.SF_FileSystem.ServiceName + +REM ----------------------------------------------------------------------------- +Property Get TemplatesFolder() As String +''' Return the folder defined in the LibreOffice paths options as intended for templates files + +Dim sPath As String ' Template property of com.sun.star.util.PathSettings +Const cstThisSub = "FileSystem.getTemplatesFolder" + + SF_Utils._EnterFunction(cstThisSub) + sPath = SF_Utils._GetUNOService("PathSettings").Template + TemplatesFolder = SF_FileSystem._ConvertFromUrl(Split(sPath, ";")(0)) + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.TemplatesFolder + +REM ----------------------------------------------------------------------------- +Property Get TemporaryFolder() As String +''' Return the folder defined in the LibreOffice paths options as intended for temporary files + +Const cstThisSub = "FileSystem.getTemporaryFolder" + + SF_Utils._EnterFunction(cstThisSub) + TemporaryFolder = SF_FileSystem._GetConfigFolder("temp") + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.TemporaryFolder + +REM ----------------------------------------------------------------------------- +Property Get UserTemplatesFolder() As String +''' Return the folder defined in the LibreOffice paths options as intended for User templates files + +Dim sPath As String ' Template_writable property of com.sun.star.util.PathSettings +Const cstThisSub = "FileSystem.getUserTemplatesFolder" + + SF_Utils._EnterFunction(cstThisSub) + sPath = SF_Utils._GetUNOService("PathSettings").Template_writable + UserTemplatesFolder = SF_FileSystem._ConvertFromUrl(sPath) + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_FileSystem.UserTemplatesFolder + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function BuildPath(Optional ByVal FolderName As Variant _ + , Optional ByVal Name As Variant _ + ) As String +''' Combines a folder path and the name of a file and returns the combination with a valid path separator +''' Inserts an additional path separator between the foldername and the name, only if necessary +''' Args: +''' FolderName: Path with which Name is combined. Path need not specify an existing folder +''' Name: To be appended to the existing path. +''' Returns: +''' The path concatenated with the file name after insertion of a path separator, if necessary +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.BuildPath("C:\Windows", "Notepad.exe") returns C:\Windows\Notepad.exe + +Dim sBuild As String ' Return value +Dim sFile As String ' Alias for Name +Const cstFileProtocol = "file:///" +Const cstThisSub = "FileSystem.BuildPath" +Const cstSubArgs = "FolderName, Name" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sBuild = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + If Not SF_Utils._Validate(Name, "Name", V_STRING) Then GoTo Finally + End If + FolderName = SF_FileSystem._ConvertToUrl(FolderName) + +Try: + ' Add separator if necessary. FolderName is now in URL notation + If Len(FolderName) > 0 Then + If Right(FolderName, 1) <> "/" Then sBuild = FolderName & "/" Else sBuild = FolderName + Else + sBuild = cstFileProtocol + End If + ' Encode the file name + sFile = ConvertToUrl(Name) + ' Some file names produce http://file.name.suffix/ + If Left(sFile, 7) = "http://" Then sFile = cstFileProtocol & Mid(sFile, 8, Len(sFile) - 8) + ' Combine both parts + If Left(sFile, Len(cstFileProtocol)) = cstFileProtocol Then sBuild = sBuild & Mid(sFile, Len(cstFileProtocol) + 1) Else sBuild = sBuild & sFile + +Finally: + BuildPath = SF_FileSystem._ConvertFromUrl(sBuild) + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.BuildPath + +REM ----------------------------------------------------------------------------- +Public Function CompareFiles(Optional ByVal FileName1 As Variant _ + , Optional ByVal FileName2 As Variant _ + , Optional ByVal CompareContents As Variant _ + ) +''' Compare 2 files and return True if they seem identical +''' The comparison may be based on the file attributes, like modification time, +''' or on their contents. +''' Args: +''' FileName1: The 1st file to compare +''' FileName2: The 2nd file to compare +''' CompareContents: When True, the contents of the files are compared. Default = False +''' Returns: +''' True when the files seem identical +''' Exceptions: +''' UNKNOWNFILEERROR One of the files does not exist +''' Example: +''' FSO.FileNaming = "SYS" +''' MsgBox FSO.CompareFiles("C:\myFile1.txt", "C:\myFile2.txt", CompareContents := True) + +Dim bCompare As Boolean ' Return value +Dim sFile As String ' Alias of FileName1 and 2 +Dim iFile As Integer ' 1 or 2 +Const cstPyHelper = "$" & "_SF_FileSystem__CompareFiles" + +Const cstThisSub = "FileSystem.CompareFiles" +Const cstSubArgs = "FileName1, FileName2, [CompareContents=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bCompare = False + +Check: + If IsMissing(CompareContents) Or IsEmpty(CompareContents) Then CompareContents = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName1, "FileName1", False) Then GoTo Finally + If Not SF_Utils._ValidateFile(FileName2, "FileName2", False) Then GoTo Finally + If Not SF_Utils._Validate(CompareContents, "CompareContents", V_BOOLEAN) Then GoTo Finally + End If + ' Do the files exist ? Otherwise raise error + sFile = FileName1 : iFile = 1 + If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists + sFile = FileName2 : iFile = 2 + If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists + +Try: + With ScriptForge.SF_Session + bCompare = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName1) _ + , _ConvertFromUrl(FileName2) _ + , CompareContents) + End With + +Finally: + CompareFiles = bCompare + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName" & iFile, sFile) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CompareFiles + +REM ----------------------------------------------------------------------------- +Public Function CopyFile(Optional ByVal Source As Variant _ + , Optional ByVal Destination As Variant _ + , Optional ByVal Overwrite As Variant _ + ) As Boolean +''' Copies one or more files from one location to another +''' Args: +''' Source: FileName or NamePattern which can include wildcard characters, for one or more files to be copied +''' Destination: FileName where the single Source file is to be copied +''' or FolderName where the multiple files from Source are to be copied +''' If FolderName does not exist, it is created +''' Anyway, wildcard characters are not allowed in Destination +''' Overwrite: If True (default), files may be overwritten +''' CopyFile will fail if Destination has the read-only attribute set, regardless of the value of Overwrite. +''' Returns: +''' True if at least one file has been copied +''' False if an error occurred +''' An error also occurs if a source using wildcard characters doesn't match any files. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFILEERROR Source does not exist +''' UNKNOWNFOLDERERROR Source folder or Destination folder does not exist +''' NOFILEMATCHERROR No file matches Source containing wildcards +''' NOTAFOLDERERROR Destination is a file, not a folder +''' NOTAFILEERROR Destination is a folder, not a file +''' OVERWRITEERROR Destination can not be overwritten +''' READONLYERROR Destination has its read-only attribute set +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.CopyFile("C:\Windows\*.*", "C:\Temp\", Overwrite := False) ' Only files are copied, subfolders are not + +Dim bCopy As Boolean ' Return value + +Const cstThisSub = "FileSystem.CopyFile" +Const cstSubArgs = "Source, Destination, [Overwrite=True]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bCopy = False + +Check: + If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(Source, "Source", True) Then GoTo Finally + If Not SF_Utils._ValidateFile(Destination, "Destination", False) Then GoTo Finally + If Not SF_Utils._Validate(Overwrite, "Overwrite", V_BOOLEAN) Then GoTo Finally + End If + +Try: + bCopy = SF_FileSystem._CopyMove("CopyFile", Source, Destination, Overwrite) + +Finally: + CopyFile = bCopy + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CopyFile + +REM ----------------------------------------------------------------------------- +Public Function CopyFolder(Optional ByVal Source As Variant _ + , Optional ByVal Destination As Variant _ + , Optional ByVal Overwrite As Variant _ + ) As Boolean +''' Copies one or more folders from one location to another +''' Args: +''' Source: FolderName or NamePattern which can include wildcard characters, for one or more folders to be copied +''' Destination: FolderName where the single Source folder is to be copied +''' or FolderName where the multiple folders from Source are to be copied +''' If FolderName does not exist, it is created +''' Anyway, wildcard characters are not allowed in Destination +''' Overwrite: If True (default), folders and their content may be overwritten +''' CopyFile will fail if Destination has the read-only attribute set, regardless of the value of Overwrite. +''' Returns: +''' True if at least one folder has been copied +''' False if an error occurred +''' An error also occurs if a source using wildcard characters doesn't match any folders. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFILEERROR Source does not exist +''' UNKNOWNFOLDERERROR Source folder or Destination folder does not exist +''' NOFILEMATCHERROR No file matches Source containing wildcards +''' NOTAFOLDERERROR Destination is a file, not a folder +''' OVERWRITEERROR Destination can not be overwritten +''' READONLYERROR Destination has its read-only attribute set +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.CopyFolder("C:\Windows\*", "C:\Temp\", Overwrite := False) + +Dim bCopy As Boolean ' Return value + +Const cstThisSub = "FileSystem.CopyFolder" +Const cstSubArgs = "Source, Destination, [Overwrite=True]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bCopy = False + +Check: + If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(Source, "Source", True) Then GoTo Finally + If Not SF_Utils._ValidateFile(Destination, "Destination", False) Then GoTo Finally + If Not SF_Utils._Validate(Overwrite, "Overwrite", V_BOOLEAN) Then GoTo Finally + End If + +Try: + bCopy = SF_FileSystem._CopyMove("CopyFolder", Source, Destination, Overwrite) + +Finally: + CopyFolder = bCopy + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CopyFolder + +REM ----------------------------------------------------------------------------- +Public Function CreateFolder(Optional ByVal FolderName As Variant) As Boolean +''' Return True if the given folder name could be created successfully +''' The parent folder does not need to exist beforehand +''' Args: +''' FolderName: a string representing the folder to create. It must not exist +''' Returns: +''' True if FolderName is a valid folder name, does not exist and creation was successful +''' False otherwise including when FolderName is a file +''' Exceptions: +''' FOLDERCREATIONERROR FolderName is an existing folder or file +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.CreateFolder("C:\NewFolder\") + +Dim bCreate As Boolean ' Return value +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess + +Const cstThisSub = "FileSystem.CreateFolder" +Const cstSubArgs = "FolderName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bCreate = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + End If + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + If SF_FileSystem.FolderExists(FolderName) Then GoTo CatchExists + If SF_FileSystem.FileExists(FolderName) Then GoTo CatchExists + oSfa.createFolder(SF_FileSystem._ConvertToUrl(FolderName)) + bCreate = True + +Finally: + CreateFolder = bCreate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchExists: + SF_Exception.RaiseFatal(FOLDERCREATIONERROR, "FolderName", FolderName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CreateFolder + +REM ----------------------------------------------------------------------------- +Public Function CreateTextFile(Optional ByVal FileName As Variant _ + , Optional ByVal Overwrite As Variant _ + , Optional ByVal Encoding As Variant _ + ) As Object +''' Creates a specified file and returns a TextStream object that can be used to write to the file +''' Args: +''' FileName: Identifies the file to create +''' Overwrite: Boolean value that indicates if an existing file can be overwritten (default = True) +''' Encoding: The character set that should be used +''' Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that LibreOffice does not implement all existing sets +''' Default = UTF-8 +''' Returns: +''' An instance of the SF_TextStream class representing the opened file or a Null object if an error occurred +''' It doesn't check either if the given encoding is implemented in LibreOffice +''' Exceptions: +''' OVERWRITEERROR File exists, creation impossible +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.CreateTextFile("C:\Temp\ThisFile.txt", Overwrite := True) + +Dim oTextStream As Object ' Return value +Const cstThisSub = "FileSystem.CreateTextFile" +Const cstSubArgs = "FileName, [Overwrite=True], [Encoding=""UTF-8""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oTextStream = Nothing + +Check: + If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True + If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = "UTF-8" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Overwrite, "Overwrite", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(Encoding, "Encoding", V_STRING) Then GoTo Finally + End If + + With SF_FileSystem + If .FileExists(FileName) Then + If Overwrite Then .DeleteFile(FileName) Else GoTo CatchOverWrite + End If + +Try: + Set oTextStream = .OpenTextFile(FileName, .ForWriting, Create := True, Encoding := Encoding) + End With + +Finally: + Set CreateTextFile = oTextStream + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchOverWrite: + SF_Exception.RaiseFatal(OVERWRITEERROR, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CreateTextFile + +REM ----------------------------------------------------------------------------- +Public Function DeleteFile(Optional ByVal FileName As Variant) As Boolean +''' Deletes one or more files +''' Args: +''' FileName: FileName or NamePattern which can include wildcard characters, for one or more files to be deleted +''' Returns: +''' True if at least one file has been deleted +''' False if an error occurred +''' An error also occurs if a FileName using wildcard characters doesn't match any files. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFILEERROR FileName does not exist +''' NOFILEMATCHERROR No file matches FileName containing wildcards +''' NOTAFILEERROR Argument is a folder, not a file +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.DeleteFile("C:\Temp\*.*") ' Only files are deleted, subfolders are not + +Dim bDelete As Boolean ' Return value + +Const cstThisSub = "FileSystem.DeleteFile" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bDelete = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName", True) Then GoTo Finally + End If + +Try: + bDelete = SF_FileSystem._Delete("DeleteFile", FileName) + +Finally: + DeleteFile = bDelete + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.DeleteFile + +REM ----------------------------------------------------------------------------- +Public Function DeleteFolder(Optional ByVal FolderName As Variant) As Boolean +''' Deletes one or more Folders +''' Args: +''' FolderName: FolderName or NamePattern which can include wildcard characters, for one or more Folders to be deleted +''' Returns: +''' True if at least one folder has been deleted +''' False if an error occurred +''' An error also occurs if a FolderName using wildcard characters doesn't match any folders. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFOLDERERROR FolderName does not exist +''' NOFILEMATCHERROR No folder matches FolderName containing wildcards +''' NOTAFOLDERERROR Argument is a file, not a folder +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.DeleteFolder("C:\Temp\*") ' Only solders are deleted, filesin the parent folder are not + +Dim bDelete As Boolean ' Return value + +Const cstThisSub = "FileSystem.DeleteFolder" +Const cstSubArgs = "FolderName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bDelete = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName", True) Then GoTo Finally + End If + +Try: + bDelete = SF_FileSystem._Delete("DeleteFolder", FolderName) + +Finally: + DeleteFolder = bDelete + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.DeleteFolder + +REM ----------------------------------------------------------------------------- +Public Function FileExists(Optional ByVal FileName As Variant) As Boolean +''' Return True if the given file exists +''' Args: +''' FileName: a string representing a file +''' Returns: +''' True if FileName is a valid File name and it exists +''' False otherwise including when FileName is a folder +''' Example: +''' FSO.FileNaming = "SYS" +''' If FSO.FileExists("C:\Notepad.exe") Then ... + +Dim bExists As Boolean ' Return value +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess + +Const cstThisSub = "FileSystem.FileExists" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExists = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + FileName = SF_FileSystem._ConvertToUrl(FileName) + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + bExists = oSfa.exists(FileName) And Not oSfa.isFolder(FileName) + +Finally: + FileExists = bExists + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.FileExists + +REM ----------------------------------------------------------------------------- +Public Function Files(Optional ByVal FolderName As Variant _ + , Optional ByVal Filter As Variant _ + ) As Variant +''' Return an array of the FileNames stored in the given folder. The folder must exist +''' Args: +''' FolderName: the folder to explore +''' Filter: contains wildcards ("?" and "*") to limit the list to the relevant files (default = "") +''' Returns: +''' An array of strings, each entry is the FileName of an existing file +''' Exceptions: +''' UNKNOWNFOLDERERROR Folder does not exist +''' NOTAFOLDERERROR FolderName is a file, not a folder +''' Example: +''' Dim a As Variant +''' FSO.FileNaming = "SYS" +''' a = FSO.Files("C:\Windows\") + +Dim vFiles As Variant ' Return value +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess +Dim sFolderName As String ' URL lias for FolderName +Dim sFile As String ' Single file +Dim i As Long + +Const cstThisSub = "FileSystem.Files" +Const cstSubArgs = "FolderName, [Filter=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vFiles = Array() + +Check: + If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + If Not SF_Utils._Validate(Filter, "Filter", V_STRING) Then GoTo Finally + End If + sFolderName = SF_FileSystem._ConvertToUrl(FolderName) + If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFile ' Must not be a file + If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolder ' Folder must exist + +Try: + ' Get files + Set oSfa = SF_Utils._GetUnoService("FileAccess") + vFiles = oSfa.getFolderContents(sFolderName, False) + ' Adjust notations + For i = 0 To UBound(vFiles) + sFile = SF_FileSystem._ConvertFromUrl(vFiles(i)) + vFiles(i) = sFile + Next i + ' Reduce list to those passing the filter + If Len(Filter) > 0 Then + For i = 0 To UBound(vFiles) + sFile = SF_FileSystem.GetName(vFiles(i)) + If Not SF_String.IsLike(sFile, Filter) Then vFiles(i) = "" + Next i + vFiles = Sf_Array.TrimArray(vFiles) + End If + +Finally: + Files = vFiles + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchFile: + SF_Exception.RaiseFatal(NOTAFOLDERERROR, "FolderName", FolderName) + GoTo Finally +CatchFolder: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "FolderName", FolderName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.Files + +REM ----------------------------------------------------------------------------- +Public Function FolderExists(Optional ByVal FolderName As Variant) As Boolean +''' Return True if the given folder name exists +''' Args: +''' FolderName: a string representing a folder +''' Returns: +''' True if FolderName is a valid folder name and it exists +''' False otherwise including when FolderName is a file +''' Example: +''' FSO.FileNaming = "SYS" +''' If FSO.FolderExists("C:\") Then ... + +Dim bExists As Boolean ' Return value +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess + +Const cstThisSub = "FileSystem.FolderExists" +Const cstSubArgs = "FolderName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExists = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + End If + FolderName = SF_FileSystem._ConvertToUrl(FolderName) + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + bExists = oSfa.isFolder(FolderName) + +Finally: + FolderExists = bExists + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.FolderExists + +REM ----------------------------------------------------------------------------- +Public Function GetBaseName(Optional ByVal FileName As Variant) As String +''' Returns the BaseName part of the last component of a File- or FolderName, without its extension +''' The method does not check for the existence of the specified file or folder +''' Args: +''' FileName: Path and file name +''' Returns: +''' The BaseName of the given argument in native operating system format. May be empty +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.GetBaseName("C:\Windows\Notepad.exe") returns Notepad + +Dim sBase As String ' Return value +Dim sExt As String ' Extension +Dim sName As String ' Last component of FileName +Dim vName As Variant ' Array of trunks of sName +Const cstThisSub = "FileSystem.GetBaseName" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sBase = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + +Try: + sName = SF_FileSystem.GetName(FileName) + If Len(sName) > 0 Then + If InStr(sName, ".") > 0 Then + vName = Split(sName, ".") + sExt = vName(UBound(vName)) + sBase = Left(sName, Len(sName) - Len(sExt) - 1) + Else + sBase = sName + End If + End If + +Finally: + GetBaseName = sBase + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetBaseName + +REM ----------------------------------------------------------------------------- +Public Function GetExtension(Optional ByVal FileName As Variant) As String +''' Returns the extension part of a File- or FolderName, without the dot (.). +''' The method does not check for the existence of the specified file or folder +''' Args: +''' FileName: Path and file name +''' Returns: +''' The extension without a leading dot. May be empty +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.GetExtension("C:\Windows\Notepad.exe") returns exe + +Dim sExt As String ' Return value +Dim sName As String ' Last component of FileName +Dim vName As Variant ' Array of trunks of sName +Const cstThisSub = "FileSystem.GetExtension" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sExt = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + +Try: + sName = SF_FileSystem.GetName(FileName) + If Len(sName) > 0 And InStr(sName, ".") > 0 Then + vName = Split(sName, ".") + sExt = vName(UBound(vName)) + End If + +Finally: + GetExtension = sExt + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetExtension + +REM ----------------------------------------------------------------------------- +Public Function GetFileLen(Optional ByVal FileName As Variant) As Currency +''' Return file size in bytes with four decimals ''' +''' Args: +''' FileName: a string representing a file +''' Returns: +''' File size if FileName exists +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist of is a folder +''' Example: +''' Print SF_FileSystem.GetFileLen("C:\pagefile.sys") + +Dim curSize As Currency ' Return value +Const cstPyHelper = "$" & "_SF_FileSystem__GetFilelen" +Const cstThisSub = "FileSystem.GetFileLen" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + curSize = 0 + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + +Try: + If SF_FileSystem.FileExists(FileName) Then + With ScriptForge.SF_Session + curSize = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName)) + End With + Else + GoTo CatchNotExists + End If + +Finally: + GetFileLen = curSize + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetFileLen + +REM ----------------------------------------------------------------------------- +Public Function GetFileModified(Optional ByVal FileName As Variant) As Date +''' Returns the last modified date for the given file +''' Args: +''' FileName: a string representing an existing file +''' Returns: +''' The modification date and time as a Basic Date +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist of is a folder +''' Example: +''' Dim a As Date +''' FSO.FileNaming = "SYS" +''' a = FSO.GetFileModified("C:\Temp\myDoc.odt") + +Dim dModified As Date ' Return value +Dim oModified As New com.sun.star.util.DateTime +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess + +Const cstThisSub = "FileSystem.GetFileModified" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + dModified = 0 + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + If SF_FileSystem.FileExists(FileName) Then + FileName = SF_FileSystem._ConvertToUrl(FileName) + Set oModified = oSfa.getDateTimeModified(FileName) + dModified = CDateFromUnoDateTime(oModified) + Else + GoTo CatchNotExists + End If + +Finally: + GetFileModified = dModified + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetFileModified + +REM ----------------------------------------------------------------------------- +Public Function GetName(Optional ByVal FileName As Variant) As String +''' Returns the last component of a File- or FolderName +''' The method does not check for the existence of the specified file or folder +''' Args: +''' FileName: Path and file name +''' Returns: +''' The last component of the full file name in native operating system format +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.GetName("C:\Windows\Notepad.exe") returns Notepad.exe + +Dim sName As String ' Return value +Dim vFile As Variant ' Array of components +Const cstThisSub = "FileSystem.GetName" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sName = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + FileName = SF_FileSystem._ConvertToUrl(FileName) + +Try: + If Len(FileName) > 0 Then + If Right(FileName, 1) = "/" Then FileName = Left(FileName, Len(FileName) - 1) + vFile = Split(FileName, "/") + sName = ConvertFromUrl(vFile(UBound(vFile))) ' Always in SYS format + End If + +Finally: + GetName = sName + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetName + +REM ----------------------------------------------------------------------------- +Public Function GetParentFolderName(Optional ByVal FileName As Variant) As String +''' Returns a string containing the name of the parent folder of the last component in a specified File- or FolderName +''' The method does not check for the existence of the specified file or folder +''' Args: +''' FileName: Path and file name +''' Returns: +''' A FolderName including its final path separator +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.GetParentFolderName("C:\Windows\Notepad.exe") returns C:\Windows\ + +Dim sFolder As String ' Return value +Dim sName As String ' Last component of FileName +Dim vFile As Variant ' Array of file components +Const cstThisSub = "FileSystem.GetParentFolderName" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFolder = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + FileName = SF_FileSystem._ConvertToUrl(FileName) + +Try: + If Right(FileName, 1) = "/" Then FileName = Left(FileName, Len(FileName) - 1) + vFile = Split(FileName, "/") + If UBound(vFile) >= 0 Then vFile(UBound(vFile)) = "" + sFolder = Join(vFile, "/") + If sFolder = "" Or Right(sFolder, 1) <> "/" Then sFolder = sFolder & "/" + +Finally: + GetParentFolderName = SF_FileSystem._ConvertFromUrl(sFolder) + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetParentFolderName + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "FileSystem.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case "ConfigFolder" : GetProperty = ConfigFolder + Case "ExtensionsFolder" : GetProperty = ExtensionsFolder + Case "FileNaming" : GetProperty = FileNaming + Case "HomeFolder" : GetProperty = HomeFolder + Case "InstallFolder" : GetProperty = InstallFolder + Case "TemplatesFolder" : GetProperty = TemplatesFolder + Case "TemporaryFolder" : GetProperty = TemporaryFolder + Case "UserTemplatesFolder" : GetProperty = UserTemplatesFolder + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function GetTempName() As String +''' Returns a randomly generated temporary file name that is useful for performing +''' operations that require a temporary file : the method does not create any file +''' Args: +''' Returns: +''' A FileName as a String that can be used f.i. with CreateTextFile() +''' The FileName does not have any suffix +''' Example: +''' Dim a As String +''' FSO.FileNaming = "SYS" +''' a = FSO.GetTempName() & ".txt" + +Dim sFile As String ' Return value +Dim sTempDir As String ' The path to a temporary folder +Dim lRandom As Long ' Random integer + +Const cstThisSub = "FileSystem.GetTempName" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFile = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + lRandom = SF_Session.ExecuteCalcFunction("RANDBETWEEN", 1, 999999) + sFile = SF_FileSystem.TemporaryFolder & "SF_" & Right("000000" & lRandom, 6) + +Finally: + GetTempName = SF_FileSystem._ConvertFromUrl(sFile) + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.GetTempName + +REM ----------------------------------------------------------------------------- +Public Function HashFile(Optional ByVal FileName As Variant _ + , Optional ByVal Algorithm As Variant _ + ) As String +''' Return an hexadecimal string representing a checksum of the given file +''' Next algorithms are supported: MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 +''' Args: +''' FileName: a string representing a file +''' Algorithm: The hashing algorithm to use +''' Returns: +''' The requested checksum as a string. Hexadecimal digits are lower-cased +''' A zero-length string when an error occurred +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist of is a folder +''' Example: +''' Print SF_FileSystem.HashFile("C:\pagefile.sys", "MD5") + +Dim sHash As String ' Return value +Const cstPyHelper = "$" & "_SF_FileSystem__HashFile" +Const cstThisSub = "FileSystem.HashFile" +Const cstSubArgs = "FileName, Algorithm=""MD5""|""SHA1""|""SHA224""|""SHA256""|""SHA384""|""SHA512""" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sHash = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Algorithm, "Algorithm", V_STRING _ + , Array("MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512")) Then GoTo Finally + End If + +Try: + If SF_FileSystem.FileExists(FileName) Then + With ScriptForge.SF_Session + sHash = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName), LCase(Algorithm)) + End With + Else + GoTo CatchNotExists + End If + +Finally: + HashFile = sHash + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.HashFile + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list or methods of the FileSystem service as an array + + Methods = Array("BuildPath" _ + , "CompareFiles" _ + , "CopyFile" _ + , "CopyFolder" _ + , "CreateFolder" _ + , "CreateTextFile" _ + , "DeleteFile" _ + , "DeleteFolder" _ + , "FileExists" _ + , "Files" _ + , "FolderExists" _ + , "GetBaseName" _ + , "GetExtension" _ + , "GetFileLen" _ + , "GetFileModified" _ + , "GetName" _ + , "GetParentFolderName" _ + , "GetTempName" _ + , "HashFile" _ + , "MoveFile" _ + , "MoveFolder" _ + , "OpenTextFile" _ + , "PickFile" _ + , "PickFolder" _ + , "SubFolders" _ + ) + +End Function ' ScriptForge.SF_FileSystem.Methods + +REM ----------------------------------------------------------------------------- +Public Function MoveFile(Optional ByVal Source As Variant _ + , Optional ByVal Destination As Variant _ + ) As Boolean +''' Moves one or more files from one location to another +''' Args: +''' Source: FileName or NamePattern which can include wildcard characters, for one or more files to be moved +''' Destination: FileName where the single Source file is to be moved +''' If Source and Destination have the same parent folder MoveFile amounts to renaming the Source +''' or FolderName where the multiple files from Source are to be moved +''' If FolderName does not exist, it is created +''' Anyway, wildcard characters are not allowed in Destination +''' Returns: +''' True if at least one file has been moved +''' False if an error occurred +''' An error also occurs if a source using wildcard characters doesn't match any files. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFILEERROR Source does not exist +''' UNKNOWNFOLDERERROR Source folder or Destination folder does not exist +''' NOFILEMATCHERROR No file matches Source containing wildcards +''' NOTAFOLDERERROR Destination is a file, not a folder +''' NOTAFILEERROR Destination is a folder, not a file +''' OVERWRITEERROR Destination can not be overwritten +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.MoveFile("C:\Temp1\*.*", "C:\Temp2\") ' Only files are moved, subfolders are not + +Dim bMove As Boolean ' Return value + +Const cstThisSub = "FileSystem.MoveFile" +Const cstSubArgs = "Source, Destination" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bMove = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(Source, "Source", True) Then GoTo Finally + If Not SF_Utils._ValidateFile(Destination, "Destination", False) Then GoTo Finally + End If + +Try: + bMove = SF_FileSystem._CopyMove("MoveFile", Source, Destination, False) + +Finally: + MoveFile = bMove + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.MoveFile + +REM ----------------------------------------------------------------------------- +Public Function MoveFolder(Optional ByVal Source As Variant _ + , Optional ByVal Destination As Variant _ + ) As Boolean +''' Moves one or more folders from one location to another +''' Args: +''' Source: FolderName or NamePattern which can include wildcard characters, for one or more folders to be moved +''' Destination: FolderName where the single Source folder is to be moved +''' FolderName must not exist +''' or FolderName where the multiple folders from Source are to be moved +''' If FolderName does not exist, it is created +''' Anyway, wildcard characters are not allowed in Destination +''' Returns: +''' True if at least one folder has been moved +''' False if an error occurred +''' An error also occurs if a source using wildcard characters doesn't match any folders. +''' The method stops on the first error it encounters +''' No attempt is made to roll back or undo any changes made before an error occurs +''' Exceptions: +''' UNKNOWNFILEERROR Source does not exist +''' UNKNOWNFOLDERERROR Source folder or Destination folder does not exist +''' NOFILEMATCHERROR No file matches Source containing wildcards +''' NOTAFOLDERERROR Destination is a file, not a folder +''' OVERWRITEERROR Destination can not be overwritten +''' Example: +''' FSO.FileNaming = "SYS" +''' FSO.MoveFolder("C:\Temp1\*", "C:\Temp2\") + +Dim bMove As Boolean ' Return value + +Const cstThisSub = "FileSystem.MoveFolder" +Const cstSubArgs = "Source, Destination" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bMove = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(Source, "Source", True) Then GoTo Finally + If Not SF_Utils._ValidateFile(Destination, "Destination", False) Then GoTo Finally + End If + +Try: + bMove = SF_FileSystem._CopyMove("MoveFolder", Source, Destination, False) + +Finally: + MoveFolder = bMove + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.MoveFolder + +REM ----------------------------------------------------------------------------- +Public Function OpenTextFile(Optional ByVal FileName As Variant _ + , Optional ByVal IOMode As Variant _ + , Optional ByVal Create As Variant _ + , Optional ByVal Encoding As Variant _ + ) As Object +''' Opens a specified file and returns a TextStream object that can be used to read from, write to, or append to the file +''' Args: +''' FileName: Identifies the file to open +''' IOMode: Indicates input/output mode. Can be one of three constants: ForReading, ForWriting, or ForAppending +''' Create: Boolean value that indicates whether a new file can be created if the specified filename doesn't exist. +''' The value is True if a new file and its parent folders may be created; False if they aren't created (default) +''' Encoding: The character set that should be used +''' Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that LibreOffice does not implement all existing sets +''' Default = UTF-8 +''' Returns: +''' An instance of the SF_TextStream class representing the opened file or a Null object if an error occurred +''' The method does not check if the file is really a text file +''' It doesn't check either if the given encoding is implemented in LibreOffice nor if it is the right one +''' Exceptions: +''' UNKNOWNFILEERROR File does not exist +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.OpenTextFile("C:\Temp\ThisFile.txt", FSO.ForReading) +''' If Not IsNull(myFile) Then ' ... Go ahead with reading text lines + +Dim oTextStream As Object ' Return value +Dim bExists As Boolean ' File to open does exist +Const cstThisSub = "FileSystem.OpenTextFile" +Const cstSubArgs = "FileName, [IOMode=1], [Create=False], [Encoding=""UTF-8""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oTextStream = Nothing + +Check: + With SF_FileSystem + If IsMissing(IOMode) Or IsEmpty(IOMode) Then IOMode = ForReading + If IsMissing(Create) Or IsEmpty(Create) Then Create = False + If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = "UTF-8" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(IOMode, "IOMode", V_NUMERIC _ + , Array(ForReading, ForWriting, ForAppending)) _ + Then GoTo Finally + If Not SF_Utils._Validate(Create, "Create", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(Encoding, "Encoding", V_STRING) Then GoTo Finally + End If + + bExists = .FileExists(FileName) + Select Case IOMode + Case ForReading : If Not bExists Then GoTo CatchNotExists + Case Else : If Not bExists And Not Create Then GoTo CatchNotExists + End Select + + If IOMode = ForAppending And Not bExists Then IOMode = ForWriting + End With + +Try: + ' Create and initialize TextStream class instance + Set oTextStream = New SF_TextStream + With oTextStream + .[Me] = oTextStream + .[_Parent] = SF_FileSystem + ._FileName = SF_FileSystem._ConvertToUrl(FileName) + ._IOMode = IOMode + ._Encoding = Encoding + ._FileExists = bExists + ._Initialize() + End With + +Finally: + Set OpenTextFile = oTextStream + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.OpenTextFile + +REM ----------------------------------------------------------------------------- +Public Function PickFile(Optional ByVal DefaultFile As Variant _ + , Optional ByVal Mode As Variant _ + , Optional ByVal Filter As Variant _ + ) As String +''' Returns the file selected with a FilePicker dialog box +''' The mode, OPEN or SAVE, and the filter may be preset +''' If mode = SAVE and the picked file exists, a warning message will be displayed +''' Modified from Andrew Pitonyak's Base Macro Programming §10.4 +''' Args: +''' DefaultFile: Folder part: the FolderName from which to start. Default = the last selected folder +''' File part: the default file to open or save +''' Mode: "OPEN" (input file) or "SAVE" (output file) +''' Filter: by default only files having the given suffix will be displayed. Default = all suffixes +''' The filter combo box will contain the given SuffixFilter (if not "*") and "*.*" +''' Returns: +''' The selected FileName in URL format or "" if the dialog was cancelled +''' Example: +''' FSO.FineNaming = "SYS" +''' FSO.PickFile("C:\", "OPEN", "txt") ' Only *.txt files are displayed + +Dim oFileDialog As Object ' com.sun.star.ui.dialogs.FilePicker +Dim oFileAccess As object ' com.sun.star.ucb.SimpleFileAccess +Dim oPath As Object ' com.sun.star.util.PathSettings +Dim iAccept As Integer ' Result of dialog execution +Dim sInitPath As String ' Current working directory +Dim sBaseFile As String +Dim iMode As Integer ' Numeric alias for SelectMode +Dim sFile As String ' Return value + +Const cstThisSub = "FileSystem.PickFile" +Const cstSubArgs = "[DefaultFile=""""], [Mode=""OPEN""|""SAVE""],[Filter=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFile = "" + +Check: + If IsMissing(DefaultFile) Or IsEmpty(DefaultFile) Then DefaultFile = "" + If IsMissing(Mode) Or IsEmpty(Mode) Then Mode = "OPEN" + If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(DefaultFile, "DefaultFile", , True) Then GoTo Finally + If Not SF_Utils._Validate(Mode, "Mode", V_STRING, Array("OPEN", "SAVE")) Then GoTo Finally + If Not SF_Utils._Validate(Filter, "Filter", V_STRING) Then GoTo Finally + End If + DefaultFile = SF_FileSystem._ConvertToUrl(DefaultFile) + +Try: + ' Derive numeric equivalent of the Mode argument: https://api.libreoffice.org/docs/idl/ref/TemplateDescription_8idl.html + With com.sun.star.ui.dialogs.TemplateDescription + If Mode = "OPEN" Then iMode = .FILEOPEN_SIMPLE Else iMode = .FILESAVE_AUTOEXTENSION + End With + + ' Activate the filepicker dialog + Set oFileDialog = SF_Utils._GetUNOService("FilePicker") + With oFileDialog + .Initialize(Array(iMode)) + + ' Set filters + If Len(Filter) > 0 Then .appendFilter("*." & Filter, "*." & Filter) ' Twice: required by API + .appendFilter("*.*", "*.*") + If Len(Filter) > 0 Then .setCurrentFilter("*." & Filter) Else .setCurrentFilter("*.*") + + ' Set initial folder + If Len(DefaultFile) = 0 Then ' TODO: SF_Session.WorkingFolder + Set oPath = SF_Utils._GetUNOService("PathSettings") + sInitPath = oPath.Work ' Probably My Documents + Else + sInitPath = SF_FileSystem._ParseUrl(ConvertToUrl(DefaultFile)).Path + End If + + ' Set default values + Set oFileAccess = SF_Utils._GetUNOService("FileAccess") + If oFileAccess.exists(sInitPath) Then .SetDisplayDirectory(sInitPath) + sBaseFile = SF_FileSystem.GetName(DefaultFile) + .setDefaultName(sBaseFile) + + ' Get selected file + iAccept = .Execute() + If iAccept = com.sun.star.ui.dialogs.ExecutableDialogResults.OK Then sFile = .getSelectedFiles()(0) + End With + +Finally: + PickFile = SF_FileSystem._ConvertFromUrl(sFile) + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.PickFile + +REM ----------------------------------------------------------------------------- +Public Function PickFolder(Optional ByVal DefaultFolder As variant _ + , Optional ByVal FreeText As Variant _ + ) As String +''' Display a FolderPicker dialog box +''' Args: +''' DefaultFolder: the FolderName from which to start. Default = the last selected folder +''' FreeText: text to display in the dialog. Default = "" +''' Returns: +''' The selected FolderName in URL or operating system format +''' The zero-length string if the dialog was cancelled +''' Example: +''' FSO.FineNaming = "SYS" +''' FSO.PickFolder("C:\", "Choose a folder or press Cancel") + +Dim oFolderDialog As Object ' com.sun.star.ui.dialogs.FolderPicker +Dim iAccept As Integer ' Value returned by the dialog (OK, Cancel, ..) +Dim sFolder As String ' Return value ' + +Const cstThisSub = "FileSystem.PickFolder" +Const cstSubArgs = "[DefaultFolder=""""], [FreeText=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFolder = "" + +Check: + If IsMissing(DefaultFolder) Or IsEmpty(DefaultFolder) Then DefaultFolder = "" + If IsMissing(FreeText) Or IsEmpty(FreeText) Then FreeText = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(DefaultFolder, "DefaultFolder", , True) Then GoTo Finally + If Not SF_Utils._Validate(FreeText, "FreeText", V_STRING) Then GoTo Finally + End If + DefaultFolder = SF_FileSystem._ConvertToUrl(DefaultFolder) + +Try: + Set oFolderDialog = SF_Utils._GetUNOService("FolderPicker") + If Not IsNull(oFolderDialog) Then + With oFolderDialog + If Len(DefaultFolder) > 0 Then .DisplayDirectory = ConvertToUrl(DefaultFolder) + .Description = FreeText + iAccept = .Execute() + ' https://api.libreoffice.org/docs/idl/ref/ExecutableDialogResults_8idl.html + If iAccept = com.sun.star.ui.dialogs.ExecutableDialogResults.OK Then + .DisplayDirectory = .Directory ' Set the next default initial folder to the selected one + sFolder = .Directory & "/" + End If + End With + End If + +Finally: + PickFolder = SF_FileSystem._ConvertFromUrl(sFolder) + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.PickFolder + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the FileSystem module as an array + + Properties = Array( _ + "ConfigFolder" _ + , "ExtensionsFolder" _ + , "FileNaming" _ + , "HomeFolder" _ + , "InstallFolder" _ + , "TemplatesFolder" _ + , "TemporaryFolder" _ + , "UserTemplatesFolder" _ + ) + +End Function ' ScriptForge.SF_FileSystem.Properties + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "FileSystem.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.SetProperty + +REM ----------------------------------------------------------------------------- +Public Function SubFolders(Optional ByVal FolderName As Variant _ + , Optional ByVal Filter As Variant _ + ) As Variant +''' Return an array of the FolderNames stored in the given folder. The folder must exist +''' Args: +''' FolderName: the folder to explore +''' Filter: contains wildcards ("?" and "*") to limit the list to the relevant folders (default = "") +''' Returns: +''' An array of strings, each entry is the FolderName of an existing folder +''' Exceptions: +''' UNKNOWNFOLDERERROR Folder does not exist +''' NOTAFOLDERERROR FolderName is a file, not a folder +''' Example: +''' Dim a As Variant +''' FSO.FileNaming = "SYS" +''' a = FSO.SubFolders("C:\Windows\") + +Dim vSubFolders As Variant ' Return value +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess +Dim sFolderName As String ' URL lias for FolderName +Dim sFolder As String ' Single folder +Dim i As Long + +Const cstThisSub = "FileSystem.SubFolders" +Const cstSubArgs = "FolderName, [Filter=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSubFolders = Array() + +Check: + If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + If Not SF_Utils._Validate(Filter, "Filter", V_STRING) Then GoTo Finally + End If + sFolderName = SF_FileSystem._ConvertToUrl(FolderName) + If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFile ' Must not be a file + If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolder ' Folder must exist + +Try: + ' Get SubFolders + Set oSfa = SF_Utils._GetUnoService("FileAccess") + vSubFolders = oSfa.getFolderContents(sFolderName, True) + ' List includes files; remove them or adjust notations of folders + For i = 0 To UBound(vSubFolders) + sFolder = SF_FileSystem._ConvertFromUrl(vSubFolders(i) & "/") + If SF_FileSystem.FileExists(sFolder) Then vSubFolders(i) = "" Else vSubFolders(i) = sFolder + ' Reduce list to those passing the filter + If Len(Filter) > 0 And Len(vSubFolders(i)) > 0 Then + sFolder = SF_FileSystem.GetName(vSubFolders(i)) + If Not SF_String.IsLike(sFolder, Filter) Then vSubFolders(i) = "" + End If + Next i + vSubFolders = SF_Array.TrimArray(vSubFolders) + +Finally: + SubFolders = vSubFolders + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchFile: + SF_Exception.RaiseFatal(NOTAFOLDERERROR, "FolderName", FolderName) + GoTo Finally +CatchFolder: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "FolderName", FolderName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.SubFolders + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _ConvertFromUrl(psFile) As String +''' Execute the builtin ConvertFromUrl function only when relevant +''' i.e. when FileNaming (how arguments and return values are provided) = "SYS" +''' Called at the bottom of methods returning file names +''' Remark: psFile might contain wildcards + +Const cstQuestion = "$QUESTION$", cstStar = "$STAR$" ' Special tokens to replace wildcards + + If SF_FileSystem.FileNaming = "SYS" Then + _ConvertFromUrl = Replace(Replace( _ + ConvertFromUrl(Replace(Replace(psFile, "?", cstQuestion), "*", cstStar)) _ + , cstQuestion, "?"), cstStar, "*") + Else + _ConvertFromUrl = psFile + End If + +End Function ' ScriptForge.FileSystem._ConvertFromUrl + +REM ----------------------------------------------------------------------------- +Private Function _ConvertToUrl(psFile) As String +''' Execute the builtin ConvertToUrl function only when relevant +''' i.e. when FileNaming (how arguments and return values are provided) = "SYS" +''' Called at the top of methods receiving file names as arguments +''' Remark: psFile might contain wildcards + + If SF_FileSystem.FileNaming = "URL" Then + _ConvertToUrl = psFile + Else + ' ConvertToUrl encodes "?" + _ConvertToUrl = Replace(ConvertToUrl(psFile), "%3F", "?") + End If + +End Function ' ScriptForge.FileSystem._ConvertToUrl + +REM ----------------------------------------------------------------------------- +Private Function _CopyMove(psMethod As String _ + , psSource As String _ + , psDestination As String _ + , pbOverWrite As Boolean _ + ) As Boolean +''' Checks the arguments and executes the given method +''' Args: +''' psMethod: CopyFile/CopyFolder or MoveFile/MoveFolder +''' psSource: Either File/FolderName +''' or NamePattern which can include wildcard characters, for one or more files/folders to be copied +''' psDestination: FileName or FolderName for copy/move of a single file/folder +''' Otherwise a destination FolderName. If it does not exist, it is created +''' pbOverWrite: If True, files/folders may be overwritten +''' Must be False for Move operations +''' Next checks are done: +''' With wildcards (multiple files/folders): +''' - Parent folder of source must exist +''' - Destination must not be a file +''' - Parent folder of Destination must exist +''' - If the Destination folder does not exist a new folder is created, +''' - At least one file matches the wildcards expression +''' - Destination files/folder must not exist if pbOverWrite = False +''' - Destination files/folders must not have the read-only attribute set +''' - Destination files must not be folders, destination folders must not be files +''' Without wildcards (single file/folder): +''' - Source file/folder must exist and be a file/folder +''' - Parent folder of Destination must exist +''' - Destination must not be an existing folder/file +''' - Destination file/folder must not exist if pbOverWrite = False +''' - Destination file must not have the read-only attribute set + +Dim bCopyMove As Boolean ' Return value +Dim bCopy As Boolean ' True if Copy, False if Move +Dim bFile As Boolean ' True if File, False if Folder +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess +Dim bWildCards As Boolean ' True if wildcards found in Source +Dim bCreateFolder As Boolean ' True when the destination folder should be created +Dim bDestExists As Boolean ' True if desination exists +Dim sSourceUrl As String ' Alias for Source +Dim sDestinationUrl As String ' Alias for Destination +Dim sDestinationFile As String ' Destination FileName +Dim sParentFolder As String ' Parent folder of Source +Dim vFiles As Variant ' Array of candidates for copy/move +Dim sFile As String ' Single file/folder +Dim sName As String ' Name (last component) of file +Dim i As Long + + ' Error handling left to calling routine + bCopyMove = False + bCopy = ( Left(psMethod, 4) = "Copy" ) + bFile = ( Right(psMethod, 4) = "File" ) + bWildCards = ( InStr(psSource, "*") + InStr(psSource, "?") + InStr(psSource, "%3F") > 0 ) 'ConvertToUrl() converts sometimes "?" to "%3F" + bDestExists = False + + With SF_FileSystem + +Check: + If bWildCards Then + sParentFolder = .GetParentFolderName(psSource) + If Not .FolderExists(sParentFolder) Then GoTo CatchNoMatch + If .FileExists(psDestination) Then GoTo CatchFileNotFolder + If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchDestFolderNotExists + bCreateFolder = Not .FolderExists(psDestination) + Else + Select Case bFile + Case True ' File + If Not .FileExists(psSource) Then GoTo CatchFileNotExists + If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchSourceFolderNotExists + If .FolderExists(psDestination) Then GoTo CatchFolderNotFile + bDestExists = .FileExists(psDestination) + If pbOverWrite = False And bDestExists = True Then GoTo CatchDestinationExists + bCreateFolder = False + Case False ' Folder + If Not .FolderExists(psSource) Then GoTo CatchSourceFolderNotExists + If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchDestFolderNotExists + If .FileExists(psDestination) Then GoTo CatchFileNotFolder + bDestExists = .FolderExists(psDestination) + If pbOverWrite = False And bDestExists Then GoTo CatchDestinationExists + bCreateFolder = Not bDestExists + End Select + End If + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + If bWildCards Then + If bFile Then vFiles = .Files(sParentFolder, .GetName(psSource)) Else vFiles = .SubFolders(sParentFolder, .GetName(psSource)) + If UBound(vFiles) < 0 Then GoTo CatchNoMatch + ' Go through the candidates + If bCreateFolder Then .CreateFolder(psDestination) + For i = 0 To UBound(vFiles) + sFile = vFiles(i) + sDestinationFile = .BuildPath(psDestination, .GetName(sFile)) + If bFile Then bDestExists = .FileExists(sDestinationFile) Else bDestExists = .FolderExists(sDestinationFile) + If pbOverWrite = False Then + If bDestExists Then GoTo CatchDestinationExists + If .FolderExists(sDestinationFile) Then GoTo CatchDestinationExists + End If + sSourceUrl = ._ConvertToUrl(sFile) + sDestinationUrl = ._ConvertToUrl(sDestinationFile) + If bDestExists Then + If oSfa.isReadOnly(sDestinationUrl) Then GoTo CatchDestinationReadOnly + End If + Select Case bCopy + Case True : oSfa.copy(sSourceUrl, sDestinationUrl) + Case False : oSfa.move(sSourceUrl, sDestinationUrl) + End Select + Next i + Else + sSourceUrl = ._ConvertToUrl(psSource) + sDestinationUrl = ._ConvertToUrl(psDestination) + If bDestExists Then + If oSfa.isReadOnly(sDestinationUrl) Then GoTo CatchDestinationReadOnly + End If + If bCreateFolder Then .CreateFolder(psDestination) + Select Case bCopy + Case True : oSfa.copy(sSourceUrl, sDestinationUrl) + Case False : oSfa.move(sSourceUrl, sDestinationUrl) + End Select + End If + + End With + + bCopyMove = True + +Finally: + _CopyMove = bCopyMove + Exit Function +CatchFileNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "Source", psSource) + GoTo Finally +CatchSourceFolderNotExists: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "Source", psSource) + GoTo Finally +CatchDestFolderNotExists: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "Destination", psDestination) + GoTo Finally +CatchFolderNotFile: + SF_Exception.RaiseFatal(NOTAFILEERROR, "Destination", psDestination) + GoTo Finally +CatchDestinationExists: + SF_Exception.RaiseFatal(OVERWRITEERROR, "Destination", psDestination) + GoTo Finally +CatchNoMatch: + SF_Exception.RaiseFatal(NOFILEMATCHERROR, "Source", psSource) + GoTo Finally +CatchFileNotFolder: + SF_Exception.RaiseFatal(NOTAFOLDERERROR, "Destination", psDestination) + GoTo Finally +CatchDestinationReadOnly: + SF_Exception.RaiseFatal(READONLYERROR, "Destination", Iif(bWildCards, sDestinationFile, psDestination)) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem._CopyMove + +REM ----------------------------------------------------------------------------- +Public Function _CountTextLines(ByVal psFileName As String _ + , Optional ByVal pbIncludeBlanks As Boolean _ + ) As Long +''' Convenient function to count the number of lines in a textfile +''' Args: +''' psFileName: the file in FileNaming notation +''' pbIncludeBlanks: if True (default), zero-length lines are included +''' Returns: +''' The number of lines, f.i. to ease array sizing. -1 if file reading error + +Dim lLines As Long ' Return value +Dim oFile As Object ' File handler +Dim sLine As String ' The last line read + +Try: + lLines = 0 + If IsMissing(pbIncludeBlanks) Then pbIncludeBlanks = True + Set oFile = SF_FileSystem.OpenTextFile(psFileName, ForReading) + With oFile + If Not IsNull(oFile) Then + Do While Not .AtEndOfStream + sLine = .ReadLine() + lLines = lLines + Iif(Len(sLine) > 0 Or pbIncludeBlanks, 1, 0) + Loop + End If + .CloseFile() + Set oFile = .Dispose() + End With + +Finally: + _CountTextLines = lLines + Exit Function +End Function ' ScriptForge.SF_FileSystem._CountTextLines + +REM ----------------------------------------------------------------------------- +Private Function _Delete(psMethod As String _ + , psFile As String _ + ) As Boolean +''' Checks the argument and executes the given psMethod +''' Args: +''' psMethod: CopyFile/CopyFolder or MoveFile/MoveFolder +''' psFile: Either File/FolderName +''' or NamePattern which can include wildcard characters, for one or more files/folders to be deleted +''' Next checks are done: +''' With wildcards (multiple files/folders): +''' - Parent folder of File must exist +''' - At least one file matches the wildcards expression +''' - Files or folders to delete must not have the read-only attribute set +''' Without wildcards (single file/folder): +''' - File/folder must exist and be a file/folder +''' - A file or folder to delete must not have the read-only attribute set + +Dim bDelete As Boolean ' Return value +Dim bFile As Boolean ' True if File, False if Folder +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess +Dim bWildCards As Boolean ' True if wildcards found in File +Dim sFileUrl As String ' Alias for File +Dim sParentFolder As String ' Parent folder of File +Dim vFiles As Variant ' Array of candidates for deletion +Dim sFile As String ' Single file/folder +Dim sName As String ' Name (last component) of file +Dim i As Long + + ' Error handling left to calling routine + bDelete = False + bFile = ( Right(psMethod, 4) = "File" ) + bWildCards = ( InStr(psFile, "*") + InStr(psFile, "?") + InStr(psFile, "%3F") > 0 ) 'ConvertToUrl() converts sometimes "?" to "%3F" + + With SF_FileSystem + +Check: + If bWildCards Then + sParentFolder = .GetParentFolderName(psFile) + If Not .FolderExists(sParentFolder) Then GoTo CatchNoMatch + Else + Select Case bFile + Case True ' File + If .FolderExists(psFile) Then GoTo CatchFolderNotFile + If Not .FileExists(psFile) Then GoTo CatchFileNotExists + Case False ' Folder + If .FileExists(psFile) Then GoTo CatchFileNotFolder + If Not .FolderExists(psFile) Then GoTo CatchFolderNotExists + End Select + End If + +Try: + Set oSfa = SF_Utils._GetUnoService("FileAccess") + If bWildCards Then + If bFile Then vFiles = .Files(sParentFolder) Else vFiles = .SubFolders(sParentFolder) + ' Select candidates + For i = 0 To UBound(vFiles) + If Not SF_String.IsLike(.GetName(vFiles(i)), .GetName(psFile)) Then vFiles(i) = "" + Next i + vFiles = SF_Array.TrimArray(vFiles) + If UBound(vFiles) < 0 Then GoTo CatchNoMatch + ' Go through the candidates + For i = 0 To UBound(vFiles) + sFile = vFiles(i) + sFileUrl = ._ConvertToUrl(sFile) + If oSfa.isReadOnly(sFileUrl) Then GoTo CatchReadOnly + oSfa.kill(sFileUrl) + Next i + Else + sFileUrl = ._ConvertToUrl(psFile) + If oSfa.isReadOnly(sFileUrl) Then GoTo CatchReadOnly + oSfa.kill(sFileUrl) + End If + + End With + + bDelete = True + +Finally: + _Delete = bDelete + Exit Function +CatchFolderNotExists: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "FolderName", psFile) + GoTo Finally +CatchFileNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", psFile) + GoTo Finally +CatchFolderNotFile: + SF_Exception.RaiseFatal(NOTAFILEERROR, "FileName", psFile) + GoTo Finally +CatchNoMatch: + SF_Exception.RaiseFatal(NOFILEMATCHERROR, Iif(bFile, "FileName", "FolderName"), psFile) + GoTo Finally +CatchFileNotFolder: + SF_Exception.RaiseFatal(NOTAFOLDERERROR, "FolderName", psFile) + GoTo Finally +CatchReadOnly: + SF_Exception.RaiseFatal(READONLYERROR, Iif(bFile, "FileName", "FolderName"), Iif(bWildCards, sFile, psFile)) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem._Delete + +REM ----------------------------------------------------------------------------- +Private Function _GetConfigFolder(ByVal psFolder As String) As String +''' Returns one of next configuration folders: see https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1util_1_1PathSubstitution.html +''' inst => Installation path of LibreOffice +''' prog => Program path of LibreOffice +''' user => The user installation/config directory +''' work => The work directory of the user. Under Windows this would be the "MyDocuments" subdirectory. Under Unix this would be the home-directory +''' home => The home directory of the user. Under Unix this would be the home- directory. +''' Under Windows this would be the CSIDL_PERSONAL directory, for example "Documents and Settings\<username>\Documents" +''' temp => The current temporary directory + +Dim oSubst As Object ' com.sun.star.util.PathSubstitution +Dim sConfig As String ' Return value + + sConfig = "" + Set oSubst = SF_Utils._GetUNOService("PathSubstitution") + If Not IsNull(oSubst) Then sConfig = oSubst.getSubstituteVariableValue("$(" & psFolder & ")") & "/" + + _GetConfigFolder = SF_FileSystem._ConvertFromUrl(sConfig) + +End Function ' ScriptForge.FileSystem._GetConfigFolder + +REM ----------------------------------------------------------------------------- +Public Function _ParseUrl(psUrl As String) As Object +''' Returns a com.sun.star.util.URL structure based on the argument + +Dim oParse As Object ' com.sun.star.util.URLTransformer +Dim bParsed As Boolean ' True if parsing is successful +Dim oUrl As New com.sun.star.util.URL ' Return value + + oUrl.Complete = psUrl + Set oParse = SF_Utils._GetUNOService("URLTransformer") + bParsed = oParse.parseStrict(oUrl, "") + If bParsed Then oUrl.Path = ConvertToUrl(oUrl.Path) + + Set _ParseUrl = oUrl + +End Function ' ScriptForge.SF_FileSystem._ParseUrl + +REM ----------------------------------------------------------------------------- +Public Function _SFInstallFolder() As String +''' Returns the installation folder of the ScriptForge library +''' Either: +''' - The library is present in [My Macros & Dialogs] +''' ($config)/basic/ScriptForge +''' - The library is present in [LibreOffice Macros & Dialogs] +''' ($install)/share/basic/ScriptForge + +Dim sFolder As String ' Folder + + _SFInstallFolder = "" + + sFolder = BuildPath(ConfigFolder, "basic/ScriptForge") + If Not FolderExists(sFolder) Then + sFolder = BuildPath(InstallFolder, "share/basic/ScriptForge") + If Not FolderExists(sFolder) Then Exit Function + End If + + _SFInstallFolder = _ConvertFromUrl(sFolder) + +End Function ' ScriptForge.SF_FileSystem._SFInstallFolder + +REM ============================================ END OF SCRIPTFORGE.SF_FileSystem +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_L10N.xba b/wizards/source/scriptforge/SF_L10N.xba new file mode 100644 index 000000000000..6ff222543a00 --- /dev/null +++ b/wizards/source/scriptforge/SF_L10N.xba @@ -0,0 +1,696 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_L10N" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule +'Option Private Module + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' L10N (aka SF_L10N) +''' ==== +''' Implementation of a Basic class for providing a number of services +''' related to the translation of user interfaces into a huge number of languages +''' with a minimal impact on the program code itself +''' +''' The design choices of this module are based on so-called PO-files +''' PO-files (portable object files) have long been promoted in the free software industry +''' as a mean of providing multilingual UIs. This is accomplished through the use of human-readable +''' text files with a well defined structure that specifies, for any given language, +''' the source language string and the localized string +''' +''' To read more about the PO format and its ecosystem of associated toolsets: +''' https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files +''' and, IMHO, a very good tutorial: +''' http://pology.nedohodnik.net/doc/user/en_US/ch-about.html +''' +''' The main advantage of the PO format is the complete dissociation between the two +''' very different profiles, i.e. the programmer and the translator(s). +''' Being independent text files, one per language to support, the programmer may give away +''' pristine PO template files (known as POT-files) for a translator to process. +''' +''' This class implements mainly 3 mechanisms: +''' - AddText: for the programmer to build a set of words or sentences +''' meant for being translated later +''' - ExportToPOTFile: All the above texts are exported into a pristine POT-file +''' - GetText: At runtime get the text in the user language +''' Note that the first two are optional: POT and PO-files may be built with a simple text editor +''' +''' Several instances of the L10N class may coexist +' The constraint however is that each instance should find its PO-files +''' in a separate directory +''' PO-files must be named with the targeted locale: f.i. "en-US.po" or "fr-BE.po" +''' +''' Service invocation syntax +''' CreateScriptService("L10N"[, FolderName[, Locale]]) +''' FolderName: the folder containing the PO-files (in SF_FileSystem.FileNaming notation) +''' Locale: in the form la-CO (language-COUNTRY) +''' Service invocation examples: +''' Dim myPO As Variant +''' myPO = CreateScriptService("L10N") ' AddText and ExportToPOTFile are allowed +''' myPO = CreateScriptService("L10N", "C:\myPOFiles\", "fr-BE") +''' 'All functionalities are available +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM =============================================================== PRIVATE TYPES + +''' The recognized elements of an entry in a PO file are (other elements are ignored) : +''' #. Extracted comments (given by the programmer to the translator) +''' #, flag (the kde-format flag when the string contains tokens) +''' msgctxt Context (to store an acronym associated with the message, this is a distorsion of the norm) +''' msgid untranslated-string +''' msgstr translated-string +''' NB: plural forms are not supported + +Type POEntry + Comment As String + Flag As String + Context As String + MsgId As String + MsgStr As String +End Type + +REM ================================================================== EXCEPTIONS + +Const DUPLICATEKEYERROR = "DUPLICATEKEYERROR" + +REM ============================================================= PRIVATE MEMBERS + +Private [Me] As Object +Private [_Parent] As Object +Private ObjectType As String ' Must be "L10N" +Private ServiceName As String +Private _POFolder As String ' PO files container +Private _Locale As String ' la-CO +Private _POFile As String ' PO file in URL format +Private _Encoding As String ' Used to open the PO file, default = UTF-8 +Private _Dictionary As Object ' SF_Dictionary + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "L10N" + ServiceName = "ScriptForge.L10N" + _POFolder = "" + _Locale = "" + _POFile = "" + Set _Dictionary = Nothing +End Sub ' ScriptForge.SF_L10N Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + + If Not IsNull(_Dictionary) Then Set _Dictionary = _Dictionary.Dispose() + Call Class_Initialize() +End Sub ' ScriptForge.SF_L10N Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Call Class_Terminate() + Set Dispose = Nothing +End Function ' ScriptForge.SF_L10N Explicit Destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get Folder() As String +''' Returns the FolderName containing the PO-files expressed as given by the current FileNaming +''' property of the SF_FileSystem service. Default = URL format +''' May be empty +''' Example: +''' myPO.Folder + + Folder = _PropertyGet("Folder") + +End Property ' ScriptForge.SF_L10N.Folder + +REM ----------------------------------------------------------------------------- +Property Get Languages() As Variant +''' Returns a zero-based array listing all the BaseNames of the PO-files found in Folder, +''' Example: +''' myPO.Languages + + Languages = _PropertyGet("Languages") + +End Property ' ScriptForge.SF_L10N.Languages + +REM ----------------------------------------------------------------------------- +Property Get Locale() As String +''' Returns the currently active language-COUNTRY combination. May be empty +''' Example: +''' myPO.Locale + + Locale = _PropertyGet("Locale") + +End Property ' ScriptForge.SF_L10N.Locale + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function AddText(Optional ByVal Context As Variant _ + , Optional ByVal MsgId As Variant _ + , Optional ByVal Comment As Variant _ + , Optional ByVal MsgStr As Variant _ + ) As Boolean +''' Add a new entry in the list of localizable text strings +''' Args: +''' Context: when not empty, the key to retrieve the translated string via GetText. Default = "" +''' MsgId: the untranslated string, i.e. the text appearing in the program code. Must not be empty +''' The key to retrieve the translated string via GetText when Context is empty +''' May contain placeholders (%1 ... %9) for dynamic arguments to be inserted in the text at run-time +''' If the string spans multiple lines, insert escape sequences (\n) where relevant +''' Comment: the so-called "extracted-comments" intended to inform/help translators +''' If the string spans multiple lines, insert escape sequences (\n) where relevant +''' MsgStr: (internal use only) the translated string +''' If the string spans multiple lines, insert escape sequences (\n) where relevant +''' Returns: +''' True if successful +''' Exceptions: +''' DUPLICATEKEYERROR: such a key exists already +''' Examples: +''' myPO.AddText(, "This is a text to be included in a POT file") + +Dim bAdd As Boolean ' Output buffer +Dim sKey As String ' The key part of the new entry in the dictionary +Dim vItem As POEntry ' The item part of the new entry in the dictionary +Const cstPipe = "|" ' Pipe forbedden in MsgId's +Const cstThisSub = "L10N.AddText" +Const cstSubArgs = "[Context=""""], MsgId, [Comment=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bAdd = False + +Check: + If IsMissing(Context) Or IsMissing(Context) Then Context = "" + If IsMissing(Comment) Or IsMissing(Comment) Then Comment = "" + If IsMissing(MsgStr) Or IsMissing(MsgStr) Then MsgStr = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Context, "Context", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(MsgId, "MsgId", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Comment, "Comment", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(MsgStr, "MsgStr", V_STRING) Then GoTo Finally + End If + If Len(MsgId) = 0 Then GoTo Finally + +Try: + If Len(Context) > 0 Then sKey = Context Else sKey = MsgId + If _Dictionary.Exists(sKey) Then GoTo CatchDuplicate + + With vItem + .Comment = Comment + If InStr(MsgId, "%") > 0 Then .Flag = "kde-format" Else .Flag = "" + .Context = Replace(Context, cstPipe, " ") + .MsgId = Replace(MsgId, cstPipe, " ") + .MsgStr = MsgStr + End With + _Dictionary.Add(sKey, vItem) + +Finally: + AddText = bAdd + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchDuplicate: + SF_Exception.RaiseFatal(DUPLICATEKEYERROR, Iif(Len(Context) > 0, "Context", "MsgId"), sKey) + GoTo Finally +End Function ' ScriptForge.SF_L10N.AddText + +REM ----------------------------------------------------------------------------- +Public Function ExportToPOTFile(Optional ByVal FileName As Variant _ + , Optional ByVal Header As Variant _ + , Optional ByVal Encoding As Variant _ + ) As Boolean +''' Export a set of untranslated strings as a POT file +''' The set of strings has been built either by a succession of AddText() methods +''' or by a successful invocation of the L10N service with the FolderName argument +''' The generated file should pass successfully the "msgfmt --check 'the pofile'" GNU command +''' Args: +''' FileName: the complete file name to export to. It it exists, it will be overwritten without warning +''' Header: Comments that will appear on top of the generated file. Do not include any leadung "#" +''' If the string spans multiple lines, insert escape sequences (\n) where relevant +''' A standard header will be added anyway +''' Encoding: The character set that should be used +''' Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that LibreOffice probably does not implement all existing sets +''' Default = UTF-8 +''' Returns: +''' True if successful +''' Examples: +''' myPO.ExportToPOTFile("myFile.pot", Header := "Top comment\nSecond line of top comment") + +Dim bExport As Boolean ' Return value +Dim oFile As Object ' Generated file handler +Dim vLines As Variant ' Wrapped lines +Dim sLine As String ' A single line +Dim vItems As Variant ' Array of dictionary items +Dim vItem As Variant ' POEntry type +Const cstSharp = "# ", cstSharpDot = "#. ", cstFlag = "#, kde-format" +Const cstTabSize = 4 +Const cstWrap = 70 +Const cstThisSub = "L10N.ExportToPOTFile" +Const cstSubArgs = "FileName, [Header=""""], [Encoding=""UTF-8""" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExport = False + +Check: + If IsMissing(Header) Or IsMissing(Header) Then Header = "" + If IsMissing(Encoding) Or IsMissing(Encoding) Then Encoding = "UTF-8" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Header, "Header", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Encoding, "Encoding", V_STRING) Then GoTo Finally + End If + +Try: + Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding) + If Not IsNull(oFile) Then + With oFile + ' Standard header + .WriteLine(cstSharp) + .WriteLine(cstSharp & "This pristine POT file has been generated by LibreOffice/ScriptForge") + .WriteLine(cstSharp & "Full documentation is available on https://help.libreoffice.org/") + ' User header + If Len(Header) > 0 Then + .WriteLine(cstSharp) + vLines = SF_String.Wrap(Header, cstWrap, cstTabSize) + For Each sLine In vLines + .WriteLine(cstSharp & Replace(sLine, SF_String.sfLF, "")) + Next sLine + End If + ' Standard header + .WriteLine(cstSharp) + .WriteLine("msgid """"") + .WriteLine("msgstr """"") + .WriteLine(SF_String.Quote("Project-Id-Version: PACKAGE VERSION\n")) + .WriteLine(SF_String.Quote("Report-Msgid-Bugs-To: " _ + & "https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n")) + .WriteLine(SF_String.Quote("POT-Creation-Date: " & SF_STring.Represent(Now()) & "\n")) + .WriteLine(SF_String.Quote("PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n")) + .WriteLine(SF_String.Quote("Last-Translator: FULL NAME <EMAIL@ADDRESS>\n")) + .WriteLine(SF_String.Quote("Language-Team: LANGUAGE <EMAIL@ADDRESS>\n")) + .WriteLine(SF_String.Quote("Language: en_US\n")) + .WriteLine(SF_String.Quote("MIME-Version: 1.0\n")) + .WriteLine(SF_String.Quote("Content-Type: text/plain; charset=" & Encoding & "\n")) + .WriteLine(SF_String.Quote("Content-Transfer-Encoding: 8bit\n")) + .WriteLine(SF_String.Quote("Plural-Forms: nplurals=2; plural=n > 1;\n")) + .WriteLine(SF_String.Quote("X-Generator: LibreOffice - ScriptForge\n")) + .WriteLine(SF_String.Quote("X-Accelerator-Marker: ~\n")) + ' Individual translatable strings + vItems = _Dictionary.Items() + For Each vItem in vItems + .WriteBlankLines(1) + ' Comments + vLines = Split(vItem.Comment, "\n") + For Each sLine In vLines + .WriteLine(cstSharpDot & SF_String.ExpandTabs(SF_String.Unescape(sLine), cstTabSize)) + Next sLine + ' Flag + If InStr(vItem.MsgId, "%") > 0 Then .WriteLine(cstFlag) + ' Context + If Len(vItem.Context) > 0 Then + .WriteLine("msgctxt " & SF_String.Quote(vItem.Context)) + End If + ' MsgId + vLines = SF_String.Wrap(vItem.MsgId, cstWrap, cstTabSize) + If UBound(vLines) = 0 Then + .WriteLine("msgid " & SF_String.Quote(SF_String.Escape(vLines(0)))) + Else + .WriteLine("msgid """"") + For Each sLine in vLines + .WriteLine(SF_String.Quote(SF_String.Escape(sLine))) + Next sLine + End If + ' MsgStr + .WriteLine("msgstr """"") + Next vItem + .CloseFile() + End With + End If + bExport = True + +Finally: + If Not IsNull(oFile) Then Set oFile = oFile.Dispose() + ExportToPOTFile = bExport + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_L10N.ExportToPOTFile + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' If the property does not exist, returns Null +''' Exceptions: +''' ARGUMENTERROR The property does not exist +''' Examples: +''' myL10N.GetProperty("MyProperty") + +Const cstThisSub = "L10N.GetProperty" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_L10N.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function GetText(Optional ByVal MsgId As Variant _ + , ParamArray pvArgs As Variant _ + ) As String +''' Get the translated string corresponding with the given argument +''' Args: +''' MsgId: the identifier of the string or the untranslated string +''' Either - the untranslated text (MsgId) +''' - the reference to the untranslated text (Context) +''' - both (Context|MsgId) : the pipe character is essential +''' pvArgs(): a list of arguments present as %1, %2, ... in the (un)translated string) +''' to be substituted in the returned string +''' Any type is admitted but only strings, numbers or dates are relevant +''' Returns: +''' The translated string +''' If not found the MsgId string or the Context string +''' Anyway the substitution is done +''' Examples: +''' myPO.GetText("This is a text to be included in a POT file") +''' ' Ceci est un text à inclure dans un fichier POT + +Dim sText As String ' Output buffer +Dim sContext As String ' Context part of argument +Dim sMsgId As String ' MsgId part of argument +Dim vItem As POEntry ' Entry in the dictionary +Dim vMsgId As Variant ' MsgId split on pipe +Dim sKey As String ' Key of dictionary +Dim sPercent As String ' %1, %2, ... placeholders +Dim i As Long +Const cstPipe = "|" +Const cstThisSub = "L10N.GetText" +Const cstSubArgs = "MsgId, [Arg0, Arg1, ...]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sText = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(MsgId, "MsgId", V_STRING) Then GoTo Finally + End If + If Len(Trim(MsgId)) = 0 Then GoTo Finally + sText = MsgId + +Try: + ' Find and load entry from dictionary + If Left(MsgId, 1) = cstPipe then MsgId = Mid(MsgId, 2) + vMsgId = Split(MsgId, cstPipe) + sKey = vMsgId(0) + If Not _Dictionary.Exists(sKey) Then ' Not found + If UBound(vMsgId) = 0 Then sText = vMsgId(0) Else sText = Mid(MsgId, InStr(MsgId, cstPipe) + 1) + Else + vItem = _Dictionary.Item(sKey) + If Len(vItem.MsgStr) > 0 Then sText = vItem.MsgStr Else sText = vItem.MsgId + End If + + ' Substitute %i placeholders + For i = UBound(pvArgs) To 0 Step -1 ' Go downwards to not have a limit in number of args + sPercent = "%" & (i + 1) + sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i))) + Next i + +Finally: + GetText = sText + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_L10N.GetText + +REM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Public Function _(Optional ByVal MsgId As Variant _ + , ParamArray pvArgs As Variant _ + ) As String +''' Get the translated string corresponding with the given argument +''' Alias of GetText() - See above +''' Examples: +''' myPO._("This is a text to be included in a POT file") +''' ' Ceci est un text à inclure dans un fichier POT + +Dim sText As String ' Output buffer +Dim sPercent As String ' %1, %2, ... placeholders +Dim i As Long +Const cstPipe = "|" +Const cstThisSub = "L10N._" +Const cstSubArgs = "MsgId, [Arg0, Arg1, ...]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sText = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(MsgId, "MsgId", V_STRING) Then GoTo Finally + End If + If Len(Trim(MsgId)) = 0 Then GoTo Finally + +Try: + ' Find and load entry from dictionary + sText = GetText(MsgId) + + ' Substitute %i placeholders - done here, not in GetText(), because # of arguments is undefined + For i = 0 To UBound(pvArgs) + sPercent = "%" & (i + 1) + sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i))) + Next i + +Finally: + _ = sText + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_L10N._ + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the L10N service as an array + + Methods = Array( _ + "AddText" _ + , "ExportToPOTFile" _ + , "GetText" _ + , "_" _ + ) + +End Function ' ScriptForge.SF_L10N.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Timer class as an array + + Properties = Array( _ + "Folder" _ + , "Languages" _ + , "Locale" _ + ) + +End Function ' ScriptForge.SF_L10N.Properties + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "L10N.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_L10N.SetProperty + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Public Sub _Initialize(ByVal psPOFile As String _ + , ByVal Encoding As String _ + ) +''' Completes initialization of the current instance requested from CreateScriptService() +''' Load the POFile in the dictionary, otherwise leave the dictionary empty +''' Args: +''' psPOFile: the file to load the translated strings from +''' Encoding: The character set that should be used. Default = UTF-8 + +Dim oFile As Object ' PO file handler +Dim sContext As String ' Collected context string +Dim sMsgId As String ' Collected untranslated string +Dim sComment As String ' Collected comment string +Dim sMsgStr As String ' Collected translated string +Dim sLine As String ' Last line read +Dim iContinue As Integer ' 0 = None, 1 = MsgId, 2 = MsgStr +Const cstMsgId = 1, cstMsgStr = 2 + +Try: + ' Initialize dictionary anyway + Set _Dictionary = SF_Services.CreateScriptService("Dictionary") + Set _Dictionary.[_Parent] = [Me] + + ' Load PO file + If Len(psPOFile) > 0 Then + With SF_FileSystem + _POFolder = ._ConvertToUrl(.GetParentFolderName(psPOFile)) + _Locale = .GetBaseName(psPOFile) + _POFile = ._ConvertToUrl(psPOFile) + End With + ' Load PO file + Set oFile = SF_FileSystem.OpenTextFile(psPOFile, IOMode := SF_FileSystem.ForReading, Encoding := Encoding) + If Not IsNull(oFile) Then + With oFile + ' The PO file is presumed valid => syntax check is not very strict + sContext = "" : sMsgId = "" : sComment = "" : sMsgStr = "" + Do While Not .AtEndOfStream + sLine = Trim(.ReadLine()) + ' Trivial examination of line header + Select Case True + Case sLine = "" + If Len(sMsgId) > 0 Then AddText(sContext, sMsgId, sComment, sMsgStr) + sContext = "" : sMsgId = "" : sComment = "" : sMsgStr = "" + iContinue = 0 + Case Left(sLine, 3) = "#. " + sComment = sComment & Iif(Len(sComment) > 0, "\n", "") & Trim(Mid(sLine, 4)) + iContinue = 0 + Case Left(sLine, 8) = "msgctxt " + sContext = SF_String.Unquote(Trim(Mid(sLine, 9))) + iContinue = 0 + Case Left(sLine, 6) = "msgid " + sMsgId = SF_String.Unquote(Trim(Mid(sLine, 7))) + iContinue = cstMsgId + Case Left(sLine, 7) = "msgstr " + sMsgStr = sMsgStr & SF_String.Unquote(Trim(Mid(sLine, 8))) + iContinue = cstMsgStr + Case Left(sLine, 1) = """" + If iContinue = cstMsgId Then + sMsgId = sMsgId & SF_String.Unquote(sLine) + ElseIf iContinue = cstMsgStr Then + sMsgStr = sMsgStr & SF_String.Unquote(sLine) + Else + iContinue = 0 + End If + Case Else ' Skip line + iContinue = 0 + End Select + Loop + ' Be sure to store the last entry + If Len(sMsgId) > 0 Then AddText(sContext, sMsgId, sComment, sMsgStr) + .CloseFile() + Set oFile = .Dispose() + End With + End If + Else + _POFolder = "" + _Locale = "" + _POFile = "" + End If + +Finally: + Exit Sub +End Sub ' ScriptForge.SF_L10N._Initialize + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) +''' Return the value of the named property +''' Args: +''' psProperty: the name of the property + +Dim vFiles As Variant ' Array of PO-files +Dim i As Long +Dim cstThisSub As String +Dim cstSubArgs As String + + cstThisSub = "SF_L10N.get" & psProperty + cstSubArgs = "" + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + With SF_FileSystem + Select Case psProperty + Case "Folder" + If Len(_POFolder) > 0 Then _PropertyGet = ._ConvertFromUrl(_POFolder) Else _PropertyGet = "" + Case "Languages" + If Len(_POFolder) > 0 Then + vFiles = .Files(._ConvertFromUrl(_POFolder), "??-??.po") + For i = 0 To UBound(vFiles) + vFiles(i) = SF_FileSystem.GetBaseName(vFiles(i)) + Next i + Else + vFiles = Array() + End If + _PropertyGet = vFiles + Case "Locale" + _PropertyGet = _Locale + Case Else + _PropertyGet = Null + End Select + End With + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_L10N._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the L10N instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[L10N]: PO file" + + _Repr = "[L10N]: " & _POFile + +End Function ' ScriptForge.SF_L10N._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_L10N +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Platform.xba b/wizards/source/scriptforge/SF_Platform.xba new file mode 100644 index 000000000000..c1ac6c8e0b3c --- /dev/null +++ b/wizards/source/scriptforge/SF_Platform.xba @@ -0,0 +1,281 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Platform" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Platform +''' =========== +''' Singleton class implementing the "ScriptForge.Platform" service +''' Implemented as a usual Basic module +''' +''' A collection of properties about the execution environment: +''' - HW platform +''' - Operating System +''' - current user +''' - LibreOffice version +''' +''' Service invocation example: +''' Dim platform As Variant +''' platform = CreateScriptService("Platform") +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +REM ============================================================ MODULE CONSTANTS + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_Array Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get Architecture() As String +''' Returns the actual bit architecture +''' Example: +''' MsgBox platform.Architecture ' 64bit + Architecture = _PropertyGet("Architecture") +End Property ' ScriptForge.SF_Platform.Architecture (get) + +REM ----------------------------------------------------------------------------- +Property Get ComputerName() As String +''' Returns the computer's network name +''' Example: +''' MsgBox platform.ComputerName + ComputerName = _PropertyGet("ComputerName") +End Property ' ScriptForge.SF_Platform.ComputerName (get) + +REM ----------------------------------------------------------------------------- +Property Get CPUCount() As Integer +''' Returns the number of Central Processor Units +''' Example: +''' MsgBox platform.CPUCount ' 4 + CPUCount = _PropertyGet("CPUCount") +End Property ' ScriptForge.SF_Platform.CPUCount (get) + +REM ----------------------------------------------------------------------------- +Property Get CurrentUser() As String +''' Returns the name of logged in user +''' Example: +''' MsgBox platform.CurrentUser + CurrentUser = _PropertyGet("CurrentUser") +End Property ' ScriptForge.SF_Platform.CurrentUser (get) + +REM ----------------------------------------------------------------------------- +Property Get Machine() As String +''' Returns the machine type like 'i386' or 'x86_64' +''' Example: +''' MsgBox platform.Machine + Machine = _PropertyGet("Machine") +End Property ' ScriptForge.SF_Platform.Machine (get) + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_Platform" +End Property ' ScriptForge.SF_Platform.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.Platform" +End Property ' ScriptForge.SF_Platform.ServiceName + +REM ----------------------------------------------------------------------------- +Property Get OfficeVersion() As String +''' Returns the office software version in the form 'LibreOffice w.x.y.z (The Document Foundation)' +''' Example: +''' MsgBox platform.OfficeVersion + OfficeVersion = _PropertyGet("OfficeVersion") +End Property ' ScriptForge.SF_Platform.OfficeVersion (get) + +REM ----------------------------------------------------------------------------- +Property Get OSName() As String +''' Returns the name of the operating system like 'Linux' or 'Windows' +''' Example: +''' MsgBox platform.OSName + OSName = _PropertyGet("OSName") +End Property ' ScriptForge.SF_Platform.OSName (get) + +REM ----------------------------------------------------------------------------- +Property Get OSPlatform() As String +''' Returns a single string identifying the underlying platform with as much useful and human-readable information as possible +''' Example: +''' MsgBox platform.OSPlatform ' Linux-4.15.0-117-generic-x86_64-with-Ubuntu-18.04-bionic + OSPlatform = _PropertyGet("OSPlatform") +End Property ' ScriptForge.SF_Platform.OSPlatform (get) + +REM ----------------------------------------------------------------------------- +Property Get OSRelease() As String +''' Returns the operating system's release +''' Example: +''' MsgBox platform.OSRelease ' 4.15.0-117-generic + OSRelease = _PropertyGet("OSRelease") +End Property ' ScriptForge.SF_Platform.OSRelease (get) + +REM ----------------------------------------------------------------------------- +Property Get OSVersion() As String +''' Returns the name of the operating system build or version +''' Example: +''' MsgBox platform.OSVersion ' #118-Ubuntu SMP Fri Sep 4 20:02:41 UTC 2020 + OSVersion = _PropertyGet("OSVersion") +End Property ' ScriptForge.SF_Platform.OSVersion (get) + +REM ----------------------------------------------------------------------------- +Property Get Processor() As String +''' Returns the (real) processor name, e.g. 'amdk6'. Might return the same value as Machine +''' Example: +''' MsgBox platform.Processor + Processor = _PropertyGet("Processor") +End Property ' ScriptForge.SF_Platform.Processor (get) + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' If the property does not exist, returns Null +''' Exceptions: +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Platform.GetProperty" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Platform.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Model service as an array + + Methods = Array( _ + ) + +End Function ' ScriptForge.SF_Platform.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Platform class as an array + + Properties = Array( _ + "Architecture" _ + , "ComputerName" _ + , "CPUCount" _ + , "CurrentUser" _ + , "Machine" _ + , "OfficeVersion" _ + , "OSName" _ + , "OSPlatform" _ + , "OSRelease" _ + , "OSVersion" _ + , "Processor" _ + ) + +End Function ' ScriptForge.SF_Platform.Properties + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Public Function _GetProductName() as String +''' Returns Office product and version numbers found in configuration registry +''' Derived from the Tools library + +Dim oProdNameAccess as Object ' configmgr.RootAccess +Dim sProdName as String +Dim sVersion as String +Dim sVendor As String + + On Local Error GoTo Catch ' Prevent any error + _GetProductName = "" + +Try: + Set oProdNameAccess = SF_Utils._GetRegistryKeyContent("org.openoffice.Setup/Product") + + sProdName = oProdNameAccess.ooName + sVersion = oProdNameAccess.ooSetupVersionAboutBox + sVendor = oProdNameAccess.ooVendor + + _GetProductName = sProdName & " " & sVersion & " (" & sVendor & ")" + +Finally: + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Platform._GetProductName + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant +''' Return the value of the named property +''' Args: +''' psProperty: the name of the property + +Dim sOSName As String ' Operating system + +Const cstPyHelper = "$" & "_SF_Platform" +Dim cstThisSub As String +Const cstSubArgs = "" + + cstThisSub = "Platform.get" & psProperty + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + Select Case psProperty + Case "Architecture", "ComputerName", "CPUCount", "CurrentUser", "Machine" _ + , "OSPlatform", "OSRelease", "OSVersion", "Processor" + With ScriptForge.SF_Session + _PropertyGet = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper, psProperty) + End With + Case "OfficeVersion" + _PropertyGet = _GetProductName() + Case "OSName" + ' Calc INFO function preferred to Python script to avoid ScriptForge initialization risks when Python is not installed + sOSName = _SF_.OSName + If sOSName = "" Then + sOSName = SF_Session.ExecuteCalcFunction("INFO", "system") + Select Case sOSName + Case "WNT" : sOSName = "Windows" + Case "MACOSX" : sOSName = "macOS" + Case "LINUX" : sOSName = "Linux" + Case "SOLARIS" : sOSName = "Solaris" + Case Else : sOSName = SF_String.Capitalize(sOSName) + End Select + EndIf + _PropertyGet = sOSName + Case Else + _PropertyGet = Null + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Platform._PropertyGet + +REM ============================================ END OF SCRIPTFORGE.SF_PLATFORM +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Root.xba b/wizards/source/scriptforge/SF_Root.xba new file mode 100644 index 000000000000..47e855421332 --- /dev/null +++ b/wizards/source/scriptforge/SF_Root.xba @@ -0,0 +1,822 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Root" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule +Option Private Module + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Root +''' ======= +''' FOR INTERNAL USE ONLY +''' Singleton class holding all persistent variables shared +''' by all the modules of the ScriptForge library +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ============================================================= PRIVATE MEMBERS + +' Internals +Private [Me] As Object +Private [_Parent] As Object +Private ObjectType As String ' Must be "ROOT" +Private MainFunction As String ' Name of method or property called by user script +Private MainFunctionArgs As String ' Syntax of method called by user script +Private StackLevel As Integer ' Depth of calls between internal methods + +' Error management +Private ErrorHandler As Boolean ' True = error handling active, False = internal debugging +Private ConsoleLines() As Variant ' Array of messages displayable in console +Private ConsoleDialog As Object ' SFDialogs.Dialog object +Private ConsoleControl As Object ' SFDialogs.DialogControl object +Private DisplayEnabled As Boolean ' When True, display of console or error messages is allowed +Private StopWhenError As Boolean ' When True, process stops after error > "WARNING" +Private DebugMode As Boolean ' When True, log enter/exit each official Sub + +' Services management +Private ServicesList As Variant ' Dictionary of provided services + +' Usual UNO services +Private FunctionAccess As Object ' com.sun.star.sheet.FunctionAccess +Private PathSettings As Object ' com.sun.star.util.PathSettings +Private PathSubstitution As Object ' com.sun.star.util.PathSubstitution +Private ScriptProvider As Object ' com.sun.star.script.provider.MasterScriptProviderFactory +Private SystemShellExecute As Object ' com.sun.star.system.SystemShellExecute +Private CoreReflection As Object ' com.sun.star.reflection.CoreReflection +Private DispatchHelper As Object ' com.sun.star.frame.DispatchHelper +Private TextSearch As Object ' com.sun.star.util.TextSearch +Private SearchOptions As Object ' com.sun.star.util.SearchOptions +Private Locale As Object ' com.sun.star.lang.Locale +Private CharacterClass As Object ' com.sun.star.i18n.CharacterClassification +Private FileAccess As Object ' com.sun.star.ucb.SimpleFileAccess +Private FilterFactory As Object ' com.sun.star.document.FilterFactory +Private FolderPicker As Object ' com.sun.star.ui.dialogs.FolderPicker +Private FilePicker As Object ' com.sun.star.ui.dialogs.FilePicker +Private URLTransformer As Object ' com.sun.star.util.URLTransformer +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 +Private ConfigurationProvider _ + As Object ' com.sun.star.configuration.ConfigurationProvider +Private MailService As Object ' com.sun.star.system.SimpleCommandMail or com.sun.star.system.SimpleSystemMail + +' Specific psersistent services objects or properties +Private FileSystemNaming As String ' If "SYS", file and folder naming is based on operating system notation +Private PythonHelper As String ' File name of Python helper functions (stored in $(inst)/share/Scripts/python) +Private Interface As Object ' ScriptForge own L10N service +Private OSName As String ' WIN, LINUX, MACOS +Private SFDialogs As Variant ' Persistent storage for the SFDialogs library + +REM ====================================================== CONSTRUCTOR/DESTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "ROOT" + MainFunction = "" + MainFunctionArgs = "" + StackLevel = 0 + ErrorHandler = True + ConsoleLines = Array() + Set ConsoleDialog = Nothing + Set ConsoleControl = Nothing + DisplayEnabled = True + StopWhenError = True + DebugMode = False + ServicesList = Empty + Set FunctionAccess = Nothing + Set PathSettings = Nothing + Set PathSubstitution = Nothing + Set ScriptProvider = Nothing + Set SystemShellExecute = Nothing + Set CoreReflection = Nothing + Set DispatchHelper = Nothing + Set TextSearch = Nothing + Set SearchOptions = Nothing + Set Locale = Nothing + Set CharacterClass = Nothing + Set FileAccess = Nothing + Set FilterFactory = Nothing + Set FolderPicker = Nothing + Set FilePicker = Nothing + Set URLTransformer = Nothing + Set Introspection = Nothing + FileSystemNaming = "ANY" + PythonHelper = "ScriptForgeHelper.py" + Set Interface = Nothing + Set BrowseNodeFactory = Nothing + Set DatabaseContext = Nothing + Set ConfigurationProvider = Nothing + Set MailService = Nothing + OSName = "" + SFDialogs = Empty +End Sub ' ScriptForge.SF_Root Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + Call Class_Initialize() +End Sub ' ScriptForge.SF_Root Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Call Class_Terminate() + Set Dispose = Nothing +End Function ' ScriptForge.SF_Root Explicit destructor + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Public Sub _AddToConsole(ByVal psLine As String) +''' Add a new line to the console +''' TAB characters are expanded before the insertion of the line +''' NB: Array redimensioning of a member of an object must be done in the class module +''' Args: +''' psLine: the line to add + +Dim lConsole As Long ' UBound of ConsoleLines +Dim sLine As String ' Alias of psLine + + ' Resize ConsoleLines + lConsole = UBound(ConsoleLines) + If lConsole < 0 Then + ReDim ConsoleLines(0) + Else + ReDim Preserve ConsoleLines(0 To lConsole + 1) + End If + + ' Add a timestamp to the line and insert it (without date) + sLine = Mid(SF_Utils._Repr(Now()), 12) & " -> " & psLine + ConsoleLines(lConsole + 1) = Mid(SF_Utils._Repr(Now()), 12) & " -> " & psLine + + ' Add the new line to the actual (probably non-modal) console, if active + If Not IsNull(ConsoleDialog) Then + If ConsoleDialog._IsStillAlive(False) Then ' False to not raise an error + If IsNull(ConsoleControl) Then Set ConsoleControl = ConsoleDialog.Controls(SF_Exception.CONSOLENAME) ' Should not happen ... + ConsoleControl.WriteLine(sLine) + End If + End If + +End Sub ' ScriptForge.SF_Root._AddToConsole + +REM ----------------------------------------------------------------------------- +Public Sub _LoadLocalizedInterface(Optional ByVal psMode As String) +''' Build the user interface in a persistent L10N object +''' Executed - only once - at first ScriptForge invocation by a user script +''' Args: +''' psMode: ADDTEXT => the (english) labels are loaded from code below +''' POFILE => the localized labels are loaded from a PO file +''' the name of the file is "la.po" where la = language part of locale +''' (fallback to ADDTEXT mode if file does not exist) + +Dim sInstallFolder As String ' ScriptForge installation directory +Dim sPOFolder As String ' Folder containing the PO files +Dim sPOFile As String ' PO File to load +Dim sLocale As String ' Locale + + If ErrorHandler Then On Local Error GoTo Catch + +Try: + 'TODO: Modify default value + If IsMissing(psMode) Then psMode = "POFILE" + + If psMode = "POFILE" Then ' Use this mode in production + ' Build the po file name + With SF_FileSystem + sInstallFolder = ._SFInstallFolder() ' ScriptForge installation folder + sLocale = SF_Utils._GetUNOService("Locale").Language + sPOFolder = .BuildPath(sInstallFolder, "po") + sPOFile = .BuildPath(sPOFolder, sLocale & ".po") + If Not .FileExists(sPOFile) Then ' File not found => load texts from code below + psMode = "ADDTEXT" + Else + Set Interface = CreateScriptService("L10N", sPOFolder, sLocale) + End If + End With + End If + + If psMode = "ADDTEXT" Then ' Use this mode in development to prepare a new POT file + Set Interface = CreateScriptService("L10N") + With Interface + ' SF_Exception.Raise + .AddText( Context := "CLOSEBUTTON" _ + , MsgId := "Close" _ + , Comment := "Text in close buttons of progress and console dialog boxes" _ + ) + .AddText( Context := "ERRORNUMBER" _ + , MsgId := "Error %1" _ + , Comment := "Title in error message box\n" _ + & "%1: an error number" _ + ) + .AddText( Context := "ERRORLOCATION" _ + , MsgId := "Location : %1" _ + , Comment := "Error message box\n" _ + & "%1: a line number" _ + ) + .AddText( Context := "LONGERRORDESC" _ + , MsgId := "Error %1 - Location = %2 - Description = %3" _ + , Comment := "Logfile record" _ + ) + .AddText( Context := "STOPEXECUTION" _ + , MsgId := "THE EXECUTION IS CANCELLED." _ + , Comment := "SF_Utils._Validate error message" _ + ) + ' SF_Exception.RaiseAbort + .AddText( Context := "INTERNALERROR" _ + , MsgId := "The ScriptForge library has crashed. The reason is unknown.\n" _ + & "Maybe a bug that could be reported on\n" _ + & "\thttps://bugs.documentfoundation.org/\n\n" _ + & "More details : \n\n" _ + , Comment := "SF_Exception.RaiseAbort error message" _ + ) + ' SF_Utils._Validate + .AddText( Context := "VALIDATESOURCE" _ + , MsgId := "Library : \t%1\nService : \t%2\nMethod : \t%3" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: probably ScriptForge\n" _ + & "%2: service or module name\n" _ + & "%3: property or method name where the error occurred" _ + ) + .AddText( Context := "VALIDATEARGS" _ + , MsgId := "Arguments: %1" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: list of arguments of the method" _ + ) + .AddText( Context := "VALIDATEERROR" _ + , MsgId := "A serious error has been detected in your code on argument : « %1 »." _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name" _ + ) + .AddText( Context := "VALIDATIONRULES" _ + , MsgId := "\tValidation rules :", Comment := "SF_Utils.Validate error message" _ + ) + .AddText( Context := "VALIDATETYPES" _ + , MsgId := "\t\t« %1 » must have next type (or one of next types) : %2" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: Comma separated list of allowed types" _ + ) + .AddText( Context := "VALIDATEVALUES" _ + , MsgId := "\t\t« %1 » must contain one of next values : %2" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: Comma separated list of allowed values" _ + ) + .AddText( Context := "VALIDATEREGEX" _ + , MsgId := "\t\t« %1 » must match next regular expression : %2" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: A regular expression" _ + ) + .AddText( Context := "VALIDATECLASS" _ + , MsgId := "\t\t« %1 » must be a Basic object of class : %2" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: The name of a Basic class" _ + ) + .AddText( Context := "VALIDATEACTUAL" _ + , MsgId := "The actual value of « %1 » is : '%2'" _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: The value of the argument as a string" _ + ) + .AddText( Context := "VALIDATEMISSING" _ + , MsgId := "The « %1 » argument is mandatory, yet it is missing." _ + , Comment := "SF_Utils._Validate error message\n" _ + & "%1: Wrong argument name" _ + ) + ' SF_Utils._ValidateArray + .AddText( Context := "VALIDATEARRAY" _ + , MsgId := "\t\t« %1 » must be an array." _ + , Comment := "SF_Utils._ValidateArray error message\n" _ + & "%1: Wrong argument name" _ + ) + .AddText( Context := "VALIDATEDIMS" _ + , MsgId := "\t\t« %1 » must have exactly %2 dimension(s)." _ + , Comment := "SF_Utils._ValidateArray error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: Number of dimensions of the array" _ + ) + .AddText( Context := "VALIDATEALLTYPES" _ + , MsgId := "\t\t« %1 » must have all elements of the same type : %2" _ + , Comment := "SF_Utils._ValidateArray error message\n" _ + & "%1: Wrong argument name\n" _ + & "%2: Either one single type or 'String, Date, Numeric'" _ + ) + .AddText( Context := "VALIDATENOTNULL" _ + , MsgId := "\t\t« %1 » must not contain any NULL or EMPTY elements." _ + , Comment := "SF_Utils._ValidateArray error message\n" _ + & "%1: Wrong argument name\n" _ + & "NULL and EMPTY should not be translated" _ + ) + ' SF_Utils._ValidateFile + .AddText( Context := "VALIDATEFILE" _ + , MsgId := "\t\t« %1 » must be of type String." _ + , Comment := "SF_Utils._ValidateFile error message\n" _ + & "%1: Wrong argument name\n" _ + & "'String' should not be translated" _ + ) + .AddText( Context := "VALIDATEFILESYS" _ + , MsgId := "\t\t« %1 » must be a valid file or folder name expressed in the operating system native notation." _ + , Comment := "SF_Utils._ValidateFile error message\n" _ + & "%1: Wrong argument name" _ + ) + .AddText( Context := "VALIDATEFILEURL" _ + , MsgId := "\t\t« %1 » must be a valid file or folder name expressed in the portable URL notation." _ + , Comment := "SF_Utils._ValidateFile error message\n" _ + & "%1: Wrong argument name\n" _ + & "'URL' should not be translated" _ + ) + .AddText( Context := "VALIDATEFILEANY" _ + , MsgId := "\t\t« %1 » must be a valid file or folder name." _ + , Comment := "SF_Utils._ValidateFile error message\n" _ + & "%1: Wrong argument name" _ + ) + .AddText( Context := "VALIDATEWILDCARD" _ + , MsgId := "\t\t« %1 » may contain one or more wildcard characters (?, *) in its last path component only." _ + , Comment := "SF_Utils._ValidateFile error message\n" _ + & "%1: Wrong argument name\n" _ + & "'(?, *)' is to be left as is" _ + ) + ' SF_Array.RangeInit + .AddText( Context := "ARRAYSEQUENCE" _ + , MsgId := "The respective values of 'From', 'UpTo' and 'ByStep' are incoherent.\n\n" _ + & "\t« From » = %1\n" _ + & "\t« UpTo » = %2\n" _ + & "\t« ByStep » = %3" _ + , Comment := "SF_Array.RangeInit error message\n" _ + & "%1, %2, %3: Numeric values\n" _ + & "'From', 'UpTo', 'ByStep' should not be translated" _ + ) + ' SF_Array.AppendColumn, AppendRow, PrependColumn, PrependRow + .AddText( Context := "ARRAYINSERT" _ + , MsgId := "The array and the vector to insert have incompatible sizes.\n\n" _ + & "\t« Array_2D » = %2\n" _ + & "\t« %1 » = %3" _ + , Comment := "SF_Array.AppendColumn (...) error message\n" _ + & "%1: 'Column' or 'Row' of a matrix\n" _ + & "%2, %3: array contents\n" _ + & "'Array_2D' should not be translated" _ + ) + ' SF_Array.ExtractColumn, ExtractRow + .AddText( Context := "ARRAYINDEX1" _ + , MsgId := "The given index does not fit within the bounds of the array.\n\n" _ + & "\t« Array_2D » = %2\n" _ + & "\t« %1 » = %3" _ + , Comment := "SF_Array.ExtractColumn (...) error message\n" _ + & "%1: 'Column' or 'Row' of a matrix\n" _ + & "%2, %3: array contents\n" _ + & "'Array_2D' should not be translated" _ + ) + ' SF_Array.ExtractColumn, ExtractRow + .AddText( Context := "ARRAYINDEX2" _ + , MsgId := "The given slice limits do not fit within the bounds of the array.\n\n" _ + & "\t« Array_2D » = %1\n" _ + & "\t« From » = %2\n" _ + & "\t« UpTo » = %3" _ + , Comment := "SF_Array.ExtractColumn (...) error message\n" _ + & "%1: 'Column' or 'Row' of a matrix\n" _ + & "%2, %3: array contents\n" _ + & "'Array_2D', 'From' and 'UpTo' should not be translated" _ + ) + ' SF_Array.ImportFromCSVFile + .AddText( Context := "CSVPARSING" _ + , MsgId := "The given file could not be parsed as a valid CSV file.\n\n" _ + & "\t« File name » = %1\n" _ + & "\tLine number = %2\n" _ + & "\tContent = %3" _ + , Comment := "SF_Array.ImportFromCSVFile error message\n" _ + & "%1: a file name\n" _ + & "%2: numeric\n" _ + & "%3: a long string" _ + ) + ' SF_Dictionary.Add/ReplaceKey + .AddText( Context := "DUPLICATEKEY" _ + , MsgId := "The insertion of a new key " _ + & "into a dictionary failed because the key already exists.\n" _ + & "Note that the comparison between keys is NOT case-sensitive.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Dictionary Add/ReplaceKey error message\n" _ + & "%1: An identifier" _ + & "%2: a (potentially long) string" _ + ) + ' SF_Dictionary.Remove/ReplaceKey/ReplaceItem + .AddText( Context := "UNKNOWNKEY" _ + , MsgId := "The requested key does not exist in the dictionary.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Dictionary Remove/ReplaceKey/ReplaceItem error message\n" _ + & "%1: An identifier" _ + & "%2: a (potentially long) string" _ + ) + ' SF_Dictionary.Add/ReplaceKey + .AddText( Context := "INVALIDKEY" _ + , MsgId := "The insertion or the update of an entry " _ + & "into a dictionary failed because the given key contains only spaces." _ + , Comment := "SF_Dictionary Add/ReplaceKey error message\n" _ + ) + ' SF_FileSystem.CopyFile/MoveFile/DeleteFile/CreateScriptService("L10N") + .AddText( Context := "UNKNOWNFILE" _ + , MsgId := "The given file could not be found on your system.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name" _ + ) + ' SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders + .AddText( Context := "UNKNOWNFOLDER" _ + , MsgId := "The given folder could not be found on your system.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A folder name" _ + ) + ' SF_FileSystem.CopyFile/MoveFolder/DeleteFile + .AddText( Context := "NOTAFILE" _ + , MsgId := "« %1 » contains the name of an existing folder, not that of a file.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name" _ + ) + ' SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders + .AddText( Context := "NOTAFOLDER" _ + , MsgId := "« %1 » contains the name of an existing file, not that of a folder.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A folder name" _ + ) + ' SF_FileSystem.Copy+Move/File+Folder/CreateTextFile/OpenTextFile + .AddText( Context := "OVERWRITE" _ + , MsgId := "You tried to create a new file which already exists. Overwriting it has been rejected.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/... error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name" _ + ) + ' SF_FileSystem.Copy+Move+Delete/File+Folder + .AddText( Context := "READONLY" _ + , MsgId := "Copying or moving a file to a destination which has its read-only attribute set, or deleting such a file or folder is forbidden.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name" _ + ) + ' SF_FileSystem.Copy+Move+Delete/File+Folder + .AddText( Context := "NOFILEMATCH" _ + , MsgId := "When « %1 » contains wildcards. at least one file or folder must match the given filter. Otherwise the operation is rejected.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem copy/move/delete error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file or folder name with wildcards" _ + ) + ' SF_FileSystem.CreateFolder + .AddText( Context := "FOLDERCREATION" _ + , MsgId := "« %1 » contains the name of an existing file or an existing folder. The operation is rejected.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_FileSystem CreateFolder error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file or folder name" _ + ) + ' SF_Services.CreateScriptService + .AddText( Context := "UNKNOWNSERVICE" _ + , MsgId := "No service named '%4' has been registered for the library '%3'.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Services.CreateScriptService error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: A Basic library name\n" _ + & "%4: A service (1 word) name" _ + ) + ' SF_Services.CreateScriptService + .AddText( Context := "SERVICESNOTLOADED" _ + , MsgId := "The library '%3' and its services could not been loaded.\n" _ + & "The reason is unknown.\n" _ + & "However, checking the '%3.SF_Services.RegisterScriptServices()' function and its return value can be a good starting point.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Services.CreateScriptService error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: A Basic library name" _ + ) + ' SF_Session.ExecuteCalcFunction + .AddText( Context := "CALCFUNC" _ + , MsgId := "The Calc '%1' function encountered an error. Either the given function does not exist or its arguments are invalid." _ + , Comment := "SF_Session.ExecuteCalcFunction error message\n" _ + & "'Calc' should not be translated" _ + ) + ' SF_Session._GetScript + .AddText( Context := "NOSCRIPT" _ + , MsgId := "The requested %1 script could not be located in the given libraries and modules.\n" _ + & "« %2 » = %3\n" _ + & "« %4 » = %5" _ + , Comment := "SF_Session._GetScript error message\n" _ + & "%1: 'Basic' or 'Python'\n" _ + & "%2: An identifier\n" _ + & "%3: A string\n" _ + & "%2: An identifier\n" _ + & "%3: A string" _ + ) + ' SF_Session.ExecuteBasicScript + .AddText( Context := "SCRIPTEXEC" _ + , MsgId := "An exception occurred during the execution of the Basic script.\n" _ + & "Cause: %3\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Session.ExecuteBasicScript error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: A (long) string" _ + ) + ' SF_Session.SendMail + .AddText( Context := "WRONGEMAIL" _ + , MsgId := "One of the email addresses has been found invalid.\n" _ + & "Invalid mail = « %1 »" _ + , Comment := "SF_Session.SendMail error message\n" _ + & "%1 = a mail address" _ + ) + ' SF_Session.SendMail + .AddText( Context := "SENDMAIL" _ + , MsgId := "The message could not be sent due to a system error.\n" _ + & "A possible cause is that LibreOffice could not find any mail client." _ + , Comment := "SF_Session.SendMail error message" _ + ) + ' SF_TextStream._IsFileOpen + .AddText( Context := "FILENOTOPEN" _ + , MsgId := "The requested file operation could not be executed because the file was closed previously.\n\n" _ + & "File name = '%1'" _ + , Comment := "SF_TextStream._IsFileOpen error message\n" _ + & "%1: A file name" _ + ) + ' SF_TextStream._IsFileOpen + .AddText( Context := "FILEOPENMODE" _ + , MsgId := "The requested file operation could not be executed because it is incompatible with the mode in which the file was opened.\n\n" _ + & "File name = '%1'\n" _ + & "Open mode = %2" _ + , Comment := "SF_TextStream._IsFileOpen error message\n" _ + & "%1: A file name\n" _ + & "%2: READ, WRITE or APPEND" _ + ) + ' SF_UI.Document + .AddText( Context := "DOCUMENT" _ + , MsgId := "The requested document could not be found.\n\n" _ + & "%1 = '%2'" _ + , Comment := "SF_UI.GetDocument error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string" _ + ) + ' SF_UI.Create + .AddText( Context := "DOCUMENTCREATION" _ + , MsgId := "The creation of a new document failed.\n" _ + & "Something must be wrong with some arguments.\n\n" _ + & "Either the document type is unknown, or no template file was given,\n" _ + & "or the given template file was not found on your system.\n\n" _ + & "%1 = '%2'\n" _ + & "%3 = '%4'" _ + , Comment := "SF_UI.GetDocument error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A string" _ + ) + ' SF_UI.OpenDocument + .AddText( Context := "DOCUMENTOPEN" _ + , MsgId := "The opening of the document failed.\n" _ + & "Something must be wrong with some arguments.\n\n" _ + & "Either the file does not exist, or the password is wrong, or the given filter is invalid.\n\n" _ + & "%1 = '%2'\n" _ + & "%3 = '%4'\n" _ + & "%5 = '%6'" _ + , Comment := "SF_UI.OpenDocument error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A string\n" _ + & "%5: An identifier\n" _ + & "%6: A string" _ + ) + ' SF_UI.OpenBaseDocument + .AddText( Context := "BASEDOCUMENTOPEN" _ + , MsgId := "The opening of the Base document failed.\n" _ + & "Something must be wrong with some arguments.\n\n" _ + & "Either the file does not exist, or the file is not registered under the given name.\n\n" _ + & "%1 = '%2'\n" _ + & "%3 = '%4'" _ + , Comment := "SF_UI.OpenDocument error message\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A string" _ + ) + ' SF_Document._IsStllAlive + .AddText( Context := "DOCUMENTDEAD" _ + , MsgId := "The requested action could not be executed because the document was closed inadvertently.\n\n" _ + & "The concerned document is '%1'" _ + , Comment := "SF_Document._IsStillAlive error message\n" _ + & "%1: A file name" _ + ) + ' SF_Document.Save + .AddText( Context := "DOCUMENTSAVE" _ + , MsgId := "The document could not be saved.\n" _ + & "Either the document has been opened read-only, or the destination file has a read-only attribute set, " _ + & "or the file where to save to is undefined.\n\n" _ + & "%1 = '%2'" _ + , Comment := "SF_Document.SaveAs error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name\n" _ + ) + ' SF_Document.SaveAs + .AddText( Context := "DOCUMENTSAVEAS" _ + , MsgId := "The document could not be saved.\n" _ + & "Either the document must not be overwritten, or the destination file has a read-only attribute set, " _ + & "or the given filter is invalid.\n\n" _ + & "%1 = '%2'\n" _ + & "%3 = %4\n" _ + & "%5 = '%6'" _ + , Comment := "SF_Document.SaveAs error message\n" _ + & "%1: An identifier\n" _ + & "%2: A file name\n" _ + & "%3: An identifier\n" _ + & "%4: True or False\n" _ + & "%5: An identifier\n" _ + & "%6: A string" _ + ) + ' SF_Document.any update + .AddText( Context := "DOCUMENTREADONLY" _ + , MsgId := "You tried to edit a document which is not modifiable. The document has not been changed.\n\n" _ + & "« %1 » = %2" _ + , Comment := "SF_Document any update\n" _ + & "%1: An identifier\n" _ + & "%2: A file name" _ + ) + ' SF_Base.GetDatabase + .AddText( Context := "DBCONNECT" _ + , MsgId := "The database related to the actual Base document could not be retrieved.\n" _ + & "Check the connection/login parameters.\n\n" _ + & "« %1 » = '%2'\n" _ + & "« %3 » = '%4'\n" _ + & "« Document » = %5" _ + , Comment := "SF_Base GetDatabase\n" _ + & "%1: An identifier\n" _ + & "%2: A user name\n" _ + & "%3: An identifier\n" _ + & "%4: A password\n" _ + & "%5: A file name" _ + ) + ' SF_Calc._ParseAddress (sheet) + .AddText( Context := "CALCADDRESS1" _ + , MsgId := "The given address does not correspond with a valid sheet name.\n\n" _ + & "« %1 » = %2\n" _ + & "« %3 » = %4" _ + , Comment := "SF_Calc _ParseAddress (sheet)\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A file name" _ + ) + ' SF_Calc._ParseAddress (range) + .AddText( Context := "CALCADDRESS2" _ + , MsgId := "The given address does not correspond with a valid range of cells.\n\n" _ + & "« %1 » = %2\n" _ + & "« %3 » = %4" _ + , Comment := "SF_Calc _ParseAddress (range)\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A file name" _ + ) + ' SF_Calc.InsertSheet + .AddText( Context := "DUPLICATESHEET" _ + , MsgId := "There exists already in the document a sheet with the same name.\n\n" _ + & "« %1 » = %2\n" _ + & "« %3 » = %4" _ + , Comment := "SF_Calc InsertSheet\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A file name" _ + ) + ' SF_Calc.Offset + .AddText( Context := "OFFSETADDRESS" _ + , MsgId := "The computed range falls beyond the sheet boundaries or is meaningless.\n\n" _ + & "« %1 » = %2\n" _ + & "« %3 » = %4\n" _ + & "« %5 » = %6\n" _ + & "« %7 » = %8\n" _ + & "« %9 » = %10\n" _ + & "« %11 » = %12" _ + , Comment := "SF_Calc Offset\n" _ + & "%1: An identifier\n" _ + & "%2: A Calc reference\n" _ + & "%3: An identifier\n" _ + & "%4: A number\n" _ + & "%5: An identifier\n" _ + & "%6: A number\n" _ + & "%7: An identifier\n" _ + & "%8: A number\n" _ + & "%9: An identifier\n" _ + & "%10: A number\n" _ + & "%11: An identifier\n" _ + & "%12: A file name" _ + ) + ' SF_Dialog._NewDialog + .AddText( Context := "DIALOGNOTFOUND" _ + , MsgId := "The requested dialog could not be located in the given container or library.\n" _ + & "« %1 » = %2\n" _ + & "« %3 » = %4\n" _ + & "« %5 » = %6\n" _ + & "« %7 » = %8" _ + , Comment := "SF_Dialog creation\n" _ + & "%1: An identifier\n" _ + & "%2: A string\n" _ + & "%3: An identifier\n" _ + & "%4: A file name\n" _ + & "%5: An identifier\n" _ + & "%6: A string\n" _ + & "%7: An identifier\n" _ + & "%8: A string" _ + ) + ' SF_Dialog._IsStillAlive + .AddText( Context := "DIALOGDEAD" _ + , MsgId := "The requested action could not be executed because the dialog was closed inadvertently.\n\n" _ + & "The concerned dialog is '%1'." _ + , Comment := "SF_Dialog._IsStillAlive error message\n" _ + & "%1: An identifier" _ + ) + ' SF_DialogControl._SetProperty + .AddText( Context := "CONTROLTYPE" _ + , MsgId := "The control '%1' in dialog '%2' is of type '%3'.\n" _ + & "The property '%4' is not applicable on that type of dialog controls." _ + , Comment := "SF_DialogControl property setting\n" _ + & "%1: An identifier\n" _ + & "%2: An identifier\n" _ + & "%3: A string\n" _ + & "%4: An identifier" _ + ) + ' SF_DialogControl.WriteLine + .AddText( Context := "TEXTFIELD" _ + , MsgId := "The control '%1' in dialog '%2' is not a multiline text field.\n" _ + & "The requested method could not be executed." _ + , Comment := "SF_DialogControl add line in textbox\n" _ + & "%1: An identifier\n" _ + & "%2: An identifier" _ + ) + ' SF_Database.RunSql + .AddText( Context := "DBREADONLY" _ + , MsgId := "The database has been opened in read-only mode.\n" _ + & "The '%1' method must not be executed in this context." _ + , Comment := "SF_Database when running update SQL statement\n" _ + & "%1: The concerned method" _ + ) + ' SF_Database._ExecuteSql + .AddText( Context := "SQLSYNTAX" _ + , MsgId := "An SQL statement could not be interpreted or executed by the database system.\n" _ + & "Check its syntax, table and/or field names, ...\n\n" _ + & "SQL Statement : « %1 »" _ + , Comment := "SF_Database cannot interprete SQL statement\n" _ + & "%1: The statement" _ + ) + End With + End If + +Finally: + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Root._LoadLocalizedInterface + +REM ----------------------------------------------------------------------------- +Public Function _Repr() As String +''' Convert the unique SF_Root instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[Root] (MainFunction: xxx, Console: yyy lines, ServicesList)" + +Dim sRoot As String ' Return value +Const cstRoot = "[Root] (" + + sRoot = cstRoot & "MainFunction: " & MainFunction & ", Console: " & UBound(ConsoleLines) + 1 & " lines" _ + & ", Libraries:" & SF_Utils._Repr(ServicesList.Keys) _ + & ")" + + _Repr = sRoot + +End Function ' ScriptForge.SF_Root._Repr + +REM ----------------------------------------------------------------------------- +Public Sub _StackReset() +''' Reset private members after a fatal/abort error to leave +''' a stable persistent storage after an unwanted interrupt + + MainFunction = "" + MainFunctionArgs = "" + StackLevel = 0 + +End Sub ' ScriptForge.SF_Root._StackReset + +REM ================================================== END OF SCRIPTFORGE.SF_ROOT +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Services.xba b/wizards/source/scriptforge/SF_Services.xba new file mode 100644 index 000000000000..be62730838b9 --- /dev/null +++ b/wizards/source/scriptforge/SF_Services.xba @@ -0,0 +1,607 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Services" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Services +''' =========== +''' Singleton class implementing the "ScriptForge.Services" service +''' Implemented as a usual Basic module +''' The ScriptForge framework includes +''' the current ScriptForge library +''' a number of "associated" libraries +''' any user/contributor extension wanting to fit into the framework +''' The methods in this module constitute the kernel of the ScriptForge framework +''' - RegisterScriptServices +''' Register for a library the list of services it implements +''' Each library in the framework must implement its own RegisterScriptServices method +''' This method consists in a series of invocations of next 2 methods +''' - ReisterService +''' Register a single service +''' - RegisterEventManager +''' Register a single event manager +''' - CreateScriptService +''' Called by user scripts to get an object giving access to a service or to the event manager +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const UNKNOWNSERVICEERROR = "UNKNOWNSERVICEERROR" ' Service not found within the registered services of the given library +Const SERVICESNOTLOADEDERROR = "SERVICESNOTLOADEDERROR" ' Failure during the registering of the services of the given library +Const UNKNOWNFILEERROR = "UNKNOWNFILEERROR" ' Source file does not exist + +REM ============================================================== PUBLIC MEMBERS + +' Defines an entry in in the services dictionary +Type _Service + ServiceName As String + ServiceType As Integer + ' 0 Undefined + ' 1 Basic module + ' 2 Method reference as a string + ServiceReference As Object + ServiceMethod As String + EventManager As Boolean ' True if registered item is an event manager +End Type + +Private vServicesArray As Variant ' List of services registered by a library + +REM ============================================================== PUBLIC METHODS + +REM ----------------------------------------------------------------------------- +Public Function CreateScriptService(Optional ByRef Service As Variant _ + , ParamArray pvArgs As Variant _ + ) As Variant +''' Create access to the services of a library for the benefit of a user script +''' A service is to understand either: +''' as a set of methods gathered in a Basic standard module +''' or a set of methods and properties gathered in a Basic class module +''' Args: +''' Service: the name of the service in 2 parts "library.service" +''' The library is a Basic library that must exist in the GlobalScope +''' (default = "ScriptForge") +''' The service is one of the services registered by the library +''' thru the RegisterScriptServices() routine +''' pvArgs: a set of arguments passed to the constructor of the service +''' This is only possible if the service refers to a Basic class module +''' Returns +''' The object containing either the reference of the Basic module +''' or of the Basic class instance +''' Both are Basic objects +''' Returns Nothing if an error occurred. +''' ==>> NOTE: The error can be within the user script creating the new class instance +''' Exceptions: +''' SERVICESNOTLOADEDERROR RegisterScriptService probable failure +''' UNKNOWNSERVICEERROR Service not found +''' Examples +''' CreateScriptService("Array") +''' => Refers to ScriptForge.Array or SF_Array +''' CreateScriptService("ScriptForge.Dictionary") +''' => Returns a new empty dictionary; "ScriptForge." is optional +''' CreateScriptService("SFDocuments.Calc") +''' => Refers to the Calc service, implemented in the SFDocuments library +''' CreateScriptService("Dialog", dlgName) +''' => Returns a Dialog instance referring to the dlgName dialog +''' CreateScriptService("SFDocuments.Event", oEvent) +''' => Refers to the Document service instance, implemented in the SFDocuments library, having triggered the event + +Dim vScriptService As Variant ' Return value +Dim vServiceItem As Variant ' A single service (see _Service type definition) +Dim vServicesList As Variant ' Output of RegisterScriptServices +Dim vSplit As Variant ' Array to split argument in +Dim sLibrary As String ' Library part of the argument +Dim sService As String ' Service part of the argument +Dim vLibrary As variant ' Dictionary of libraries +Dim vService As Variant ' An individual service object +Const cstThisSub = "SF_Services.CreateScriptService" +Const cstSubArgs = "Service, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set vScriptService = Nothing + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Service, "Service", V_STRING) Then GoTo Catch + If Len(Service) = 0 Then GoTo CatchNotFound + End If + +Try: + ' Initialize the list of services when CreateScriptService called for the very 1st time + If IsEmpty(_SF_.ServicesList) Then _SF_.ServicesList = SF_Services._NewDictionary() + + ' Simple parsing of argument + vSplit = Split(Service, ".") + If UBound(vSplit) > 1 Then GoTo CatchNotFound + If UBound(vSplit) = 0 Then + sLibrary = "ScriptForge" ' Yes, the default value ! + sService = vSplit(0) + ' Accept other default values for associated libraries + Select Case sService + Case "Document", "Calc", "Base" : sLibrary = "SFDocuments" + Case "Dialog", "DialogEvent" : sLibrary = "SFDialogs" + Case "Database" : sLibrary = "SFDatabases" + Case Else + End Select + Else + sLibrary = vSplit(0) + sService = vSplit(1) + End If + + With _SF_.ServicesList + + ' Load the set of services from the library, if not yet done + If Not .Exists(sLibrary) Then + If Not SF_Services._LoadLibraryServices(sLibrary) Then GoTo CatchNotLoaded + End If + + ' Find and return the requested service + vServicesList = .Item(sLibrary) + If Not vServicesList.Exists(sService) Then GoTo CatchNotFound + vServiceItem = vServicesList.Item(sService) + Select Case vServiceItem.ServiceType + Case 1 ' Basic module + vScriptService = vServiceItem.ServiceReference + Case 2 ' Method to call + If sLibrary = "ScriptForge" Then ' Direct call + Select Case UCase(sService) + Case "DICTIONARY" : vScriptService = SF_Services._NewDictionary() + Case "L10N" : vScriptService = SF_Services._NewL10N(pvArgs) + Case "TIMER" : vScriptService = SF_Services._NewTimer(pvArgs) + Case Else + End Select + Else ' Call via script provider + Set vService = SF_Session._GetScript("Basic", SF_Session.SCRIPTISAPPLICATION, vServiceItem.ServiceMethod) + vScriptService = vService.Invoke(Array(pvArgs()), Array(), Array()) + End If + Case Else + End Select + + End With + +Finally: + CreateScriptService = vScriptService + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotFound: + SF_Exception.RaiseFatal(UNKNOWNSERVICEERROR, "Service", Service, sLibrary, sService) + GoTo Finally +CatchNotLoaded: + SF_Exception.RaiseFatal(SERVICESNOTLOADEDERROR, "Service", Service, sLibrary) + GoTo Finally +End Function ' ScriptForge.SF_Services.CreateScriptService + +REM ----------------------------------------------------------------------------- +Public Function RegisterEventManager(Optional ByVal ServiceName As Variant _ + , Optional ByRef ServiceReference As Variant _ + ) As Boolean +''' Register into ScriptForge a new event entry for the library +''' from which this method is called +''' MUST BE CALLED ONLY from a specific RegisterScriptServices() method +''' Usually the method should be called only once by library +''' Args: +''' ServiceName: the name of the service as a string. It the service exists +''' already for the library the method overwrites the existing entry +''' ServiceReference: the finction which will identify the source of the triggered event +''' something like: "libraryname.modulename.function" +''' Returns: +''' True if successful +''' Example: +''' ' Code snippet stored in a module contained in the SFDocuments library +''' Sub RegisterScriptServices() +''' ' Register the events manager of the library +''' RegisterEventManager("DocumentEvent", "SFDocuments.SF_Register._EventManager") +''' End Sub +''' ' Code snippet stored in a user script +''' Sub Trigger(poEvent As Object) ' Triggered by a DOCUMENTEVENT event +''' Dim myDoc As Object +''' ' To get the document concerned by the event: +''' Set myDoc = CreateScriptService("SFDocuments.DocumentEvent", poEvent) +''' End Sub + +Dim bRegister As Boolean ' Return value +Const cstThisSub = "SF_Services.RegisterEventManager" +Const cstSubArgs = "ServiceName, ServiceReference" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bRegister = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(ServiceName, "ServiceName", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(ServiceReference, "ServiceReference",V_STRING) Then GoTo Finally + End If + +Try: + bRegister = _AddToServicesArray(ServiceName, ServiceReference, True) + +Finally: + RegisterEventManager = bRegister + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Services.RegisterEventManager + +REM ----------------------------------------------------------------------------- +Public Function RegisterService(Optional ByVal ServiceName As Variant _ + , Optional ByRef ServiceReference As Variant _ + ) As Boolean +''' Register into ScriptForge a new service entry for the library +''' from which this method is called +''' MUST BE CALLED ONLY from a specific RegisterScriptServices() method +''' Args: +''' ServiceName: the name of the service as a string. It the service exists +''' already for the library the method overwrites the existing entry +''' ServiceReference: either +''' - the Basic module that implements the methods of the service +''' something like: GlobalScope.Library.Module +''' - an instance of the class implementing the methods and properties of the service +''' something like: "libraryname.modulename.function" +''' Returns: +''' True if successful + +Dim bRegister As Boolean ' Return value +Const cstThisSub = "SF_Services.RegisterService" +Const cstSubArgs = "ServiceName, ServiceReference" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bRegister = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(ServiceName, "ServiceName", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(ServiceReference, "ServiceReference", Array(V_STRING, V_OBJECT)) Then GoTo Finally + End If + +Try: + bRegister = _AddToServicesArray(ServiceName, ServiceReference, False) + +Finally: + RegisterService = bRegister + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Services.RegisterService + +REM ----------------------------------------------------------------------------- +Public Sub RegisterScriptServices() As Variant +''' Register into ScriptForge the list of the services implemented by the current library +''' Each library pertaining to the framework must implement its own version of this method +''' This method may be stored in any standard (i.e. not class-) module +''' +''' Each individual service is reistered by calling the RegisterService() method +''' +''' The current version is given as an example +''' + With GlobalScope.ScriptForge.SF_Services + .RegisterService("Array", GlobalScope.ScriptForge.SF_Array) ' Reference to the Basic module + .RegisterService("Dictionary", "ScriptForge.SF_Services._NewDictionary") ' Reference to the function initializing the service + .RegisterService("Exception", GlobalScope.ScriptForge.SF_Exception) + .RegisterService("FileSystem", GlobalScope.ScriptForge.SF_FileSystem) + .RegisterService("L10N", "ScriptForge.SF_Services._NewL10N") + .RegisterService("Platform", GlobalScope.ScriptForge.SF_Platform) + .RegisterService("Session", GlobalScope.ScriptForge.SF_Session) + .RegisterService("String", GlobalScope.ScriptForge.SF_String) + .RegisterService("Timer", "ScriptForge.SF_Services._NewTimer") + .RegisterService("UI", GlobalScope.ScriptForge.SF_UI) + 'TODO + End With + +End Sub ' ScriptForge.SF_Services.RegisterScriptServices + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _AddToServicesArray(ByVal psServiceName As String _ + , ByRef pvServiceReference As Variant _ + , ByVal pbEvent As Boolean _ + ) As Boolean +''' Add the arguments as an additional row in vServicesArray (Public variable) +''' Called from RegisterService and RegisterEvent methods + +Dim bRegister As Boolean ' Return value +Dim lMax As Long ' Number of rows in vServicesArray + + bRegister = False + +Check: + ' Ignore when method is not called from RegisterScriptServices() + If IsEmpty(vServicesArray) Or IsNull(vServicesArray) Or Not IsArray(vServicesArray) Then GoTo Finally + +Try: + lMax = UBound(vServicesArray, 1) + 1 + If lMax <= 0 Then + ReDim vServicesArray(0 To 0, 0 To 2) + Else + ReDim Preserve vServicesArray(0 To lMax, 0 To 2) + End If + vServicesArray(lMax, 0) = psServiceName + vServicesArray(lMax, 1) = pvServiceReference + vServicesArray(lMax, 2) = pbEvent + bRegister = True + +Finally: + _AddToServicesArray = bRegister + Exit Function +End Function ' ScriptForge.SF_Services._AddToServicesArray + +REM ----------------------------------------------------------------------------- +Private Function _FindModuleFromMethod(ByVal psLibrary As String _ + , ByVal psMethod As String _ + ) As String +''' Find in the given library the name of the module containing +''' the method given as 2nd argument (usually RegisterScriptServices) +''' Args: +''' psLibrary: the name of the Basic library +''' psMethod: the method to locate +''' Returns: +''' The name of the module or a zero-lengt string if not found + +Dim vCategories As Variant ' "user" or "share" library categories +Dim sCategory As String +Dim vLanguages As Variant ' "Basic", "Python", ... programming languages +Dim sLanguage As String +Dim vLibraries As Variant ' Library names +Dim sLibrary As String +Dim vModules As Variant ' Module names +Dim sModule As String ' Return value +Dim vMethods As Variant ' Method/properties/subs/functions +Dim sMethod As String +Dim oRoot As Object ' com.sun.star.script.browse.BrowseNodeFactory +Dim i As Integer, j As Integer, k As Integer, l As Integer, m As Integer + + _FindModuleFromMethod = "" + Set oRoot = SF_Utils._GetUNOService("BrowseNodeFactory").createView(com.sun.star.script.browse.BrowseNodeFactoryViewTypes.MACROORGANIZER) + + ' Exploration is done via tree nodes + If Not IsNull(oRoot) Then + If oRoot.hasChildNodes() Then + vCategories = oRoot.getChildNodes() + For i = 0 To UBound(vCategories) + sCategory = vCategories(i).getName() + ' Consider "My macros & Dialogs" and "LibreOffice Macros & Dialogs" only + If sCategory = "user" Or sCategory = "share" Then + If vCategories(i).hasChildNodes() Then + vLanguages = vCategories(i).getChildNodes() + For j = 0 To UBound(vLanguages) + sLanguage = vLanguages(j).getName() + ' Consider Basic libraries only + If sLanguage = "Basic" Then + If vLanguages(j).hasChildNodes() Then + vLibraries = vLanguages(j).getChildNodes() + For k = 0 To UBound(vLibraries) + sLibrary = vLibraries(k).getName() + ' Consider the given library only + If sLibrary = psLibrary Then + If vLibraries(k).hasChildNodes() Then + vModules = vLibraries(k).getChildNodes() + For l = 0 To UBound(vModules) + sModule = vModules(l).getName() + ' Check if the module contains the targeted method + If vModules(l).hasChildNodes() Then + vMethods = vModules(l).getChildNodes() + For m = 0 To UBound(vMethods) + sMethod = vMethods(m).getName() + If sMethod = psMethod Then + _FindModuleFromMethod = sModule + Exit Function + End If + Next m + End If + Next l + End If + End If + Next k + End If + End If + Next j + End If + End If + Next i + End If + End If + +End Function ' ScriptForge.SF_Services._FindModuleFromMethod + +REM ----------------------------------------------------------------------------- +Private Function _LoadLibraryServices(ByVal psLibrary As String) As Boolean +''' Execute psLibrary.RegisterScriptServices() and load its services into the persistent storage +''' Args: +''' psLibrary: the name of the Basic library +''' Library will be loaded if not yet done +''' Returns: +''' True if success +''' The list of services is loaded directly into the persistent storage + + +Dim vServicesList As Variant ' Dictionary of services +Dim vService As Variant ' Single service entry in dictionary +Dim vServiceItem As Variant ' Single service in vServicesArray +Dim sModule As String ' Name of module containing the RegisterScriptServices method +Dim i As Long +Const cstRegister = "RegisterScriptServices" + +Try: + _LoadLibraryServices = False + + vServicesArray = Array() + + If psLibrary = "ScriptForge" Then + ' Direct call + ScriptForge.SF_Services.RegisterScriptServices() + Else + ' Register services via script provider + If GlobalScope.BasicLibraries.hasByName(psLibrary) Then + If Not GlobalScope.BasicLibraries.isLibraryLoaded(psLibrary) Then + GlobalScope.BasicLibraries.LoadLibrary(psLibrary) + End If + Else + GoTo Finally + End If + sModule = SF_Services._FindModuleFromMethod(psLibrary, cstRegister) + If Len(sModule) = 0 Then GoTo Finally + SF_Session.ExecuteBasicScript(, psLibrary & "." & sModule & "." & cstRegister) + End If + + ' Store in persistent storage + ' - Create list of services for the current library + Set vServicesList = SF_Services._NewDictionary() + For i = 0 To UBound(vServicesArray, 1) + Set vService = New _Service + With vService + .ServiceName = vServicesArray(i, 0) + vServiceItem = vServicesArray(i, 1) + If VarType(vServiceItem) = V_STRING Then + .ServiceType = 2 + .ServiceMethod = vServiceItem + Set .ServiceReference = Nothing + Else ' OBJECT + .ServiceType = 1 + .ServiceMethod = "" + Set .ServiceReference = vServiceItem + End If + .EventManager = vServicesArray(i, 2) + End With + vServicesList.Add(vServicesArray(i, 0), vService) + Next i + ' - Add the new dictionary to the persistent dictionary + _SF_.ServicesList.Add(psLibrary, vServicesList) + _LoadLibraryServices = True + vServicesArray = Empty + +Finally: + Exit Function +End Function ' ScriptForge.SF_Services._LoadLibraryServices + +REM ----------------------------------------------------------------------------- +Public Function _NewDictionary() As Variant +''' Create a new instance of the SF_Dictionary class +''' Returns: the instance or Nothing + +Dim oDict As Variant + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + +Try: + Set oDict = New SF_Dictionary + Set oDict.[Me] = oDict + +Finally: + Set _NewDictionary = oDict + Exit Function +Catch: + Set oDict = Nothing + GoTo Finally +End Function ' ScriptForge.SF_Services._NewDictionary + +REM ----------------------------------------------------------------------------- +Public Function _NewL10N(Optional ByVal pvArgs As Variant) As Variant +''' Create a new instance of the SF_L10N class +' Args: +''' FolderName: the folder containing the PO files in SF_FileSystem.FileNaming notation +''' Locale: locale of user session (default) or any other valid la{nguage]-CO[UNTRY] combination +''' The country part is optional. Valid are f.i. "fr", "fr-CH", "en-US" +''' Encoding: The character set that should be used +''' Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that LibreOffice probably does not implement all existing sets +''' Default = UTF-8 +''' Returns: the instance or Nothing +''' Exceptions: +''' UNKNOWNFILEERROR The PO file does not exist + +Dim oL10N As Variant ' Return value +Dim sFolderName As String ' Folder containing the PO files +Dim sLocale As String ' Passed argument or that of the user session +Dim oLocale As Variant ' com.sun.star.lang.Locale +Dim sPOFile As String ' PO file must exist +Dim sEncoding As String ' Alias for Encoding + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(pvArgs) Then pvArgs = Array() + If UBound(pvArgs) < 0 Then + sPOFile = "" + sEncoding = "" + Else + If Not SF_Utils._ValidateFile(pvArgs(0), "Folder (Arg0)") Then GoTo Catch + sFolderName = pvArgs(0) + If UBound(pvArgs) >= 1 Then + If Not SF_Utils._Validate(pvArgs(1), "Locale (Arg1)", V_STRING) Then GoTo Catch + sLocale = pvArgs(1) + Else + Set oLocale = SF_Utils._GetUNOService("Locale") + sLocale = oLocale.Language & "-" & oLocale.Country + End If + If UBound(pvArgs) >= 2 Then + If Not SF_Utils._Validate(pvArgs(2), "Encoding (Arg2)", V_STRING) Then GoTo Catch + sEncoding = pvArgs(2) + Else + sEncoding = "UTF-8" + End If + sPOFile = SF_FileSystem.BuildPath(sFolderName, sLocale & ".po") + If Not SF_FileSystem.FileExists(sPOFile) Then GoTo CatchNotExists + End If + +Try: + Set oL10N = New SF_L10N + Set oL10N.[Me] = oL10N + oL10N._Initialize(sPOFile, sEncoding) + +Finally: + Set _NewL10N = oL10N + Exit Function +Catch: + Set oL10N = Nothing + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", sPOFile) + GoTo Finally +End Function ' ScriptForge.SF_Services._NewL10N + +REM ----------------------------------------------------------------------------- +Public Function _NewTimer(Optional ByVal pvArgs As Variant) As Variant +''' Create a new instance of the SF_Timer class +''' Args: +''' [0] : If True, start the timer immediately +''' Returns: the instance or Nothing + +Dim oTimer As Variant ' Return value +Dim bStart As Boolean ' Automatic start ? + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(pvArgs) Then pvArgs = Array() + If UBound(pvArgs) < 0 Then + bStart = False + Else + If Not SF_Utils._Validate(pvArgs(0), "Start (Arg0)", V_BOOLEAN) Then GoTo Catch + bStart = pvArgs(0) + End If +Try: + Set oTimer = New SF_Timer + Set oTimer.[Me] = oTimer + If bStart Then oTimer.Start() + +Finally: + Set _NewTimer = oTimer + Exit Function +Catch: + Set oTimer = Nothing + GoTo Finally +End Function ' ScriptForge.SF_Services._NewTimer + +REM ============================================== END OF SCRIPTFORGE.SF_SERVICES +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Session.xba b/wizards/source/scriptforge/SF_Session.xba new file mode 100644 index 000000000000..19117bc5796e --- /dev/null +++ b/wizards/source/scriptforge/SF_Session.xba @@ -0,0 +1,918 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Session" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Session +''' ========== +''' Singleton class implementing the "ScriptForge.Session" service +''' Implemented as a usual Basic module +''' +''' Gathers diverse general-purpose properties and methods about : +''' - installation/execution environment +''' - UNO introspection utilities +''' - clipboard management +''' - invocation of external scripts or programs +''' +''' Service invocation example: +''' Dim session As Variant +''' session = CreateScriptService("Session") +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const CALCFUNCERROR = "CALCFUNCERROR" ' Calc function execution failed +Const NOSCRIPTERROR = "NOSCRIPTERROR" ' Script could not be located +Const SCRIPTEXECERROR = "SCRIPTEXECERROR" ' Exception during script execution +Const WRONGEMAILERROR = "WRONGEMAILERROR" ' Wrong email address +Const SENDMAILERROR = "SENDMAILERROR" ' Mail could not be sent +Const UNKNOWNFILEERROR = "UNKNOWNFILEERROR" ' Source file does not exist + +REM ============================================================ MODULE CONSTANTS + +''' Script locations +''' ================ +''' Use next constants as Scope argument when invoking next methods: +''' ExecuteBasicScript() +''' ExecutePythonScript() +''' Example: +''' session.ExecuteBasicScript(session.SCRIPTISEMBEDDED, "Standard.myLib.myFunc", etc) + +Const cstSCRIPTISEMBEDDED = "document" ' a library of the document (BASIC + PYTHON) +Const cstSCRIPTISAPPLICATION = "application" ' a shared library (BASIC) +Const cstSCRIPTISPERSONAL = "user" ' a library of My Macros (PYTHON) +Const cstSCRIPTISPERSOXT = "user:uno_packages" ' an extension for the current user (PYTHON) +Const cstSCRIPTISSHARED = "share" ' a library of LibreOffice Macros (PYTHON) +Const cstSCRIPTISSHAROXT = "share:uno_packages" ' an extension for all users (PYTHON) +Const cstSCRIPTISOXT = "uno_packages" ' an extension but install params are unknown (PYTHON) + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_Array Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_Session" +End Property ' ScriptForge.SF_Session.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.Session" +End Property ' ScriptForge.SF_Array.ServiceName + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISAPPLICATION As String +''' Convenient constants + SCRIPTISAPPLICATION = cstSCRIPTISAPPLICATION +End Property ' ScriptForge.SF_Session.SCRIPTISAPPLICATION + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISEMBEDDED As String +''' Convenient constants + SCRIPTISEMBEDDED = cstSCRIPTISEMBEDDED +End Property ' ScriptForge.SF_Session.SCRIPTISEMBEDDED + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISOXT As String +''' Convenient constants + SCRIPTISOXT = cstSCRIPTISOXT +End Property ' ScriptForge.SF_Session.SCRIPTISOXT + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISPERSONAL As String +''' Convenient constants + SCRIPTISPERSONAL = cstSCRIPTISPERSONAL +End Property ' ScriptForge.SF_Session.SCRIPTISPERSONAL + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISPERSOXT As String +''' Convenient constants + SCRIPTISPERSOXT = cstSCRIPTISPERSOXT +End Property ' ScriptForge.SF_Session.SCRIPTISPERSOXT + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISSHARED As String +''' Convenient constants + SCRIPTISSHARED = cstSCRIPTISSHARED +End Property ' ScriptForge.SF_Session.SCRIPTISSHARED + +REM ----------------------------------------------------------------------------- +Property Get SCRIPTISSHAROXT As String +''' Convenient constants + SCRIPTISSHAROXT = cstSCRIPTISSHAROXT +End Property ' ScriptForge.SF_Session.SCRIPTISSHAROXT + +REM ============================================================== PUBLIC METHODS + +REM ----------------------------------------------------------------------------- +Public Function ExecuteBasicScript(Optional ByVal Scope As Variant _ + , Optional ByVal Script As Variant _ + , ParamArray pvArgs As Variant _ + ) As Variant +''' Execute the Basic script given as a string and return the value returned by the script +''' Args: +''' Scope: "Application" (default) or "Document" (NOT case-sensitive) +''' (or use one of the SCRIPTIS... public constants above) +''' Script: library.module.method (Case sensitive) +''' library => The library may be not loaded yet +''' module => Must not be a class module +''' method => Sub or Function +''' Read https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification +''' pvArgs: the arguments of the called script +''' Returns: +''' The value returned by the call to the script +''' Exceptions: +''' NOSCRIPTERROR The script could not be found +''' Examples: +''' session.ExecuteBasicScript(, "XrayTool._Main.Xray", someuno) ' Sub: no return expected + +Dim oScript As Object ' Script to be invoked +Dim vReturn As Variant ' Returned value + +Const cstThisSub = "Session.ExecuteBasicScript" +Const cstSubArgs = "[Scope], Script, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + vReturn = Empty + +Check: + If IsMissing(Scope) Or IsEmpty(Scope) Then Scope = SCRIPTISAPPLICATION + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Scope, "Scope", V_STRING _ + , Array(SCRIPTISAPPLICATION, SCRIPTISEMBEDDED)) Then GoTo Finally + If Not SF_Utils._Validate(Script, "Script", V_STRING) Then GoTo Finally + End If + +Try: + ' Execute script + Set oScript = SF_Session._GetScript("Basic", Scope, Script) + On Local Error GoTo CatchExec + If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs(), Array(), Array()) + +Finally: + ExecuteBasicScript = vReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchExec: + SF_Exception.RaiseFatal(SCRIPTEXECERROR, "Script", Script, Error$) + GoTo Finally +End Function ' ScriptForge.SF_Session.ExecuteBasicScript + +REM ----------------------------------------------------------------------------- +Public Function ExecuteCalcFunction(Optional ByVal CalcFunction As Variant _ + , ParamArray pvArgs As Variant _ + ) As Variant +''' Execute a Calc function by its (english) name and based on the given arguments +''' Args: +''' CalcFunction: the english name of the function to execute +''' pvArgs: the arguments of the called function +''' Each argument must be either a string, a numeric value +''' or an array of arrays combining those types +''' Returns: +''' The (string or numeric) value or the array of arrays returned by the call to the function +''' When the arguments contain arrays, the function is executed as an array function +''' Wrong arguments generate an error +''' Exceptions: +''' CALCFUNCERROR ' Execution error in calc function +''' Examples: +''' session.ExecuteCalcFunction("AVERAGE", 1, 5, 3, 7) returns 4 +''' session.ExecuteCalcFunction("ABS", Array(Array(-1,2,3),Array(4,-5,6),Array(7,8,-9)))(2)(2) returns 9 +''' session.ExecuteCalcFunction("LN", -3) generates an error + +Dim oCalc As Object ' Give access to the com.sun.star.sheet.FunctionAccess service +Dim vReturn As Variant ' Returned value +Const cstThisSub = "Session.ExecuteCalcFunction" +Const cstSubArgs = "CalcFunction, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vReturn = Empty + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(CalcFunction, "CalcFunction", V_STRING) Then GoTo Finally + End If + +Try: + ' Execute function + Set oCalc = SF_Utils._GetUNOService("FunctionAccess") + On Local Error GoTo CatchCall + vReturn = oCalc.callFunction(UCase(CalcFunction), pvArgs()) + +Finally: + ExecuteCalcFunction = vReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchCall: + SF_Exception.RaiseFatal(CALCFUNCERROR, CalcFunction) + GoTo Finally +End Function ' ScriptForge.SF_Session.ExecuteCalcFunction + +REM ----------------------------------------------------------------------------- +Public Function ExecutePythonScript(Optional ByVal Scope As Variant _ + , Optional ByVal Script As Variant _ + , ParamArray pvArgs As Variant _ + ) As Variant +''' Execute the Python script given as a string and return the value returned by the script +''' Args: +''' Scope: one of the SCRIPTIS... public constants above (default = "share") +''' Script: (Case sensitive) +''' "library/module.py$method" +''' or "module.py$method" +''' or "myExtension.oxt|myScript|module.py$method" +''' library => The library may be not loaded yet +''' myScript => The directory containing the python module +''' module.py => The python module +''' method => The python function +''' Read https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification +''' pvArgs: the arguments of the called script +''' Date arguments are converted to iso format. However dates in arrays are not converted +''' Returns: +''' The value(s) returned by the call to the script. If >1 values, enclosed in an array +''' Exceptions: +''' NOSCRIPTERROR The script could not be found +''' Examples: +''' session.ExecutePythonScript(session.SCRIPTISSHARED, "Capitalise.py$getNewString", "Abc") returns "abc" + +Dim oScript As Object ' Script to be invoked +Dim vArg As Variant ' Individual argument +Dim vReturn As Variant ' Returned value +Dim i As Long + +Const cstThisSub = "Session.ExecutePythonScript" +Const cstSubArgs = "[Scope], Script, arg0[, arg1] ..." + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + + vReturn = Empty + +Check: + If IsError(Scope) Or IsMissing(Scope) Then Scope = SCRIPTISSHARED + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Scope, "Scope", V_STRING _ + , Array(SCRIPTISSHARED, SCRIPTISEMBEDDED, SCRIPTISPERSONAL, SCRIPTISSHAROXT, SCRIPTISPERSOXT, SCRIPTISOXT) _ + ) Then GoTo Finally + If Not SF_Utils._Validate(Script, "Script", V_STRING) Then GoTo Finally + End If + +Try: + ' Filter date arguments - NB: dates in arrays are not filtered + For i = 0 To UBound(pvArgs) ' pvArgs always zero-based + vArg = pvArgs(i) + If VarType(vArg) = V_DATE Then pvArgs(i) = SF_Utils._CDateToIso(vArg) + Next i + + ' Find script + Set oScript = SF_Session._GetScript("Python", Scope, Script) + + ' Execute script + If Not IsNull(oScript) Then + vReturn = oScript.Invoke(pvArgs(), Array(), Array()) + ' Remove surrounding array when single returned value + If IsArray(vReturn) Then + If UBound(vReturn) = 0 Then vReturn = vReturn(0) + End If + End If + +Finally: + ExecutePythonScript = vReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.ExecutePythonScript + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Session.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function HasUnoMethod(Optional ByRef UnoObject As Variant _ + , Optional ByVal MethodName As Variant _ + ) As Boolean +''' Returns True if a UNO object contains the given method +''' Code-snippet derived from XRAY +''' Args: +''' UnoObject: the object to identify +''' MethodName: the name of the method as a string. The search is case-sensitive +''' Returns: +''' False when the method is not found or when an argument is invalid + +Dim oIntrospect As Object ' com.sun.star.beans.Introspection +Dim oInspect As Object ' com.sun.star.beans.XIntrospectionAccess +Dim bMethod As Boolean ' Return value +Const cstThisSub = "Session.HasUnoMethod" +Const cstSubArgs = "UnoObject, MethodName" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Check: + bMethod = False + If VarType(UnoObject) <> V_OBJECT Then GoTo Finally + If IsNull(UnoObject) Then GoTo Finally + If VarType(MethodName) <> V_STRING Then GoTo Finally + If MethodName = Space(Len(MethodName)) Then GoTo Finally + +Try: + On Local Error GoTo Catch + Set oIntrospect = SF_Utils._GetUNOService("Introspection") + Set oInspect = oIntrospect.inspect(UnoObject) + bMethod = oInspect.hasMethod(MethodName, com.sun.star.beans.MethodConcept.ALL) + +Finally: + HasUnoMethod = bMethod + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + On Local Error GoTo 0 + GoTo Finally +End Function ' ScriptForge.SF_Session.HasUnoMethod + +REM ----------------------------------------------------------------------------- +Public Function HasUnoProperty(Optional ByRef UnoObject As Variant _ + , Optional ByVal PropertyName As Variant _ + ) As Boolean +''' Returns True if a UNO object contains the given property +''' Code-snippet derived from XRAY +''' Args: +''' UnoObject: the object to identify +''' PropertyName: the name of the property as a string. The search is case-sensitive +''' Returns: +''' False when the property is not found or when an argument is invalid + +Dim oIntrospect As Object ' com.sun.star.beans.Introspection +Dim oInspect As Object ' com.sun.star.beans.XIntrospectionAccess +Dim bProperty As Boolean ' Return value +Const cstThisSub = "Session.HasUnoProperty" +Const cstSubArgs = "UnoObject, PropertyName" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Check: + bProperty = False + If VarType(UnoObject) <> V_OBJECT Then GoTo Finally + If IsNull(UnoObject) Then GoTo Finally + If VarType(PropertyName) <> V_STRING Then GoTo Finally + If PropertyName = Space(Len(PropertyName)) Then GoTo Finally + +Try: + On Local Error GoTo Catch + Set oIntrospect = SF_Utils._GetUNOService("Introspection") + Set oInspect = oIntrospect.inspect(UnoObject) + bProperty = oInspect.hasProperty(PropertyName, com.sun.star.beans.PropertyConcept.ALL) + +Finally: + HasUnoProperty = bProperty + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + On Local Error GoTo 0 + GoTo Finally +End Function ' ScriptForge.SF_Session.HasUnoProperty + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Session service as an array + + Methods = Array( _ + "ExecuteBasicScript" _ + , "ExecuteCalcFunction" _ + , "ExecutePythonScript" _ + , "HasUnoMethod" _ + , "HasUnoProperty" _ + , "OpenURLInBrowser" _ + , "RunApplication" _ + , "SendMail" _ + , "UnoMethods" _ + , "UnoObjectType" _ + , "UnoProperties" _ + , "WebService" _ + ) + +End Function ' ScriptForge.SF_Session.Methods + +REM ----------------------------------------------------------------------------- +Public Sub OpenURLInBrowser(Optional ByVal URL As Variant) +''' Opens a URL in the default browser +''' Args: +''' URL: The URL to open in the browser +''' Examples: +''' session.OpenURLInBrowser("https://docs.python.org/3/library/webbrowser.html") + +Const cstPyHelper = "$" & "_SF_Session__OpenURLInBrowser" + +Const cstThisSub = "Session.OpenURLInBrowser" +Const cstSubArgs = "URL" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(URL, "URL", V_STRING) Then GoTo Finally + End If + +Try: + ExecutePythonScript(SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper, URL) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_Session.OpenURLInBrowser + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties as an array + + Properties = Array( _ + ) + +End Function ' ScriptForge.SF_Session.Properties + +REM ----------------------------------------------------------------------------- +Public Function RunApplication(Optional ByVal Command As Variant _ + , Optional ByVal Parameters As Variant _ + ) As Boolean +''' Executes an arbitrary system command +''' Args: +''' Command: The command to execute +''' This may be an executable file or a document which is registered with an application +''' so that the system knows what application to launch for that document +''' Parameters: a list of space separated parameters as a single string +''' The method does not validate the given parameters, but only passes them to the specified command +''' Returns: +''' True if success +''' Examples: +''' session.RunApplication("Notepad.exe") +''' session.RunApplication("C:\myFolder\myDocument.odt") +''' session.RunApplication("kate", "/home/me/install.txt") ' (Linux) + +Dim bReturn As Boolean ' Returned value +Dim oShell As Object ' com.sun.star.system.SystemShellExecute +Dim sCommand As String ' Command as an URL +Const cstThisSub = "Session.RunApplication" +Const cstSubArgs = "Command, [Parameters]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bReturn = False + +Check: + If IsMissing(Parameters) Then Parameters = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(Command, "Command") Then GoTo Finally + If Not SF_Utils._Validate(Parameters, "Parameters", V_STRING) Then GoTo Finally + End If + +Try: + Set oShell = SF_Utils._GetUNOService("SystemShellExecute") + sCommand = SF_FileSystem._ConvertToUrl(Command) + oShell.execute(sCommand, Parameters, com.sun.star.system.SystemShellExecuteFlags.DEFAULTS) + bReturn = True + +Finally: + RunApplication = bReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.RunApplication + +REM ----------------------------------------------------------------------------- +Public Sub SendMail(Optional ByVal Recipient As Variant _ + , Optional ByRef Cc As Variant _ + , Optional ByRef Bcc As Variant _ + , Optional ByVal Subject As Variant _ + , Optional ByRef Body As Variant _ + , Optional ByVal FileNames As Variant _ + , Optional ByVal EditMessage As Variant _ + ) +''' Send a message (with or without attachments) to recipients from the user's mail client +''' The message may be edited by the user before sending or, alternatively, be sent immediately +''' Args: +''' Recipient: an email addresses (To recipient) +''' Cc: a comma-delimited list of email addresses (carbon copy) +''' Bcc: a comma-delimited list of email addresses (blind carbon copy) +''' Subject: the header of the message +''' FileNames: a comma-separated list of filenames to attach to the mail. SF_FileSystem naming conventions apply +''' Body: the unformatted text of the message +''' EditMessage: when True (default) the message is editable before being sent +''' Exceptions: +''' UNKNOWNFILEERROR File does not exist +''' WRONGEMAILERROR String not recognized as an email address +''' SENDMAILERROR System error, probably no mail client + +Dim sEmail As String ' An single email address +Dim sFile As String ' A single file name +Dim sArg As String ' Argument name +Dim vCc As Variant ' Array alias of Cc +Dim vBcc As Variant ' Array alias of Bcc +Dim vFileNames As Variant ' Array alias of FileNames +Dim oMailService As Object ' com.sun.star.system.SimpleCommandMail or com.sun.star.system.SimpleSystemMail +Dim oMail As Object ' com.sun.star.system.XSimpleMailClient +Dim oMessage As Object ' com.sun.star.system.XSimpleMailMessage +Dim lFlag As Long ' com.sun.star.system.SimpleMailClientFlags.XXX +Dim ARR As Object : ARR = ScriptForge.SF_Array +Dim i As Long +Const cstComma = ",", cstSemiColon = ";" +Const cstThisSub = "Session.SendMail" +Const cstSubArgs = "Recipient, [Cc=""""], [Bcc=""""], [Subject=""""], [FileNames=""""], [Body=""""], [EditMessage=True]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Cc) Or IsEmpty(Cc) Then Cc = "" + If IsMissing(Bcc) Or IsEmpty(Bcc) Then Bcc = "" + If IsMissing(Subject) Or IsEmpty(Subject) Then Subject = "" + If IsMissing(FileNames) Or IsEmpty(FileNames) Then FileNames = "" + If IsMissing(Body) Or IsEmpty(Body) Then Body = "" + If IsMissing(EditMessage) Or IsEmpty(EditMessage) Then EditMessage = True + + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Cc, "Recipient", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Cc, "Cc", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Bcc, "Bcc", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Subject, "Subject", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(FileNames, "FileNames", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Body, "Body", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(EditMessage, "EditMessage", V_BOOLEAN) Then GoTo Finally + End If + + ' Check email addresses + sArg = "Recipient" : sEmail = Recipient + If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail + sArg = "Cc" : vCc = ARR.TrimArray(Split(Cc, cstComma)) + For Each sEmail In vCc + If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail + Next sEmail + sArg = "Bcc" : vBcc = ARR.TrimArray(Split(Bcc, cstComma)) + For Each sEmail In vBcc + If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail + Next sEmail + + ' Check file existence + If Len(FileNames) > 0 Then + vFileNames = ARR.TrimArray(Split(FileNames, cstComma)) + For i = 0 To UBound(vFileNames) + sFile = vFileNames(i) + If Not SF_Utils._ValidateFile(sFile, "FileNames") Then GoTo Finally + If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists + vFileNames(i) = ConvertToUrl(sFile) + Next i + End If + +Try: + ' Initialize the mail service + Set oMailService = SF_Utils._GetUNOService("MailService") + If IsNull(oMailService) Then GoTo CatchMail + Set oMail = oMailService.querySimpleMailClient() + If IsNull(oMail) Then GoTo CatchMail + Set oMessage = oMail.createSimpleMailMessage() + If IsNull(oMessage) Then GoTo CatchMail + + ' Feed the new mail message + With oMessage + .setRecipient(Recipient) + If Subject <> "" Then .setSubject(Subject) + If UBound(vCc) >= 0 Then .setCcRecipient(vCc) + If UBound(vBcc) >= 0 Then .setBccRecipient(vBcc) + .Body = Iif(Len(Body) = 0, " ", Body) ' Body must not be the empty string ?? + .setAttachement(vFileNames) + End With + lFlag = Iif(EditMessage, com.sun.star.system.SimpleMailClientFlags.DEFAULTS, com.sun.star.system.SimpleMailClientFlags.NO_USER_INTERFACE) + + ' Send using the mail service + oMail.sendSimpleMailMessage(oMessage, lFlag) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +CatchEmail: + SF_Exception.RaiseFatal(WRONGEMAILERROR, sArg, sEmail) + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileNames", sFile) + GoTo Finally +CatchMail: + SF_Exception.RaiseFatal(SENDMAILERROR) + GoTo Finally +End Sub ' ScriptForge.SF_Session.SendMail + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Session.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.SetProperty + +REM ----------------------------------------------------------------------------- +Public Function UnoMethods(Optional ByRef UnoObject As Variant) As Variant +''' Returns a list of the methods callable from an UNO object +''' Code-snippet derived from XRAY +''' Args: +''' UnoObject: the object to identify +''' Returns: +''' A zero-based sorted array. May be empty + +Dim oIntrospect As Object ' com.sun.star.beans.Introspection +Dim oInspect As Object ' com.sun.star.beans.XIntrospectionAccess +Dim vMethods As Variant ' Array of com.sun.star.reflection.XIdlMethod +Dim vMethod As Object ' com.sun.star.reflection.XIdlMethod +Dim lMax As Long ' UBounf of vMethods +Dim vMethodsList As Variant ' Return value +Dim i As Long +Const cstThisSub = "Session.UnoMethods" +Const cstSubArgs = "UnoObject" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Check: + vMethodsList = Array() + If VarType(UnoObject) <> V_OBJECT Then GoTo Finally + If IsNull(UnoObject) Then GoTo Finally + +Try: + On Local Error GoTo Catch + Set oIntrospect = SF_Utils._GetUNOService("Introspection") + Set oInspect = oIntrospect.inspect(UnoObject) + vMethods = oInspect.getMethods(com.sun.star.beans.MethodConcept.ALL) + + ' The names must be extracted from com.sun.star.reflection.XIdlMethod structures + lMax = UBound(vMethods) + If lMax >= 0 Then + ReDim vMethodsList(0 To lMax) + For i = 0 To lMax + vMethodsList(i) = vMethods(i).Name + Next i + vMethodsList = SF_Array.Sort(vMethodsList, CaseSensitive := True) + End If + +Finally: + UnoMethods = vMethodsList + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + On Local Error GoTo 0 + GoTo Finally +End Function ' ScriptForge.SF_Session.UnoMethods + +REM ----------------------------------------------------------------------------- +Public Function UnoObjectType(Optional ByRef UnoObject As Variant) As String +''' Identify the UNO type of an UNO object +''' Code-snippet derived from XRAY +''' Args: +''' UnoObject: the object to identify +''' Returns: +''' com.sun.star. ... as a string +''' a zero-length string if identification was not successful + +Dim oService As Object ' com.sun.star.reflection.CoreReflection +Dim vClass as Variant ' com.sun.star.reflection.XIdlClass +Dim sObjectType As String ' Return value +Const cstThisSub = "Session.UnoObjectType" +Const cstSubArgs = "UnoObject" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Check: + sObjectType = "" + If VarType(UnoObject) <> V_OBJECT Then GoTo Finally + If IsNull(UnoObject) Then GoTo Finally + +Try: + On Local Error Resume Next + ' Try usual ImplementationName method + sObjectType = UnoObject.getImplementationName() + If sObjectType = "" Then + ' Now try CoreReflection trick + Set oService = SF_Utils._GetUNOService("CoreReflection") + vClass = oService.getType(UnoObject) + If vClass.TypeClass >= com.sun.star.uno.TypeClass.STRUCT Then sObjectType = vClass.Name + End If + +Finally: + UnoObjectType = sObjectType + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Session.UnoObjectType + +REM ----------------------------------------------------------------------------- +Public Function UnoProperties(Optional ByRef UnoObject As Variant) As Variant +''' Returns a list of the properties of an UNO object +''' Code-snippet derived from XRAY +''' Args: +''' UnoObject: the object to identify +''' Returns: +''' A zero-based sorted array. May be empty + +Dim oIntrospect As Object ' com.sun.star.beans.Introspection +Dim oInspect As Object ' com.sun.star.beans.XIntrospectionAccess +Dim vProperties As Variant ' Array of com.sun.star.beans.Property +Dim vProperty As Object ' com.sun.star.beans.Property +Dim lMax As Long ' UBounf of vProperties +Dim vPropertiesList As Variant ' Return value +Dim i As Long +Const cstThisSub = "Session.UnoProperties" +Const cstSubArgs = "UnoObject" + + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Check: + vPropertiesList = Array() + If VarType(UnoObject) <> V_OBJECT Then GoTo Finally + If IsNull(UnoObject) Then GoTo Finally + +Try: + On Local Error GoTo Catch + Set oIntrospect = SF_Utils._GetUNOService("Introspection") + Set oInspect = oIntrospect.inspect(UnoObject) + vProperties = oInspect.getProperties(com.sun.star.beans.PropertyConcept.ALL) + + ' The names must be extracted from com.sun.star.beans.Property structures + lMax = UBound(vProperties) + If lMax >= 0 Then + ReDim vPropertiesList(0 To lMax) + For i = 0 To lMax + vPropertiesList(i) = vProperties(i).Name + Next i + vPropertiesList = SF_Array.Sort(vPropertiesList, CaseSensitive := True) + End If + +Finally: + UnoProperties = vPropertiesList + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + On Local Error GoTo 0 + GoTo Finally +End Function ' ScriptForge.SF_Session.UnoProperties + +REM ----------------------------------------------------------------------------- +Public Function WebService(Optional ByVal URI As Variant) As String +''' Get some web content from a URI +''' Args: +''' URI: URI text of the web service +''' Returns: +''' The web page content of the URI +''' Exceptions: +''' CALCFUNCERROR +''' Examples: +''' session.WebService("wiki.documentfoundation.org/api.php?" _ +''' & "hidebots=1&days=7&limit=50&action=feedrecentchanges&feedformat=rss") + +Dim sReturn As String ' Returned value +Const cstThisSub = "Session.WebService" +Const cstSubArgs = "URI" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sReturn = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(URI, "URI", V_STRING) Then GoTo Finally + End If + +Try: + sReturn = SF_Session.ExecuteCalcFunction("WEBSERVICE", URI) + +Finally: + WebService = sReturn + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Session.WebService + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _GetScript(ByVal psLanguage As String _ + , ByVal psScope As String _ + , ByVal psScript As String _ + ) As Object +''' Get the adequate script provider and from there the requested script +''' Called by ExecuteBasicScript() and ExecutePythonScript() +''' The execution of the script is done by the caller +''' Args: +''' psLanguage: Basic or Python +''' psScope: one of the SCRIPTISxxx constants +''' The SCRIPTISOXT constant is an alias for 2 cases, extension either +''' installed for one user only, or for all users +''' Managed here by trial and error +''' psScript: Read https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification +''' Returns: +''' A com.sun.star.script.provider.XScript object + +Dim sScript As String ' The complete script string +Dim oScriptProvider As Object ' Script provider singleton +Dim oScript As Object ' Return value +Const cstScript1 = "vnd.sun.star.script:" +Const cstScript2 = "?language=" +Const cstScript3 = "&location=" + +Try: + ' Build script string + sScript = cstScript1 & psScript & cstScript2 & psLanguage & cstScript3 & LCase(psScope) + + ' Find script + Set oScript = Nothing + ' Python only: installation of extension is determined by user => unknown to script author + If psScope = SCRIPTISOXT Then ' => Trial and error + On Local Error GoTo ForAllUsers + sScript = cstScript1 & psScript & cstScript2 & psLanguage & cstScript3 & SCRIPTISPERSOXT + Set oScriptProvider = SF_Utils._GetUNOService("ScriptProvider", SCRIPTISPERSOXT) + Set oScript = oScriptProvider.getScript(sScript) + End If + ForAllUsers: + On Local Error GoTo CatchNotFound + If IsNull(oScript) Then + If psScope = SCRIPTISOXT Then psScope = SCRIPTISSHAROXT + Set oScriptProvider = SF_Utils._GetUNOService("ScriptProvider", psScope) + Set oScript = oScriptProvider.getScript(sScript) + End If + +Finally: + _GetScript = oScript + Exit Function +CatchNotFound: + SF_Exception.RaiseFatal(NOSCRIPTERROR, psLanguage, "Scope", psScope, "Script", psScript) + GoTo Finally +End Function ' ScriptForge.SF_Session._GetScript + +REM =============================================== END OF SCRIPTFORGE.SF_SESSION +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_String.xba b/wizards/source/scriptforge/SF_String.xba new file mode 100644 index 000000000000..24acd984ad16 --- /dev/null +++ b/wizards/source/scriptforge/SF_String.xba @@ -0,0 +1,2642 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_String" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_String +''' ========= +''' Singleton class implementing the "ScriptForge.String" service +''' Implemented as a usual Basic module +''' Focus on string manipulation, regular expressions, encodings and hashing algorithms +''' The first argument of almost every method is the string to consider +''' It is always passed by reference and left unchanged +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' Definitions +''' Line breaks: symbolic name(Ascii number) +''' LF(10), VT(12), CR(13), LF+CR, File separator(28), Group separator(29), Record separator(30), +''' Next Line(133), Line separator(8232), Paragraph separator(8233) +''' Whitespaces: symbolic name(Ascii number) +''' Space(32), HT(9), LF(10), VT(11), FF(12), CR(13), Next Line(133), No-break space(160), +''' Line separator(8232), Paragraph separator(8233) +''' A quoted string: +''' The quoting chararacter must be the double quote (") +''' To preserve a quoting character inside the quoted substring, use (\) or (") as escape character +''' => [str\"i""ng] means [str"i"ng] +''' Escape sequences: symbolic name(Ascii number) = escape sequence +''' Line feed(10) = "\n" +''' Carriage return(13) = "\r" +''' Horizontal tab(9) = "\t" +''' Double the backslash to ignore the sequence, e.g. "\\n" means "\n" (not "\" & Chr(10)). +''' Not printable characters: +''' Defined in the Unicode character database as “Other” or “Separator” +''' In particular, "control" characters (ascii code <= 0x1F) are not printable +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' Some references: +''' https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1i18n_1_1KCharacterType.html +''' com.sun.star.i18n.KCharacterType.### +''' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html +''' com.sun.star.i18n.XCharacterClassification + +REM ============================================================ MODULE CONSTANTS + +''' Most expressions below are derived from https://www.regular-expressions.info/ + +Const REGEXALPHA = "^[A-Za-z]+$" ' Not used +Const REGEXALPHANUM = "^[\w]+$" +Const REGEXDATEDAY = "(0[1-9]|[12][0-9]|3[01])" +Const REGEXDATEMONTH = "(0[1-9]|1[012])" +Const REGEXDATEYEAR = "(19|20)\d\d" +Const REGEXTIMEHOUR = "(0[1-9]|1[0-9]|2[0123])" +Const REGEXTIMEMIN = "([0-5][0-9])" +Const REGEXTIMESEC = REGEXTIMEMIN +Const REGEXDIGITS = "^[0-9]+$" +Const REGEXEMAIL = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$" +Const REGEXFILELINUX = "^[^<>:;,?""*|\\]+$" +Const REGEXFILEWIN = "^([A-Z]|[a-z]:)?[^<>:;,?""*|]+$" +Const REGEXHEXA = "^(0X|&H)?[0-9A-F]+$" ' Includes 0xFF and &HFF +Const REGEXIPV4 = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" +Const REGEXNUMBER = "^[-+]?(([0-9]+)?\.)?[0-9]+([eE][-+]?[0-9]+)?$" +Const REGEXURL = "^(https?|ftp)://[^\s/$.?#].[^\s]*$" +Const REGEXWHITESPACES = "^[\s]+$" +Const REGEXLTRIM = "^[\s]+" +Const REGEXRTRIM = "[\s]+$" +Const REGEXSPACES = "[\s]+" + +''' Accented characters substitution: https://docs.google.com/spreadsheets/d/1pJKSueZK8RkAcJFQIiKpYUamWSC1u1xVQchK7Z7BIwc/edit#gid=0 +''' (Many of them are in the list, but do not consider the list as closed vs. the Unicode database) + +Const cstCHARSWITHACCENT = "ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðñòóôõöùúûüýÿŠšŸŽž" _ + & "ĂăĐđĨĩŨũƠơƯưẠạẢảẤấẦầẨẩẪẫẬậẮắẰằẲẳẴẵẶặẸẹẺẻẼẽẾếỀềỂểỄễỆệỈỉỊịỌọỎỏỐốỒồỔổỖỗỘộỚớỜờỞởỠỡỢợỤụỦủỨứỪừỬửỮữỰựỲỳỴỵỶỷỸỹ₫" +Const cstCHARSWITHOUTACCENT = "AAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyySsYZz" _ + & "AaDdIiUuOoUuAaAaAaAaAaAaAaAaAaAaAaAaEeEeEeEeEeEeEeEeIiIiOoOoOoOoOoOoOoOoOoOoOoOoUuUuUuUuUuUuUuYyYyYyYyd" + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_String Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get CHARSWITHACCENT() As String +''' Latin accents + CHARSWITHACCENT = cstCHARSWITHACCENT +End Property ' ScriptForge.SF_String.CHARSWITHACCENT + +REM ----------------------------------------------------------------------------- +Property Get CHARSWITHOUTACCENT() As String +''' Latin accents + CHARSWITHOUTACCENT = cstCHARSWITHOUTACCENT +End Property ' ScriptForge.SF_String.CHARSWITHOUTACCENT + +''' Symbolic constants for linebreaks +REM ----------------------------------------------------------------------------- +Property Get sfCR() As Variant +''' Carriage return + sfCR = Chr(13) +End Property ' ScriptForge.SF_String.sfCR + +REM ----------------------------------------------------------------------------- +Property Get sfCRLF() As Variant +''' Carriage return + sfCRLF = Chr(13) & Chr(10) +End Property ' ScriptForge.SF_String.sfCRLF + +REM ----------------------------------------------------------------------------- +Property Get sfLF() As Variant +''' Linefeed + sfLF = Chr(10) +End Property ' ScriptForge.SF_String.sfLF + +REM ----------------------------------------------------------------------------- +Property Get sfNEWLINE() As Variant +''' Linefeed or Carriage return + Linefeed + sfNEWLINE = Iif(GetGuiType() = 1, Chr(13), "") & Chr(10) +End Property ' ScriptForge.SF_String.sfNEWLINE + +REM ----------------------------------------------------------------------------- +Property Get sfTAB() As Variant +''' Horizontal tabulation + sfTAB = Chr(9) +End Property ' ScriptForge.SF_String.sfTAB + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_String" +End Property ' ScriptForge.SF_String.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.String" +End Property ' ScriptForge.SF_String.ServiceName + +REM ============================================================== PUBLIC METHODS + +REM ----------------------------------------------------------------------------- +Public Function Capitalize(Optional ByRef InputStr As Variant) As String +''' Return the input string with the 1st character of each word in title case +''' Args: +''' InputStr: the input string +''' Returns: +''' The input string with the 1st character of each word in title case +''' Examples: +''' SF_String.Capitalize("this is a title for jean-pierre") returns "This Is A Title For Jean-Pierre" + +Dim sCapital As String ' Return value +Dim lLength As Long ' Length of input string +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim oChar As Object ' com.sun.star.i18n.CharacterClassification +Const cstThisSub = "String.Capitalize" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sCapital = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + Set oLocale = SF_Utils._GetUNOService("Locale") + Set oChar = SF_Utils._GetUNOService("CharacterClass") + sCapital = oChar.toTitle(InputStr, 0, lLength * 4, oLocale) ' length * 4 because length is expressed in bytes + End If + +Finally: + Capitalize = sCapital + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Capitalize + +REM ----------------------------------------------------------------------------- +Public Function Count(Optional ByRef InputStr As Variant _ + , Optional ByVal Substring As Variant _ + , Optional ByRef IsRegex As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Long +''' Counts the number of occurrences of a substring or a regular exprsession within a string +''' Args: +''' InputStr: the input stringto examine +''' Substring: the substring to identify +''' IsRegex: True if Substring is a regular expression (default = False) +''' CaseSensitive: default = False +''' Returns: +''' The numer of occurrences as a Long +''' Examples: +''' SF_String.Count("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "\b[a-z]+\b", IsRegex := True, CaseSensitive := True) +''' returns 7 (the number of words in lower case) +''' SF_String.Count("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "or", CaseSensitive := False) +''' returns 2 + + +Dim lOccurrences As Long ' Return value +Dim lStart As Long ' Start index of search +Dim sSubstring As String ' Substring to replace +Dim iCaseSensitive As Integer ' Integer alias for boolean CaseSensitive +Const cstThisSub = "String.Count" +Const cstSubArgs = "InputStr, Substring, [IsRegex=False], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + lOccurrences = 0 + +Check: + If IsMissing(IsRegex) Or IsEmpty(IsRegex) Then IsRegex = False + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Substring, "Substring", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(IsRegex, "IsRegex", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + iCaseSensitive = Iif(CaseSensitive, 0, 1) ' 1 = False ;) + lStart = 1 + + Do While lStart >= 1 And lStart <= Len(InputStr) + Select Case IsRegex + Case False ' Use InStr + lStart = InStr(lStart, InputStr, Substring, iCaseSensitive) + If lStart = 0 Then Exit Do + lStart = lStart + Len(Substring) + Case True ' Use FindRegex + sSubstring = SF_String.FindRegex(InputStr, Substring, lStart, CaseSensitive) + If lStart = 0 Then Exit Do + lStart = lStart + Len(sSubstring) + End Select + lOccurrences = lOccurrences + 1 + Loop + +Finally: + Count = lOccurrences + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Count + +REM ----------------------------------------------------------------------------- +Public Function EndsWith(Optional ByRef InputStr As Variant _ + , Optional ByVal Substring As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Boolean +''' Returns True if the last characters of InputStr are identical to Substring +''' Args: +''' InputStr: the input string +''' Substring: the suffixing characters +''' CaseSensitive: default = False +''' Returns: +''' True if the comparison is satisfactory +''' False if either InputStr or Substring have a length = 0 +''' False if Substr is longer than InputStr +''' Examples: +''' SF_String.EndsWith("abcdefg", "EFG") returns True +''' SF_String.EndsWith("abcdefg", "EFG", CaseSensitive := True) returns False + +Dim bEndsWith As Boolean ' Return value +Dim lSub As Long ' Length of SUbstring +Const cstThisSub = "String.EndsWith" +Const cstSubArgs = "InputStr, Substring, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bEndsWith = False + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Substring, "Substring", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lSub = Len(Substring) + If Len(InputStr) > 0 And lSub > 0 And lSub <= Len(InputStr) Then + bEndsWith = ( StrComp(Right(InputStr, lSub), Substring, Iif(CaseSensitive, 1, 0)) = 0 ) + End If + +Finally: + EndsWith = bEndsWith + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.EndsWith + +REM ----------------------------------------------------------------------------- +Public Function Escape(Optional ByRef InputStr As Variant) As String +''' Convert any hard line breaks or tabs by their escaped equivalent +''' Args: +''' InputStr: the input string +''' Returns: +''' The input string after replacement of "\", Chr(10), Chr(13), Chr(9)characters +''' Examples: +''' SF_String.Escape("abc" & Chr(10) & Chr(9) & "def\n") returns "abc\n\tdef\\n" + +Dim sEscape As String ' Return value +Const cstThisSub = "String.Escape" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sEscape = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + sEscape = SF_String.ReplaceStr( InputStr _ + , Array("\", SF_String.sfLF, SF_String.sfCR, SF_String.sfTAB) _ + , Array("\\", "\n", "\r", "\t") _ + ) + +Finally: + Escape = sEscape + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Escape + +REM ----------------------------------------------------------------------------- +Public Function ExpandTabs(Optional ByRef InputStr As Variant _ + , Optional ByVal TabSize As Variant _ + ) As String +''' Return the input string with each TAB (Chr(9)) character replaced by the adequate number of spaces +''' Args: +''' InputStr: the input string +''' TabSize: defines the TAB positions at TabSize + 1, 2 * TabSize + 1 , ... N * TabSize + 1 +''' Default = 8 +''' Returns: +''' The input string with spaces replacing the TAB characters +''' If the input string contains line breaks, the TAB positions are reset +''' Examples: +''' SF_String.ExpandTabs("abc" & SF_String.sfTAB & SF_String.sfTAB & "def", 4) returns "abc def" +''' SF_String.ExpandTabs("abc" & SF_String.sfTAB & "def" & SF_String.sfLF & SF_String.sfTAB & "ghi") +''' returns "abc def" & SF_String.sfLF & " ghi" + +Dim sExpanded As String ' Return value +Dim lCharPosition As Long ' Position of current character in current line in expanded string +Dim lSpaces As Long ' Spaces counter +Dim sChar As String ' A single character +Dim i As Long +Const cstTabSize = 8 +Const cstThisSub = "String.ExpandTabs" +Const cstSubArgs = "InputStr, [TabSize=8]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sExpanded = "" + +Check: + If IsMissing(TabSize) Or IsEmpty(TabSize) Then TabSize = cstTabSize + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(TabSize, "TabSize", V_NUMERIC) Then GoTo Finally + End If + If TabSize <= 0 Then TabSize = cstTabSize + +Try: + lCharPosition = 0 + If Len(InputStr) > 0 Then + For i = 1 To Len(InputStr) + sChar = Mid(InputStr, i, 1) + Select Case sChar + Case SF_String.sfLF, Chr(12), SF_String.sfCR, Chr(28), Chr(29), Chr(30), Chr(133), Chr(8232), Chr(8233) + sExpanded = sExpanded & sChar + lCharPosition = 0 + Case SF_String.sfTAB + lSpaces = Int(lCharPosition / TabSize + 1) * TabSize - lCharPosition + sExpanded = sExpanded & Space(lSpaces) + lCharPosition = lCharPosition + lSpaces + Case Else + sExpanded = sExpanded & sChar + lCharPosition = lCharPosition + 1 + End Select + Next i + End If + +Finally: + ExpandTabs = sExpanded + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.ExpandTabs + +REM ----------------------------------------------------------------------------- +Public Function FilterNotPrintable(Optional ByRef InputStr As Variant _ + , Optional ByVal ReplacedBy As Variant _ + ) As String +''' Return the input string in which all the not printable characters are replaced by ReplacedBy +''' Among others, control characters (Ascii <= 1F) are not printable +''' Args: +''' InputStr: the input string +''' ReplacedBy: zero, one or more characters replacing the found not printable characters +''' Default = the zero-length string +''' Returns: +''' The input string in which all the not printable characters are replaced by ReplacedBy +''' Examples: +''' SF_String.FilterNotPrintable("àén ΣlPµ" & Chr(10) & " Русский", "\n") returns "àén ΣlPµ\n Русский" + +Dim sPrintable As String ' Return value +Dim bPrintable As Boolean ' Is a single character printable ? +Dim lLength As Long ' Length of InputStr +Dim lReplace As Long ' Length of ReplacedBy +Dim oChar As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim lType As Long ' com.sun.star.i18n.KCharacterType +Dim sChar As String ' A single character +Dim lPRINTABLE As Long : lPRINTABLE = com.sun.star.i18n.KCharacterType.PRINTABLE +Dim i As Long +Const cstThisSub = "String.FilterNotPrintable" +Const cstSubArgs = "InputStr, [ReplacedBy=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sPrintable = "" + +Check: + If IsMissing(ReplacedBy) Or IsEmpty(ReplacedBy) Then ReplacedBy = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(ReplacedBy, "ReplacedBy", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + lReplace = Len(ReplacedBy) + If lLength > 0 Then + Set oLocale = SF_Utils._GetUNOService("Locale") + Set oChar = SF_Utils._GetUNOService("CharacterClass") + For i = 0 To lLength - 1 + sChar = Mid(InputStr, i + 1, 1) + lType = oChar.getCharacterType(sChar, 0, oLocale) + ' Parenthses (), [], {} have a KCharacterType = 0 + bPrintable = ( (lType And lPRINTABLE) = lPRINTABLE Or (lType = 0 And Asc(sChar) <= 127) ) + If Not bPrintable Then + If lReplace > 0 Then sPrintable = sPrintable & ReplacedBy + Else + sPrintable = sPrintable & sChar + End If + Next i + End If + +Finally: + FilterNotPrintable = sPrintable + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.FilterNotPrintable + +REM ----------------------------------------------------------------------------- +Public Function FindRegex(Optional ByRef InputStr As Variant _ + , Optional ByVal Regex As Variant _ + , Optional ByRef Start As Variant _ + , Optional ByVal CaseSensitive As Variant _ + , Optional ByVal Forward As Variant _ + ) As String +''' Find in InputStr a substring matching a given regular expression +''' Args: +''' InputStr: the input string to be searched for the expression +''' Regex: the regular expression +''' Start (passed by reference): where to start searching from +''' Should be = 1 (Forward = True) or = Len(InputStr) (Forward = False) the 1st time +''' After execution points to the first character of the found substring +''' CaseSensitive: default = False +''' Forward: True (default) or False (backward) +''' Returns: +''' The found substring matching the regular expression +''' A zero-length string if not found (Start is set to 0) +''' Examples: +''' Dim lStart As Long : lStart = 1 +''' SF_String.FindRegex("abCcdefghHij", "C.*H", lStart, CaseSensitive := True) returns "CcdefghH" +''' Above statement may be reexecuted for searching the same or another pattern +''' by starting from lStart + Len(matching string) + +Dim sOutput As String ' Return value +Dim oTextSearch As Object ' com.sun.star.util.TextSearch +Dim vOptions As Variant ' com.sun.star.util.SearchOptions +Dim lEnd As Long ' Upper limit of search area +Dim vResult As Object ' com.sun.star.util.SearchResult +Const cstThisSub = "String.FindRegex" +Const cstSubArgs = "InputStr, Regex, [Start=1], [CaseSensitive=False], [Forward=True]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sOutput = "" + +Check: + If IsMissing(Start) Or IsEmpty(Start) Then Start = 1 + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If IsMissing(Forward) Or IsEmpty(Forward) Then Forward = True + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Regex, "Regex", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Start, "Start", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(Forward, "Forward", V_BOOLEAN) Then GoTo Finally + End If + If Start <= 0 Or Start > Len(InputStr) Then GoTo Finally + +Try: + sOutput = "" + Set oTextSearch = SF_Utils._GetUNOService("TextSearch") + ' Set pattern search options + vOptions = SF_Utils._GetUNOService("SearchOptions") + With vOptions + .searchString = Regex + If CaseSensitive Then .transliterateFlags = 0 Else .transliterateFlags = com.sun.star.i18n.TransliterationModules.IGNORE_CASE + End With + ' Run search + With oTextSearch + .setOptions(vOptions) + If Forward Then + lEnd = Len(InputStr) + vResult = .searchForward(InputStr, Start - 1, lEnd) + Else + lEnd = 1 + vResult = .searchBackward(InputStr, Start, lEnd - 1) + End If + End With + ' https://api.libreoffice.org/docs/idl/ref/structcom_1_1sun_1_1star_1_1util_1_1SearchResult.html + With vResult + If .subRegExpressions >= 1 Then + If Forward Then + Start = .startOffset(0) + 1 + lEnd = .endOffset(0) + 1 + Else + Start = .endOffset(0) + 1 + lEnd = .startOffset(0) + 1 + End If + sOutput = Mid(InputStr, Start, lEnd - Start) + Else + Start = 0 + End If + End With + +Finally: + FindRegex = sOutput + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.FindRegex + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "String.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case "SFCR" : GetProperty = sfCR + Case "SFCRLF" : GetProperty = sfCRLF + Case "SFLF" : GetProperty = sfLF + Case "SFNEWLINE" : GetProperty = sfNEWLINE + Case "SFTAB" : GetProperty = sfTAB + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function HashStr(Optional ByVal InputStr As Variant _ + , Optional ByVal Algorithm As Variant _ + ) As String +''' Return an hexadecimal string representing a checksum of the given input string +''' Next algorithms are supported: MD5, SHA1, SHA224, SHA256, SHA384 and SHA512 +''' Args: +''' InputStr: the string to be hashed +''' Algorithm: The hashing algorithm to use +''' Returns: +''' The requested checksum as a string. Hexadecimal digits are lower-cased +''' A zero-length string when an error occurred +''' Example: +''' Print SF_String.HashStr("œ∑¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ“‘åß∂ƒ©˙∆˚¬", "MD5") ' 616eb9c513ad07cd02924b4d285b9987 + +Dim sHash As String ' Return value +Const cstPyHelper = "$" & "_SF_String__HashStr" +Const cstThisSub = "String.HashStr" +Const cstSubArgs = "InputStr, Algorithm=""MD5""|""SHA1""|""SHA224""|""SHA256""|""SHA384""|""SHA512""" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sHash = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Algorithm, "Algorithm", V_STRING _ + , Array("MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512")) Then GoTo Finally + End If + +Try: + With ScriptForge.SF_Session + sHash = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , InputStr, LCase(Algorithm)) + End With + +Finally: + HashStr = sHash + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.HashStr + +REM ----------------------------------------------------------------------------- +Public Function HtmlEncode(Optional ByRef InputStr As Variant) As String +''' &-encoding of the input string (e.g. "é" becomes "&eacute;" or numeric equivalent +''' Args: +''' InputStr: the input string +''' Returns: +''' the encoded string +''' Examples: +''' SF_String.HtmlEncode("<a href=""https://a.b.com"">From α to ω</a>") +''' returns "&lt;a href=&quot;https://a.b.com&quot;&gt;From &#945; to &#969;&lt;/a&gt;" + +Dim sEncode As String ' Return value +Dim lPos As Long ' Position in InputStr +Dim sChar As String ' A single character extracted from InputStr +Dim i As Long +Const cstThisSub = "String.HtmlEncode" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sEncode = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then + lPos = 1 + sEncode = InputStr + Do While lPos <= Len(sEncode) + sChar = Mid(sEncode, lPos, 1) + ' Leave as is or encode every single char + Select Case sChar + Case """" : sChar = "&quot;" + Case "&" : sChar = "&amp;" + Case "<" : sChar = "&lt;" + Case ">" : sChar = "&gt;" + Case "'" : sChar = "&apos;" + Case ":", "/", "?", "#", "[", "]", "@" ' Reserved characters + Case SF_String.sfCR : sChar = "" ' Carriage return + Case SF_String.sfLF : sChar = "<br>" ' Line Feed + Case < Chr(126) + Case "€" : sChar = "&euro;" + Case Else : sChar = "&#" & Asc(sChar) & ";" + End Select + If Len(sChar) = 1 Then + Mid(sEncode, lPos, 1) = sChar + Else + sEncode = Left(sEncode, lPos - 1) & sChar & Mid(sEncode, lPos + 1) + End If + lPos = lPos + Len(sChar) + Loop + End If + +Finally: + HtmlEncode = sEncode + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.HtmlEncode + +REM ----------------------------------------------------------------------------- +Public Function IsADate(Optional ByRef InputStr As Variant _ + , Optional ByVal DateFormat _ + ) As Boolean +''' Return True if the string is a valid date respecting the given format +''' Args: +''' InputStr: the input string +''' DateFormat: either YYYY-MM-DD (default), DD-MM-YYYY or MM-DD-YYYY +''' The dash (-) may be replaced by a dot (.), a slash (/) or a space +''' Returns: +''' True if the string contains a valid date and there is at least one character +''' False otherwise or if the date format is invalid +''' Examples: +''' SF_String.IsADate("2019-12-31", "YYYY-MM-DD") returns True + +Dim bADate As Boolean ' Return value +Dim sFormat As String ' Alias for DateFormat +Dim sRegex As String ' The regex to check against the input string +Const cstFormat = "YYYY-MM-DD" ' Default date format +Const cstFormatRegex = "(YYYY[- /.]MM[- /.]DD|MM[- /.]DD[- /.]YYYY|DD[- /.]MM[- /.]YYYY)" + ' The regular expression the format must match +Const cstThisSub = "String.IsADate" +Const cstSubArgs = "InputStr, [DateFormat=""" & cstFormat & """]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bADate = False + +Check: + If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = "YYYY-MM-DD" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(DateFormat, "DateFormat", V_STRING) Then GoTo Finally + End If + sFormat = UCase(DateFormat) + If Len(sFormat) <> Len(cstFormat)Then GoTo Finally + If sFormat <> cstFormat Then ' Do not check if default format + If Not SF_String.IsRegex(sFormat, cstFormatRegex) Then GoTo Finally + End If + +Try: + If Len(InputStr) = Len(DateFormat) Then + sRegex = ReplaceStr(sFormat, Array("YYYY", "MM", "DD") _ + , Array(REGEXDATEYEAR, REGEXDATEMONTH, REGEXDATEDAY) _ + , CaseSensitive := False) + bADate = SF_String.IsRegex(InputStr, sRegex, CaseSensitive := False) + End If + +Finally: + IsADate = bADate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsADate + +REM ----------------------------------------------------------------------------- +Public Function IsAlpha(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are alphabetic +''' Alphabetic characters are those characters defined in the Unicode character database as “Letter” +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is alphabetic and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsAlpha("àénΣlPµ") returns True +''' Note: +''' Use SF_String.IsRegex("...", REGEXALPHA) to limit characters to latin alphabet + +Dim bAlpha As Boolean ' Return value +Dim lLength As Long ' Length of InputStr +Dim oChar As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim lType As Long ' com.sun.star.i18n.KCharacterType +Dim lLETTER As Long : lLETTER = com.sun.star.i18n.KCharacterType.LETTER +Dim i As Long +Const cstThisSub = "String.IsAlpha" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bAlpha = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + Set oLocale = SF_Utils._GetUNOService("Locale") + Set oChar = SF_Utils._GetUNOService("CharacterClass") + For i = 0 To lLength - 1 + lType = oChar.getCharacterType(InputStr, i, oLocale) + bAlpha = ( (lType And lLETTER) = lLETTER ) + If Not bAlpha Then Exit For + Next i + End If + +Finally: + IsAlpha = bAlpha + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsAlpha + +REM ----------------------------------------------------------------------------- +Public Function IsAlphaNum(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are alphabetic, digits or "_" (underscore) +''' The first character must not be a digit +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is alphanumeric and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsAlphaNum("_ABC_123456_abcàénΣlPµ") returns True + +Dim bAlphaNum As Boolean ' Return value +Dim sInputStr As String ' Alias of InputStr without underscores +Dim sFirst As String ' Leftmost character of InputStr +Dim lLength As Long ' Length of InputStr +Dim oChar As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim lType As Long ' com.sun.star.i18n.KCharacterType +Dim lLETTER As Long : lLETTER = com.sun.star.i18n.KCharacterType.LETTER +Dim lDIGIT As Long : lDIGIT = com.sun.star.i18n.KCharacterType.DIGIT +Dim i As Long +Const cstThisSub = "String.IsAlphaNum" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bAlphaNum = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + sFirst = Left(InputStr, 1) + bAlphanum = ( sFirst < "0" Or sFirst > "9" ) + If bAlphaNum Then + sInputStr = Replace(InputStr, "_", "A") ' Replace by an arbitrary alphabetic character + Set oLocale = SF_Utils._GetUNOService("Locale") + Set oChar = SF_Utils._GetUNOService("CharacterClass") + For i = 0 To lLength - 1 + lType = oChar.getCharacterType(sInputStr, i, oLocale) + bAlphaNum = ( (lType And lLETTER) = lLETTER _ + Or (lType And lDIGIT) = lDIGIT ) + If Not bAlphaNum Then Exit For + Next i + End If + End If + +Finally: + IsAlphaNum = bAlphaNum + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsAlphaNum + +REM ----------------------------------------------------------------------------- +Public Function IsAscii(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are Ascii characters +''' Ascii characters are those characters defined between &H00 and &H7F +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is Ascii and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsAscii("a%?,25") returns True + +Dim bAscii As Boolean ' Return value +Dim lLength As Long ' Length of InputStr +Dim sChar As String ' Single character +Dim i As Long +Const cstThisSub = "String.IsAscii" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bAscii = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + For i = 1 To lLength + sChar = Mid(InputStr, i, 1) + bAscii = ( Asc(sChar) <= 127 ) + If Not bAscii Then Exit For + Next i + End If + +Finally: + IsAscii = bAscii + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsAscii + +REM ----------------------------------------------------------------------------- +Public Function IsDigit(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are digits +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains only digits and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsDigit("123456") returns True + +Dim bDigit As Boolean ' Return value +Const cstThisSub = "String.IsDigit" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bDigit = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bDigit = SF_String.IsRegex(InputStr, REGEXDIGITS, CaseSensitive := False) + +Finally: + IsDigit = bDigit + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsDigit + +REM ----------------------------------------------------------------------------- +Public Function IsEmail(Optional ByRef InputStr As Variant) As Boolean +''' Return True if the string is a valid email address +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains an email address and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsEmail("first.last@something.org") returns True + +Dim bEmail As Boolean ' Return value +Const cstThisSub = "String.IsEmail" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bEmail = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bEmail = SF_String.IsRegex(InputStr, REGEXEMAIL, CaseSensitive := False) + +Finally: + IsEmail = bEmail + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsEmail + +REM ----------------------------------------------------------------------------- +Public Function IsFileName(Optional ByRef InputStr As Variant _ + , Optional ByVal OSName As Variant _ + ) As Boolean +''' Return True if the string is a valid filename in a given operating system +''' Args: +''' InputStr: the input string +''' OSName: Windows, Linux, macOS or Solaris +''' The default is the current operating system on which the script is run +''' Returns: +''' True if the string contains a valid filename and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsFileName("/home/a file name.odt", "LINUX") returns True + +Dim bFileName As Boolean ' Return value +Dim sRegex As String ' Regex to apply depending on OS +Const cstThisSub = "String.IsFileName" +Const cstSubArgs = "InputStr, [OSName=""Windows""|""Linux""|""macOS""|Solaris""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bFileName = False + +Check: + If IsMissing(OSName) Or IsEmpty(OSName) Then + If _SF_.OSname = "" Then _SF_.OSName = SF_Platform.OSName + OSName = _SF_.OSName + End If + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(OSName, "OSName", V_STRING, Array("Windows", "Linux", "macOS", "Solaris")) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then + Select Case UCase(OSName) + Case "LINUX", "MACOS", "SOLARIS" : sRegex = REGEXFILELINUX + Case "WINDOWS" : sRegex = REGEXFILEWIN + End Select + bFileName = SF_String.IsRegex(InputStr, sRegex, CaseSensitive := False) + End If + +Finally: + IsFileName = bFileName + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsFileName + +REM ----------------------------------------------------------------------------- +Public Function IsHexDigit(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are hexadecimal digits +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains only hexadecimal igits and there is at least one character +''' The prefixes "0x" and "&H" are admitted +''' False otherwise +''' Examples: +''' SF_String.IsHexDigit("&H00FF") returns True + +Dim bHexDigit As Boolean ' Return value +Const cstThisSub = "String.IsHexDigit" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bHexDigit = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bHexDigit = SF_String.IsRegex(InputStr, REGEXHEXA, CaseSensitive := False) + +Finally: + IsHexDigit = bHexDigit + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsHexDigit + +REM ----------------------------------------------------------------------------- +Public Function IsIPv4(Optional ByRef InputStr As Variant) As Boolean +''' Return True if the string is a valid IPv4 address +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains a valid IPv4 address and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsIPv4("192.168.1.50") returns True + +Dim bIPv4 As Boolean ' Return value +Const cstThisSub = "String.IsIPv4" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bIPv4 = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bIPv4 = SF_String.IsRegex(InputStr, REGEXIPV4, CaseSensitive := False) + +Finally: + IsIPv4 = bIPv4 + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsIPv4 + +REM ----------------------------------------------------------------------------- +Public Function IsLike(Optional ByRef InputStr As Variant _ + , Optional ByVal Pattern As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Boolean +''' Returns True if the whole input string matches a given pattern containing wildcards +''' Args: +''' InputStr: the input string +''' Pattern: the pattern as a string +''' Admitted wildcard are: the "?" represents any single character +''' the "*" represents zero, one, or multiple characters +''' CaseSensitive: default = False +''' Returns: +''' True if a match is found +''' Zero-length input or pattern strings always return False +''' Examples: +''' SF_String.IsLike("aAbB", "?A*") returns True +''' SF_String.IsLike("C:\a\b\c\f.odb", "?:*.*") returns True + +Dim bLike As Boolean ' Return value +' Build an equivalent regular expression by escaping the special characters present in Pattern +Dim sRegex As String ' Equivalent regular expression +Const cstSpecialChars = "\,^,$,.,|,+,(,),[,{,?,*" ' List of special chars in regular expressions +Const cstEscapedChars = "\\,\^,\$,\.,\|,\+,\(,\),\[,\{,.,.*" + +Const cstThisSub = "String.IsLike" +Const cstSubArgs = "InputStr, Pattern, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bLike = False + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Pattern, "Pattern", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 And Len(Pattern) > 0 Then + ' Substiture special chars by escaped chars + sRegex = SF_String.ReplaceStr(Pattern, Split(cstSPecialChars, ","), Split(cstEscapedChars, ",")) + bLike = SF_String.IsRegex(InputStr, sRegex, CaseSensitive) + End If + +Finally: + IsLike = bLike + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsLike + +REM ----------------------------------------------------------------------------- +Public Function IsLower(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are in lower case +''' Non alphabetic characters are ignored +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains only lower case characters and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsLower("abc'(-xyz") returns True + +Dim bLower As Boolean ' Return value +Const cstThisSub = "String.IsLower" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bLower = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bLower = ( StrComp(InputStr, LCase(InputStr), 1) = 0 ) + +Finally: + IsLower = bLower + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsLower + +REM ----------------------------------------------------------------------------- +Public Function IsPrintable(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are printable +''' In particular, control characters (Ascii <= 1F) are not printable +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is printable and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsPrintable("àén ΣlPµ Русский") returns True + +Dim bPrintable As Boolean ' Return value +Dim lLength As Long ' Length of InputStr +Dim oChar As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim lType As Long ' com.sun.star.i18n.KCharacterType +Dim sChar As String ' A single character +Dim lPRINTABLE As Long : lPRINTABLE = com.sun.star.i18n.KCharacterType.PRINTABLE +Dim i As Long +Const cstThisSub = "String.IsPrintable" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bPrintable = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + Set oLocale = SF_Utils._GetUNOService("Locale") + Set oChar = SF_Utils._GetUNOService("CharacterClass") + For i = 0 To lLength - 1 + sChar = Mid(InputStr, i + 1, 1) + lType = oChar.getCharacterType(sChar, 0, oLocale) + ' Parenthses (), [], {} have a KCharacterType = 0 + bPrintable = ( (lType And lPRINTABLE) = lPRINTABLE Or (lType = 0 And Asc(sChar) <= 127) ) + If Not bPrintable Then Exit For + Next i + End If + +Finally: + IsPrintable = bPrintable + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsPrintable + +REM ----------------------------------------------------------------------------- +Public Function IsRegex(Optional ByRef InputStr As Variant _ + , Optional ByVal Regex As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Boolean +''' Returns True if the whole input string matches a given regular expression +''' Args: +''' InputStr: the input string +''' Regex: the regular expression as a string +''' CaseSensitive: default = False +''' Returns: +''' True if a match is found +''' Zero-length input or regex strings always return False +''' Examples: +''' SF_String.IsRegex("aAbB", "[A-Za-z]+") returns True + +Dim bRegex As Boolean ' Return value +Dim lStart As Long ' Must be 1 +Dim sMatch As String ' Matching string +Const cstBegin = "^" ' Beginning of line symbol +Const cstEnd = "$" ' End of line symbol +Const cstThisSub = "String.IsRegex" +Const cstSubArgs = "InputStr, Regex, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bRegex = False + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Regex, "Regex", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 And Len(Regex) > 0 Then + ' Whole string must match Regex + lStart = 1 + If Left(Regex, 1) <> cstBegin Then Regex = cstBegin & Regex + If Right(Regex, 1) <> cstEnd Then Regex = Regex & cstEnd + sMatch = SF_String.FindRegex(InputStr, Regex, lStart, CaseSensitive) + ' Match ? + bRegex = ( lStart = 1 And Len(sMatch) = Len(InputStr) ) + End If + +Finally: + IsRegex = bRegex + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsRegex + +REM ----------------------------------------------------------------------------- +Public Function IsSheetName(Optional ByRef InputStr As Variant) As Boolean +''' Return True if the input string can serve as a valid Calc sheet name +''' The sheet name must not contain the characters [ ] * ? : / \ +''' or the character ' (apostrophe) as first or last character. + +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is validated as a potential Calc sheet name, False otherwise +''' Examples: +''' SF_String.IsSheetName("1àbc + ""def""") returns True + +Dim bSheetName As Boolean ' Return value +Const cstThisSub = "String.IsSheetName" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bSheetName = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then + If Left(InputStr, 1) = "'" Or Right(InputStr, 1) = "'" Then + ElseIf InStr(InputStr, "[") _ + + InStr(InputStr, "]") _ + + InStr(InputStr, "*") _ + + InStr(InputStr, "?") _ + + InStr(InputStr, ":") _ + + InStr(InputStr, "/") _ + + InStr(InputStr, "\") _ + = 0 Then + bSheetName = True + End If + End If + +Finally: + IsSheetName = bSheetName + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsSheetName + +REM ----------------------------------------------------------------------------- +Public Function IsTitle(Optional ByRef InputStr As Variant) As Boolean +''' Return True if the 1st character of every word is in upper case and the other characters are in lower case +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string is capitalized and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsTitle("This Is A Title For Jean-Pierre") returns True + +Dim bTitle As Boolean ' Return value +Const cstThisSub = "String.IsTitle" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bTitle = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bTitle = ( StrComp(InputStr, SF_String.Capitalize(InputStr), 1) = 0 ) + +Finally: + IsTitle = bTitle + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsTitle + +REM ----------------------------------------------------------------------------- +Public Function IsUpper(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are in upper case +''' Non alphabetic characters are ignored +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains only upper case characters and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsUpper("ABC'(-XYZ") returns True + +Dim bUpper As Boolean ' Return value +Const cstThisSub = "String.IsUpper" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bUpper = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bUpper = ( StrComp(InputStr, UCase(InputStr), 1) = 0 ) + +Finally: + IsUpper = bUpper + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsUpper + +REM ----------------------------------------------------------------------------- +Public Function IsUrl(Optional ByRef InputStr As Variant) As Boolean +''' Return True if the string is a valid absolute URL (Uniform Resource Locator) +''' The parsing is done by the ParseStrict methof of the URLTransformer UNO service +''' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XURLTransformer.html +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains a URL and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsUrl("http://foo.bar/?q=Test%20URL-encoded%20stuff") returns True + +Dim bUrl As Boolean ' Return value +Const cstThisSub = "String.IsUrl" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bUrl = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bUrl = ( Len(SF_FileSystem._ParseUrl(InputStr).Main) > 0 ) + +Finally: + IsUrl = bUrl + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsUrl + +REM ----------------------------------------------------------------------------- +Public Function IsWhitespace(Optional ByRef InputStr As Variant) As Boolean +''' Return True if all characters in the string are whitespaces +''' Whitespaces include Space(32), HT(9), LF(10), VT(11), FF(12), CR(13), Next Line(133), No-break space(160), +''' Line separator(8232), Paragraph separator(8233) +''' Args: +''' InputStr: the input string +''' Returns: +''' True if the string contains only whitespaces and there is at least one character, False otherwise +''' Examples: +''' SF_String.IsWhitespace(" " & Chr(9) & Chr(10)) returns True + +Dim bWhitespace As Boolean ' Return value +Const cstThisSub = "String.IsWhitespace" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bWhitespace = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then bWhitespace = SF_String.IsRegex(InputStr, REGEXWHITESPACES, CaseSensitive := False) + +Finally: + IsWhitespace = bWhitespace + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.IsWhitespace + +REM ----------------------------------------------------------------------------- +Public Function JustifyCenter(Optional ByRef InputStr As Variant _ + , Optional ByVal Length As Variant _ + , Optional ByVal Padding As Variant _ + ) As String +''' Return the input string center justified +''' Args: +''' InputStr: the input string +''' Length: the resulting string length (default = length of input string) +''' Padding: the padding (single) character (default = the ascii space) +''' Returns: +''' The input string without its leading and trailing white spaces +''' completed left and right up to a total length of Length with the character Padding +''' If the input string is empty, the returned string is empty too +''' If the requested length is shorter than the center justified input string, +''' then the returned string is truncated +''' Examples: +''' SF_String.JustifyCenter(" ABCDE ", Padding := "x") returns "xxABCDEFxx" + +Dim sJustify As String ' Return value +Dim lLength As Long ' Length of input string +Dim lJustLength As Long ' Length of trimmed input string +Dim sPadding As String ' Series of Padding characters +Const cstThisSub = "String.JustifyCenter" +Const cstSubArgs = "InputStr, [length=Len(InputStr)], [Padding="" ""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sJustify = "" + +Check: + If IsMissing(Length) Or IsEmpty(Length) Then Length = 0 + If IsMissing(Padding) Or IsMissing(Padding) Then Padding = " " + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Length, "Length", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Padding, "Padding", V_STRING) Then GoTo Finally + End If + If Len(Padding) = 0 Then Padding = " " Else Padding = Left(Padding, 1) + +Try: + lLength = Len(InputStr) + If Length = 0 Then Length = lLength + If lLength > 0 Then + sJustify = SF_String.TrimExt(InputStr) ' Trim left and right + lJustLength = Len(sJustify) + If lJustLength > Length Then + sJustify = Mid(sJustify, Int((lJustLength - Length) / 2) + 1, Length) + ElseIf lJustLength < Length Then + sPadding = String(Int((Length - lJustLength) / 2), Padding) + sJustify = sPadding & sJustify & sPadding + If Len(sJustify) < Length Then sJustify = sJustify & Padding ' One Padding char is lacking when lJustLength is odd + End If + End If + +Finally: + JustifyCenter = sJustify + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.JustifyCenter + +REM ----------------------------------------------------------------------------- +Public Function JustifyLeft(Optional ByRef InputStr As Variant _ + , Optional ByVal Length As Variant _ + , Optional ByVal Padding As Variant _ + ) As String +''' Return the input string left justified +''' Args: +''' InputStr: the input string +''' Length: the resulting string length (default = length of input string) +''' Padding: the padding (single) character (default = the ascii space) +''' Returns: +''' The input string without its leading white spaces +''' filled up to a total length of Length with the character Padding +''' If the input string is empty, the returned string is empty too +''' If the requested length is shorter than the left justified input string, +''' then the returned string is truncated +''' Examples: +''' SF_String.JustifyLeft(" ABCDE ", Padding := "x") returns "ABCDE xxx" + +Dim sJustify As String ' Return value +Dim lLength As Long ' Length of input string +Const cstThisSub = "String.JustifyLeft" +Const cstSubArgs = "InputStr, [length=Len(InputStr)], [Padding="" ""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sJustify = "" + +Check: + If IsMissing(Length) Or IsEmpty(Length) Then Length = 0 + If IsMissing(Padding) Or IsMissing(Padding) Then Padding = " " + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Length, "Length", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Padding, "Padding", V_STRING) Then GoTo Finally + End If + If Len(Padding) = 0 Then Padding = " " Else Padding = Left(Padding, 1) + +Try: + lLength = Len(InputStr) + If Length = 0 Then Length = lLength + If lLength > 0 Then + sJustify = SF_String.ReplaceRegex(InputStr, REGEXLTRIM, "") ' Trim left + If Len(sJustify) >= Length Then + sJustify = Left(sJustify, Length) + Else + sJustify = sJustify & String(Length - Len(sJustify), Padding) + End If + End If + +Finally: + JustifyLeft = sJustify + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.JustifyLeft + +REM ----------------------------------------------------------------------------- +Public Function JustifyRight(Optional ByRef InputStr As Variant _ + , Optional ByVal Length As Variant _ + , Optional ByVal Padding As Variant _ + ) As String +''' Return the input string right justified +''' Args: +''' InputStr: the input string +''' Length: the resulting string length (default = length of input string) +''' Padding: the padding (single) character (default = the ascii space) +''' Returns: +''' The input string without its trailing white spaces +''' preceded up to a total length of Length with the character Padding +''' If the input string is empty, the returned string is empty too +''' If the requested length is shorter than the right justified input string, +''' then the returned string is right-truncated +''' Examples: +''' SF_String.JustifyRight(" ABCDE ", Padding := "x") returns "x ABCDE" + +Dim sJustify As String ' Return value +Dim lLength As Long ' Length of input string +Const cstThisSub = "String.JustifyRight" +Const cstSubArgs = "InputStr, [length=Len(InputStr)], [Padding="" ""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sJustify = "" + +Check: + If IsMissing(Length) Or IsEmpty(Length) Then Length = 0 + If IsMissing(Padding) Or IsMissing(Padding) Then Padding = " " + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Length, "Length", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Padding, "Padding", V_STRING) Then GoTo Finally + End If + If Len(Padding) = 0 Then Padding = " " Else Padding = Left(Padding, 1) + +Try: + lLength = Len(InputStr) + If Length = 0 Then Length = lLength + If lLength > 0 Then + sJustify = SF_String.ReplaceRegex(InputStr, REGEXRTRIM, "") ' Trim right + If Len(sJustify) >= Length Then + sJustify = Right(sJustify, Length) + Else + sJustify = String(Length - Len(sJustify), Padding) & sJustify + End If + End If + +Finally: + JustifyRight = sJustify + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.JustifyRight + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the String service as an array + + Methods = Array( _ + "Capitalize" _ + , "Count" _ + , "EndWith" _ + , "Escape" _ + , "ExpandTabs" _ + , "FilterNotPrintable" _ + , "FindRegex" _ + , "HashStr" _ + , "HtmlEncode" _ + , "IsADate" _ + , "IsAlpha" _ + , "IsAlphaNum" _ + , "IsAscii" _ + , "IsDigit" _ + , "IsEmail" _ + , "IsFileName" _ + , "IsHexDigit" _ + , "IsIPv4" _ + , "IsLike" _ + , "IsLower" _ + , "IsPrintable" _ + , "IsRegex" _ + , "IsSheetName" _ + , "IsTitle" _ + , "IsUpper" _ + , "IsUrl" _ + , "IsWhitespace" _ + , "JustifyCenter" _ + , "JustifyLeft" _ + , "JustifyRight" _ + , "Quote" _ + , "ReplaceChar" _ + , "ReplaceRegex" _ + , "ReplaceStr" _ + , "Represent" _ + , "Reverse" _ + , "SplitLines" _ + , "SplitNotQuoted" _ + , "StartsWith" _ + , "TrimExt" _ + , "Unescape" _ + , "Unquote" _ + , "Wrap" _ + ) + +End Function ' ScriptForge.SF_String.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties as an array + + Properties = Array( _ + "sfCR" _ + , "sfCRLF" _ + , "sfLF" _ + , "sfNEWLINE" _ + , "sfTAB" _ + ) + +End Function ' ScriptForge.SF_Session.Properties + +REM ----------------------------------------------------------------------------- +Public Function Quote(Optional ByRef InputStr As Variant _ + , Optional ByVal QuoteChar As String _ + ) As String +''' Return the input string surrounded with double quotes +''' Used f.i. to prepare a string field to be stored in a csv-like file +''' Args: +''' InputStr: the input string +''' QuoteChar: either " (default) or ' +''' Returns: +''' Existing - including leading and/or trailing - double quotes are doubled +''' Examples: +''' SF_String.Quote("àé""n ΣlPµ Русский") returns """àé""""n ΣlPµ Русский""" + +Dim sQuote As String ' Return value +Const cstDouble = """" : Const cstSingle = "'" +Const cstEscape = "\" +Const cstThisSub = "String.Quote" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sQuote = "" + +Check: + If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(QuoteChar, "QuoteChar", V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally + End If + +Try: + If QuoteChar = cstDouble Then + sQuote = cstDouble & Replace(InputStr, cstDouble, cstDouble & cstDouble) & cstDouble + Else + sQuote = Replace(InputStr, cstEscape, cstEscape & cstEscape) + sQuote = cstSingle & Replace(sQuote, cstSingle, cstEscape & cstSingle) & cstSingle + End If + +Finally: + Quote = sQuote + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Quote + +REM ----------------------------------------------------------------------------- +Public Function ReplaceChar(Optional ByRef InputStr As Variant _ + , Optional ByVal Before As Variant _ + , Optional ByVal After As Variant _ + ) As String +''' Replace in InputStr all occurrences of any character from Before +''' by the corresponding character in After +''' Args: +''' InputStr: the input string on which replacements should occur +''' Before: a string of characters to replace 1 by 1 in InputStr +''' After: the replacing characters +''' Returns: +''' The new string after replacement of Nth character of Before by the Nth character of After +''' Replacements are done one by one => potential overlaps +''' If the length of Before is larger than the length of After, +''' the residual characters of Before are replaced by the last character of After +''' The input string when Before is the zero-length string +''' Examples: easily remove accents +''' SF_String.ReplaceChar("Protégez votre vie privée", "àâãçèéêëîïôöûüýÿ", "aaaceeeeiioouuyy") +''' returns "Protegez votre vie privee" +''' SF_String.ReplaceChar("Protégez votre vie privée", SF_String.CHARSWITHACCENT, SF_String.CHARSWITHOUTACCENT) + +Dim sOutput As String ' Return value +Dim iCaseSensitive As Integer ' Always 0 (True) +Dim sBefore As String ' A single character extracted from InputStr +Dim sAfter As String ' A single character extracted from After +Dim lInStr As Long ' Output of InStr() +Dim i As Long +Const cstThisSub = "String.ReplaceChar" +Const cstSubArgs = "InputStr, Before, After" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sOutput = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Before, "Before", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(After, "After", V_STRING) Then GoTo Finally + End If + +Try: + ' Replace standard function => Replace(string, before, after, start, occurrences, casesensitive) + sOutput = InputStr + iCaseSensitive = 0 + + ' Replace one by one up length of Before and After + If Len(Before) > 0 Then + i = 1 + Do While i <= Len(sOutput) + sBefore = Mid(sOutput, i, 1) + lInStr = InStr(1, Before, sBefore, iCaseSensitive) + If lInStr > 0 Then + If Len(After) = 0 Then + sAfter = "" + ElseIf lInStr > Len(After) Then + sAfter = Right(After, 1) + Else + sAfter = Mid(After, lInStr, 1) + End If + sOutput = Left(sOutput, i - 1) & Replace(sOutput, sBefore, sAfter, i, Empty, iCaseSensitive) + End If + i = i + 1 + Loop + End If + +Finally: + ReplaceChar = sOutput + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.ReplaceChar + +REM ----------------------------------------------------------------------------- +Public Function ReplaceRegex(Optional ByRef InputStr As Variant _ + , Optional ByVal Regex As Variant _ + , Optional ByRef NewStr As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As String +''' Replace in InputStr all occurrences of a given regular expression by NewStr +''' Args: +''' InputStr: the input string where replacements should occur +''' Regex: the regular expression +''' NewStr: the replacing string +''' CaseSensitive: default = False +''' Returns: +''' The new string after all replacements +''' Examples: +''' SF_String.ReplaceRegex("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "[a-z]", "x", CaseSensitive := True) +''' returns "Lxxxx xxxxx xxxxx xxx xxxx, xxxxxxxxxxx xxxxxxxxxx xxxx." +''' SF_String.ReplaceRegex("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "\b[a-z]+\b", "x", CaseSensitive := False) +''' returns "x x x x x, x x x." (each word is replaced by x) + + +Dim sOutput As String ' Return value +Dim lStartOld As Long ' Previous start of search +Dim lStartNew As Long ' Next start of search +Dim sSubstring As String ' Substring to replace +Const cstThisSub = "String.ReplaceRegex" +Const cstSubArgs = "InputStr, Regex, NewStr, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sOutput = "" + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Regex, "Regex", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(NewStr, "NewStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + sOutput = "" + lStartNew = 1 + lStartOld = 1 + + Do While lStartNew >= 1 And lStartNew <= Len(InputStr) + sSubstring = SF_String.FindRegex(InputStr, Regex, lStartNew, CaseSensitive) + If lStartNew = 0 Then ' Regex not found + ' Copy remaining substring of InputStr before leaving + sOutput = sOutput & Mid(InputStr, lStartOld) + Exit Do + End If + ' Append the interval between 2 occurrences and the replacing string + If lStartNew > lStartOld Then sOutput = sOutput & Mid(InputStr, lStartOld, lStartNew - lStartOld) + sOutput = sOutput & NewStr + lStartOld = lStartNew + Len(sSubstring) + lStartNew = lStartOld + Loop + +Finally: + ReplaceRegex = sOutput + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.ReplaceRegex + +REM ----------------------------------------------------------------------------- +Public Function ReplaceStr(Optional ByRef InputStr As Variant _ + , Optional ByVal OldStr As Variant _ + , Optional ByVal NewStr As Variant _ + , Optional ByVal Occurrences As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As String +''' Replace in InputStr some or all occurrences of OldStr by NewStr +''' Args: +''' InputStr: the input string on which replacements should occur +''' OldStr: the string to replace or a 1D array of strings to replace +''' Zero-length strings are ignored +''' NewStr: the replacing string or a 1D array of replacing strings +''' If OldStr is an array +''' each occurrence of any of the items of OldStr is replaced by NewStr +''' If OldStr and NewStr are arrays +''' replacements occur one by one up to the UBound of NewStr +''' remaining OldStr(ings) are replaced by the last element of NewStr +''' Occurrences: the maximum number of replacements (0, default, = all occurrences) +''' Is applied for each single replacement when OldStr is an array +''' CaseSensitive: True or False (default) +''' Returns: +''' The new string after replacements +''' Replacements are done one by one when OldStr is an array => potential overlaps +''' Examples: +''' SF_String.ReplaceStr("abCcdefghHij", Array("c", "h"), Array("Y", "Z"), CaseSensitive := False) returns "abYYdefgZZij" + +Dim sOutput As String ' Return value +Dim iCaseSensitive As Integer ' Integer alias for boolean CaseSensitive +Dim vOccurrences As Variant ' Variant alias for Integer Occurrences +Dim sNewStr As String ' Alias for a NewStr item +Dim i As Long, j As Long +Const cstThisSub = "String.ReplaceStr" +Const cstSubArgs = "InputStr, OldStr, NewStr, [Occurrences=0], [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sOutput = "" + +Check: + If IsMissing(Occurrences) Or IsEmpty(Occurrences) Then Occurrences = 0 + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If IsArray(OldStr) Then + If Not SF_Utils._ValidateArray(OldStr, "OldStr", 1, V_STRING, True) Then GoTo Finally + Else + If Not SF_Utils._Validate(OldStr, "OldStr", V_STRING) Then GoTo Finally + End If + If IsArray(NewStr) Then + If Not SF_Utils._ValidateArray(NewStr, "NewStr", 1, V_STRING, True) Then GoTo Finally + Else + If Not SF_Utils._Validate(NewStr, "NewStr", V_STRING) Then GoTo Finally + End If + If Not SF_Utils._Validate(Occurrences, "Occurrences", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + ' Replace standard function => Replace(string, before, after, start, occurrences, casesensitive) + sOutput = InputStr + iCaseSensitive = Iif(CaseSensitive, 0, 1) ' 1 = False ;) + vOccurrences = Iif(Occurrences = 0, Empty, Occurrences) ' Empty = no limit + If Not IsArray(OldStr) Then OldStr = Array(OldStr) + If Not IsArray(NewStr) Then NewStr = Array(NewStr) + + ' Replace one by one up to UBounds of Old and NewStr + j = LBound(NewStr) - 1 + For i = LBound(OldStr) To UBound(OldStr) + j = j + 1 + If j <= UBound(NewStr) Then sNewStr = NewStr(j) ' Else do not change + If StrComp(OldStr(i), sNewStr, 1) <> 0 Then + sOutput = Replace(sOutput, OldStr(i), sNewStr, 1, vOccurrences, iCaseSensitive) + End If + Next i + +Finally: + ReplaceStr = sOutput + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.ReplaceStr + +REM ----------------------------------------------------------------------------- +Public Function Represent(Optional ByRef AnyValue As Variant _ + , Optional ByVal MaxLength As Variant _ + ) As String +''' Return a readable (string) form of the argument, truncated at MaxLength +''' Args: +''' AnyValue: really any value (object, date, whatever) +''' MaxLength: the maximum length of the resulting string (Default = 0, unlimited) +''' Returns: +''' The argument converted or transformed into a string of a maximum length = MaxLength +''' Objects are surrounded with square brackets ([]) +''' In strings, tabs and line breaks are replaced by \t, \n or \r +''' If the effective length exceeds MaxLength, the final part of the string is replaced by " ... (N)" +''' where N = the total length of the string before truncation +''' Examples: +''' SF_String.Represent("this is a usual string") returns "this is a usual string" +''' SF_String.Represent("this is a usual string", 15) returns "this i ... (22)" +''' SF_String.Represent("this is a" & Chr(10) & " 2-lines string") returns "this is a\n 2-lines string" +''' SF_String.Represent(Empty) returns "[EMPTY]" +''' SF_String.Represent(Null) returns "[NULL]" +''' SF_String.Represent(Pi) returns "3.142" +''' SF_String.Represent(CreateUnoService("com.sun.star.util.PathSettings")) returns "[com.sun.star.comp.framework.PathSettings]" +''' SF_String.Represent(Array(1, 2, "Text" & Chr(9) & "here")) returns "[ARRAY] (0:2) (1, 2, Text\there)" +''' Dim myDict As Variant : myDict = CreateScriptService("Dictionary") +''' myDict.Add("A", 1) : myDict.Add("B", 2) +''' SF_String.Represent(myDict) returns "[Dictionary] ("A":1, "B":2)" + +Dim sRepr As String ' Return value +Const cstThisSub = "String.Represent" +Const cstSubArgs = "AnyValue, [MaxLength=0]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sRepr = "" + +Check: + If IsMissing(AnyValue) Then AnyValue = Empty + If IsMissing(MaxLength) Or IsEmpty(MaxLength) Then MaxLength = 0 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(MaxLength, "MaxLength", V_NUMERIC) Then GoTo Finally + End If + +Try: + sRepr = SF_Utils._Repr(AnyValue, MaxLength) + If MaxLength > 0 And MaxLength < Len(sRepr) Then sRepr = sRepr & " ... (" & Len(sRepr) & ")" + +Finally: + Represent = sRepr + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Represent + +REM ----------------------------------------------------------------------------- +Public Function Reverse(Optional ByRef InputStr As Variant) As String +''' Return the input string in reversed order +''' It is equivalent to the standard StrReverse Basic function +''' The latter requires the OpTion VBASupport 1 statement to be present in the module +''' Args: +''' InputStr: the input string +''' Returns: +''' The input string in reversed order +''' Examples: +''' SF_String.Reverse("abcdefghij") returns "jihgfedcba" + +Dim sReversed As String ' Return value +Dim lLength As Long ' Length of input string +Dim i As Long +Const cstThisSub = "String.Reverse" +Const cstSubArgs = "InputSt" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sReversed = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + lLength = Len(InputStr) + If lLength > 0 Then + sReversed = Space(lLength) + For i = 1 To lLength + Mid(sReversed, i, 1) = Mid(InputStr, lLength - i + 1) + Next i + End If + +Finally: + Reverse = sReversed + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Reverse + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "String.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.SetProperty + +REM ----------------------------------------------------------------------------- +Public Function SplitLines(Optional ByRef InputStr As Variant _ + , Optional ByVal KeepBreaks As Variant _ + ) As Variant +''' Return an array of the lines in a string, breaking at line boundaries +''' Line boundariess include LF(10), VT(12), CR(13), LF+CR, File separator(28), Group separator(29), Record separator(30), +''' Next Line(133), Line separator(8232), Paragraph separator(8233) +''' Args: +''' InputStr: the input string +''' KeepBreaks: when True, line breaks are preserved in the output array (default = False) +''' Returns: +''' An array of all the individual lines +''' Examples: +''' SF_String.SplitLines("Line1" & Chr(10) & "Line2" & Chr(13) & "Line3") returns ("Line1", "Line2", "Line3") +''' SF_String.SplitLines("Line1" & Chr(10) & "Line2" & Chr(13) & "Line3" & Chr(10)) returns ("Line1", "Line2", "Line3", "") + +Dim vSplit As Variant ' Return value +Dim vLineBreaks As Variant ' Array of recognized line breaks +Dim vTokenizedBreaks As Variant ' Array of line breaks extended with tokens +Dim sAlias As String ' Alias for input string +' The procedure uses (dirty) placeholders to identify line breaks +' The used tokens are presumed unlikely present in text strings +Dim sTokenCRLF As String ' Token to identify combined CR + LF +Dim sToken As String ' Token to identify any line break +Dim i As Long +Const cstThisSub = "String.SplitLines" +Const cstSubArgs = "InputStr, [KeepBreaks=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSplit = Array() + +Check: + If IsMissing(KeepBreaks) Or IsEmpty(KeepBreaks) Then KeepBreaks = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(KeepBreaks, "KeepBreaks", V_BOOLEAN) Then GoTo Finally + End If + +Try: + ' In next list CR + LF must preceede CR and LF + vLineBreaks = Array(SF_String.sfCRLF, SF_String.sfLF, Chr(12), SF_String.sfCR _ + , Chr(28), Chr(29), Chr(30), Chr(133), Chr(8232), Chr(8233)) + + If KeepBreaks = False Then + ' Replace line breaks by linefeeds and split on linefeeds + vSplit = Split(SF_String.ReplaceStr(InputStr, vLineBreaks, SF_String.sfLF, CaseSensitive := False), SF_String.sfLF) + Else + sTokenCRLF = Chr(1) & "$" & Chr(2) & "*" & Chr(3) & "$" & Chr(1) + sToken = Chr(1) & "$" & Chr(2) & "*" & Chr(3) & "$" & Chr(2) + vTokenizedBreaks = Array() : ReDim vTokenizedBreaks(0 To UBound(vLineBreaks)) + ' Extend breaks with token + For i = 0 To UBound(vLineBreaks) + vTokenizedBreaks(i) = Iif(i = 0, sTokenCRLF, vLineBreaks(i)) & sToken + Next i + sAlias = SF_String.ReplaceStr(InputStr, vLineBreaks, vTokenizedBreaks, CaseSensitive := False) + ' Suppress CRLF tokens and split + vSplit = Split(Replace(sAlias, sTokenCRLF, SF_String.sfCRLF), sToken) + End If + +Finally: + SplitLines = vSplit + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.SplitLines + +REM ----------------------------------------------------------------------------- +Public Function SplitNotQuoted(Optional ByRef InputStr As Variant _ + , Optional ByVal Delimiter As Variant _ + , Optional ByVal Occurrences As Variant _ + , Optional ByVal QuoteChar As Variant _ + ) As Variant +''' Split a string on Delimiter into an array. If Delimiter is part of a quoted (sub)string, it is ignored +''' (used f.i. for parsing of csv-like records) +''' Args: +''' InputStr: the input string +''' Might contain quoted substrings: +''' The quoting chararacter must be the double quote (") +''' To preserve a quoting character inside the quoted substring, use (\) or (") as escape character +''' => [str\"i""ng] means [str"i"ng] +''' Delimiter: A string of one or more characters that is used to delimit the input string +''' The default is the space character +''' Occurrences: The number of substrings to return (Default = 0, meaning no limit) +''' QuoteChar: The quoting character, either " (default) or ' +''' Returns: +''' An array whose items are chunks of the input string, Delimiter not included +''' Examples: +''' SF_String.SplitNotQuoted("abc def ghi") returns ("abc", "def", "ghi") +''' SF_String.SplitNotQuoted("abc,""def,ghi""", ",") returns ("abc", """def,ghi""") +''' SF_String.SplitNotQuoted("abc,""def\"",ghi""", ",") returns ("abc", """def\"",ghi""") +''' SF_String.SplitNotQuoted("abc,""def\"",ghi"""",", ",") returns ("abc", """def\"",ghi""", "") + +Dim vSplit As Variant ' Return value +Dim lDelimLen As Long ' Length of Delimiter +Dim vStart As Variant ' Array of start positions of quoted strings +Dim vEnd As Variant ' Array of end positions of quoted strings +Dim lInStr As Long ' InStr() on input string +Dim lInStrPrev As Long ' Previous value of lInputStr +Dim lBound As Long ' UBound of vStart and vEnd +Dim lMin As Long ' Lower bound to consider when searching vStart and vEnd +Dim oCharacterClass As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim oParse As Object ' com.sun.star.i18n.ParseResult +Dim sChunk As String ' Substring of InputStr +Dim bSplit As Boolean ' New chunk found or not +Dim i As Long +Const cstDouble = """" : Const cstSingle = "'" +Const cstThisSub = "String.SplitNotQuoted" +Const cstSubArgs = "InputStr, [Delimiter="" ""], [Occurrences=0], [QuoteChar=""" & cstDouble & """" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vSplit = Array() + +Check: + If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = " " + If IsMissing(Occurrences) Or IsEmpty(Occurrences) Then Occurrences = 0 + If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Delimiter, "Delimiter", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Occurrences, "Occurrences", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(QuoteChar, "QuoteChar", V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally + End If + If Len(Delimiter) = 0 Then Delimiter = " " + +Try: + If Occurrences = 1 Or InStr(1, InputStr, Delimiter, 0) = 0 Then ' No reason to split + vSplit = Array(InputStr) + ElseIf InStr(1, InputStr, QuoteChar, 0) = 0 Then ' No reason to make a complex split + If Occurrences > 0 Then vSplit = Split(InputStr, Delimiter, Occurrences) Else vSplit = Split(InputStr, Delimiter) + Else + If Occurrences < 0 Then Occurrences = 0 + Set oCharacterClass = SF_Utils._GetUNOService("CharacterClass") + Set oLocale = SF_Utils._GetUNOService("Locale") + + ' Build an array of start/end positions of quoted strings containing at least 1x the Delimiter + vStart = Array() : vEnd = Array() + lInStr = InStr(1, InputStr, QuoteChar) + Do While lInStr > 0 + lBound = UBound(vStart) + ' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html#ad5f1be91fbe86853200391f828d4166b + Set oParse = oCharacterClass.parsePredefinedToken( _ + Iif(QuoteChar = cstDouble, com.sun.star.i18n.KParseType.DOUBLE_QUOTE_STRING, com.sun.star.i18n.KParseType.SINGLE_QUOTE_NAME) _ + , InputStr, lInStr - 1, oLocale, 0, "", 0, "") + If oParse.CharLen > 0 Then ' Is parsing successful ? + ' Is there some delimiter ? + If InStr(1, oParse.DequotedNameOrString, Delimiter, 0) > 0 Then + vStart = SF_Array.Append(vStart, lInStr + 0) + vEnd = SF_Array.Append(vEnd, lInStr + oParse.CharLen - 1) + End If + lInStr = InStr(lInStr + oParse.CharLen, InputStr, QuoteChar) + Else + lInStr = 0 + End If + Loop + + lBound = UBound(vStart) + lDelimLen = Len(Delimiter) + If lBound < 0 Then ' Usual split is applicable + vSplit = Split(InputStr, Delimiter, Occurrences) + Else + ' Split chunk by chunk + lMin = 0 + lInStrPrev = 0 + lInStr = InStr(1, InputStr, Delimiter, 0) + Do While lInStr > 0 + If Occurrences > 0 And Occurrences = UBound(vSplit) - 1 Then Exit Do + bSplit = False + ' Ignore found Delimiter if in quoted string + For i = lMin To lBound + If lInStr < vStart(i) Then + bSplit = True + Exit For + ElseIf lInStr > vStart(i) And lInStr < vEnd (i) Then + Exit For + Else + lMin = i + 1 + If i = lBound Then bSplit = True Else bSplit = ( lInStr < vStart(lMin) ) + End If + Next i + ' Build next chunk and store in split array + If bSplit Then + If lInStrPrev = 0 Then ' First chunk + sChunk = Left(InputStr, lInStr - 1) + Else + sChunk = Mid(InputStr, lInStrPrev + lDelimLen, lInStr - lInStrPrev - lDelimLen) + End If + vSplit = SF_Array.Append(vSplit, sChunk & "") + lInStrPrev = lInStr + End If + lInStr = InStr(lInStr + lDelimLen, InputStr, Delimiter, 0) + Loop + If Occurrences = 0 Or Occurrences > UBound(vSplit) + 1 Then + sChunk = Mid(InputStr, lInStrPrev + lDelimLen) ' Append last chunk + vSplit = SF_Array.Append(vSplit, sChunk & "") + End If + End If + End If + +Finally: + SplitNotQuoted = vSplit + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.SplitNotQuoted + +REM ----------------------------------------------------------------------------- +Public Function StartsWith(Optional ByRef InputStr As Variant _ + , Optional ByVal Substring As Variant _ + , Optional ByVal CaseSensitive As Variant _ + ) As Boolean +''' Returns True if the first characters of InputStr are identical to Substring +''' Args: +''' InputStr: the input string +''' Substring: the prefixing characters +''' CaseSensitive: default = False +''' Returns: +''' True if the comparison is satisfactory +''' False if either InputStr or Substring have a length = 0 +''' False if Substr is longer than InputStr +''' Examples: +''' SF_String.StartsWith("abcdefg", "ABC") returns True +''' SF_String.StartsWith("abcdefg", "ABC", CaseSensitive := True) returns False + +Dim bStartsWith As Boolean ' Return value +Dim lSub As Long ' Length of SUbstring +Const cstThisSub = "String.StartsWith" +Const cstSubArgs = "InputStr, Substring, [CaseSensitive=False]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bStartsWith = False + +Check: + If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Substring, "Substring", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(CaseSensitive, "CaseSensitive", V_BOOLEAN) Then GoTo Finally + End If + +Try: + lSub = Len(Substring) + If Len(InputStr) > 0 And lSub > 0 And lSub <= Len(InputStr) Then + bStartsWith = ( StrComp(Left(InputStr, lSub), Substring, Iif(CaseSensitive, 1, 0)) = 0 ) + End If + +Finally: + StartsWith = bStartsWith + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.StartsWith + +REM ----------------------------------------------------------------------------- +Public Function TrimExt(Optional ByRef InputStr As Variant) As String +''' Return the input string without its leading and trailing whitespaces +''' Args: +''' InputStr: the input string +''' Returns: +''' The input string without its leading and trailing white spaces +''' Examples: +''' SF_String.TrimExt(" ABCDE" & Chr(9) & Chr(10) & Chr(13) & " ") returns "ABCDE" + +Dim sTrim As String ' Return value +Const cstThisSub = "String.TrimExt" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sTrim = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then + sTrim = SF_String.ReplaceRegex(InputStr, REGEXLTRIM, "") ' Trim left + sTrim = SF_String.ReplaceRegex(sTrim, REGEXRTRIM, "") ' Trim right + End If + +Finally: + TrimExt = sTrim + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.TrimExt + +REM ----------------------------------------------------------------------------- +Public Function Unescape(Optional ByRef InputStr As Variant) As String +''' Convert any escaped characters in the input string +''' Args: +''' InputStr: the input string +''' Returns: +''' The input string after replacement of \\, \n, \r, \t sequences +''' Examples: +''' SF_String.Unescape("abc\n\tdef\\n") returns "abc" & Chr(10) & Chr(9) & "def\n" + +Dim sUnescape As String ' Return value +Dim sToken As String ' Placeholder unlikely to be present in input string +Const cstThisSub = "String.Unescape" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sUnescape = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + End If + +Try: + sToken = Chr(1) & "$" & Chr(2) & "*" & Chr(3) & "$" & Chr(1) ' Placeholder for "\\" + sUnescape = SF_String.ReplaceStr( InputStr _ + , Array("\\", "\n", "\r", "\t", sToken) _ + , Array(sToken, SF_String.sfLF, SF_String.sfCR, SF_String.sfTAB, "\") _ + ) + +Finally: + Unescape = sUnescape + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Unescape + +REM ----------------------------------------------------------------------------- +Public Function Unquote(Optional ByRef InputStr As Variant _ + , Optional ByVal QuoteChar As String _ + ) As String +''' Reset a quoted string to its original content +''' (used f.i. for parsing of csv-like records) +''' Args: +''' InputStr: the input string +''' QuoteChar: either " (default) or ' +''' Returns: +''' The input string after removal of leading/trailing quotes and escaped single/double quotes +''' The input string if not a quoted string +''' Examples: +''' SF_String.Unquote("""àé""""n ΣlPµ Русский""") returns "àé""n ΣlPµ Русский" + +Dim sUnquote As String ' Return value +Dim oCharacterClass As Object ' com.sun.star.i18n.CharacterClassification +Dim oLocale As Object ' com.sun.star.lang.Locale +Dim oParse As Object ' com.sun.star.i18n.ParseResult +Const cstDouble = """" : Const cstSingle = "'" +Const cstThisSub = "String.Unquote" +Const cstSubArgs = "InputStr" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sUnquote = "" + +Check: + If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(QuoteChar, "QuoteChar", V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally + End If + +Try: + If Left(InputStr, 1) <> """" Then ' No need to parse further + sUnquote = InputStr + Else + Set oCharacterClass = SF_Utils._GetUNOService("CharacterClass") + Set oLocale = SF_Utils._GetUNOService("Locale") + + ' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html#ad5f1be91fbe86853200391f828d4166b + Set oParse = oCharacterClass.parsePredefinedToken( _ + Iif(QuoteChar = cstDouble, com.sun.star.i18n.KParseType.DOUBLE_QUOTE_STRING, com.sun.star.i18n.KParseType.SINGLE_QUOTE_NAME) _ + , InputStr, 0, oLocale, 0, "", 0, "") + If oParse.CharLen > 0 Then ' Is parsing successful ? + sUnquote = oParse.DequotedNameOrString + Else + sUnquote = InputStr + End If + End If + +Finally: + Unquote = sUnquote + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Unquote + +REM ----------------------------------------------------------------------------- +Public Function Wrap(Optional ByRef InputStr As Variant _ + , Optional ByVal Width As Variant _ + , Optional ByVal TabSize As Variant _ + ) As Variant +''' Wraps every single paragraph in text (a string) so every line is at most Width characters long +''' Args: +''' InputStr: the input string +''' Width: the maximum number of characters in each line, default = 70 +''' TabSize: before wrapping the text, the existing TAB (Chr(9)) characters are replaced with spaces. +''' TabSize defines the TAB positions at TabSize + 1, 2 * TabSize + 1 , ... N * TabSize + 1 +''' Default = 8 +''' Returns: +''' Returns a zero-based array of output lines, without final newlines except the pre-existing line-breaks +''' Tabs are expanded. Symbolic line breaks are replaced by their hard equivalents +''' If the wrapped output has no content, the returned array is empty. +''' Examples: +''' SF_String.Wrap("Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", 20) + +Dim vWrap As Variant ' Return value +Dim vWrapLines ' Input string split on line breaks +Dim sWrap As String ' Intermediate string +Dim sLine As String ' Line after splitting on line breaks +Dim lPos As Long ' Position in sLine already wrapped +Dim lStart As Long ' Start position before and after regex search +Dim sSpace As String ' Next whitepaces +Dim sChunk As String ' Next wrappable text chunk +Const cstThisSub = "String.Wrap" +Const cstSubArgs = "InputStr, [Width=70], [TabSize=8]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vWrap = Array() + +Check: + If IsMissing(Width) Or IsEmpty(Width) Then Width = 70 + If IsMissing(TabSize) Or IsEmpty(TabSize) Then TabSize = 8 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(InputStr, "InputStr", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Width, "Width", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(TabSize, "TabSize", V_NUMERIC) Then GoTo Finally + End If + +Try: + If Len(InputStr) > 0 Then + sWrap = SF_String.Unescape(InputStr) ' Replace symbolic breaks + sWrap = SF_String.ExpandTabs(sWrap, TabSize) ' Interprete TABs to have a meaningful Width + ' First, split full string + vWrapLines = SF_String.SplitLines(sWrap, KeepBreaks := True) ' Keep pre-existing breaks + If UBound(vWrapLines) = 0 And Len(sWrap) <= Width Then ' Output a single line + vWrap = Array(sWrap) + Else + ' Second, split each line on Width + For Each sLine In vWrapLines + If Len(sLine) <= Width Then + If UBound(vWrap) < 0 Then vWrap = Array(sLine) Else vWrap = SF_Array.Append(vWrap, sLine) + Else + ' Scan sLine and accumulate found substrings up to Width + lStart = 1 + lPos = 0 + sWrap = "" + Do While lStart <= Len(sLine) + sSpace = SF_String.FindRegex(sLine, REGEXSPACES, lStart) + If lStart = 0 Then lStart = Len(sLine) + 1 + sChunk = Mid(sLine, lPos + 1, lStart - 1 - lPos + Len(sSpace)) + If Len(sWrap) + Len(sChunk) < Width Then ' Add chunk to current piece of line + sWrap = sWrap & sChunk + Else ' Save current line and initialize next one + If UBound(vWrap) < 0 Then vWrap = Array(sWrap) Else vWrap = SF_Array.Append(vWrap, sWrap) + sWrap = sChunk + End If + lPos = lPos + Len(sChunk) + lStart = lPos + 1 + Loop + ' Add last chunk + If Len(sWrap) > 0 Then + If UBound(vWrap) < 0 Then vWrap = Array(sWrap) Else vWrap = SF_Array.Append(vWrap, sWrap) + End If + End If + Next sLine + End If + End If + +Finally: + Wrap = vWrap + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_String.Wrap + +REM ============================================================= PRIVATE METHODS + +REM ----------------------------------------------------------------------------- +Private Function _Repr(ByRef pvString As String) As String +''' Convert an arbitrary string to a readable string, typically for debugging purposes (DebugPrint ...) +''' Carriage Returns are replaced by \r. Other line breaks are replaced by \n +''' Tabs are replaced by \t +''' Backslashes are doubled +''' Other non printable characters are replaced by \x00 to \xFF or \x0000 to \xFFFF +''' Args: +''' pvString: the string to make readable +''' Return: +''' the converted string + +Dim sString As String ' Return value +Dim sChar As String ' A single chararacter +Dim lAsc As Long ' Ascii value +Dim lPos As Long ' Position in sString +Dim i As Long + + ' Process TABs, CRs and LFs + sString = Replace(Replace(Replace(pvString, "\", "\\"), SF_String.sfCR, "\r"), SF_String.sfTAB, "\t") + sString = Join(SF_String.SplitLines(sString, KeepBreaks := False), "\n") + ' Process not printable characters + If Len(sString) > 0 Then + lPos = 1 + Do While lPos <= Len(sString) + sChar = Mid(sString, lPos, 1) + If Not SF_String.IsPrintable(sChar) Then + lAsc = Asc(sChar) + sChar = "\x" & Iif(lAsc < 255, Right("00" & Hex(lAsc, 2)), Right("0000" & Hex(lAsc, 4))) + If lPos < Len(sString) Then + sString = Left(sString, lPos - 1) & sChar & Mid(sString, lPos + 1) + Else + sString = Left(sString, lPos - 1) & sChar + End If + End If + lPos = lPos + Len(sChar) + Loop + End If + + _Repr = sString + +End Function ' ScriptForge.SF_String._Repr + +REM ================================================ END OF SCRIPTFORGE.SF_STRING +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_TextStream.xba b/wizards/source/scriptforge/SF_TextStream.xba new file mode 100644 index 000000000000..6b042f92e435 --- /dev/null +++ b/wizards/source/scriptforge/SF_TextStream.xba @@ -0,0 +1,701 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_TextStream" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_TextStream +''' ============= +''' Class instantiated by the +''' SF_FileSystem.CreateTextFile +''' SF_FileSystem.OpenTextFile +''' methods to facilitate the sequential processing of text files +''' All open/read/write/close operations are presumed to happen during the same macro run +''' The encoding to be used may be chosen by the user +''' The list is in the Name column of https://www.iana.org/assignments/character-sets/character-sets.xhtml +''' Note that probably not all values are available +''' Line delimiters may be chosen by the user +''' In input, CR, LF or CR+LF are supported +''' In output, the default value is the usual newline on the actual operating system (see SF_FileSystem.sfNEWLINE) +''' +''' The design choices are largely inspired by +''' https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/textstream-object +''' The implementation is mainly based on the XTextInputStream and XTextOutputStream UNO interfaces +''' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextInputStream.html +''' https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextOutputStream.html +''' +''' Instantiation example: +''' Dim FSO As Object, myFile As Object +''' Set FSO = CreateScriptService("FileSystem") +''' Set myFile = FSO.OpenTextFile("C:\Temp\ThisFile.txt", FSO.ForReading) ' Once per file +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const FILENOTOPENERROR = "FILENOTOPENERROR" ' The file is already closed +Const FILEOPENMODEERROR = "FILEOPENMODEERROR" ' The file is open in incompatible mode + +REM ============================================================= PRIVATE MEMBERS + +Private [Me] As Object +Private [_Parent] As Object +Private ObjectType As String ' Must be TEXTSTREAM +Private ServiceName As String +Private _FileName As String ' File where it is about +Private _IOMode As Integer ' ForReading, ForWriting or ForAppending +Private _Encoding As String ' https://www.iana.org/assignments/character-sets/character-sets.xhtml +Private _NewLine As String ' Line break in write mode +Private _FileExists As Boolean ' True if file exists before open +Private _LineNumber As Long ' Number of lines read or written +Private _FileHandler As Object ' com.sun.star.io.XInputStream or + ' com.sun.star.io.XOutputStream or + ' com.sun.star.io.XStream +Private _InputStream As Object ' com.sun.star.io.TextInputStream +Private _OutputStream As Object ' com.sun.star.io.TextOutputStream +Private _ForceBlankLine As Boolean ' Workaround: XTextInputStream misses last line if file ends with newline + +REM ============================================================ MODULE CONSTANTS + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "TEXTSTREAM" + ServiceName = "ScriptForge.TextStream" + _FileName = "" + _IOMode = -1 + _Encoding = "" + _NewLine = "" + _FileExists = False + _LineNumber = 0 + Set _FileHandler = Nothing + Set _InputStream = Nothing + Set _OutputStream = Nothing + _ForceBlankLine = False +End Sub ' ScriptForge.SF_TextStream Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + Call Class_Initialize() +End Sub ' ScriptForge.SF_TextStream Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Call Class_Terminate() + Set Dispose = Nothing +End Function ' ScriptForge.SF_TextStream Explicit Destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get AtEndOfStream() As Boolean +''' In reading mode, True indicates that the end of the file has been reached +''' In write and append modes, or if the file is not ready => always True +''' The property should be invoked BEFORE each ReadLine() method: +''' A ReadLine() executed while AtEndOfStream is True will raise an error +''' Example: +''' Dim sLine As String +''' Do While Not myFile.AtEndOfStream +''' sLine = myFile.ReadLine() +''' ' ... +''' Loop + + AtEndOfStream = _PropertyGet("AtEndOfStream") + +End Property ' ScriptForge.SF_TextStream.AtEndOfStream + +REM ----------------------------------------------------------------------------- +Property Get Encoding() As String +''' Returns the name of the text file either in url or in native operating system format +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.OpenTextFile("C:\Temp\myFile.txt") +''' MsgBox myFile.Encoding ' UTF-8 + + Encoding = _PropertyGet("Encoding") + +End Property ' ScriptForge.SF_TextStream.Encoding + +REM ----------------------------------------------------------------------------- +Property Get FileName() As String +''' Returns the name of the text file either in url or in native operating system format +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.OpenTextFile("C:\Temp\myFile.txt") +''' MsgBox myFile.FileName ' C:\Temp\myFile.txt + + FileName = _PropertyGet("FileName") + +End Property ' ScriptForge.SF_TextStream.FileName + +REM ----------------------------------------------------------------------------- +Property Get IOMode() As String +''' Returns either "READ", "WRITE" or "APPEND" +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.OpenTextFile("C:\Temp\myFile.txt") +''' MsgBox myFile.IOMode ' READ + + IOMode = _PropertyGet("IOMode") + +End Property ' ScriptForge.SF_TextStream.IOMode + +REM ----------------------------------------------------------------------------- +Property Get Line() As Long +''' Returns the number of lines read or written so far +''' Example: +''' Dim myFile As Object +''' FSO.FileNaming = "SYS" +''' Set myFile = FSO.OpenTextFile("C:\Temp\myFile.txt", FSO.ForAppending) +''' MsgBox myFile.Line ' The number of lines already present in myFile + + Line = _PropertyGet("Line") + +End Property ' ScriptForge.SF_TextStream.Line + +REM ----------------------------------------------------------------------------- +Property Get NewLine() As Variant +''' Returns the current character string to be inserted between 2 successive written lines +''' The default value is the native line separator in the current operating system +''' Example: +''' MsgBox myFile.NewLine + + NewLine = _PropertyGet("NewLine") + +End Property ' ScriptForge.SF_TextStream.NewLine (get) + +REM ----------------------------------------------------------------------------- +Property Let NewLine(ByVal pvLineBreak As Variant) +''' Sets the current character string to be inserted between 2 successive written lines +''' Example: +''' myFile.NewLine = Chr(13) & Chr(10) + +Const cstThisSub = "TextStream.setNewLine" + + SF_Utils._EnterFunction(cstThisSub) + If VarType(pvLineBreak) = V_STRING Then _NewLine = pvLineBreak + SF_Utils._ExitFunction(cstThisSub) + +End Property ' ScriptForge.SF_TextStream.NewLine (let) + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function CloseFile() As Boolean +''' Empties the output buffer if relevant. Closes the actual input or output stream +''' Args: +''' Returns: +''' True if the closure was successful +''' Exceptions: +''' FILENOTOPENERROR Nothing found to close +''' Examples: +''' myFile.CloseFile() + +Dim bClose As Boolean ' Return value +Const cstThisSub = "TextStream.CloseFile" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bClose = False + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + If Not _IsFileOpen() Then GoTo Finally + +Try: + If Not IsNull(_InputStream) Then _InputStream.closeInput() + If Not IsNull(_OutputStream) Then + _OutputStream.flush() + _OutputStream.closeOutput() + End If + Set _InputStream = Nothing + Set _OutputStream = Nothing + Set _FileHandler = Nothing + bClose = True + +Finally: + CloseFile = bClose + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_TextStream.CloseFile + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' If the property does not exist, returns Null +''' Exceptions: +''' see the exceptions of the individual properties +''' Examples: +''' myModel.GetProperty("MyProperty") + +Const cstThisSub = "TextStream.GetProperty" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_TextStream.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Model service as an array + + Methods = Array( _ + "CloseFile" _ + , "ReadAll" _ + , "readLine" _ + , "SkipLine" _ + , "WriteBlankLines" _ + , "WriteLine" _ + ) + +End Function ' ScriptForge.SF_TextStream.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Timer class as an array + + Properties = Array( _ + "AtEndOfStream" _ + , "Encoding" _ + , "FileName" _ + , "IOMode" _ + , "Line" _ + , "NewLine" _ + ) + +End Function ' ScriptForge.SF_TextStream.Properties + +REM ----------------------------------------------------------------------------- +Public Function ReadAll() As String +''' Returns all the remaining lines in the text stream as one string. Line breaks are NOT removed +''' The resulting string can be split in lines +''' either by using the usual Split Basic builtin function if the line delimiter is known +''' or with the SF_String.SplitLines method +''' For large files, using the ReadAll method wastes memory resources. +''' Other techniques should be used to input a file, such as reading a file line-by-line +''' Args: +''' Returns: +''' The read lines. The string may be empty. +''' Note that the Line property in incremented only by 1 +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in write or append modes +''' ENDOFFILEERROR Previous reads aleardy reached the end of the file +''' Examples: +''' Dim a As String +''' a = myFile.ReadAll() + +Dim sRead As String ' Return value +Const cstThisSub = "TextStream.ReadAll" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sRead = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not _IsFileOpen("READ") Then GoTo Finally + If _InputStream.isEOF() Then GoTo CatchEOF + End If + +Try: + sRead = _InputStream.readString(Array(), False) + _LineNumber = _LineNumber + 1 + +Finally: + ReadAll = sRead + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchEOF: + 'TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub) + MsgBox "END OF FILE ERROR !!" + GoTo Finally +End Function ' ScriptForge.SF_TextStream.ReadAll + +REM ----------------------------------------------------------------------------- +Public Function ReadLine() As String +''' Returns the next line in the text stream as a string. Line breaks are removed. +''' Args: +''' Returns: +''' The read line. The string may be empty. +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in write or append modes +''' ENDOFFILEERROR Previous reads aleardy reached the end of the file +''' Examples: +''' Dim a As String +''' a = myFile.ReadLine() + +Dim sRead As String ' Return value +Dim iRead As Integer ' Length of line break +Const cstThisSub = "TextStream.ReadLine" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sRead = "" + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not _IsFileOpen("READ") Then GoTo Finally + If AtEndOfStream Then GoTo CatchEOF + End If + +Try: + ' When the text file ends with a line break, + ' XTextInputStream.readLine() returns the line break together with the last line + ' Hence the workaround to force a blank line at the end + If _ForceBlankLine Then + sRead = "" + _ForceBlankLine = False + Else + sRead = _InputStream.readLine() + ' The isEOF() is set immediately after having read the last line + If _InputStream.isEOF() And Len(sRead) > 0 Then + iRead = 0 + If SF_String.EndsWith(sRead, SF_String.sfCRLF) Then + iRead = 2 + ElseIf SF_String.EndsWith(sRead, SF_String.sfLF) Or SF_String.EndsWith(sRead, SF_String.sfCR) Then + iRead = 1 + End If + If iRead > 0 Then + sRead = Left(sRead, Len(sRead) - iRead) + _ForceBlankLine = True ' Provision for a last empty line at the next read loop + End If + End If + End If + _LineNumber = _LineNumber + 1 + +Finally: + ReadLine = sRead + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchEOF: + 'TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub) + MsgBox "END OF FILE ERROR !!" + GoTo Finally +End Function ' ScriptForge.SF_TextStream.ReadLine + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Dim bSet As Boolean ' Return value +Const cstThisSub = "TextStream.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bSet = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + bSet = True + Select Case UCase(PropertyName) + Case "NEWLINE" + If Not SF_Utils._Validate(Value, "Value", V_STRING) Then GoTo Catch + NewLine = Value + Case Else + bSet = False + End Select + +Finally: + SetProperty = bSet + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_TextStream.SetProperty + +REM ----------------------------------------------------------------------------- +Public Sub SkipLine() +''' Skips the next line when reading a TextStream file. +''' Args: +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in write or append modes +''' ENDOFFILEERROR Previous reads aleardy reached the end of the file +''' Examples: +''' myFile.SkipLine() + +Dim sRead As String ' Read buffer +Const cstThisSub = "TextStream.SkipLine" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not _IsFileOpen("READ") Then GoTo Finally + If Not _ForceBlankLine Then ' The file ends with a newline => return one empty line more + If _InputStream.isEOF() Then GoTo CatchEOF + End If + End If + +Try: + sRead = ReadLine() + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +CatchEOF: + 'TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub) + MsfBox "END OF FILE ERROR !!" + GoTo Finally +End Sub ' ScriptForge.SF_TextStream.SkipLine + +REM ----------------------------------------------------------------------------- +Public Sub WriteBlankLines(Optional ByVal Lines As Variant) +''' Writes a number of empty lines in the output stream +''' Args: +''' Lines: the number of lines to write +''' Returns: +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in in read mode +''' Examples: +''' myFile.WriteBlankLines(10) +Dim i As Long +Const cstThisSub = "TextStream.WriteBlankLines" +Const cstSubArgs = "Lines" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not _IsFileOpen("WRITE") Then GoTo Finally + If Not SF_Utils._Validate(Lines, "Lines", V_NUMERIC) Then GoTo Finally + End If + +Try: + For i = 1 To Lines + _OutputStream.writeString(_NewLine) + Next i + _LineNumber = _LineNumber + Lines + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_TextStream.WriteBlankLines + +REM ----------------------------------------------------------------------------- +Public Sub WriteLine(Optional ByVal Line As Variant) +''' Writes the given line to the output stream. A newline is inserted if relevant +''' Args: +''' Line: the line to write, may be empty +''' Returns: +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in in read mode +''' Examples: +''' myFile.WriteLine("Next line") +Dim i As Long +Const cstThisSub = "TextStream.WriteLine" +Const cstSubArgs = "Line" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not _IsFileOpen("WRITE") Then GoTo Finally + If Not SF_Utils._Validate(Line, "Line", V_STRING) Then GoTo Finally + End If + +Try: + _OutputStream.writeString(Iif(_LineNumber > 0, _NewLine, "") & Line) + _LineNumber = _LineNumber + 1 + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_TextStream.WriteLine + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Public Sub _Initialize() +''' Opens file and setup input and/or output streams (ForAppending requires both) + +Dim oSfa As Object ' com.sun.star.ucb.SimpleFileAccess + + ' Default newline related to current operating system + _NewLine = SF_String.sfNEWLINE + + Set oSfa = SF_Utils._GetUNOService("FileAccess") + + ' Setup input and/or output streams based on READ/WRITE/APPEND IO modes + Select Case _IOMode + Case SF_FileSystem.ForReading + Set _FileHandler = oSfa.openFileRead(_FileName) + Set _InputStream = CreateUnoService("com.sun.star.io.TextInputStream") + _InputStream.setInputStream(_FileHandler) + Case SF_FileSystem.ForWriting + ' Output file is deleted beforehand + If _FileExists Then oSfa.kill(_FileName) + Set _FileHandler = oSfa.openFileWrite(_FileName) + Set _OutputStream = CreateUnoService("com.sun.star.io.TextOutputStream") + _OutputStream.setOutputStream(_FileHandler) + Case SF_FileSystem.ForAppending + Set _FileHandler = oSfa.openFileReadWrite(_FileName) + Set _InputStream = CreateUnoService("com.sun.star.io.TextInputStream") + Set _OutputStream = CreateUnoService("com.sun.star.io.TextOutputStream") + _InputStream.setInputStream(_FileHandler) + ' Position at end of file: Skip and count existing lines + _LineNumber = 0 + Do While Not _InputStream.isEOF() + _InputStream.readLine() + _LineNumber = _LineNumber + 1 + Loop + _OutputStream.setOutputStream(_FileHandler) + End Select + + If _Encoding = "" Then _Encoding = "UTF-8" + If Not IsNull(_InputStream) Then _InputStream.setEncoding(_Encoding) + If Not IsNull(_OutputStream) Then _OutputStream.setEncoding(_Encoding) + +End Sub ' ScriptForge.SF_TextStream._Initialize + +REM ----------------------------------------------------------------------------- +Private Function _IsFileOpen(Optional ByVal psMode As String) As Boolean +''' Checks if file is open with the right mode (READ or WRITE) +''' Raises an exception if the file is not open at all or not in the right mode +''' Args: +''' psMode: READ or WRITE or zero-length string +''' Exceptions: +''' FILENOTOPENERROR File not open or already closed +''' FILEOPENMODEERROR File opened in incompatible mode + + _IsFileOpen = False + If IsMissing(psMode) Then psMode = "" + If IsNull(_InputStream) And IsNull(_OutputStream) Then GoTo CatchNotOpen + Select Case psMode + Case "READ" + If IsNull(_InputStream) Then GoTo CatchOpenMode + If _IOMode <> SF_FileSystem.ForReading Then GoTo CatchOpenMode + Case "WRITE" + If IsNull(_OutputStream) Then GoTo CatchOpenMode + If _IOMode = SF_FileSystem.ForReading Then GoTo CatchOpenMode + Case Else + End Select + _IsFileOpen = True + +Finally: + Exit Function +CatchNotOpen: + SF_Exception.RaiseFatal(FILENOTOPENERROR, FileName) + GoTo Finally +CatchOpenMode: + SF_Exception.RaiseFatal(FILEOPENMODEERROR, FileName, IOMode) + GoTo Finally +End Function ' ScriptForge.SF_TextStream._IsFileOpen + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) +''' Return the value of the named property +''' Args: +''' psProperty: the name of the property + +Dim cstThisSub As String +Dim cstSubArgs As String + + cstThisSub = "TextStream.get" & psProperty + cstSubArgs = "" + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + Select Case UCase(psProperty) + Case UCase("AtEndOfStream") + Select Case _IOMode + Case SF_FileSystem.ForReading + If IsNull(_InputStream) Then _PropertyGet = True Else _PropertyGet = _InputStream.isEOF() And Not _ForceBlankLine + Case Else : _PropertyGet = True + End Select + Case UCase("Encoding") + _PropertyGet = _Encoding + Case UCase("FileName") + _PropertyGet = SF_FileSystem._ConvertFromUrl(_FileName) ' Depends on FileNaming + Case UCase("IOMode") + With SF_FileSystem + Select Case _IOMode + Case .ForReading : _PropertyGet = "READ" + Case .ForWriting : _PropertyGet = "WRITE" + Case .ForAppending : _PropertyGet = "APPEND" + Case Else : _PropertyGet = "" + End Select + End With + Case UCase("Line") + _PropertyGet = _LineNumber + Case UCase("NewLine") + _PropertyGet = _NewLine + Case Else + _PropertyGet = Null + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_TextStream._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the TextStream instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[TextStream]: File name, IOMode, LineNumber" + + _Repr = "[TextStream]: " & FileName & "," & IOMode & "," & CStr(Line) + +End Function ' ScriptForge.SF_TextStream._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_TextStream +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Timer.xba b/wizards/source/scriptforge/SF_Timer.xba new file mode 100644 index 000000000000..5ed3a7d9546d --- /dev/null +++ b/wizards/source/scriptforge/SF_Timer.xba @@ -0,0 +1,463 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Timer" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Timer +''' ======== +''' Class for management of scripts execution performance +''' A Timer measures durations. It can be suspended, resumed, restarted +''' Duration properties are expressed in seconds with a precision of 3 decimal digits +''' +''' Service invocation example: +''' Dim myTimer As Variant +''' myTimer = CreateScriptService("Timer") +''' myTimer = CreateScriptService("Timer", True) ' => To start timer immediately +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +REM ============================================================= PRIVATE MEMBERS + +Private [Me] As Object +Private [_Parent] As Object +Private ObjectType As String ' Must be "TIMER" +Private ServiceName As String +Private _TimerStatus As Integer ' inactive, started, suspended or stopped +Private _StartTime As Double ' Moment when timer started, restarted +Private _EndTime As Double ' Moment when timer stopped +Private _SuspendTime As Double ' Moment when timer suspended +Private _SuspendDuration As Double ' Duration of suspended status as a difference of times + +REM ============================================================ MODULE CONSTANTS + +Private Const STATUSINACTIVE = 0 +Private Const STATUSSTARTED = 1 +Private Const STATUSSUSPENDED = 2 +Private Const STATUSSTOPPED = 3 + +Private Const DSECOND As Double = 1 / (24 * 60 * 60) ' Duration of 1 second as compared to 1.0 = 1 day + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "TIMER" + ServiceName = "ScriptForge.Timer" + _TimerStatus = STATUSINACTIVE + _StartTime = 0 + _EndTime = 0 + _SuspendTime = 0 + _SuspendDuration = 0 +End Sub ' ScriptForge.SF_Timer Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + Call Class_Initialize() +End Sub ' ScriptForge.SF_Timer Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Call Calss_Terminate() + Set Dispose = Nothing +End Function ' ScriptForge.SF_Timer Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Public Function Duration() As Double +''' Returns the actual (out of suspensions) time elapsed since start or between start and stop +''' Args: +''' Returns: +''' A Double expressing the duration in seconds +''' Example: +''' myTimer.Duration returns 1.234 (1 sec, 234 ms) + + Duration = _PropertyGet("Duration") + +End Function ' ScriptForge.SF_Timer.Duration + +REM ----------------------------------------------------------------------------- +Property Get IsStarted() As Boolean +''' Returns True if timer is started or suspended +''' Example: +''' myTimer.IsStarted + + IsStarted = _PropertyGet("IsStarted") + +End Property ' ScriptForge.SF_Timer.IsStarted + +REM ----------------------------------------------------------------------------- +Property Get IsSuspended() As Boolean +''' Returns True if timer is started and suspended +''' Example: +''' myTimer.IsSuspended + + IsSuspended = _PropertyGet("IsSuspended") + +End Property ' ScriptForge.SF_Timer.IsSuspended + +REM ----------------------------------------------------------------------------- +Public Function SuspendDuration() As Double +''' Returns the actual time elapsed while suspended since start or between start and stop +''' Args: +''' Returns: +''' A Double expressing the duration in seconds +''' Example: +''' myTimer.SuspendDuration returns 1.234 (1 sec, 234 ms) + + SuspendDuration = _PropertyGet("SuspendDuration") + +End Function ' ScriptForge.SF_Timer.SuspendDuration + +REM ----------------------------------------------------------------------------- +Public Function TotalDuration() As Double +''' Returns the actual time elapsed (including suspensions) since start or between start and stop +''' Args: +''' Returns: +''' A Double expressing the duration in seconds +''' Example: +''' myTimer.TotalDuration returns 1.234 (1 sec, 234 ms) + + TotalDuration = _PropertyGet("TotalDuration") + +End Function ' ScriptForge.SF_Timer.TotalDuration + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function Continue() As Boolean +''' Halt suspension of a running timer +''' Args: +''' Returns: +''' True if successful, False if the timer is not suspended +''' Examples: +''' myTimer.Continue() + +Const cstThisSub = "Timer.Continue" +Const cstSubArgs = "" + +Check: + Continue = False + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If _TimerStatus = STATUSSUSPENDED Then + _TimerStatus = STATUSSTARTED + _SuspendDuration = _SuspendDuration + _Now() - _SuspendTime + _SuspendTime = 0 + Continue = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer.Continue + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist +''' Examples: +''' myTimer.GetProperty("Duration") + +Const cstThisSub = "Timer.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Timer.Properties + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list or methods of the Timer class as an array + + Methods = Array( _ + "Continue" _ + , "Restart" _ + , "Start" _ + , "Suspend" _ + , "Terminate" _ + ) + +End Function ' ScriptForge.SF_Timer.Methods + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Timer class as an array + + Properties = Array( _ + "Duration" _ + , "IsStarted" _ + , "IsSuspended" _ + , "SuspendDuration" _ + , "TotalDuration" _ + ) + +End Function ' ScriptForge.SF_Timer.Properties + +REM ----------------------------------------------------------------------------- +Public Function Restart() As Boolean +''' Terminate the timer and restart a new clean timer +''' Args: +''' Returns: +''' True if successful, False if the timer is inactive +''' Examples: +''' myTimer.Restart() + +Const cstThisSub = "Timer.Restart" +Const cstSubArgs = "" + +Check: + Restart = False + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If _TimerStatus <> STATUSINACTIVE Then + If _TimerStatus <> STATUSSTOPPED Then Terminate() + Start() + Restart = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer.Restart + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "Timer.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Timer.SetProperty + +REM ----------------------------------------------------------------------------- +Public Function Start() As Boolean +''' Start a new clean timer +''' Args: +''' Returns: +''' True if successful, False if the timer is already started +''' Examples: +''' myTimer.Start() + +Const cstThisSub = "Timer.Start" +Const cstSubArgs = "" + +Check: + Start = False + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If _TimerStatus = STATUSINACTIVE Or _TimerStatus = STATUSSTOPPED Then + _TimerStatus = STATUSSTARTED + _StartTime = _Now() + _EndTime = 0 + _SuspendTime = 0 + _SuspendDuration = 0 + Start = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer.Start + +REM ----------------------------------------------------------------------------- +Public Function Suspend() As Boolean +''' Suspend a running timer +''' Args: +''' Returns: +''' True if successful, False if the timer is not started or already suspended +''' Examples: +''' myTimer.Suspend() + +Const cstThisSub = "Timer.Suspend" +Const cstSubArgs = "" + +Check: + Suspend = False + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If _TimerStatus = STATUSSTARTED Then + _TimerStatus = STATUSSUSPENDED + _SuspendTime = _Now() + Suspend = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer.Suspend + +REM ----------------------------------------------------------------------------- +Public Function Terminate() As Boolean +''' Terminate a running timer +''' Args: +''' Returns: +''' True if successful, False if the timer is neither started nor suspended +''' Examples: +''' myTimer.Terminate() + +Const cstThisSub = "Timer.Terminate" +Const cstSubArgs = "" + +Check: + Terminate = False + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED Then + If _TimerSTatus = STATUSSUSPENDED Then Continue() + _TimerStatus = STATUSSTOPPED + _EndTime = _Now() + Terminate = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer.Terminate + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _Now() As Double +''' Returns the current date and time +''' Uses the Calc NOW() function to get a higher precision than the usual Basic Now() function +''' Args: +''' Returns: +''' The actual time as a number +''' The integer part represents the date, the decimal part represents the time + + _Now = SF_Session.ExecuteCalcFunction("NOW") + +End Function ' ScriptForge.SF_Timer._Now + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) +''' Return the named property +''' Args: +''' psProperty: the name of the property + +Dim dDuration As Double ' Computed duration +Dim cstThisSub As String +Dim cstSubArgs As String + + cstThisSub = "Timer.get" & psProperty + cstSubArgs = "" + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + Select Case UCase(psProperty) + Case UCase("Duration") + Select Case _TimerStatus + Case STATUSINACTIVE : dDuration = 0.0 + Case STATUSSTARTED + dDuration = _Now() - _StartTime - _SuspendDuration + Case STATUSSUSPENDED + dDuration = _SuspendTime - _StartTime - _SuspendDuration + Case STATUSSTOPPED + dDuration = _EndTime - _StartTime - _SuspendDuration + End Select + _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000 + Case UCase("IsStarted") + _PropertyGet = ( _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED ) + Case UCase("IsSuspended") + _PropertyGet = ( _TimerStatus = STATUSSUSPENDED ) + Case UCase("SuspendDuration") + Select Case _TimerStatus + Case STATUSINACTIVE : dDuration = 0.0 + Case STATUSSTARTED, STATUSSTOPPED + dDuration = _SuspendDuration + Case STATUSSUSPENDED + dDuration = _Now() - _SuspendTime + _SuspendDuration + End Select + _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000 + Case UCase("TotalDuration") + Select Case _TimerStatus + Case STATUSINACTIVE : dDuration = 0.0 + Case STATUSSTARTED, STATUSSUSPENDED + dDuration = _Now() - _StartTime + Case STATUSSTOPPED + dDuration = _EndTime - _StartTime + End Select + _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000 + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Timer._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the Timer instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[Timer] Duration:xxx.yyy + +Const cstTimer = "[Timer] Duration: " +Const cstMaxLength = 50 ' Maximum length for items + + _Repr = cstTimer & Replace(SF_Utils._Repr(Duration), ".", """") + +End Function ' ScriptForge.SF_Timer._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_TIMER +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_UI.xba b/wizards/source/scriptforge/SF_UI.xba new file mode 100644 index 000000000000..5cbd6b8aae1f --- /dev/null +++ b/wizards/source/scriptforge/SF_UI.xba @@ -0,0 +1,1175 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_UI" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_UI +''' ===== +''' Singleton class module for the identification and the manipulation of the +''' different windows composing the whole LibreOffice application: +''' - Windows selection +''' - Windows moving and resizing +''' - Statusbar settings +''' - Creation of new windows +''' - Access to the underlying "documents" +''' +''' WindowName: how to designate a window. It can be either +''' a full FileName given in the notation indicated by the current value of SF_FileSystem.FileNaming +''' or the last component of the full FileName or even only its BaseName +''' or the title of the window +''' or, for new documents, something like "Untitled 1" +''' or one of the special windows "BASICIDE" and "WELCOMESCREEN" +''' The window search is case-sensitive +''' +''' Service invocation example: +''' Dim ui As Variant +''' ui = CreateScriptService("UI") + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +Const DOCUMENTERROR = "DOCUMENTERROR" ' Requested document was not found +Const DOCUMENTCREATIONERROR = "DOCUMENTCREATIONERROR" ' Incoherent arguments, new document could not be created +Const DOCUMENTOPENERROR = "DOCUMENTOPENERROR" ' Document could not be opened, check the arguments +Const BASEDOCUMENTOPENERROR = "BASEDOCUMENTOPENERROR" ' Id. for Base document + +REM ============================================================= PRIVATE MEMBERS + +Type Window + Component As Object ' com.sun.star.lang.XComponent + Frame As Object ' com.sun.star.comp.framework.Frame + WindowName As String ' Object Name + WindowTitle As String ' Only mean to identify new documents + WindowFileName As String ' URL of file name + DocumentType As String ' Writer, Calc, ... +End Type + +' The progress/status bar of the active window +'Private oStatusBar As Object ' com.sun.star.task.XStatusIndicator + +REM ============================================================ MODULE CONSTANTS + +' Special windows +Const BASICIDE = "BASICIDE" +Const WELCOMESCREEN = "WELCOMESCREEN" + +' Document types (only if not 1 of the special windows) +Const BASEDOCUMENT = "Base" +Const CALCDOCUMENT = "Calc" +Const DRAWDOCUMENT = "Draw" +Const IMPRESSDOCUMENT = "Impress" +Const MATHDOCUMENT = "Math" +Const WRITERDOCUMENT = "Writer" + +' Window subtypes - Not supported yet +Const BASETABLE = "BASETABLE" +Const BASEQUERY = "BASEQUERY" +Const BASEREPORT = "BASEREPORT" +Const BASEDIAGRAM = "BASEDIAGRAM" + +' Macro execution modes +Const cstMACROEXECNORMAL = 0 ' Default, execution depends on user configuration and choice +Const cstMACROEXECNEVER = 1 ' Macros are not executed +Const cstMACROEXECALWAYS = 2 ' Macros are always executed + +REM ===================================================== CONSTRUCTOR/DESCTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_UI Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Public Function ActiveWindow() As String +''' Returns a valid WindowName for the currently active window +''' When "" is returned, the window could not be identified + +Dim vWindow As Window ' A component +Dim oComp As Object ' com.sun.star.lang.XComponent + + Set oComp = StarDesktop.CurrentComponent + If Not IsNull(oComp) Then + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + If Len(.WindowFileName) > 0 Then + ActiveWindow = SF_FileSystem._ConvertFromUrl(.WindowFileName) + ElseIf Len(.WindowName) > 0 Then + ActiveWindow = .WindowName + ElseIf Len(.WindowTitle) > 0 Then + ActiveWindow = .WindowTitle + Else + ActiveWindow = "" + End If + End With + End If + +End Function ' ScriptForge.SF_UI.ActiveWindow + +REM ----------------------------------------------------------------------------- +Property Get MACROEXECALWAYS As Integer +''' Macros are always executed + MACROEXECALWAYS = cstMACROEXECALWAYS +End Property ' ScriptForge.SF_UI.MACROEXECALWAYS + +REM ----------------------------------------------------------------------------- +Property Get MACROEXECNEVER As Integer +''' Macros are not executed + MACROEXECNEVER = cstMACROEXECNEVER +End Property ' ScriptForge.SF_UI.MACROEXECNEVER + +REM ----------------------------------------------------------------------------- +Property Get MACROEXECNORMAL As Integer +''' Default, execution depends on user configuration and choice + MACROEXECNORMAL = cstMACROEXECNORMAL +End Property ' ScriptForge.SF_UI.MACROEXECNORMAL + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_UI" +End Property ' ScriptForge.SF_UI.ObjectType + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function Activate(Optional ByVal WindowName As Variant) As Boolean +''' Make the specified window active +''' Args: +''' WindowName: see definitions +''' Returns: +''' True if the given window is found and can be activated +''' There is no change in the actual user interface if no window matches the selection +''' Examples: +''' ui.Activate("C:\Me\My file.odt") + +Dim bActivate As Boolean ' Return value +Dim oEnum As Object ' com.sun.star.container.XEnumeration +Dim oComp As Object ' com.sun.star.lang.XComponent +Dim vWindow As Window ' A single component +Dim oContainer As Object ' com.sun.star.awt.XWindow +Const cstThisSub = "UI.Activate" +Const cstSubArgs = "WindowName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bActivate = False + +Check: + If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(WindowName, "WindowName") Then GoTo Finally + End If + +Try: + Set oEnum = StarDesktop.Components().createEnumeration + Do While oEnum.hasMoreElements + Set oComp = oEnum.nextElement + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + ' Does the current window match the arguments ? + If (Len(.WindowFileName) > 0 And .WindowFileName = SF_FileSystem._ConvertToUrl(WindowName)) _ + Or (Len(.WindowName) > 0 And .WindowName = WindowName) _ + Or (Len(.WindowTitle) > 0 And .WindowTitle = WindowName) Then + Set oContainer = vWindow.Frame.ContainerWindow + With oContainer + If .isVisible() = False Then .setVisible(True) + .IsMinimized = False + .setFocus() + .toFront() ' Force window change in Linux + Wait 1 ' Bypass desynchro issue in Linux + End With + bActivate = True + Exit Do + End If + End With + Loop + +Finally: + Activate = bActivate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.Activate + +REM ----------------------------------------------------------------------------- +Public Function CreateBaseDocument(Optional ByVal FileName As Variant _ + , Optional ByVal EmbeddedDatabase As Variant _ + , Optional ByVal RegistrationName As Variant _ + ) As Object +''' Create a new LibreOffice Base document embedding an empty database of the given type +''' Args: +''' FileName: Identifies the file to create. It must follow the SF_FileSystem.FileNaming notation +''' If the file altready exists, it is overwritten without warning +''' EmbeddedDatabase: either "HSQLDB" (default) or "FIREBIRD" +''' RegistrationName: the name used to store the new database in the databases register +''' If "" (default), no registration takes place +''' If the name already exists it is overwritten without warning +''' Returns: +''' A SFDocuments.SF_Document object or one of its subclasses +''' Examples: +''' Dim myBase As Object +''' Set myBase = ui.CreateBaseDocument("C:\Databases\MyBaseFile.odb", "FIREBIRD") + +Dim oCreate As Variant ' Return value +Dim oDBContext As Object ' com.sun.star.sdb.DatabaseContext +Dim oDatabase As Object ' com.sun.star.comp.dba.ODatabaseSource +Dim oComp As Object ' Loaded component com.sun.star.lang.XComponent +Dim sFileName As String ' Alias of FileName +Dim FSO As Object ' Alias for FileSystem service +Const cstDocType = "private:factory/s" +Const cstThisSub = "UI.CreateBaseDocument" +Const cstSubArgs = "FileName, [EmbeddedDatabase=""HSQLDB""|""FIREBIRD""], [RegistrationName=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oCreate = Nothing + +Check: + If IsMissing(EmbeddedDatabase) Or IsEmpty(EmbeddedDatabase) Then EmbeddedDatabase = "HSQLDB" + If IsMissing(RegistrationName) Or IsEmpty(RegistrationName) Then RegistrationName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(EmbeddedDatabase, "EmbeddedDatabase", V_STRING, Array("HSQLDB", "FIREBIRD")) Then GoTo Finally + If Not SF_Utils._Validate(RegistrationName, "RegistrationName", V_STRING) Then GoTo Finally + End If + +Try: + Set oDBContext = SF_Utils._GetUNOService("DatabaseContext") + With oDBContext + Set oDatabase = .createInstance() + oDatabase.URL = "sdbc:embedded:" & LCase(EmbeddedDatabase) + ' Create empty Base document + Set FSO = CreateScriptService("FileSystem") + sFileName = FSO._ConvertToUrl(FileName) + ' An existing file is overwritten without warning + If FSO.FileExists(FileName) Then FSO.DeleteFile(FileName) + If FSO.FileExists(FileName & ".lck") Then FSO.DeleteFile(FileName & ".lck") + oDatabase.DatabaseDocument.storeAsURL(sFileName, Array(SF_Utils._MakePropertyValue("Overwrite", True))) + ' Register database if requested + If Len(RegistrationName) > 0 Then + If .hasRegisteredDatabase(RegistrationName) Then + .changeDatabaseLocation(RegistrationName, sFileName) + Else + .registerDatabaseLocation(RegistrationName, sFileName) + End If + End If + End With + + Set oCreate = OpenBaseDocument(FileName) + +Finally: + Set CreateBaseDocument = oCreate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.CreateBaseDocument + +REM ----------------------------------------------------------------------------- +Public Function CreateDocument(Optional ByVal DocumentType As Variant _ + , Optional ByVal TemplateFile As Variant _ + , Optional ByVal Hidden As Variant _ + ) As Object +''' Create a new LibreOffice document of a given type or based on a given template +''' Args: +''' DocumentType: "Calc", "Writer", etc. If absent, a TemplateFile must be given +''' TemplateFile: the full FileName of the template to build the new document on +''' If the file does not exist, the argument is ignored +''' The "FileSystem" service provides the TemplatesFolder and UserTemplatesFolder +''' properties to help to build the argument +''' Hidden: if True, open in the background (default = False) +''' To use with caution: activation or closure can only happen programmatically +''' Returns: +''' A SFDocuments.SF_Document object or one of its subclasses +''' Exceptions: +''' DOCUMENTCREATIONERROR Wrong arguments +''' Examples: +''' Dim myDoc1 As Object, myDoc2 As Object, FSO As Object +''' Set myDoc1 = ui.CreateDocument("Calc") +''' Set FSO = CreateScriptService("FileSystem") +''' Set myDoc2 = ui.CreateDocument(, FSO.BuildPath(FSO.TemplatesFolder, "personal/CV.ott")) + +Dim oCreate As Variant ' Return value +Dim vProperties As Variant ' Array of com.sun.star.beans.PropertyValue +Dim bTemplateExists As Boolean ' True if TemplateFile is valid +Dim sNew As String ' File url +Dim oComp As Object ' Loaded component com.sun.star.lang.XComponent +Const cstDocType = "private:factory/s" +Const cstThisSub = "UI.CreateDocument" +Const cstSubArgs = "[DocumentType=""""], [TemplateFile=""""], [Hidden=False]" + +'>>> If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oCreate = Nothing + +Check: + If IsMissing(DocumentType) Or IsEmpty(DocumentType) Then DocumentType = "" + If IsMissing(TemplateFile) Or IsEmpty(TemplateFile) Then TemplateFile = "" + If IsMissing(Hidden) Or IsEmpty(Hidden) Then Hidden = False + + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(DocumentType, "DocumentType", V_STRING _ + , Array("", BASEDOCUMENT, CALCDOCUMENT, DRAWDOCUMENT _ + , IMPRESSDOCUMENT, MATHDOCUMENT, WRITERDOCUMENT)) Then GoTo Finally + If Not SF_Utils._ValidateFile(TemplateFile, "TemplateFile", , True) Then GoTo Finally + If Not SF_Utils._Validate(Hidden, "Hidden", V_BOOLEAN) Then GoTo Finally + End If + + If Len(DocumentType) + Len(TemplateFile) = 0 Then GoTo CatchError + If Len(TemplateFile) > 0 Then bTemplateExists = SF_FileSystem.FileExists(TemplateFile) Else bTemplateExists = False + If Len(DocumentType) = 0 Then + If Not bTemplateExists Then GoTo CatchError + End If + +Try: + If bTemplateExists Then sNew = SF_FileSystem._ConvertToUrl(TemplateFile) Else sNew = cstDocType & LCase(DocumentType) + vProperties = Array( _ + SF_Utils._MakePropertyValue("AsTemplate", bTemplateExists) _ + , SF_Utils._MakePropertyValue("Hidden", Hidden) _ + ) + Set oComp = StarDesktop.loadComponentFromURL(sNew, "_blank", 0, vProperties) + If Not IsNull(oComp) Then Set oCreate = CreateScriptService("SFDocuments.Document", oComp) + +Finally: + Set CreateDocument = oCreate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchError: + SF_Exception.RaiseFatal(DOCUMENTCREATIONERROR, "DocumentType", DocumentType, "TemplateFile", TemplateFile) + GoTo Finally +End Function ' ScriptForge.SF_UI.CreateDocument + +REM ----------------------------------------------------------------------------- +Public Function Documents() As Variant +''' Returns the list of the currently open documents. Special windows are ignored. +''' Returns: +''' A zero-based 1D array of filenames (in SF_FileSystem.FileNaming notation) +''' or of window titles for unsaved documents +''' Examples: +''' Dim vDocs As Variant, sDoc As String +''' vDocs = ui.Documents() +''' For each sDoc In vDocs +''' ... + +Dim vDocuments As Variant ' Return value +Dim oEnum As Object ' com.sun.star.container.XEnumeration +Dim oComp As Object ' com.sun.star.lang.XComponent +Dim vWindow As Window ' A single component +Const cstThisSub = "UI.Documents" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vDocuments = Array() + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + Set oEnum = StarDesktop.Components().createEnumeration + Do While oEnum.hasMoreElements + Set oComp = oEnum.nextElement + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + If Len(.WindowFileName) > 0 Then + vDocuments = SF_Array.Append(vDocuments, SF_FileSystem._ConvertFromUrl(.WindowFileName)) + ElseIf Len(.WindowTitle) > 0 Then + vDocuments = SF_Array.Append(vDocuments, .WindowTitle) + End If + End With + Loop + +Finally: + Documents = vDocuments + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.Documents + +REM ----------------------------------------------------------------------------- +Public Function GetDocument(Optional ByVal WindowName As Variant) As Variant +''' Returns a SFDocuments.Document object referring to the active window or the given window +''' Args: +''' WindowName: see definitions. If absent the active window is considered +''' Exceptions: +''' DOCUMENTERROR The targeted window could not be found +''' Examples: +''' Dim oDoc As Object +''' Set oDoc = ui.GetDocument +''' oDoc.Save() + +Dim oDocument As Object ' Return value +Const cstThisSub = "UI.GetDocument" +Const cstSubArgs = "[WindowName]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oDocument = Nothing + +Check: + If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(WindowName, "WindowName", , True) Then GoTo Finally + End If + +Try: + Set oDocument = SF_Services.CreateScriptService("SFDocuments.Document", WindowName) + If IsNull(oDocument) Then GoTo CatchDeliver + +Finally: + Set GetDocument = oDocument + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchDeliver: + SF_Exception.RaiseFatal(DOCUMENTERROR, "WindowName", WindowName) + GoTo Finally +End Function ' ScriptForge.SF_UI.GetDocument + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "UI.GetProperty" +Const cstSubArgs = "PropertyName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case "ACTIVEWINDOW" : GetProperty = ActiveWindow() + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.GetProperty + +REM ----------------------------------------------------------------------------- +Public Sub Maximize(Optional ByVal WindowName As Variant) +''' Maximizes the active window or the given window +''' Args: +''' WindowName: see definitions. If absent the active window is considered +''' Examples: +''' ui.Maximize +''' ... + +Dim oEnum As Object ' com.sun.star.container.XEnumeration +Dim oComp As Object ' com.sun.star.lang.XComponent +Dim vWindow As Window ' A single component +Dim oContainer As Object ' com.sun.star.awt.XWindow +Dim bFound As Boolean ' True if window found +Const cstThisSub = "UI.Maximize" +Const cstSubArgs = "[WindowName]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(WindowName, "WindowName", , True) Then GoTo Finally + End If + +Try: + bFound = False + If Len(WindowName) > 0 Then + Set oEnum = StarDesktop.Components().createEnumeration + Do While oEnum.hasMoreElements And Not bFound + Set oComp = oEnum.nextElement + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + ' Does the current window match the arguments ? + If (Len(.WindowFileName) > 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _ + Or (Len(.WindowName) > 0 And .WindowName = WindowName) _ + Or (Len(.WindowTitle) > 0 And .WindowTitle = WindowName) Then bFound = True + End With + Loop + Else + vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent) + bFound = True + End If + + If bFound Then + Set oContainer = vWindow.Frame.ContainerWindow + oContainer.IsMaximized = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_UI.Maximize + +REM ----------------------------------------------------------------------------- +Public Sub Minimize(Optional ByVal WindowName As Variant) +''' Minimizes the current window or the given window +''' Args: +''' WindowName: see definitions. If absent the current window is considered +''' Examples: +''' ui.Minimize("myFile.ods") +''' ... + +Dim oEnum As Object ' com.sun.star.container.XEnumeration +Dim oComp As Object ' com.sun.star.lang.XComponent +Dim vWindow As Window ' A single component +Dim oContainer As Object ' com.sun.star.awt.XWindow +Dim bFound As Boolean ' True if window found +Const cstThisSub = "UI.Minimize" +Const cstSubArgs = "[WindowName]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(WindowName, "WindowName", , True) Then GoTo Finally + End If + +Try: + bFound = False + If Len(WindowName) > 0 Then + Set oEnum = StarDesktop.Components().createEnumeration + Do While oEnum.hasMoreElements And Not bFound + Set oComp = oEnum.nextElement + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + ' Does the current window match the arguments ? + If (Len(.WindowFileName) > 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _ + Or (Len(.WindowName) > 0 And .WindowName = WindowName) _ + Or (Len(.WindowTitle) > 0 And .WindowTitle = WindowName) Then bFound = True + End With + Loop + Else + vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent) + bFound = True + End If + + If bFound Then + Set oContainer = vWindow.Frame.ContainerWindow + oContainer.IsMinimized = True + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_UI.Minimize + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the UI service as an array + + Methods = Array("Activate" _ + , "CreateBaseDocument" _ + , "CreateDocument" _ + , "Documents" _ + , "GetDocument" _ + , "Maximize" _ + , "Minimize" _ + , "OpenBaseDocument" _ + , "OpenDocument" _ + , "Resize" _ + , "SetStatusbar" _ + , "ShowProgressBar" _ + , "WindowExists" _ + ) + +End Function ' ScriptForge.SF_UI.Methods + +REM ----------------------------------------------------------------------------- +Public Function OpenBaseDocument(Optional ByVal FileName As Variant _ + , Optional ByVal RegistrationName As Variant _ + , Optional ByVal MacroExecution As Variant _ + ) As Object +''' Open an existing LibreOffice Base document and return a SFDocuments.Document object +''' Args: +''' FileName: Identifies the file to open. It must follow the SF_FileSystem.FileNaming notation +''' RegistrationName: the name of a registered database +''' It is ignored if FileName <> "" +''' MacroExecution: one of the MACROEXECxxx constants +''' Returns: +''' A SFDocuments.SF_Base object +''' Null if the opening failed, including when due to a user decision +''' Exceptions: +''' BASEDOCUMENTOPENERROR Wrong arguments +''' Examples: +''' Dim mBasec As Object, FSO As Object +''' Set myBase = ui.OpenBaseDocument("C:\Temp\myDB.odb", MacroExecution := ui.MACROEXECNEVER) + +Dim oOpen As Variant ' Return value +Dim vProperties As Variant ' Array of com.sun.star.beans.PropertyValue +Dim oDBContext As Object ' com.sun.star.sdb.DatabaseContext +Dim oComp As Object ' Loaded component com.sun.star.lang.XComponent +Dim sFile As String ' Alias for FileName +Dim iMacro As Integer ' Alias for MacroExecution +Const cstThisSub = "UI.OpenBaseDocument" +Const cstSubArgs = "[FileName=""""], [RegistrationName=""""], [MacroExecution=0|1|2]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oOpen = Nothing + +Check: + If IsMissing(FileName) Or IsEmpty(FileName) Then FileName = "" + If IsMissing(RegistrationName) Or IsEmpty(RegistrationName) Then RegistrationName = "" + If IsMissing(MacroExecution) Or IsEmpty(MacroExecution) Then MacroExecution = MACROEXECNORMAL + + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName", , True) Then GoTo Finally + If Not SF_Utils._Validate(RegistrationName, "RegistrationName", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(MacroExecution, "MacroExecution", V_NUMERIC _ + , Array(MACROEXECNORMAL, MACROEXECNEVER, MACROEXECALWAYS)) Then GoTo Finally + End If + + ' Check the existence of FileName + If Len(FileName) = 0 Then ' FileName has precedence over RegistrationName + If Len(RegistrationName) = 0 Then GoTo CatchError + Set oDBContext = SF_Utils._GetUNOService("DatabaseContext") + If Not oDBContext.hasRegisteredDatabase(RegistrationName) Then GoTo CatchError + FileName = SF_FileSystem._ConvertFromUrl(oDBContext.getDatabaseLocation(RegistrationName)) + End If + If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchError + +Try: + With com.sun.star.document.MacroExecMode + Select Case MacroExecution + Case 0 : iMacro = .USE_CONFIG + Case 1 : iMacro = .NEVER_EXECUTE + Case 2 : iMacro = .ALWAYS_EXECUTE_NO_WARN + End Select + End With + + vProperties = Array(SF_Utils._MakePropertyValue("MacroExecutionMode", iMacro)) + + sFile = SF_FileSystem._ConvertToUrl(FileName) + Set oComp = StarDesktop.loadComponentFromURL(sFile, "_blank", 0, vProperties) + If Not IsNull(oComp) Then Set oOpen = CreateScriptService("SFDocuments.Document", oComp) + +Finally: + Set OpenBaseDocument = oOpen + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchError: + SF_Exception.RaiseFatal(BASEDOCUMENTOPENERROR, "FileName", FileName, "RegistrationName", RegistrationName) + GoTo Finally +End Function ' ScriptForge.SF_UI.OpenBaseDocument + +REM ----------------------------------------------------------------------------- +Public Function OpenDocument(Optional ByVal FileName As Variant _ + , Optional ByVal Password As Variant _ + , Optional ByVal ReadOnly As Variant _ + , Optional ByVal Hidden As Variant _ + , Optional ByVal MacroExecution As Variant _ + , Optional ByVal FilterName As Variant _ + , Optional ByVal FilterOptions As Variant _ + ) As Object +''' Open an existing LibreOffice document with the given options +''' Args: +''' FileName: Identifies the file to open. It must follow the SF_FileSystem.FileNaming notation +''' Password: To use when the document is protected +''' If wrong or absent while the document is protected, the user will be prompted to enter a password +''' ReadOnly: Default = False +''' Hidden: if True, open in the background (default = False) +''' To use with caution: activation or closure can only happen programmatically +''' MacroExecution: one of the MACROEXECxxx constants +''' FilterName: the name of a filter that should be used for loading the document +''' If present, the filter must exist +''' FilterOptions: an optional string of options associated with the filter +''' Returns: +''' A SFDocuments.SF_Document object or one of its subclasses +''' Null if the opening failed, including when due to a user decision +''' Exceptions: +''' DOCUMENTOPENERROR Wrong arguments +''' Examples: +''' Dim myDoc As Object, FSO As Object +''' Set myDoc = ui.OpenDocument("C:\Temp\myFile.odt", MacroExecution := ui.MACROEXECNEVER) + +Dim oOpen As Variant ' Return value +Dim oFilterFactory As Object ' com.sun.star.document.FilterFactory +Dim vProperties As Variant ' Array of com.sun.star.beans.PropertyValue +Dim oComp As Object ' Loaded component com.sun.star.lang.XComponent +Dim sFile As String ' Alias for FileName +Dim iMacro As Integer ' Alias for MacroExecution +Const cstThisSub = "UI.OpenDocument" +Const cstSubArgs = "FileName, [Password=""""], [ReadOnly=False], [Hidden=False], [MacroExecution=0|1|2], [FilterName=""""], [FilterOptions=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set oOpen = Nothing + +Check: + If IsMissing(Password) Or IsEmpty(Password) Then Password = "" + If IsMissing(ReadOnly) Or IsEmpty(ReadOnly) Then ReadOnly = False + If IsMissing(Hidden) Or IsEmpty(Hidden) Then Hidden = False + If IsMissing(MacroExecution) Or IsEmpty(MacroExecution) Then MacroExecution = MACROEXECNORMAL + If IsMissing(FilterName) Or IsEmpty(FilterName) Then FilterName = "" + If IsMissing(FilterOptions) Or IsEmpty(FilterOptions) Then FilterOptions = "" + + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Password, "Password", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(ReadOnly, "ReadOnly", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(Hidden, "Hidden", V_BOOLEAN) Then GoTo Finally + If Not SF_Utils._Validate(MacroExecution, "MacroExecution", V_NUMERIC _ + , Array(MACROEXECNORMAL, MACROEXECNEVER, MACROEXECALWAYS)) Then GoTo Finally + If Not SF_Utils._Validate(FilterName, "FilterName", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(FilterOptions, "FilterOptions", V_STRING) Then GoTo Finally + End If + + ' Check the existence of FileName and FilterName + If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchError + If Len(FilterName) > 0 Then + Set oFilterFactory = SF_Utils._GetUNOService("FilterFactory") + If Not oFilterFactory.hasByName(FilterName) Then GoTo CatchError + End If + +Try: + With com.sun.star.document.MacroExecMode + Select Case MacroExecution + Case 0 : iMacro = .USE_CONFIG + Case 1 : iMacro = .NEVER_EXECUTE + Case 2 : iMacro = .ALWAYS_EXECUTE_NO_WARN + End Select + End With + + vProperties = Array( _ + SF_Utils._MakePropertyValue("ReadOnly", ReadOnly) _ + , SF_Utils._MakePropertyValue("Hidden", Hidden) _ + , SF_Utils._MakePropertyValue("MacroExecutionMode", iMacro) _ + , SF_Utils._MakePropertyValue("FilterName", FilterName) _ + , SF_Utils._MakePropertyValue("FilterOptions", FilterOptions) _ + ) + If Len(Password) > 0 Then ' Password is to add only if <> "" !? + vProperties = SF_Array.Append(vProperties, SF_Utils._MakePropertyValue("Password", Password)) + End If + + sFile = SF_FileSystem._ConvertToUrl(FileName) + Set oComp = StarDesktop.loadComponentFromURL(sFile, "_blank", 0, vProperties) + If Not IsNull(oComp) Then Set oOpen = CreateScriptService("SFDocuments.Document", oComp) + +Finally: + Set OpenDocument = oOpen + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchError: + SF_Exception.RaiseFatal(DOCUMENTOPENERROR, "FileName", FileName, "Password", Password, "FilterName", FilterName) + GoTo Finally +End Function ' ScriptForge.SF_UI.OpenDocument + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Timer class as an array + + Properties = Array( _ + "ActiveWindow" _ + ) + +End Function ' ScriptForge.SF_UI.Properties + +REM ----------------------------------------------------------------------------- +Public Sub Resize(Optional ByVal Left As Variant _ + , Optional ByVal Top As Variant _ + , Optional ByVal Width As Variant _ + , Optional ByVal Height As Variant _ + ) +''' Resizes and/or moves the active window. Negative arguments are ignored. +''' If the window was minimized or without arguments, it is restored +''' Args: +''' Left, Top: Distances from top and left edges of the screen +''' Width, Height: Dimensions of the window +''' Examples: +''' ui.Resize(10,,500) ' Top and Height are unchanged +''' ... + +Dim vWindow As Window ' A single component +Dim oContainer As Object ' com.sun.star.awt.XWindow +Dim iPosSize As Integer ' Computes which of the 4 arguments should be considered +Const cstThisSub = "UI.Resize" +Const cstSubArgs = "[Left], [Top], [Width], [Height]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Left) Or IsEmpty(Left) Then Left = -1 + If IsMissing(Top) Or IsEmpty(Top) Then Top = -1 + If IsMissing(Width) Or IsEmpty(Width) Then Width = -1 + If IsMissing(Height) Or IsEmpty(Height) Then Height = -1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Left, "Left", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Top, "Top", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Width, "Width", V_NUMERIC) Then GoTo Finally + If Not SF_Utils._Validate(Height, "Height", V_NUMERIC) Then GoTo Finally + End If + +Try: + vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent) + If Not IsNull(vWindow.Frame) Then + Set oContainer = vWindow.Frame.ContainerWindow + iPosSize = 0 + If Left >= 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.X + If Top >= 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.Y + If Width > 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.WIDTH + If Height > 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.HEIGHT + With oContainer + .IsMaximized = False + .IsMinimized = False + .setPosSize(Left, Top, Width, Height, iPosSize) + End With + End If + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_UI.Resize + +REM ----------------------------------------------------------------------------- +Public Function SetProperty(Optional ByVal PropertyName As Variant _ + , Optional ByRef Value As Variant _ + ) As Boolean +''' Set a new value to the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Value: its new value +''' Exceptions +''' ARGUMENTERROR The property does not exist + +Const cstThisSub = "UI.SetProperty" +Const cstSubArgs = "PropertyName, Value" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + SetProperty = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + Select Case UCase(PropertyName) + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.SetProperty + +REM ----------------------------------------------------------------------------- +Public Sub SetStatusbar(Optional ByVal Text As Variant _ + , Optional ByVal Percentage As Variant _ + ) +''' Display a text and a progressbar in the status bar of the active window +''' Any subsequent calls in the same macro run refer to the same status bar of the same window, +''' even if the window is not active anymore +''' A call without arguments resets the status bar to its normal state. +''' Args: +''' Text: the optional text to be displayed before the progress bar +''' Percentage: the optional degree of progress between 0 and 100 +''' Examples: +''' Dim i As Integer +''' For i = 0 To 100 +''' ui.SetStatusbar("Progress ...", i) +''' Wait 50 +''' Next i +''' ui.SetStatusbar + +Dim oComp As Object +Dim oControl As Object +Static oStatusbar As Object +Const cstThisSub = "UI.SetStatusbar" +Const cstSubArgs = "[Text], [Percentage]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Text) Or IsEmpty(Text) Then Text = "" + If IsMissing(Percentage) Or IsEmpty(Percentage) Then Percentage = -1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Text, "Text", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Percentage, "Percentage", V_NUMERIC) Then GoTo Finally + End If + +Try: + With oStatusbar + If IsNull(oStatusbar) Then ' Initial call + Set oComp = StarDesktop.CurrentComponent + If Not IsNull(oComp) Then + Set oControl = Nothing + If SF_Session.HasUnoProperty(oComp, "CurrentController") Then Set oControl = oComp.CurrentController + If Not IsNull(oControl) Then + If SF_Session.HasUnoMethod(oControl, "getStatusIndicator") Then oStatusbar = oControl.getStatusIndicator() + End If + End If + If Not IsNull(oStatusbar) Then + .start("", 100) + End If + End If + If Not IsNull(oStatusbar) Then + If Len(Text) = 0 And Percentage = -1 Then + .end() + Else + If Len(Text) > 0 Then .setText(Text) + If Percentage >= 0 And Percentage <= 100 Then .setValue(Percentage) + End If + End If + End With + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_UI.SetStatusbar + +REM ----------------------------------------------------------------------------- +Public Sub ShowProgressBar(Optional Title As Variant _ + , Optional ByVal Text As Variant _ + , Optional ByVal Percentage As Variant _ + ) +''' Display a non-modal dialog box. Specify its title, an explicatory text and the progress on a progressbar +''' A call without arguments erases the progress bar dialog. +''' The box will anyway vanish at the end of the macro run. +''' Args: +''' Title: the title appearing on top of the dialog box (Default = "ScriptForge") +''' Text: the optional text to be displayed above the progress bar (default = zero-length string) +''' Percentage: the degree of progress between 0 and 100. Default = 0 +''' Examples: +''' Dim i As Integer +''' For i = 0 To 100 +''' ui.ShowProgressBar(, "Progress ... " & i & "/100", i) +''' Wait 50 +''' Next i +''' ui.ShowProgressBar + +Dim bFirstCall As Boolean ' True at first invocation of method +Static oDialog As Object ' SFDialogs.Dialog object +Static oFixedText As Object ' SFDialogs.DialogControl object +Static oProgressBar As Object ' SFDialogs.DialogControl object +Dim sTitle As String ' Alias of Title +Const cstThisSub = "UI.ShowProgressBar" +Const cstSubArgs = "[Title], [Text], [Percentage]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + +Check: + If IsMissing(Title) Or IsEmpty(Title) Then Title = "" + If IsMissing(Text) Or IsEmpty(Text) Then Text = "" + If IsMissing(Percentage) Or IsEmpty(Percentage) Then Percentage = -1 + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(Title, "Title", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Text, "Text", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Percentage, "Percentage", V_NUMERIC) Then GoTo Finally + End If + +Try: + With oDialog + bFirstCall = ( IsNull(oDialog) ) + If Not bFirstCall Then bFirstCall = Not ._IsStillAlive(False) ' False to not raise an error + If bFirstCall Then Set oDialog = CreateScriptService("SFDialogs.Dialog", "GlobalScope", "ScriptForge", "dlgProgress") + + If Not IsNull(oDialog) Then + If Len(Title) = 0 And Len(Text) = 0 And Percentage = -1 Then + Set oDialog = .Dispose() + Else + .Caption = Iif(Len(Title) > 0, Title, "ScriptForge") + If bFirstCall Then + Set oFixedText = .Controls("ProgressText") + Set oProgressBar = .Controls("ProgressBar") + .Controls("CloseButton").Caption = _SF_.Interface.GetText("CLOSEBUTTON") + .Execute(Modal := False) + End If + If Len(Text) > 0 Then oFixedText.Caption = Text + oProgressBar.Value = Iif(Percentage >= 0 And Percentage <= 100, Percentage, 0) + End If + End If + End With + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Sub +Catch: + GoTo Finally +End Sub ' ScriptForge.SF_UI.ShowProgressBar + +REM ----------------------------------------------------------------------------- +Public Function WindowExists(Optional ByVal WindowName As Variant) As Boolean +''' Returns True if the specified window exists +''' Args: +''' WindowName: see definitions +''' Returns: +''' True if the given window is found +''' Examples: +''' ui.WindowExists("C:\Me\My file.odt") + +Dim bWindowExists As Boolean ' Return value +Dim oEnum As Object ' com.sun.star.container.XEnumeration +Dim oComp As Object ' com.sun.star.lang.XComponent +Dim vWindow As Window ' A single component +Dim oContainer As Object ' com.sun.star.awt.XWindow +Const cstThisSub = "UI.WindowExists" +Const cstSubArgs = "WindowName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bWindowExists = False + +Check: + If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(WindowName, "WindowName") Then GoTo Finally + End If + +Try: + Set oEnum = StarDesktop.Components().createEnumeration + Do While oEnum.hasMoreElements + Set oComp = oEnum.nextElement + vWindow = SF_UI._IdentifyWindow(oComp) + With vWindow + ' Does the current window match the arguments ? + If (Len(.WindowFileName) > 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _ + Or (Len(.WindowName) > 0 And .WindowName = WindowName) _ + Or (Len(.WindowTitle) > 0 And .WindowTitle = WindowName) Then + bWindowExists = True + Exit Do + End If + End With + Loop + +Finally: + WindowExists = bWindowExists + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI.WindowExists + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Public Sub _CloseProgressBar(Optional ByRef poEvent As Object) +''' Triggered by the Close button in the dlgProgress dialog +''' to simply close the dialog + + ShowProgressBar() ' Without arguments => close the dialog + +End Sub ' ScriptForge.SF_UI._CloseProgressBar + +REM ----------------------------------------------------------------------------- +Public Function _IdentifyWindow(ByRef poComponent As Object) As Object +''' Return a Window object (definition on top of module) based on component given as argument +''' Is a shortcut to explore the most relevant properties or objects bound to a UNO component + +Dim oWindow As Window ' Return value +Dim sImplementation As String ' Component's implementationname +Dim sIdentifier As String ' Component's identifier +Dim vArg As Variant ' One single item of the Args UNO property +Dim FSO As Object ' Alias for SF_FileSystem + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set _IdentifyWindow = Nothing + sImplementation = "" : sIdentifier = "" + + Set FSO = SF_FileSystem + With oWindow + Set .Frame = Nothing + Set .Component = Nothing + .WindowName = "" + .WindowTitle = "" + .WindowFileName = "" + .DocumentType = "" + If IsNull(poComponent) Then GoTo Finally + If SF_Session.HasUnoProperty(poComponent, "ImplementationName") Then sImplementation = poComponent.ImplementationName + If SF_Session.HasUnoProperty(poComponent, "Identifier") Then sIdentifier = poComponent.Identifier + Set .Component = poComponent + Select Case sImplementation + Case "com.sun.star.comp.basic.BasicIDE" + .WindowName = BASICIDE + Case "com.sun.star.comp.dba.ODatabaseDocument" ' No identifier + .WindowFileName = SF_Utils._GetPropertyValue(poComponent.Args, "URL") + If Len(.WindowFileName) > 0 Then .WindowName = FSO.GetName(FSO._ConvertFromUrl(.WindowFileName)) + .DocumentType = BASEDOCUMENT + Case "org.openoffice.comp.dbu.ODatasourceBrowser" + Case "org.openoffice.comp.dbu.OTableDesign", "org.openoffice.comp.dbu.OQueryDesign" ' Table or Query in Edit mode + Case "org.openoffice.comp.dbu.ORelationDesign" + Case "com.sun.star.comp.sfx2.BackingComp" ' Welcome screen + Set .Frame = poComponent.Frame + .WindowName = WELCOMESCREEN + Case Else + If Len(sIdentifier) > 0 Then + ' Do not use URL : it contains the TemplateFile when new documents are created from a template + .WindowFileName = poComponent.Location + If Len(.WindowFileName) > 0 Then .WindowName = FSO.GetName(FSO._ConvertFromUrl(.WindowFileName)) + If SF_Session.HasUnoProperty(poComponent, "Title") Then .WindowTitle = poComponent.Title + Select Case sIdentifier + Case "com.sun.star.sdb.FormDesign" ' Form + Case "com.sun.star.sdb.TextReportDesign" ' Report + Case "com.sun.star.text.TextDocument" ' Writer + .DocumentType = WRITERDOCUMENT + Case "com.sun.star.sheet.SpreadsheetDocument" ' Calc + .DocumentType = CALCDOCUMENT + Case "com.sun.star.presentation.PresentationDocument" ' Impress + .DocumentType = IMPRESSDOCUMENT + Case "com.sun.star.drawing.DrawingDocument" ' Draw + .DocumentType = DRAWDOCUMENT + Case "com.sun.star.formula.FormulaProperties" ' Math + .DocumentType = MATHDOCUMENT + Case Else + End Select + End If + End Select + If IsNull(.Frame) Then + If Not IsNull(poComponent.CurrentController) Then Set .Frame = poComponent.CurrentController.Frame + End If + End With + +Finally: + Set _IdentifyWindow = oWindow + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_UI._IdentifyWindow + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the UI instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[UI]" + + _Repr = "[UI]" + +End Function ' ScriptForge.SF_UI._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_UI +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Utils.xba b/wizards/source/scriptforge/SF_Utils.xba new file mode 100644 index 000000000000..eed61f074e53 --- /dev/null +++ b/wizards/source/scriptforge/SF_Utils.xba @@ -0,0 +1,967 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Utils" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Explicit +Option Private Module + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_Utils +''' ======== +''' FOR INTERNAL USE ONLY +''' Groups all private functions used by the official modules +''' Declares the Global variable _SF_ +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ===================================================================== GLOBALS + +Global _SF_ As Variant ' SF_Root (Basic) object) + +''' ScriptForge version +Const SF_Version = "7.1" + +''' Standard symbolic names for VarTypes +' V_EMPTY = 0 +' V_NULL = 1 +' V_INTEGER = 2 +' V_LONG = 3 +' V_SINGLE = 4 +' V_DOUBLE = 5 +' V_CURRENCY = 6 +' V_DATE = 7 +' V_STRING = 8 +''' Additional symbolic names for VarTypes +Global Const V_OBJECT = 9 +Global Const V_BOOLEAN = 11 +Global Const V_VARIANT = 12 +Global Const V_BYTE = 17 +Global Const V_USHORT = 18 +Global Const V_ULONG = 19 +Global Const V_BIGINT = 35 +Global Const V_DECIMAL = 37 +Global Const V_ARRAY = 8192 +Global Const V_NUMERIC = 99 ' Fictive VarType synonym of any numeric value + +REM ================================================================== EXCEPTIONS + +Const MISSINGARGERROR = "MISSINGARGERROR" ' A mandatory argument is missing +Const ARGUMENTERROR = "ARGUMENTERROR" ' An argument does not pass the _Validate() validation +Const ARRAYERROR = "ARRAYERROR" ' An argument does not pass the _ValidateArray() validation +Const FILEERROR = "FILEERROR" ' An argument does not pass the _ValidateFile() validation + +REM =========================================pvA==================== PRIVATE METHODS + +REM ----------------------------------------------------------------------------- +Public Function _CDateToIso(pvDate As Variant) As Variant +''' Returns a string representation of the given Basic date +''' Dates as strings are essential in property values, where Basic dates are evil + +Dim sIsoDate As Variant ' Return value + + If VarType(pvDate) = V_DATE Then + If Year(pvDate) < 1900 Then ' Time only + sIsoDate = Right("0" & Hour(pvDate), 2) & ":" & Right("0" & Minute(pvDate), 2) & ":" & Right("0" & Second(pvDate), 2) + ElseIf Hour(pvDate) + Minute(pvDate) + Second(pvDate) = 0 Then ' Date only + sIsoDate = Year(pvDate) & "-" & Right("0" & Month(pvDate), 2) & "-" & Right("0" & Day(pvDate), 2) + Else + sIsoDate = Year(pvDate) & "-" & Right("0" & Month(pvDate), 2) & "-" & Right("0" & Day(pvDate), 2) _ + & " " & Right("0" & Hour(pvDate), 2) & ":" & Right("0" & Minute(pvDate), 2) _ + & ":" & Right("0" & Second(pvDate), 2) + End If + Else + sIsoDate = pvDate + End If + + _CDateToIso = sIsoDate + +End Function ' ScriptForge.SF_Utils._CDateToIso + +REM ----------------------------------------------------------------------------- +Public Function _CDateToUnoDate(pvDate As Variant) As Variant +''' Returns a UNO com.sun.star.util.DateTime/Date/Time object depending on the given date +''' by using the appropriate CDateToUnoDateXxx builtin function +''' UNO dates are essential in property values, where Basic dates are evil + +Dim vUnoDate As Variant ' Return value + + If VarType(pvDate) = V_DATE Then + If Year(pvDate) < 1900 Then + vUnoDate = CDateToUnoTime(pvDate) + ElseIf Hour(pvDate) + Minute(pvDate) + Second(pvDate) = 0 Then + vUnoDate = CDateToUnoDate(pvDate) + Else + vUnoDate = CDateToUnoDateTime(pvDate) + End If + Else + vUnoDate = pvDate + End If + + _CDateToUnoDate = vUnoDate + +End Function ' ScriptForge.SF_Utils._CDateToUnoDate + +REM ----------------------------------------------------------------------------- +Public Function _CPropertyValue(ByRef pvValue As Variant) As Variant +''' Set a value of a correct type in a com.sun.star.beans.PropertyValue +''' Date BASIC variables give error. Change them to UNO types +''' Empty arrays should be replaced by Null + +Dim vValue As Variant ' Return value + + If VarType(pvValue) = V_DATE Then + vValue = SF_Utils._CDateToUnoDate(pvValue) + ElseIf IsArray(pvValue) Then + If UBound(pvValue, 1) < LBound(pvValue, 1) Then vValue = Null Else vValue = pvValue + Else + vValue = pvValue + End If + _CPropertyValue() = vValue + +End Function ' ScriptForge.SF_Utils._CPropertyValue + +REM ----------------------------------------------------------------------------- +Public Function _CStrToDate(ByRef pvStr As String) As Date +''' Attempt to convert the input string to a Date variable with the CDate builtin function +''' If not successful, returns conventionally -1 (29/12/1899) +''' Date patterns: YYYY-MM-DD, HH:MM:DD and YYYY-MM-DD HH:MM:DD + +Dim dDate As Date ' Return value +Const cstNoDate = -1 + + dDate = cstNoDate +Try: + On Local Error Resume Next + dDate = CDate(pvStr) + +Finally: + _CStrToDate = dDate + Exit Function +End Function ' ScriptForge.SF_Utils._CStrToDate + +REM ----------------------------------------------------------------------------- +Public Function _EnterFunction(ByVal psSub As String, Optional ByVal psArgs As String) +''' Called on top of each public function +''' Used to trace routine in/outs (debug mode) +''' and to allow the explicit mention of the user call which caused an error +''' Args: +''' psSub = the called Sub/Function/Property, usually something like "service.sub" +''' Return: True when psSub is called from a user script +''' Used to bypass the validation of the arguments when unnecessary + + If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() ' First use of ScriptForge during current LibO session + If IsMissing(psArgs) Then psArgs = "" + With _SF_ + If .StackLevel = 0 Then + .MainFunction = psSub + .MainFunctionArgs = psArgs + _EnterFunction = True + Else + _EnterFunction = False + End If + .StackLevel = .StackLevel + 1 + If .DebugMode Then ._AddToConsole("==> " & psSub & "(" & .StackLevel & ")") + End With + +End Function ' ScriptForge.SF_Utils._EnterFunction + +REM ----------------------------------------------------------------------------- +Public Function _ErrorHandling(Optional ByVal pbErrorHandler As Boolean) As Boolean +''' Error handling is normally ON and can be set OFF for debugging purposes +''' Each user visible routine starts with a call to this function to enable/disable +''' standard handling of internal errors +''' Args: +''' pbErrorHandler = if present, set its value +''' Return: the current value of the error handler + + If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() ' First use of ScriptForge during current LibO session + If Not IsMissing(pbErrorHandler) Then _SF_.ErrorHandler = pbErrorHandler + _ErrorHandling = _SF_.ErrorHandler + +End Function ' ScriptForge.SF_Utils._ErrorHandling + +REM ----------------------------------------------------------------------------- +Public Sub _ExitFunction(ByVal psSub As String) +''' Called in the Finally block of each public function +''' Manage ScriptForge internal aborts +''' Resets MainFunction (root) when exiting the method called by a user script +''' Used to trace routine in/outs (debug mode) +''' Args: +''' psSub = the called Sub/Function/Property, usually something like "service.sub" + + If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() ' Useful only when current module has been recompiled + With _SF_ + If Err > 0 Then + SF_Exception.RaiseAbort(psSub) + End If + If .StackLevel = 1 Then + .MainFunction = "" + .MainFunctionArgs = "" + End If + If .DebugMode Then ._AddToConsole("<== " & psSub & "(" & .StackLevel & ")") + If .StackLevel > 0 Then .StackLevel = .StackLevel - 1 + End With + +End Sub ' ScriptForge.SF_Utils._ExitFunction + +REM ----------------------------------------------------------------------------- +Public Sub _ExportScriptForgePOTFile(ByVal FileName As String) +''' Export the ScriptForge POT file related to its own user interface +''' Should be called only before issuing new ScriptForge releases only +''' Args: +''' FileName: the resulting file. It it exists, it is overwritten without warning + +Dim sHeader As String ' The specific header to insert + + sHeader = "" _ + & "*********************************************************************\n" _ + & "*** The ScriptForge library and its associated libraries ***\n" _ + & "*** are part of the LibreOffice project. ***\n" _ + & "*********************************************************************\n" _ + & "\n" _ + & "ScriptForge Release " & SF_Version & "\n" _ + & "-----------------------" + +Try: + With _SF_ + .Interface.ExportToPOTFile(FileName, Header := sHeader) + End With + +Finally: + Exit Sub +End Sub ' ScriptForge.SF_Utils._ExportScriptForgePOTFile + +REM ----------------------------------------------------------------------------- +Public Function _GetPropertyValue(ByRef pvArgs As Variant, ByVal psName As String) As Variant +''' Returns the Value corresponding to the given name +''' Args +''' pvArgs: a zero_based array of PropertyValues +''' psName: the comparison is not case-sensitive +''' Retuns: +''' Zero-length string if not found + +Dim vValue As Variant ' Return value +Dim i As Long + + vValue = "" + If IsArray(pvArgs) Then + For i = LBound(pvArgs) To UBound(pvArgs) + If UCase(psName) = UCase(pvArgs(i).Name) Then + vValue = pvArgs(i).Value + Exit For + End If + Next i + End If + _GetPropertyValue = vValue + +End Function ' ScriptForge.SF_Utils._GetPropertyValue + +REM ----------------------------------------------------------------------------- +Public Function _GetRegistryKeyContent(ByVal psKeyName as string _ + , Optional pbForUpdate as Boolean _ + ) As Variant +''' Implement a ConfigurationProvider service +''' Derived from the Tools library +''' Args: +''' psKeyName: the name of the node in the configuration tree +''' pbForUpdate: default = False + +Dim oConfigProvider as Object ' com.sun.star.configuration.ConfigurationProvider +Dim vNodePath(0) as New com.sun.star.beans.PropertyValue +Dim sConfig As String ' One of next 2 constants +Const cstConfig = "com.sun.star.configuration.ConfigurationAccess" +Const cstConfigUpdate = "com.sun.star.configuration.ConfigurationUpdateAccess" + + Set oConfigProvider = _GetUNOService("ConfigurationProvider") + vNodePath(0).Name = "nodepath" + vNodePath(0).Value = psKeyName + + If IsMissing(pbForUpdate) Then pbForUpdate = False + If pbForUpdate Then sConfig = cstConfigUpdate Else sConfig = cstConfig + + Set _GetRegistryKeyContent = oConfigProvider.createInstanceWithArguments(sConfig, vNodePath()) + +End Function ' ScriptForge.SF_Utils._GetRegistryKeyContent + +REM ----------------------------------------------------------------------------- +Public Function _GetUNOService(ByVal psService As String _ + , Optional ByVal pvArg As Variant _ + ) As Object +''' Create a UNO service +''' Each service is called only once +''' Args: +''' psService: shortcut to service +''' pvArg: some services might require an argument + +Dim sLocale As String ' fr-BE f.i. +Dim oConfigProvider As Object +Dim oDefaultContext As Object +Dim vNodePath As Variant + + Set _GetUNOService = Nothing + With _SF_ + Select Case psService + Case "BrowseNodeFactory" + Set oDefaultContext = GetDefaultContext() + If Not IsNull(oDefaultContext) Then Set _GetUNOService = oDefaultContext.getValueByName("/singletons/com.sun.star.script.browse.theBrowseNodeFactory") + Case "CharacterClass" + If IsEmpty(.CharacterClass) Or IsNull(.CharacterClass) Then + Set .CharacterClass = CreateUnoService("com.sun.star.i18n.CharacterClassification") + End If + Set _GetUNOService = .CharacterClass + Case "ConfigurationProvider" + If IsEmpty(.ConfigurationProvider) Or IsNull(.ConfigurationProvider) Then + Set .ConfigurationProvider = CreateUnoService("com.sun.star.configuration.ConfigurationProvider") + End If + Set _GetUNOService = .ConfigurationProvider + Case "CoreReflection" + If IsEmpty(.CoreReflection) Or IsNull(.CoreReflection) Then + Set .CoreReflection = CreateUnoService("com.sun.star.reflection.CoreReflection") + End If + Set _GetUNOService = .CoreReflection + Case "DatabaseContext" + If IsEmpty(.DatabaseContext) Or IsNull(.DatabaseContext) Then + Set .DatabaseContext = CreateUnoService("com.sun.star.sdb.DatabaseContext") + End If + Set _GetUNOService = .DatabaseContext + Case "DispatchHelper" + If IsEmpty(.DispatchHelper) Or IsNull(.DispatchHelper) Then + Set .DispatchHelper = CreateUnoService("com.sun.star.frame.DispatchHelper") + End If + Set _GetUNOService = .DispatchHelper + Case "FileAccess" + If IsEmpty(.FileAccess) Or IsNull(.FileAccess) Then + Set .FileAccess = CreateUnoService("com.sun.star.ucb.SimpleFileAccess") + End If + Set _GetUNOService = .FileAccess + Case "FilePicker" + If IsEmpty(.FilePicker) Or IsNull(.FilePicker) Then + Set .FilePicker = CreateUnoService("com.sun.star.ui.dialogs.FilePicker") + End If + Set _GetUNOService = .FilePicker + Case "FilterFactory" + If IsEmpty(.FilterFactory) Or IsNull(.FilterFactory) Then + Set .FilterFactory = CreateUnoService("com.sun.star.document.FilterFactory") + End If + Set _GetUNOService = .FilterFactory + Case "FolderPicker" + If IsEmpty(.FolderPicker) Or IsNull(.FolderPicker) Then + Set .FolderPicker = CreateUnoService("com.sun.star.ui.dialogs.FolderPicker") + End If + Set _GetUNOService = .FolderPicker + Case "FunctionAccess" + If IsEmpty(.FunctionAccess) Or IsNull(.FunctionAccess) Then + Set .FunctionAccess = CreateUnoService("com.sun.star.sheet.FunctionAccess") + End If + Set _GetUNOService = .FunctionAccess + Case "Introspection" + If IsEmpty(.Introspection) Or IsNull(.Introspection) Then + Set .Introspection = CreateUnoService("com.sun.star.beans.Introspection") + End If + Set _GetUNOService = .Introspection + Case "Locale" + If IsEmpty(.Locale) Or IsNull(.Locale) Then + .Locale = CreateUnoStruct("com.sun.star.lang.Locale") + ' Derived from the Tools library + Set oConfigProvider = createUnoService("com.sun.star.configuration.ConfigurationProvider") + vNodePath = Array() : ReDim vNodePath(0) + vNodePath(0) = New com.sun.star.beans.PropertyValue + vNodePath(0).Name = "nodepath" : vNodePath(0).Value = "org.openoffice.Setup/L10N" + sLocale = oConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", vNodePath()).getByName("ooLocale") + .Locale.Language = Left(sLocale, 2) + .Locale.Country = Right(sLocale, 2) + End If + Set _GetUNOService = .Locale + Case "MacroExpander" + Set oDefaultContext = GetDefaultContext() + If Not IsNull(oDefaultContext) Then Set _GetUNOService = oDefaultContext.getValueByName("/singletons/com.sun.star.util.theMacroExpander") + Case "MailService" + If IsEmpty(.MailService) Or IsNull(.MailService) Then + If GetGuiType = 1 Then ' Windows + Set .MailService = CreateUnoService("com.sun.star.system.SimpleSystemMail") + Else + Set .MailService = CreateUnoService("com.sun.star.system.SimpleCommandMail") + End If + End If + Set _GetUNOService = .MailService + Case "PathSettings" + If IsEmpty(.PathSettings) Or IsNull(.PathSettings) Then + Set .PathSettings = CreateUnoService("com.sun.star.util.PathSettings") + End If + Set _GetUNOService = .PathSettings + Case "PathSubstitution" + If IsEmpty(.PathSubstitution) Or IsNull(.PathSubstitution) Then + Set .PathSubstitution = CreateUnoService("com.sun.star.util.PathSubstitution") + End If + Set _GetUNOService = .PathSubstitution + Case "ScriptProvider" + If IsMissing(pvArg) Then pvArg = SF_Session.SCRIPTISAPPLICATION + Select Case pvArg + Case SF_Session.SCRIPTISEMBEDDED ' Document + If Not IsNull(ThisComponent) Then Set _GetUNOService = ThisComponent.getScriptProvider() + Case Else + If IsEmpty(.ScriptProvider) Or IsNull(.ScriptProvider) Then + Set .ScriptProvider = _ + CreateUnoService("com.sun.star.script.provider.MasterScriptProviderFactory").createScriptProvider("") + End If + Set _GetUNOService = .ScriptProvider + End Select + Case "SearchOptions" + If IsEmpty(.SearchOptions) Or IsNull(.SearchOptions) Then + Set .SearchOptions = New com.sun.star.util.SearchOptions + With .SearchOptions + .algorithmType = com.sun.star.util.SearchAlgorithms.REGEXP + .searchFlag = 0 + End With + End If + Set _GetUNOService = .SearchOptions + Case "SystemShellExecute" + If IsEmpty(.SystemShellExecute) Or IsNull(.SystemShellExecute) Then + Set .SystemShellExecute = CreateUnoService("com.sun.star.system.SystemShellExecute") + End If + Set _GetUNOService = .SystemShellExecute + Case "TextSearch" + If IsEmpty(.TextSearch) Or IsNull(.TextSearch) Then + Set .TextSearch = CreateUnoService("com.sun.star.util.TextSearch") + End If + Set _GetUNOService = .TextSearch + Case "URLTransformer" + If IsEmpty(.URLTransformer) Or IsNull(.URLTransformer) Then + Set .URLTransformer = CreateUnoService("com.sun.star.util.URLTransformer") + End If + Set _GetUNOService = .URLTransformer + Case Else + End Select + End With + +End Function ' ScriptForge.SF_Utils._GetUNOService + +REM ----------------------------------------------------------------------------- +Public Sub _InitializeRoot(Optional ByVal pbForce As Boolean) +''' Initialize _SF_ as SF_Root basic object +''' Args: +''' pbForce = True forces the reinit (default = False) + + If IsMissing(pbForce) Then pbForce = False + If pbForce Then Set _SF_ = Nothing + If IsEmpty(_SF_) Or IsNull(_SF_) Then + Set _SF_ = New SF_Root + Set _SF_.[Me] = _SF_ + ' Localization + _SF_._LoadLocalizedInterface() + End If + +End Sub ' ScriptForge.SF_Utils._InitializeRoot + +REM ----------------------------------------------------------------------------- +Public Function _MakePropertyValue(ByVal psName As String _ + , ByRef pvValue As Variant _ + ) As com.sun.star.beans.PropertyValue +''' Create and return a new com.sun.star.beans.PropertyValue + +Dim oPropertyValue As New com.sun.star.beans.PropertyValue + + With oPropertyValue + .Name = psName + .Value = SF_Utils._CPropertyValue(pvValue) + End With + _MakePropertyValue() = oPropertyValue + +End Function ' ScriptForge.SF_Utils._MakePropertyValue + +REM ----------------------------------------------------------------------------- +Public Function _Repr(ByVal pvArg As Variant, Optional ByVal plMax As Long) As String +''' Convert pvArg into a readable string (truncated if length > plMax) +''' Args +''' pvArg: may be of any type +''' plMax: maximum length of the resulting string (default = 32K) + +Dim sArg As String ' Return value +Dim oObject As Object ' Alias of argument to avoid "Object variable not set" +Dim sObject As String ' Object representation +Dim sObjectType As String ' ObjectType attribute of Basic objects +Dim sLength As String ' String length as a string +Dim i As Long +Const cstBasicObject = "com.sun.star.script.NativeObjectWrapper" + +Const cstMaxLength = 2^15 - 1 ' 32767 +Const cstByteLength = 25 +Const cstEtc = " … " + + If IsMissing(plMax) Or plMax = 0 Then plMax = cstMaxLength + If IsArray(pvArg) Then + sArg = SF_Array._Repr(pvArg) + Else + Select Case VarType(pvArg) + Case V_EMPTY : sArg = "[EMPTY]" + Case V_NULL : sArg = "[NULL]" + Case V_OBJECT + If IsNull(pvArg) Then + sArg = "[NULL]" + Else + sObject = SF_Session.UnoObjectType(pvArg) + If sObject = "" Or sObject = cstBasicObject Then ' Not a UNO object + ' Test if argument is a ScriptForge object + sObjectType = "" + On Local Error Resume Next + Set oObject = pvArg + sObjectType = oObject.ObjectType + On Error GoTo 0 + If sObjectType = "" Then + sArg = "[OBJECT]" + ElseIf Left(sObjectType, 3) = "SF_" Then + sArg = "[" & sObjectType & "]" + Else + sArg = oObject._Repr() + End If + Else + sArg = "[" & sObject & "]" + End If + End If + Case V_VARIANT : sArg = "[VARIANT]" + Case V_STRING + sArg = SF_String._Repr(pvArg) + Case V_BOOLEAN : sArg = Iif(pvArg, "[TRUE]", "[FALSE]") + Case V_BYTE : sArg = Right("00" & Hex(pvArg), 2) + Case V_SINGLE, V_DOUBLE, V_CURRENCY + sArg = Format(pvArg) + If InStr(1, sArg, "E", 1) = 0 Then sArg = Format(pvArg, "##0.0##") + sArg = Replace(sArg, ",", ".") 'Force decimal point + Case V_BIGINT : sArg = CStr(CLng(pvArg)) + Case V_DATE : sArg = _CDateToIso(pvArg) + Case Else : sArg = CStr(pvArg) + End Select + End If + If Len(sArg) > plMax Then + sLength = "(" & Len(sArg) & ")" + sArg = Left(sArg, plMax - Len(cstEtc) - Len(slength)) & cstEtc & sLength + End If + _Repr = sArg + +End Function ' ScriptForge.SF_Utils._Repr + +REM ----------------------------------------------------------------------------- +Private Function _ReprValues(Optional ByVal pvArgs As Variant _ + , Optional ByVal plMax As Long _ + ) As String +''' Convert an array of values to a comma-separated list of readable strings + +Dim sValues As String ' Return value +Dim sValue As String ' A single value +Dim vValue As Variant ' A single item in the argument +Dim i As Long ' Items counter +Const cstMax = 20 ' Maximum length of single string +Const cstContinue = "…" ' Unicode continuation char U+2026 + + _ReprValues = "" + If IsMissing(pvArgs) Then Exit Function + If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs) + sValues = "" + For i = 0 To UBound(pvArgs) + vValue = pvArgs(i) + If i < plMax Then + If VarType(vValue) = V_STRING Then sValue = """" & vValue & """" Else sValue = SF_Utils._Repr(vValue, cstMax) + If Len(sValues) = 0 Then sValues = sValue Else sValues = sValues & ", " & sValue + ElseIf i < UBound(pvArgs) Then + sValues = sValues & ", " & cstContinue + Exit For + End If + Next i + _ReprValues = sValues + +End Function ' ScriptForge.SF_Utils._ReprValues + +REM ----------------------------------------------------------------------------- +Public Sub _SetPropertyValue(ByRef pvPropertyValue As Variant _ + , ByVal psName As String _ + , ByRef pvValue As Variant _ + ) +''' Update the 1st argument (passed by reference), which is an array of property values +''' If the property psName exists, update it with pvValue, otherwise create it on top of the array + +Dim oPropertyValue As New com.sun.star.beans.PropertyValue +Dim lIndex As Long ' Found entry +Dim vValue As Variant ' Alias of pvValue +Dim i As Long + + lIndex = -1 + For i = 0 To UBound(pvPropertyValue) + If pvPropertyValue(i).Name = psName Then + lIndex = i + Exit For + End If + Next i + If lIndex < 0 Then ' Not found + lIndex = UBound(pvPropertyValue) + 1 + ReDim Preserve pvPropertyValue(0 To lIndex) + Set oPropertyValue = SF_Utils._MakePropertyValue(psName, pvValue) + pvPropertyValue(lIndex) = oPropertyValue + Else ' psName exists already in array of property values + pvPropertyValue(lIndex).Value = SF_Utils._CPropertyValue(pvValue) + End If + +End Sub ' ScriptForge.SF_Utils._SetPropertyValue + +REM ----------------------------------------------------------------------------- +Private Function _TypeNames(Optional ByVal pvArgs As Variant) As String +''' Converts the array of VarTypes to a comma-sepatrated list of TypeNames + +Dim sTypes As String ' Return value +Dim sType As String ' A single type +Dim iType As Integer ' A single item of the argument + + _TypeNames = "" + If IsMissing(pvArgs) Then Exit Function + If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs) + sTypes = "" + For Each iType In pvArgs + Select Case iType + Case V_EMPTY : sType = "Empty" + Case V_NULL : sType = "Null" + Case V_INTEGER : sType = "Integer" + Case V_LONG : sType = "Long" + Case V_SINGLE : sType = "Single" + Case V_DOUBLE : sType = "Double" + Case V_CURRENCY : sType = "Currency" + Case V_DATE : sType = "Date" + Case V_STRING : sType = "String" + Case V_OBJECT : sType = "Object" + Case V_BOOLEAN : sType = "Boolean" + Case V_VARIANT : sType = "Variant" + Case V_DECIMAL : sType = "Decimal" + Case >= V_ARRAY : sType = "Array" + Case V_NUMERIC : sType = "Numeric" + End Select + If Len(sTypes) = 0 Then sTypes = sType Else sTypes = sTypes & ", " & sType + Next iType + _TypeNames = sTypes + +End Function ' ScriptForge.SF_Utils._TypeNames + +REM ----------------------------------------------------------------------------- +Public Function _Validate(Optional ByRef pvArgument As Variant _ + , ByVal psName As String _ + , Optional ByVal pvTypes As Variant _ + , Optional ByVal pvValues As Variant _ + , Optional ByVal pvRegex As Variant _ + , Optional ByVal pvObjectType As Variant _ + ) As Boolean +''' Validate the arguments set by user scripts +''' The arguments of the function define the validation rules +''' This function ignores arrays. Use _ValidateArray instead +''' Args: +''' pvArgument: the argument to (in)validate +''' psName: the documented name of the argument (can be inserted in an error message) +''' pvTypes: array of allowed VarTypes +''' pvValues: array of allowed values +''' pvRegex: regular expression to comply with +''' pvObjectType: mandatory Basic class +''' Return: True if validation OK +''' Otherwise an error is raised +''' Exceptions: +''' ARGUMENTERROR + +Dim iVarType As Integer ' Extended VarType of argument +Dim bValid As Boolean ' Returned value +Dim oArgument As Variant ' Workaround "Object variable not set" error on 1st executable statement +Const cstMaxLength = 256 ' Maximum length of readable value +Const cstMaxValues = 10 ' Maximum number of allowed items to list in an error message + + ' To avoid useless recursions, keep main function, only increase stack depth + _SF_.StackLevel = _SF_.StackLevel + 1 + On Local Error GoTo Finally ' Do never interrupt + +Try: + bValid = True + If IsMissing(pvArgument) Then GoTo CatchMissing + If IsMissing(pvRegex) Or IsEmpty(pvRegex) Then pvRegex = "" + If IsMissing(pvObjectType) Or IsEmpty(pvObjectType) Then pvObjectType = "" + iVarType = SF_Utils._VarTypeExt(pvArgument) + + ' Arrays NEVER pass validation + If iVarType >= V_ARRAY Then + bValid = False + Else + ' Check existence of argument + bValid = iVarType <> V_NULL And iVarType <> V_EMPTY + ' Check if argument's VarType is valid + If bValid And Not IsMissing(pvTypes) Then + If Not IsArray(pvTypes) Then bValid = ( pvTypes = iVarType ) Else bValid = SF_Array.Contains(pvTypes, iVarType) + End If + ' Check if argument's value is valid + If bValid And Not IsMissing(pvValues) Then + If Not IsArray(pvValues) Then pvValues = Array(pvValues) + bValid = SF_Array.Contains(pvValues, pvArgument, CaseSensitive := False) + End If + ' Check regular expression + If bValid And Len(pvRegex) > 0 And iVarType = V_STRING Then + If Len(pvArgument) > 0 Then bValid = SF_String.IsRegex(pvArgument, pvRegex, CaseSensitive := False) + End If + ' Check instance types + If bValid And Len(pvObjectType) > 0 And iVarType = V_OBJECT Then + Set oArgument = pvArgument + bValid = ( pvObjectType = oArgument.ObjectType ) + End If + End If + + If Not bValid Then + ''' Library: ScriptForge + ''' Service: Array + ''' Method: Contains + ''' Arguments: Array_1D, ToFind, [CaseSensitive=False], [SortOrder=""] + ''' A serious error has been detected on argument SortOrder + ''' Rules: SortOrder is of type String + ''' SortOrder must contain one of next values: "ASC", "DESC", "" + ''' Actual value: "Ascending" + SF_Exception.RaiseFatal(ARGUMENTERROR _ + , SF_Utils._Repr(pvArgument, cstMaxLength), psName, SF_Utils._TypeNames(pvTypes) _ + , SF_Utils._ReprValues(pvValues, cstMaxValues), pvRegex, pvObjectType _ + ) + End If + +Finally: + _Validate = bValid + _SF_.StackLevel = _SF_.StackLevel - 1 + Exit Function +CatchMissing: + bValid = False + SF_Exception.RaiseFatal(MISSINGARGERROR, psName) + GoTo Finally +End Function ' ScriptForge.SF_Utils._Validate + +REM ----------------------------------------------------------------------------- +Public Function _ValidateArray(Optional ByRef pvArray As Variant _ + , ByVal psName As String _ + , Optional ByVal piDimensions As Integer _ + , Optional ByVal piType As Integer _ + , Optional ByVal pbNotNull As Boolean _ + ) As Boolean +''' Validate the (array) arguments set by user scripts +''' The arguments of the function define the validation rules +''' This function ignores non-arrays. Use _Validate instead +''' Args: +''' pvArray: the argument to (in)validate +''' psName: the documented name of the array (can be inserted in an error message) +''' piDimensions: the # of dimensions the array must have. 0 = Any (default) +''' piType: (default = -1, i.e. not applicable) +''' For 2D arrays, the 1st column is checked +''' 0 => all items must be any out of next types: string, date or numeric, +''' but homogeneously: all strings or all dates or all numeric +''' V_STRING or V_DATE or V_NUMERIC => that specific type is required +''' pbNotNull: piType must be >=0, otherwise ignored +''' If True: Empty, Null items are rejected +''' Return: True if validation OK +''' Otherwise an error is raised +''' Exceptions: +''' ARRAYERROR + +Dim iVarType As Integer ' VarType of argument +Dim vItem As Variant ' Array item +Dim iItemType As Integer ' VarType of individual items of argument +Dim iDims As Integer ' Number of dimensions of the argument +Dim bValid As Boolean ' Returned value +Dim iArrayType As Integer ' Static array type +Dim iFirstItemType As Integer ' Type of 1st non-null/empty item +Dim sType As String ' Allowed item types as a string +Dim i As Long +Const cstMaxLength = 256 ' Maximum length of readable value + + ' To avoid useless recursions, keep main function, only increase stack depth + + _SF_.StackLevel = _SF_.StackLevel + 1 + On Local Error GoTo Finally ' Do never interrupt + +Try: + bValid = True + If IsMissing(pvArray) Then GoTo CatchMissing + If IsMissing(piDimensions) Then piDimensions = 0 + If IsMissing(piType) Then piType = -1 + If IsMissing(pbNotNull) Then pbNotNull = False + iVarType = VarType(pvArray) + + ' Scalars NEVER pass validation + If iVarType < V_ARRAY Then + bValid = False + Else + ' Check dimensions + iDims = SF_Array.CountDims(pvArray) + If iDims > 2 Then bValid = False ' Only 1D and 2D arrays + If bValid And piDimensions > 0 Then + bValid = ( iDims = piDimensions Or (iDims = 0 And piDimensions = 1) ) ' Allow empty vectors + End If + ' Check VarType and Empty/Null status of the array items + If bValid And iDims = 1 And piType >= 0 Then + iArrayType = SF_Array._StaticType(pvArray) + If (piType = 0 And iArrayType > 0) Or (piType > 0 And iArrayType = piType) Then + ' If static array of the right VarType ..., OK + Else + ' Go through array and check individual items + iFirstItemType = -1 + For i = LBound(pvArray, 1) To UBound(pvArray, 1) + If iDims = 1 Then vItem = pvArray(i) Else vItem = pvArray(i, LBound(pvArray, 2)) + iItemType = SF_Utils._VarTypeExt(vItem) + If iItemType > V_NULL Then ' Exclude Empty and Null + ' Initialization at first non-null item + If iFirstItemType < 0 Then + iFirstItemType = iItemType + If piType > 0 Then bValid = ( iFirstItemType = piType ) Else bValid = SF_Array.Contains(Array(V_STRING, V_DATE, V_NUMERIC), iFirstItemType) + Else + bValid = (iItemType = iFirstItemType) + End If + Else + bValid = Not pbNotNull + End If + If Not bValid Then Exit For + Next i + End If + End If + End If + + If Not bValid Then + ''' Library: ScriptForge + ''' Service: Array + ''' Method: Contains + ''' Arguments: Array_1D, ToFind, [CaseSensitive=False], [SortOrder=""|"ASC"|"DESC"] + ''' An error was detected on argument Array_1D + ''' Rules: Array_1D is of type Array + ''' Array_1D must have maximum 1 dimension + ''' Array_1D must have all elements of the same type: either String, Date or Numeric + ''' Actual value: (0:2, 0:3) + sType = "" + If piType = 0 Then + sType = "String, Date, Numeric" + ElseIf piType > 0 Then + sType = SF_Utils._TypeNames(piType) + End If + SF_Exception.RaiseFatal(ARRAYERROR _ + , SF_Utils._Repr(pvArray, cstMaxLength), psName, piDimensions, sType, pbNotNull) + End If + +Finally: + _ValidateArray = bValid + _SF_.StackLevel = _SF_.StackLevel - 1 + Exit Function +CatchMissing: + bValid = False + SF_Exception.RaiseFatal(MISSINGARGERROR, psName) + GoTo Finally +End Function ' ScriptForge.SF_Utils._ValidateArray + +REM ----------------------------------------------------------------------------- +Public Function _ValidateFile(Optional ByRef pvArgument As Variant _ + , ByVal psName As String _ + , Optional ByVal pbWildCards As Boolean _ + , Optional ByVal pbSpace As Boolean _ + ) +''' Validate the argument as a valid FileName +''' Args: +''' pvArgument: the argument to (in)validate +''' pbWildCards: if True, wildcard characters are accepted in the last component of the 1st argument +''' pbSpace: if True, the argument may be an empty string. Default = False +''' Return: True if validation OK +''' Otherwise an error is raised +''' Exceptions: +''' ARGUMENTERROR + +Dim iVarType As Integer ' VarType of argument +Dim sFile As String ' Alias for argument +Dim bValid As Boolean ' Returned value +Dim sFileNaming As String ' Alias of SF_FileSystem.FileNaming +Dim oArgument As Variant ' Workaround "Object variable not set" error on 1st executable statement +Const cstMaxLength = 256 ' Maximum length of readable value + + ' To avoid useless recursions, keep main function, only increase stack depth + + _SF_.StackLevel = _SF_.StackLevel + 1 + On Local Error GoTo Finally ' Do never interrupt + +Try: + bValid = True + If IsMissing(pvArgument) Then GoTo CatchMissing + If IsMissing(pbWildCards) Then pbWildCards = False + If IsMissing(pbSpace) Then pbSpace = False + iVarType = VarType(pvArgument) + + ' Arrays NEVER pass validation + If iVarType >= V_ARRAY Then + bValid = False + Else + ' Argument must be a string containing a valid file name + bValid = ( iVarType = V_STRING ) + If bValid Then + bValid = ( Len(pvArgument) > 0 Or pbSpace ) + If bValid And Len(pvArgument) > 0 Then + ' Wildcards are replaced by arbitrary alpha characters + If pbWildCards Then + sFile = Replace(Replace(pvArgument, "?", "Z"), "*", "A") + Else + sFile = pvArgument + bValid = ( InStr(sFile, "?") + InStr(sFile, "*") = 0 ) + End If + ' Check file format without wildcards + If bValid Then + With SF_FileSystem + sFileNaming = .FileNaming + Select Case sFileNaming + Case "ANY" : bValid = SF_String.IsUrl(ConvertToUrl(sFile)) + Case "URL" : bValid = SF_String.IsUrl(sFile) + Case "SYS" : bValid = SF_String.IsFileName(sFile) + End Select + End With + End If + ' Chech wildcards are only present in last component + If bValid And pbWildCards Then + sFile = SF_FileSystem.GetParentFolderName(pvArgument) + bValid = ( InStr(sFile, "*") + InStr(sFile, "?") + InStr(sFile,"%3F") = 0 ) ' ConvertToUrl replaces ? by %3F + End If + End If + End If + End If + + If Not bValid Then + ''' Library: ScriptForge + ''' Service: FileSystem + ''' Method: CopyFile + ''' Arguments: Source, Destination + ''' A serious error has been detected on argument Source + ''' Rules: Source is of type String + ''' Source must be a valid file name expressed in operating system notation + ''' Source may contain one or more wildcard characters in its last component + ''' Actual value: /home/jean-*/SomeFile.odt + SF_Exception.RaiseFatal(FILEERROR _ + , SF_Utils._Repr(pvArgument, cstMaxLength), psName, pbWildCards) + End If + +Finally: + _ValidateFile = bValid + _SF_.StackLevel = _SF_.StackLevel - 1 + Exit Function +CatchMissing: + bValid = False + SF_Exception.RaiseFatal(MISSINGARGERROR, psName) + GoTo Finally +End Function ' ScriptForge.SF_Utils._ValidateFile + +REM ----------------------------------------------------------------------------- +Public Function _VarTypeExt(ByRef pvValue As Variant) As Integer +''' Return the VarType of the argument but all numeric types are aggregated into V_NUMERIC +''' Args: +''' pvValue: value to examine +''' Return: +''' The extended VarType + +Dim iType As Integer ' VarType of argument + + iType = VarType(pvValue) + Select Case iType + Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL + _VarTypeExt = V_NUMERIC + Case Else : _VarTypeExt = iType + End Select + +End Function ' ScriptForge.SF_Utils._VarTypeExt + +REM ================================================= END OF SCRIPTFORGE.SF_UTILS +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/_CodingConventions.xba b/wizards/source/scriptforge/_CodingConventions.xba new file mode 100644 index 000000000000..b0443e0a1efa --- /dev/null +++ b/wizards/source/scriptforge/_CodingConventions.xba @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="_CodingConventions" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +''' +' Conventions used in the coding of the *ScriptForge* library +' ----------------------------------------------------------- +''' +' Library and Modules +' =================== +' * Module names are all prefixed with "SF_". +' * The *Option Explicit* statement is mandatory in every module. +' * The *Option Private Module* statement is recommended in internal modules. +' * A standard header presenting the module/class is mandatory +' * An end of file (eof) comment line is mandatory +' * Every module lists the constants that are related to it and documented as return values, arguments, etc. +' They are defined as *Global Const*. +' The scope of global constants being limited to one single library, their invocation from user scripts shall be qualified. +' * The Basic reserved words are *Proper-Cased*. +''' +' Functions and Subroutines +' ========================= +' * LibreOffice ignores the Private/Public attribute in Functions or Subs declarations. +' Nevertheless the attribute must be present. +' Rules to recognize their scope are: +' * Public + name starts with a letter +' The Sub/Function belongs to the official ScriptForge API. +' As such it may be called from any user script. +' * Public + name starts with an underscore "_" +' The Sub/Function may be called only from within the ScriptForge library. +' As such it MUST NOT be called from another library or from a user script, +' as there is no guarantee about the arguments, the semantic or even the existence of that piece of code in a later release. +' * Private - The Sub/Function name must start with an underscore "_". +' The Sub/Function may be called only from the module in which it is located. +' * Functions and Subroutines belonging to the API (= "standard" functions/Subs) are defined in their module in alphabetical order. +' For class modules, all the properties precede the methods which precede the events. +' * Functions and Subroutines not belonging to the API are defined in their module in alphabetical order below the standard ones. +' * The return value of a function is always declared explicitly. +' * The parameters are always declared explicitly even if they're variants. +' * The Function and Sub declarations start at the 1st column of the line. +' * The End Function/Sub statement is followed by a comment reminding the name of the containing library.module and of the function or sub. +' If the Function/Sub is declared for the first time or modified in a release > initial public release, the actual release number is mentioned as well. +''' +' Variable declarations +' ===================== +' * Variable names use only alpha characters, the undercore and digits (no accented characters). +' Exceptionally, names of private variables may be embraced with `[` and `]` if `Option Compatible` is present. +' * The Global, Dim and Const statements always start in the first column of the line. +' * The type (*Dim ... As ...*, *Function ... As ...*) is always declared explicitly, even if the type is Variant. +' * Variables are *Proper-Cased*. They are always preceded by a lower-case letter indicating their type. +' With next exception: variables i, j, k, l, m and n must be declared as integers or longs. +' > b Boolean +' > d Date +' > v Variant +' > o Object +' > i Integer +' > l Long +' > s String +' Example: +' Dim sValue As String +' * Parameters are preceded by the letter *p* which itself precedes the single *typing letter*. +' In official methods, to match their published documentation, the *p* and the *typing letter* may be omitted. Like in: +' Private Function MyFunction(psValue As String) As Variant +' Public Function MyOfficialFunction(Value As String) As Variant +' * Global variables in the ScriptForge library are ALL preceded by an underscore "_" as NONE of them should be invoked from outside the library. +' * Constant values with a local scope are *Proper-Cased* and preceded by the letters *cst*. +' * Constants with a global scope are *UPPER-CASED*. +' Example: +' Global Const ACONSTANT = "This is a global constant" +' Function MyFunction(pocControl As Object, piValue) As Variant +' Dim iValue As Integer +' Const cstMyConstant = 3 +''' +' Indentation +' =========== +' Code shall be indented with TAB characters. +''' +' Goto/Gosub +' ========== +' The *GoSub* … *Return* statement is forbidden. +' The *GoTo* statement is forbidden. +' However *GoTo* is highly recommended for *error* and *exception* handling. +''' +' Comments (english only) +' ======== +' * Every public routine should be documented with a python-like "docstring": +' 1. Role of Sub/Function +' 2. List of arguments, mandatory/optional, role +' 3. Returned value(s) type and meaning +' 4. Examples when useful +' 5. Eventual specific exception codes +' * The "docstring" comments shall be marked by a triple (single) quote character at the beginning of the line +' * Meaningful variables shall be declared one per line. Comment on same line. +' * Comments about a code block should be left indented. +' If it concerns only the next line, no indent required (may also be put at the end of the line). +''' +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/_ModuleModel.xba b/wizards/source/scriptforge/_ModuleModel.xba new file mode 100644 index 000000000000..30f1aa8f170d --- /dev/null +++ b/wizards/source/scriptforge/_ModuleModel.xba @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="_ModuleModel" script:language="StarBasic" script:moduleType="normal">REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +Option Compatible +Option ClassModule +'Option Private Module + +Option Explicit + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' ModuleModel (aka SF_Model) +''' =========== +''' Illustration of how the ScriptForge modules are structured +''' Copy and paste this code in an empty Basic module to start a new service +''' Comment in, comment out, erase what you want, but at the end respect the overall structure +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +''' FAKENEWSERROR + +REM ============================================================= PRIVATE MEMBERS + +Private [Me] As Object ' Should be initialized immediately after the New statement + ' Dim obj As Object : Set obj = New SF_Model + ' Set obj.[Me] = obj +Private [_Parent] As Object ' To keep trace of the instance having created a sub-instance + ' Set obj._Parent = [Me] +Private ObjectType As String ' Must be UNIQUE + +REM ============================================================ MODULE CONSTANTS + +Private Const SOMECONSTANT = 1 + +REM ====================================================== CONSTRUCTOR/DESTRUCTOR + +REM ----------------------------------------------------------------------------- +Private Sub Class_Initialize() + Set [Me] = Nothing + Set [_Parent] = Nothing + ObjectType = "MODEL" +End Sub ' ScriptForge.SF_Model Constructor + +REM ----------------------------------------------------------------------------- +Private Sub Class_Terminate() + Call Class_Initialize() +End Sub ' ScriptForge.SF_Model Destructor + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Call Class_Terminate() + Set Dispose = Nothing +End Function ' ScriptForge.SF_Model Explicit Destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get MyProperty() As Boolean +''' Returns True or False +''' Example: +''' myModel.MyProperty + + MyProperty = _PropertyGet("MyProperty") + +End Property ' ScriptForge.SF_Model.MyProperty + +REM ===================================================================== METHODS + +REM ----------------------------------------------------------------------------- +Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant +''' Return the actual value of the given property +''' Args: +''' PropertyName: the name of the property as a string +''' Returns: +''' The actual value of the property +''' If the property does not exist, returns Null +''' Exceptions: +''' see the exceptions of the individual properties +''' Examples: +''' myModel.GetProperty("MyProperty") + +Const cstThisSub = "Model.GetProperty" +Const cstSubArgs = "" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + GetProperty = Null + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch + End If + +Try: + GetProperty = _PropertyGet(PropertyName) + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_Model.GetProperty + +REM ----------------------------------------------------------------------------- +Public Function Methods() As Variant +''' Return the list of public methods of the Model service as an array + + Methods = Array( _ + "MyFunction" _ + , "etc" _ + ) + +End Function ' ScriptForge.SF_Model.Methods + +REM ----------------------------------------------------------------------------- +Public Function MyFunction(Optional ByVal Arg1 As Variant _ + , Optional ByVal Arg2 As Variant _ + ) As Variant +''' Fictive function that concatenates Arg1 Arg2 times +''' Args: +''' Arg1 String Text +''' Arg2 Numeric Number of times (default = 2) +''' Returns: +''' The new string +''' Exceptions: +''' FAKENEWSERROR +''' Examples: +''' MyFunction("value1") returns "value1value1" + +Dim sOutput As String ' Output buffer +Dim i As Integer +Const cstThisSub = "Model.myFunction" +Const cstSubArgs = "Arg1, [Arg2=2]" + + ' _ErrorHandling returns False when, for debugging, the standard error handling is preferred + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + myFunction = "" + +Check: + If IsMissing(Arg2) Then Arg2 = 2 + ' _EnterFunction returns True when current method is invoked from a user script + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + ' Check Arg1 is a string and Arg2 is a number. + ' Validation rules for scalars and arrays are desribed in SF_Utils + If Not SF_Utils._Validate(Arg1, "Arg1", V_STRING) Then GoTo Finally + If Not SF_Utils._Validate(Arg2, "Arg2", V_NUMERIC) Then GoTo Finally + ' Fatal error ? + If Arg2 < 0 Then GoTo CatchFake + End If + +Try: + sOutput = "" + For i = 0 To Arg2 + sOutput = sOutput & Arg1 + Next i + myFunction = sOutput + +Finally: + ' _ExitFunction manages internal (On Local) errors + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchFake: + SF_Exception.RaiseFatal("FAKENEWSERROR", cstThisSub) + GoTo Finally +End Function ' ScriptForge.SF_Model.myFunction + +REM ----------------------------------------------------------------------------- +Public Function Properties() As Variant +''' Return the list or properties of the Model class as an array + + Properties = Array( _ + "MyProperty" _ + , "etc" _ + ) + +End Function ' ScriptForge.SF_Model.Properties + +REM =========================================================== PRIVATE FUNCTIONS + +REM ----------------------------------------------------------------------------- +Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant +''' Return the value of the named property +''' Args: +''' psProperty: the name of the property + +Dim cstThisSub As String +Const cstSubArgs = "" + + cstThisSub = "SF_Model.get" & psProperty + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + + Select Case psProperty + Case "MyProperty" + _PropertyGet = TBD + Case Else + _PropertyGet = Null + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_Model._PropertyGet + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the Model instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[MODEL]: A readable string" + + _Repr = "[MODEL]: A readable string" + +End Function ' ScriptForge.SF_Model._Repr + +REM ============================================ END OF SCRIPTFORGE.SF_MODEL +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/__License.xba b/wizards/source/scriptforge/__License.xba new file mode 100644 index 000000000000..37bb6a75f29d --- /dev/null +++ b/wizards/source/scriptforge/__License.xba @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd"> +<script:module xmlns:script="http://openoffice.org/2000/script" script:name="__License" script:language="StarBasic" script:moduleType="normal"> +''' Copyright 2019-2020 Jean-Pierre LEDURE, Jean-François NIFENECKER, Alain ROMEDENNE + +REM ======================================================================================================================= +REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +REM === Full documentation is available on https://help.libreoffice.org/ === +REM ======================================================================================================================= + +''' ScriptForge is distributed in the hope that it will be useful, +''' but WITHOUT ANY WARRANTY; without even the implied warranty of +''' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +''' ScriptForge is free software; you can redistribute it and/or modify it under the terms of either (at your option): + +''' 1) The Mozilla Public License, v. 2.0. If a copy of the MPL was not +''' distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/ . + +''' 2) The GNU Lesser General Public License as published by +''' the Free Software Foundation, either version 3 of the License, or +''' (at your option) any later version. If a copy of the LGPL was not +''' distributed with this file, see http://www.gnu.org/licenses/ . + +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/dialog.xlb b/wizards/source/scriptforge/dialog.xlb new file mode 100644 index 000000000000..7b54d071c4f9 --- /dev/null +++ b/wizards/source/scriptforge/dialog.xlb @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd"> +<library:library xmlns:library="http://openoffice.org/2000/library" library:name="ScriptForge" library:readonly="false" library:passwordprotected="false"> + <library:element library:name="dlgConsole"/> + <library:element library:name="dlgProgress"/> +</library:library>
\ No newline at end of file diff --git a/wizards/source/scriptforge/dlgConsole.xdl b/wizards/source/scriptforge/dlgConsole.xdl new file mode 100644 index 000000000000..626be565d8b7 --- /dev/null +++ b/wizards/source/scriptforge/dlgConsole.xdl @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd"> +<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="dlgConsole" dlg:left="114" dlg:top="32" dlg:width="321" dlg:height="239" dlg:closeable="true" dlg:moveable="true" dlg:title="ScriptForge"> + <dlg:styles> + <dlg:style dlg:style-id="0" dlg:font-name="Courier New" dlg:font-stylename="Regular" dlg:font-family="modern"/> + </dlg:styles> + <dlg:bulletinboard> + <dlg:textfield dlg:style-id="0" dlg:id="ConsoleLines" dlg:tab-index="0" dlg:left="4" dlg:top="2" dlg:width="312" dlg:height="225" dlg:hscroll="true" dlg:vscroll="true" dlg:multiline="true" dlg:readonly="true"/> + <dlg:button dlg:id="CloseNonModalButton" dlg:tab-index="2" dlg:left="265" dlg:top="228" dlg:width="50" dlg:height="10" dlg:default="true" dlg:value="Close"> + <script:event script:event-name="on-performaction" script:macro-name="vnd.sun.star.script:ScriptForge.SF_Exception._CloseConsole?language=Basic&location=application" script:language="Script"/> + </dlg:button> + <dlg:button dlg:id="CloseModalButton" dlg:tab-index="1" dlg:left="265" dlg:top="228" dlg:width="50" dlg:height="10" dlg:default="true" dlg:value="Close" dlg:button-type="ok"/> + </dlg:bulletinboard> +</dlg:window>
\ No newline at end of file diff --git a/wizards/source/scriptforge/dlgProgress.xdl b/wizards/source/scriptforge/dlgProgress.xdl new file mode 100644 index 000000000000..cdb8f313214b --- /dev/null +++ b/wizards/source/scriptforge/dlgProgress.xdl @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd"> +<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="dlgProgress" dlg:left="180" dlg:top="90" dlg:width="275" dlg:height="37" dlg:closeable="true" dlg:moveable="true"> + <dlg:bulletinboard> + <dlg:text dlg:id="ProgressText" dlg:tab-index="1" dlg:left="16" dlg:top="7" dlg:width="245" dlg:height="8" dlg:value="ProgressText" dlg:tabstop="true"/> + <dlg:progressmeter dlg:id="ProgressBar" dlg:tab-index="0" dlg:left="16" dlg:top="18" dlg:width="190" dlg:height="10" dlg:value="50"/> + <dlg:button dlg:id="CloseButton" dlg:tab-index="2" dlg:left="210" dlg:top="18" dlg:width="50" dlg:height="10" dlg:value="Close"> + <script:event script:event-name="on-performaction" script:macro-name="vnd.sun.star.script:ScriptForge.SF_UI._CloseProgressBar?language=Basic&location=application" script:language="Script"/> + </dlg:button> + </dlg:bulletinboard> +</dlg:window>
\ No newline at end of file diff --git a/wizards/source/scriptforge/script.xlb b/wizards/source/scriptforge/script.xlb new file mode 100644 index 000000000000..d4c21b652ebe --- /dev/null +++ b/wizards/source/scriptforge/script.xlb @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd"> +<library:library xmlns:library="http://openoffice.org/2000/library" library:name="ScriptForge" library:readonly="false" library:passwordprotected="false"> + <library:element library:name="__License"/> + <library:element library:name="SF_String"/> + <library:element library:name="_CodingConventions"/> + <library:element library:name="SF_Timer"/> + <library:element library:name="_ModuleModel"/> + <library:element library:name="SF_Utils"/> + <library:element library:name="SF_Root"/> + <library:element library:name="SF_Array"/> + <library:element library:name="SF_Services"/> + <library:element library:name="SF_Dictionary"/> + <library:element library:name="SF_Session"/> + <library:element library:name="SF_FileSystem"/> + <library:element library:name="SF_TextStream"/> + <library:element library:name="SF_L10N"/> + <library:element library:name="SF_Exception"/> + <library:element library:name="SF_UI"/> + <library:element library:name="SF_Platform"/> +</library:library>
\ No newline at end of file |