From 152c6e471ea40d9e47367010e44f77cf1487571e Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:46:40 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + api/API_Path.cls | 183 ++++++ api/API_Project.cls | 235 ++++++++ api/API_Python.cls | 221 +++++++ api/API_Ribbon.cls | 85 +++ api/API_UserInteraction.cls | 121 ++++ api/API_VsoWrapper.cls | 376 ++++++++++++ api/API_WordWrapper.cls | 394 +++++++++++++ api/API_XLWrapper.cls | 412 +++++++++++++ api/ex_ConceptCore.bas | 117 ++++ api/ex_Metadata.bas | 40 ++ api/ex_Python.bas | 125 ++++ api/ex_WinAPI.bas | 541 +++++++++++++++++ api/z_LoadPictureAPI.bas | 97 ++++ api/z_PastePictureAPI.bas | 156 +++++ dev/API_Logger.cls | 96 +++ dev/API_MockInteraction.cls | 247 ++++++++ dev/API_TestRunner.cls | 119 ++++ dev/CDS_InfoFunction.cls | 16 + dev/CDS_InfoTests.cls | 33 ++ dev/DevTester.bas | 679 ++++++++++++++++++++++ dev/DevTesterUI.bas | 21 + dev/DevTools.bas | 194 +++++++ dev/ex_ConceptOrganization.bas | 84 +++ excel/API_XLRecordsWrapper.cls | 49 ++ excel/ex_Excel.bas | 151 +++++ parsers/DetectorClassifier.cls | 50 ++ parsers/DetectorListWords.cls | 105 ++++ parsers/DetectorMorpho.cls | 47 ++ parsers/DetectorRegex.cls | 42 ++ parsers/ExtractionOptions.cls | 44 ++ parsers/PC_Fragment.cls | 34 ++ parsers/PC_InfoNPA.cls | 58 ++ parsers/PC_ParsedData.cls | 42 ++ parsers/PC_Tools.cls | 127 ++++ parsers/ParserDate.cls | 151 +++++ parsers/ParserDeclarations.bas | 54 ++ parsers/ParserNPA.cls | 179 ++++++ parsers/z_ParserRegex.bas | 232 ++++++++ samples/20150923 Таймлайнер.xlsm | Bin 0 -> 390743 bytes samples/20180725 Технология разметки.docm | Bin 0 -> 211104 bytes samples/20200214 Иерархизатор 3.5.vsdm | Bin 0 -> 224685 bytes samples/20200214 Подстановки Visio.xlsm | Bin 0 -> 119639 bytes samples/20200225 Блоки_v1.1.vsdm | Bin 0 -> 219399 bytes samples/DB_Data_sample.cls | 57 ++ samples/DevHelper_sample.bas | 20 + samples/Iterator_sample.cls | 81 +++ samples/VBAMake_sample.txt | 31 + samples/manifest_sample.txt | 38 ++ samples/s_Test_sample.cls | 31 + samples/z_UIMessages_sample.bas | 71 +++ samples/z_UIRibbon_sample.bas | 15 + ui/CSE_ListSelector.frm | 198 +++++++ ui/CSE_ListSelector.frx | Bin 0 -> 4632 bytes ui/CSE_ProgressBar.frm | 329 +++++++++++ ui/CSE_ProgressBar.frx | Bin 0 -> 8216 bytes ui/Calendar/CSE_Calendar.frm | 483 +++++++++++++++ ui/Calendar/CSE_Calendar.frx | Bin 0 -> 18456 bytes ui/Calendar/CSE_CallbackCalendar.cls | 53 ++ ui/Calendar/z_CalendarUI.bas | 49 ++ ui/ribbonVSO/.rels | 2 + ui/ribbonVSO/customUI1.xml | 16 + ui/ribbonWord/.rels | 2 + ui/ribbonWord/customUI.xml | 16 + ui/ribbonXL/.rels | 2 + ui/ribbonXL/customUI.xml | 16 + utility/API_Config.cls | 103 ++++ utility/API_DistrManifest.cls | 73 +++ utility/API_GraphOrdering.cls | 360 ++++++++++++ utility/API_JSON.cls | 618 ++++++++++++++++++++ utility/API_LinkedComponents.cls | 61 ++ utility/API_StrongComponents.cls | 101 ++++ utility/API_Timer.cls | 63 ++ utility/CDS_CompoundIntervals.cls | 67 +++ utility/CDS_Edge.cls | 48 ++ utility/CDS_Factorizator.cls | 76 +++ utility/CDS_Graph.cls | 172 ++++++ utility/CDS_Interval.cls | 34 ++ utility/CDS_Node.cls | 19 + utility/CDS_NodeSH.cls | 33 ++ utility/CDS_StaticHierarchy.cls | 78 +++ utility/ex_Collection.bas | 138 +++++ utility/ex_Color.bas | 298 ++++++++++ utility/ex_DataPreparation.bas | 345 +++++++++++ utility/ex_Hash.bas | 29 + utility/ex_MSHook.bas | 74 +++ utility/ex_Regex.bas | 108 ++++ utility/ex_Time.bas | 180 ++++++ utility/ex_VBA.bas | 469 +++++++++++++++ utility/ex_Version.bas | 130 +++++ utility/z_QuickSort.bas | 30 + visio/API_ShapeStorage.cls | 102 ++++ visio/API_UndoWrapper.cls | 45 ++ visio/z_CCVsoExtension.bas | 432 ++++++++++++++ visio/z_VsoGraph.bas | 132 +++++ visio/z_VsoUtilities.bas | 303 ++++++++++ word/API_WordEditGuard.cls | 219 +++++++ word/ex_Word.bas | 226 +++++++ 98 files changed, 12635 insertions(+) create mode 100644 .gitignore create mode 100644 api/API_Path.cls create mode 100644 api/API_Project.cls create mode 100644 api/API_Python.cls create mode 100644 api/API_Ribbon.cls create mode 100644 api/API_UserInteraction.cls create mode 100644 api/API_VsoWrapper.cls create mode 100644 api/API_WordWrapper.cls create mode 100644 api/API_XLWrapper.cls create mode 100644 api/ex_ConceptCore.bas create mode 100644 api/ex_Metadata.bas create mode 100644 api/ex_Python.bas create mode 100644 api/ex_WinAPI.bas create mode 100644 api/z_LoadPictureAPI.bas create mode 100644 api/z_PastePictureAPI.bas create mode 100644 dev/API_Logger.cls create mode 100644 dev/API_MockInteraction.cls create mode 100644 dev/API_TestRunner.cls create mode 100644 dev/CDS_InfoFunction.cls create mode 100644 dev/CDS_InfoTests.cls create mode 100644 dev/DevTester.bas create mode 100644 dev/DevTesterUI.bas create mode 100644 dev/DevTools.bas create mode 100644 dev/ex_ConceptOrganization.bas create mode 100644 excel/API_XLRecordsWrapper.cls create mode 100644 excel/ex_Excel.bas create mode 100644 parsers/DetectorClassifier.cls create mode 100644 parsers/DetectorListWords.cls create mode 100644 parsers/DetectorMorpho.cls create mode 100644 parsers/DetectorRegex.cls create mode 100644 parsers/ExtractionOptions.cls create mode 100644 parsers/PC_Fragment.cls create mode 100644 parsers/PC_InfoNPA.cls create mode 100644 parsers/PC_ParsedData.cls create mode 100644 parsers/PC_Tools.cls create mode 100644 parsers/ParserDate.cls create mode 100644 parsers/ParserDeclarations.bas create mode 100644 parsers/ParserNPA.cls create mode 100644 parsers/z_ParserRegex.bas create mode 100644 samples/20150923 Таймлайнер.xlsm create mode 100644 samples/20180725 Технология разметки.docm create mode 100644 samples/20200214 Иерархизатор 3.5.vsdm create mode 100644 samples/20200214 Подстановки Visio.xlsm create mode 100644 samples/20200225 Блоки_v1.1.vsdm create mode 100644 samples/DB_Data_sample.cls create mode 100644 samples/DevHelper_sample.bas create mode 100644 samples/Iterator_sample.cls create mode 100644 samples/VBAMake_sample.txt create mode 100644 samples/manifest_sample.txt create mode 100644 samples/s_Test_sample.cls create mode 100644 samples/z_UIMessages_sample.bas create mode 100644 samples/z_UIRibbon_sample.bas create mode 100644 ui/CSE_ListSelector.frm create mode 100644 ui/CSE_ListSelector.frx create mode 100644 ui/CSE_ProgressBar.frm create mode 100644 ui/CSE_ProgressBar.frx create mode 100644 ui/Calendar/CSE_Calendar.frm create mode 100644 ui/Calendar/CSE_Calendar.frx create mode 100644 ui/Calendar/CSE_CallbackCalendar.cls create mode 100644 ui/Calendar/z_CalendarUI.bas create mode 100644 ui/ribbonVSO/.rels create mode 100644 ui/ribbonVSO/customUI1.xml create mode 100644 ui/ribbonWord/.rels create mode 100644 ui/ribbonWord/customUI.xml create mode 100644 ui/ribbonXL/.rels create mode 100644 ui/ribbonXL/customUI.xml create mode 100644 utility/API_Config.cls create mode 100644 utility/API_DistrManifest.cls create mode 100644 utility/API_GraphOrdering.cls create mode 100644 utility/API_JSON.cls create mode 100644 utility/API_LinkedComponents.cls create mode 100644 utility/API_StrongComponents.cls create mode 100644 utility/API_Timer.cls create mode 100644 utility/CDS_CompoundIntervals.cls create mode 100644 utility/CDS_Edge.cls create mode 100644 utility/CDS_Factorizator.cls create mode 100644 utility/CDS_Graph.cls create mode 100644 utility/CDS_Interval.cls create mode 100644 utility/CDS_Node.cls create mode 100644 utility/CDS_NodeSH.cls create mode 100644 utility/CDS_StaticHierarchy.cls create mode 100644 utility/ex_Collection.bas create mode 100644 utility/ex_Color.bas create mode 100644 utility/ex_DataPreparation.bas create mode 100644 utility/ex_Hash.bas create mode 100644 utility/ex_MSHook.bas create mode 100644 utility/ex_Regex.bas create mode 100644 utility/ex_Time.bas create mode 100644 utility/ex_VBA.bas create mode 100644 utility/ex_Version.bas create mode 100644 utility/z_QuickSort.bas create mode 100644 visio/API_ShapeStorage.cls create mode 100644 visio/API_UndoWrapper.cls create mode 100644 visio/z_CCVsoExtension.bas create mode 100644 visio/z_VsoGraph.bas create mode 100644 visio/z_VsoUtilities.bas create mode 100644 word/API_WordEditGuard.cls create mode 100644 word/ex_Word.bas diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66825a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +~* + diff --git a/api/API_Path.cls b/api/API_Path.cls new file mode 100644 index 0000000..b22a6bb --- /dev/null +++ b/api/API_Path.cls @@ -0,0 +1,183 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_Path" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ ============= +' Shared module version: 20221031 +' Tested in: TestCommons +' Depends on: ex_VBA +' Required reference: Scripting +Option Explicit + +Private Enum PathType + T_PATH_INVALID = 0 + + T_PATH_GLOBAL = 1 + T_PATH_LOCAL = 2 + T_PATH_ENVIRON = 3 +End Enum + +Private data_ As String +Private type_ As PathType + +Public Function FromString(sText$) As API_Path + data_ = sText + type_ = InternalEvaluate(data_) + Set FromString = Me +End Function + +Public Function Clone() As API_Path + Set Clone = New API_Path + Call Clone.FromString(data_) +End Function + +Public Property Get Text() As String + Text = data_ +End Property + +Public Property Get ParentFolder() As String + Dim fso As New Scripting.FileSystemObject + ParentFolder = fso.GetParentFolderName(data_) +End Property + +Public Property Get FileName() As String + Dim fso As New Scripting.FileSystemObject + FileName = fso.GetFileName(data_) +End Property + +Public Property Get BaseName() As String + Dim fso As New Scripting.FileSystemObject + BaseName = fso.GetBaseName(data_) +End Property + +Public Property Get Extension() As String + Dim fso As New Scripting.FileSystemObject + Extension = fso.GetExtensionName(data_) +End Property + +Public Function ToGlobal(sHome$) As API_Path + Select Case type_ + Case T_PATH_LOCAL: data_ = ConvertLocal(data_, sHome) + Case T_PATH_ENVIRON: data_ = ConvertEnviron(data_) + Case T_PATH_INVALID, T_PATH_GLOBAL: + End Select + type_ = InternalEvaluate(data_) + Set ToGlobal = Me +End Function + +Public Function ToLocal(sHome$) As API_Path + Set ToLocal = Me + Dim nPrefix&: nPrefix = CommonPrefixLength(data_, sHome, vbTextCompare) + If nPrefix = 0 Then _ + Exit Function + + If VBA.Mid(data_, nPrefix, 1) = "\" Then + nPrefix = nPrefix - 1 + ElseIf VBA.Mid(data_, nPrefix + 1, 1) <> "\" Then + nPrefix = VBA.InStrRev(data_, "\", nPrefix) + If nPrefix < 3 Then _ + Exit Function + End If + + Dim fso As New Scripting.FileSystemObject + Dim sFolder$: sFolder = sHome + Dim sPrefix$ + Do While VBA.Len(sFolder) > nPrefix + sFolder = fso.GetParentFolderName(sFolder) + sPrefix = sPrefix & "..\" + Loop + + data_ = VBA.Right(data_, VBA.Len(data_) - nPrefix - 1) + If sPrefix <> vbNullString Then _ + data_ = sPrefix & data_ + type_ = InternalEvaluate(data_) +End Function + +Public Function ToServer(sHome$) As API_Path + Call ToGlobal(sHome) + data_ = SubstituteServer(data_) + type_ = InternalEvaluate(data_) + Set ToServer = Me +End Function + +Public Function GlobalToServer() As API_Path + data_ = SubstituteServer(data_) + type_ = InternalEvaluate(data_) + Set GlobalToServer = Me +End Function + +Public Function LocalExists(sHome$) As Boolean + Dim sFile$ + If type_ = T_PATH_LOCAL Then + sFile = ConvertLocal(data_, sHome) + Else + sFile = data_ + End If + LocalExists = CheckPath(sFile) +End Function + +Public Function GlobalExists() As Boolean + GlobalExists = CheckPath(data_) +End Function + +' ======== +Private Function InternalEvaluate(sPath$) As PathType + If VBA.Len(sPath) < 3 Then + InternalEvaluate = T_PATH_INVALID + ElseIf VBA.Mid(sPath, 2, 1) = ":" Or VBA.Left(sPath, 2) = "\\" Then + InternalEvaluate = T_PATH_GLOBAL + ElseIf sPath Like "%*%*" Then + InternalEvaluate = T_PATH_ENVIRON + Else + InternalEvaluate = T_PATH_LOCAL + End If +End Function + +Private Function SubstituteServer(sPath$) As String + If VBA.Len(sPath) < 3 Then + SubstituteServer = sPath + ElseIf VBA.Left(sPath, 3) = "P:\" Then + SubstituteServer = "\\fs1.concept.ru\projects\" & VBA.Right(sPath, VBA.Len(sPath) - 3) + ElseIf VBA.Left(sPath, 3) = "X:\" Then + SubstituteServer = "\\fs1.concept.ru\Exchange\" & VBA.Right(sPath, VBA.Len(sPath) - 3) + Else + SubstituteServer = sPath + End If +End Function + +Private Function ConvertLocal(sPath$, sHome$) As String + Dim nEllipsis&: nEllipsis = VBA.InStrRev(sPath, "..\") + If nEllipsis = 0 Then + ConvertLocal = sHome & "\" & sPath + Else + Dim fso As New Scripting.FileSystemObject + Dim nCount&: nCount = 1 + nEllipsis / 3 + Dim sFolder$: sFolder = sHome + Do While nCount > 0 + sFolder = fso.GetParentFolderName(sFolder) + nCount = nCount - 1 + Loop + ConvertLocal = sFolder & "\" & VBA.Right(sPath, VBA.Len(sPath) - nEllipsis - 2) + End If +End Function + +Private Function ConvertEnviron(sPath$) As String + Dim nHeader&: nHeader = VBA.InStr(2, sPath, "%") + Dim sVariable$: sVariable = VBA.Mid(sPath, 2, nHeader - 2) + ConvertEnviron = VBA.Environ$(sVariable) & "\" & VBA.Right(sPath, VBA.Len(sPath) - nHeader - 1) +End Function + +Private Function CheckPath(sPath$) As Boolean + Dim fso As New Scripting.FileSystemObject + CheckPath = True + If fso.FileExists(sPath) Then _ + Exit Function + If fso.FolderExists(sPath) Then _ + Exit Function + CheckPath = False +End Function diff --git a/api/API_Project.cls b/api/API_Project.cls new file mode 100644 index 0000000..796b909 --- /dev/null +++ b/api/API_Project.cls @@ -0,0 +1,235 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_Project" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ Import / export code functionality ============= +' Shared module version: 20220407 +' Tested in: +' Depends on: +' Required reference: Scripting, VBIDE +Option Explicit + +Private fso_ As Scripting.FileSystemObject +Private proj_ As VBIDE.VBProject + +Private contents_ As Scripting.Dictionary +Private sharedContents_ As Scripting.Dictionary + +Private sourceHome_ As String +Private importsHome_ As String + +Private Sub Class_Initialize() + Set fso_ = New Scripting.FileSystemObject +End Sub + +Private Sub Class_Terminate() + Call Detach +End Sub + +Public Function Init(target As VBIDE.VBProject, Optional sImportsHome$ = "") As Boolean + Init = target.Protection <> 1 + If Not Init Then + Call MsgBox("Project code is protected, cannot acccess", vbCritical) + Exit Function + End If + + Set proj_ = target + importsHome_ = sImportsHome + + Set contents_ = New Scripting.Dictionary + Set sharedContents_ = New Scripting.Dictionary +End Function + +Public Function Detach() + Set proj_ = Nothing + importsHome_ = "" +End Function + +Public Function SetInternals(ByRef oContents As Scripting.Dictionary, _ + ByRef oSharedCont As Scripting.Dictionary) + Set contents_ = oContents + Set sharedContents_ = oSharedCont +End Function + +Public Function ExportSrcTo(sPath$) + sourceHome_ = sPath + Call PrepareFolders(contents_) + Call InternalExport(contents_) +End Function + +Public Function ExportShared() + sourceHome_ = importsHome_ + Call PrepareFolders(sharedContents_) + Call InternalExport(sharedContents_) +End Function + +Public Function ImportSrcFrom(sPath$) + sourceHome_ = sPath + Call InternalImport(contents_) +End Function + +Public Function ImportShared() + sourceHome_ = importsHome_ + Call InternalImport(sharedContents_) +End Function + +Public Function RemoveAll() + Dim nItem&: nItem = 1 + Dim aComponent As VBIDE.VBComponent + Do While nItem <= proj_.VBComponents.Count + Set aComponent = proj_.VBComponents.Item(nItem) + If aComponent.Type <> vbext_ct_Document Then + If Not TryRemoving(aComponent) Then _ + nItem = nItem + 1 + Else + nItem = nItem + 1 + End If + Loop +End Function + +Public Function AddItem(sName$, sFolder$) + Call contents_.Add(sName, sFolder) +End Function + +Public Function AddSharedItem(sName$, sFolder$) + Call sharedContents_.Add(sName, sFolder) +End Function + +Public Function ReloadFrom(sFile$) As Boolean + ReloadFrom = False + If fso_.FileExists(sFile) Then _ + ReloadFrom = TryImportFile(fso_.GetFile(sFile)) +End Function + +' ========= +Private Function PrepareFolders(target As Scripting.Dictionary) + Call ProjEnsureFolderExists(sourceHome_) + + Dim sKey As Variant + For Each sKey In target + Call ProjEnsureFolderExists(sourceHome_ & "\" & CStr(target(sKey))) + Next sKey +End Function + +Private Function InternalExport(target As Scripting.Dictionary) + Dim fso As New Scripting.FileSystemObject + Dim aComponent As VBComponent + For Each aComponent In proj_.VBComponents + Dim sName$: sName = ComponentName(aComponent) + If Not target.Exists(sName) Then _ + GoTo NEXT_COMPONENT + + Dim sPath$: sPath = sourceHome_ & "\" & target(sName) & "\" & sName + If aComponent.Type = vbext_ct_MSForm Then _ + If HasNoChanges(aComponent, sPath) Then _ + GoTo NEXT_COMPONENT + Call aComponent.Export(sPath) +NEXT_COMPONENT: + Next aComponent +End Function + +Private Function InternalImport(tContents As Scripting.Dictionary) + Dim sFileName As Variant + Dim sPath$ + For Each sFileName In tContents + sPath = sourceHome_ & "\" & tContents(sFileName) & "\" & CStr(sFileName) + If fso_.FileExists(sPath) Then _ + Call TryImportFile(fso_.GetFile(sPath)) + Next sFileName +End Function + +Private Function TryImportFile(target As Scripting.File) As Boolean + TryImportFile = False + + Dim sExt$: sExt = fso_.GetExtensionName(target.Name) + Dim sName$: sName = fso_.GetBaseName(target.Name) + Dim sComp As VBIDE.VBComponent: Set sComp = ComponentByName(sName) + If Not sComp Is Nothing Then _ + If Not TryRemoving(sComp) Then _ + Exit Function + + Call proj_.VBComponents.Import(target.Path) + If sExt = "frm" Then _ + Call RemoveEmptyFirstLine(ComponentByName(sName).CodeModule) + + TryImportFile = True +End Function + +Private Function ComponentName(target As VBComponent) As String + ComponentName = target.Name + Select Case target.Type + Case vbext_ct_ClassModule: ComponentName = ComponentName & ".cls" + Case vbext_ct_MSForm: ComponentName = ComponentName & ".frm" + Case vbext_ct_StdModule: ComponentName = ComponentName & ".bas" + Case Else: ComponentName = vbNullString + End Select +End Function + +Private Function RemoveEmptyFirstLine(target As CodeModule) + Dim firstLineTxt$: firstLineTxt = target.Lines(1, 1) + If firstLineTxt = vbNullString Then _ + Call target.DeleteLines(1, 1) +End Function + +Private Function TryRemoving(target As VBIDE.VBComponent) As Boolean + Dim nCount& + nCount = proj_.VBComponents.Count + Call proj_.VBComponents.Remove(target) + TryRemoving = nCount <> proj_.VBComponents.Count +End Function + +Private Function ComponentByName(sName$) As VBIDE.VBComponent + Dim aComponent As VBComponent + For Each aComponent In proj_.VBComponents + If aComponent.Name = sName Then + Set ComponentByName = aComponent + Exit Function + End If + Next aComponent +End Function + +Private Function ProjEnsureFolderExists(sPath$) + If fso_.FolderExists(sPath) Then _ + Exit Function + + Dim sParent$: sParent = sPath + Do While VBA.Right(sParent, 1) = "\" + sParent = VBA.Left(sParent, VBA.Len(sParent) - 1) + Loop + sParent = VBA.Left(sParent, VBA.InStrRev(sParent, "\") - 1) + + Call ProjEnsureFolderExists(sParent) + Call fso_.CreateFolder(sPath) +End Function + +Private Function HasNoChanges(aForm As VBIDE.VBComponent, sPath$) As Boolean + HasNoChanges = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sPath) Then _ + Exit Function + + Dim tmpDir$: tmpDir = fso.GetParentFolderName(sPath) & "\tmpFrm" + Dim tmpFile$: tmpFile = tmpDir & "\" & aForm.Name & ".frm" + Call fso.CreateFolder(tmpDir) + Call aForm.Export(tmpFile) + + Dim nFile1&: nFile1 = FreeFile + Open sPath For Input As #nFile1 + Dim cont1 As Variant: cont1 = Input(LOF(nFile1), nFile1) + Close nFile1 + + Dim nFile2&: nFile2 = FreeFile + Open tmpFile For Input As #nFile2 + Dim cont2 As Variant: cont2 = Input(LOF(nFile2), nFile2) + Close nFile2 + + HasNoChanges = cont1 = cont2 + Call fso.DeleteFolder(tmpDir) +End Function + diff --git a/api/API_Python.cls b/api/API_Python.cls new file mode 100644 index 0000000..d6c76fd --- /dev/null +++ b/api/API_Python.cls @@ -0,0 +1,221 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_Python" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'================ Python process object ========================= +' Shared module version: 20220713 +' Tested in: PythonManager +' Depends on: +' Required reference: +Option Explicit + +Private Const CONCEPT_DLL_LOCATION = "C:\Tools\dll" + +#If Win64 Then + Private Const PY_DLL_NAME As String = "vbatopy-connector64.dll" + + Private Declare PtrSafe Function StartPythonServer Lib "vbatopy-connector64.dll" ( _ + ByRef vResult As Variant, _ + ByVal sPython As String, _ + ByVal sModules As String, _ + ByVal bShowConsole As Long) As Long + + Private Declare PtrSafe Function KillPythonServer Lib "vbatopy-connector64.dll" Alias "KillServer" () As Long + Private Declare PtrSafe Function GetServer Lib "vbatopy-connector64.dll" (ByRef vResult As Variant) As Long +#Else + Private Const PY_DLL_NAME As String = "vbatopy-connector32.dll" + + Private Declare PtrSafe Function StartPythonServer Lib "vbatopy-connector32.dll" ( _ + ByRef vResult As Variant, _ + ByVal sPython As String, _ + ByVal sModules As String, _ + ByVal bShowConsole As Long) As Long + + Private Declare PtrSafe Function KillPythonServer Lib "vbatopy-connector32.dll" Alias "KillServer" () As Long + Private Declare PtrSafe Function GetServer Lib "vbatopy-connector32.dll" (ByRef vResult As Variant) As Long +#End If + +Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal sLibrary As String) As Long +Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal nLibraryHandle As Long) As Long + +Private python_ As Variant +Private pythonPath_ As String +Private modules_ As String +Private showConsole_ As Boolean + +Private Sub Class_Initialize() + Call ClearCahce +End Sub + +Private Sub Class_Terminate() + Call ClearCahce +End Sub + +Public Function ClearCahce() + Set python_ = Nothing +End Function + +' Initialize Python process +' iPython - path to python / python command +' sModules - path to python source code modules, delimiter = ';' +' bDoDebug - flag to show console output for python process +Public Function Init(iPython$, sModules$, Optional bDoDebug As Boolean = False) + Set python_ = Nothing + pythonPath_ = VBA.LCase(iPython) + pythonPath_ = VBA.Replace(pythonPath_, "program files (x86)", """program files (x86)""") + pythonPath_ = VBA.Replace(pythonPath_, "program files", """program files""") + pythonPath_ = VBA.Replace(pythonPath_, "\", "\\") + modules_ = sModules + showConsole_ = bDoDebug +End Function + +' Validate server is running +' If @python_ is not setup, then load current server dispatch into @python_ +Public Function Validate() As Boolean + Call LoadDLL + Call GetServer(python_) + Validate = Not python_ Is Nothing +End Function + +Public Function StartServer() + Call Validate + If Not python_ Is Nothing Then _ + Exit Function + + If StartPythonServer(python_, pythonPath_, modules_, IIf(showConsole_, 1, 0)) <> 0 Then _ + Call RaiseServerError + + If VBA.VarType(python_) = vbString Then + Call RaiseServerError + Set python_ = Nothing + End If +End Function + +Public Function KillServer() + Call Validate + If python_ Is Nothing Then _ + Exit Function + If KillPythonServer() <> 0 Then _ + Call RaiseServerError + Set python_ = Nothing +End Function + +' Preload Python module before using it - use if long setup is needed to load module +Public Function LoadModule(sFullPath$) As Boolean + On Error GoTo HANDLE_ERROR + LoadModule = Py.ImportModule(sFullPath) <> "" + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + LoadModule = False + On Error GoTo 0 +End Function + +' Run python command +Public Function Execute(sPyCommand$) As Boolean + On Error GoTo HANDLE_ERROR + Call Py.Exec("" & sPyCommand & "") + On Error GoTo 0 + Execute = True + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + Execute = False + On Error GoTo 0 +End Function + +' Evaluate python statement +' Warning! Returns only basic types. Objects are not supported +Public Function Evaluate(sPyStatement$) As Variant + On Error GoTo HANDLE_ERROR + Evaluate = Py.Eval("" & sPyStatement & "") + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + Evaluate = Err.Description + On Error GoTo 0 +End Function + +' Call function from module. Arguments will be available from python including COM wrappers for any Office objects +' Warning! Returns basic types. To get objects from python use CallFunctionReturnObject +Public Function CallFunction(sModule$, sFunc$, Optional vArgs As Variant) As Variant + On Error GoTo HANDLE_ERROR + If IsMissing(vArgs) Then + CallFunction = Py.CallFunction(sModule, sFunc) + Else + CallFunction = Py.CallFunction(sModule, sFunc, vArgs) + End If + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + CallFunction = Err.Description +End Function + +Public Function CallFunctionReturnObject(sModule$, sFunc$, Optional vArgs As Variant) As Object + On Error GoTo HANDLE_ERROR + If IsMissing(vArgs) Then + Set CallFunctionReturnObject = Py.CallFunction(sModule, sFunc) + Else + Set CallFunctionReturnObject = Py.CallFunction(sModule, sFunc, vArgs) + End If + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + Set CallFunctionReturnObject = Nothing +End Function + +' Create VBA wrapper for target Python module in destination file path +Public Function WrapPython(sSourceModule$, sDestination$) As String + WrapPython = Py.WrapPython(sSourceModule, sDestination) +End Function + +' Preload @vbatopy DLL +' Note: Mostly usefull for testing setup because it allows using DLL calls without starting a server +Public Function LoadDLL() + If LoadLibrary(PY_DLL_NAME) <> 0 Then _ + Exit Function + If LoadLibrary(CONCEPT_DLL_LOCATION & "\" & PY_DLL_NAME) <> 0 Then _ + Exit Function + ' TODO: remove fallback after some time + ' fallback path for earlier versions of distribution + If LoadLibrary(VBA.Environ("USERPROFILE") & "\.concept\dll\" & PY_DLL_NAME) = 0 Then _ + Call Err.Raise(1, Description:="Could not load " & PY_DLL_NAME) +End Function + +' Unload DLL - need to call this before replacing dll file +Public Function UnloadDLL() + Dim nHandle&: nHandle = LoadLibrary(PY_DLL_NAME) + If nHandle <> 0 Then + Call KillServer + Call FreeLibrary(nHandle) + End If +End Function + +' ========= +Private Property Get Py() As Variant + If python_ Is Nothing Then _ + Call StartServer + Set Py = python_ +End Property + +Private Function RaiseServerError() + Dim sErr$: sErr = python_ + Set python_ = Nothing + Debug.Print sErr + Call Err.Raise(1000, Description:=sErr) +End Function + diff --git a/api/API_Ribbon.cls b/api/API_Ribbon.cls new file mode 100644 index 0000000..1b18eec --- /dev/null +++ b/api/API_Ribbon.cls @@ -0,0 +1,85 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_Ribbon" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'==== (Ribbon) ========================= +' Shared module version: 20210217 +' Required reference: Microsoft Scripting Runtime +Option Explicit + +Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, src As Any, ByVal nLen As Long) + +Private ribbon_ As IRibbonUI +Private backup_ As String + +Private Sub Class_Terminate() + If Not ribbon_ Is Nothing Then _ + Call KillBackup +End Sub + +Public Function Init(target As IRibbonUI, sBackup$) + Set ribbon_ = target + backup_ = sBackup + Call SaveBackup +End Function + +Public Property Get Value() As IRibbonUI + Set Value = ribbon_ +End Property + +Public Function LoadFrom(sFilePath$) As IRibbonUI + Set ribbon_ = Nothing + backup_ = sFilePath + + Call LoadBackup + + Set LoadFrom = ribbon_ +End Function + +' ======= +Private Function SaveBackup() + Dim ribbonPtr As LongPtr: ribbonPtr = ObjPtr(ribbon_) + Dim fso As New Scripting.FileSystemObject + Dim textOut As Scripting.TextStream: Set textOut = fso.CreateTextFile(backup_, Overwrite:=True) + If Not textOut Is Nothing Then + Call textOut.WriteLine(ribbonPtr) + Call textOut.Close + End If +End Function + +Private Function LoadBackup() + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(backup_) Then _ + Exit Function + + Dim textIn As Scripting.TextStream: Set textIn = fso.OpenTextFile(backup_) + If textIn Is Nothing Then _ + Exit Function + + Dim ptrSize& + #If Win64 Then + ptrSize = 8 + #Else + ptrSize = 4 + #End If + + Dim aLine$: aLine = textIn.ReadLine + If IsNumeric(aLine) Then + Dim nPtr As LongPtr: nPtr = CLngPtr(aLine) + Dim objRibbon As Object + Call CopyMemory(objRibbon, nPtr, ptrSize) + Set ribbon_ = objRibbon + Call CopyMemory(objRibbon, 0&, ptrSize) + End If +End Function + +Private Function KillBackup() + Dim fso As New Scripting.FileSystemObject + If fso.FileExists(backup_) Then _ + Call fso.DeleteFile(backup_) +End Function diff --git a/api/API_UserInteraction.cls b/api/API_UserInteraction.cls new file mode 100644 index 0000000..e8a02d3 --- /dev/null +++ b/api/API_UserInteraction.cls @@ -0,0 +1,121 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_UserInteraction" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'================ ========================= +' Shared module version: 20220611 +' Depends on: +' Required reference: +Option Explicit + +Public Function ShowMessage(nMsg&, ParamArray params() As Variant) + Call UIShowMessage(nMsg, params) +End Function + +Public Function AskQuestion(nQuestion&, ParamArray params() As Variant) As Boolean + AskQuestion = UIAskQuestion(nQuestion, params) +End Function + +Public Function FollowHyperlink(oDocument As Object, sAddress$) + Call oDocument.FollowHyperlink(sAddress) +End Function + +Public Function PromptInput(sPrompt$, Optional sTitle$ = vbNullString, Optional sInitial As Variant) As String + PromptInput = VBA.InputBox(sPrompt, sTitle, sInitial) +End Function + +Public Function PromptFileFilter(sInitialPath$, sDescription$, sFilter$, _ + Optional sTitle$ = " ", _ + Optional bNewApplication As Boolean = False) As String + Dim cFilters As New Collection + Dim cDescriptions As New Collection + + Call cFilters.Add("*.*") + Call cDescriptions.Add(" ") + Call cFilters.Add(sFilter) + Call cDescriptions.Add(sDescription) + + PromptFileFilter = PromptFile(sInitialPath, sTitle, cDescriptions, cFilters, bNewApplication) +End Function + +Public Function PromptFile(sInitialPath$, _ + Optional sTitle$ = " ", _ + Optional cDescriptions As Collection = Nothing, _ + Optional cFilters As Collection = Nothing, _ + Optional bNewApplication As Boolean = False) As String + PromptFile = vbNullString + Dim oApplication As Object + If Not bNewApplication Then + Set oApplication = Application + Else + Set oApplication = CreateObject("Excel.Application") + oApplication.Visible = True + End If + + Dim filterCount&: filterCount = 0 + If Not cDescriptions Is Nothing And Not cFilters Is Nothing Then _ + If cDescriptions.Count = cFilters.Count Then _ + filterCount = cFilters.Count + + On Error Resume Next + + With oApplication.FileDialog(msoFileDialogFilePicker) + .InitialFileName = sInitialPath + .AllowMultiSelect = False + .ButtonName = "" + .Title = sTitle + + Call .Filters.Clear + If filterCount > 0 Then + + Dim nItem& + For nItem = 1 To filterCount Step 1 + Call .Filters.Add(cDescriptions.Item(nItem), cFilters.Item(nItem), 1) + Next nItem + Else + Call .Filters.Add(" ", "*.*") + End If + + Call .Show + If .SelectedItems.Count > 0 Then _ + PromptFile = .SelectedItems(1) + End With + + If bNewApplication Then _ + Call oApplication.Quit +End Function + +Public Function PromptFolder(sInitialPath$, _ + Optional sTitle$ = " ", _ + Optional bNewApplication As Boolean = False) As String + PromptFolder = vbNullString + Dim oApplication As Object + If Not bNewApplication Then + Set oApplication = Application + Else + Set oApplication = CreateObject("Excel.Application") + oApplication.Visible = True + End If + + On Error Resume Next + With oApplication.FileDialog(msoFileDialogFolderPicker) + .InitialFileName = sInitialPath + .AllowMultiSelect = False + .ButtonName = "" + .Title = sTitle + + Call .Show + + If .SelectedItems.Count > 0 Then _ + PromptFolder = .SelectedItems(1) + End With + + If bNewApplication Then _ + Call oApplication.Quit +End Function + diff --git a/api/API_VsoWrapper.cls b/api/API_VsoWrapper.cls new file mode 100644 index 0000000..a75160a --- /dev/null +++ b/api/API_VsoWrapper.cls @@ -0,0 +1,376 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_VsoWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ - MS Visio ========================= +' Shared module version: 20220410 +' Tested in: TestCommons +' Depends on: +' Required reference: Scripting, Visio +Option Explicit + +Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal nHwnd As Long, nProcID As Long) As Long + +Private application_ As Visio.Application +Private document_ As Visio.Document +Private reporter_ As Object +Private bSilent_ As Boolean + +Private deferRel_ As Boolean +Private deferRecalc_ As Boolean +Private isPaused_ As Boolean + +Private bCloseOnFail_ As Boolean +Private bOwnsDoc_ As Boolean +Private bOwnsApp_ As Boolean + +Private Sub Class_Initialize() + Call ResetAll +End Sub + +Private Sub Class_Terminate() + If application_ Is Nothing Then _ + Exit Sub + ' Note: application could be owned by another Wrapper and already quit + On Error GoTo SKIP_RELEASE + If application_.Documents.Count = 0 Then _ + Call ReleaseApplication +SKIP_RELEASE: +End Sub + +Public Function ResetAll() + Call ResetDocument + + deferRel_ = False + deferRecalc_ = False + bSilent_ = False +End Function + +Public Function ResetDocument() + Set application_ = Nothing + Set document_ = Nothing + bOwnsDoc_ = False + bOwnsApp_ = False + bCloseOnFail_ = False + isPaused_ = False +End Function + +' Reporter object should implement callback function Report(sMsg$, nFlags&) +Public Function SetReporter(aReporter As Object) + Set reporter_ = aReporter +End Function + +Public Property Get Document() As Visio.Document + Set Document = document_ +End Property + +Public Property Get Application() As Visio.Application + Set Application = application_ +End Property + +Public Property Get IsUIPaused() As Boolean + IsUIPaused = isPaused_ +End Property + +Public Function DisableMessages() + bSilent_ = True +End Function + +Public Function EnableMessages() + bSilent_ = False +End Function + +Public Function CreateApplication(Optional bIsVisible As Boolean = True) As Visio.Application + Call ResetDocument + Set application_ = CreateObject("Visio.Application") + bOwnsApp_ = True + application_.Visible = bIsVisible + Set CreateApplication = application_ +End Function + +Public Function SetApplication(target As Visio.Application) + If ObjPtr(application_) = ObjPtr(target) Then _ + Exit Function + Call ResetDocument + Set application_ = target +End Function + +Public Function SetDocument(target As Visio.Document, Optional bOwnership = False) + If ObjPtr(target) = ObjPtr(document_) Then + bOwnsDoc_ = bOwnership + Exit Function + End If + If Not target Is Nothing Then _ + Call SetApplication(target.Application) + bOwnsDoc_ = bOwnership + Set document_ = target +End Function + +Public Function PauseUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + Debug.Assert Not isPaused_ + isPaused_ = True + deferRel_ = application_.DeferRelationshipRecalc + deferRecalc_ = application_.DeferRecalc + application_.ScreenUpdating = False + application_.DeferRecalc = True + application_.DeferRelationshipRecalc = True +End Function + +Public Function ResumeUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + isPaused_ = False + application_.DeferRelationshipRecalc = deferRel_ + application_.DeferRecalc = deferRecalc_ + application_.ScreenUpdating = True +End Function + +Public Function Run(sFunc$, ParamArray vArgs() As Variant) + ' Note: running code in Visio cannot return value, so use file IO instead if needed (pass output filename through args) + Dim nArgCount&: nArgCount = UBound(vArgs) - LBound(vArgs) + 1 + Dim sCommand$: sCommand = "Call " & sFunc & "(" + Dim nArg& + For nArg = 0 To nArgCount - 1 Step 1 + If nArg <> 0 Then _ + sCommand = sCommand & ", " + sCommand = sCommand & """" & CStr(vArgs(nArg)) & """" + Next nArg + sCommand = sCommand & ")" + Call Document.ExecuteLine(sCommand) +End Function + +Public Function NewDocument(Optional sTemplate$ = vbNullString, _ + Optional bDefaultIfFail As Boolean = True) As Visio.Document + If Not document_ Is Nothing Then _ + Exit Function + + If application_ Is Nothing Then + bCloseOnFail_ = True + Call CreateApplication + End If + + On Error GoTo TRY_DEFAULT + + Set document_ = application_.Documents.Add(sTemplate) + bOwnsDoc_ = True + Set NewDocument = document_ + + Exit Function + +TRY_DEFAULT: + If sTemplate <> vbNullString Then + Call Report(" " & sTemplate) + If Not bDefaultIfFail Then + If bCloseOnFail_ Then _ + Call ReleaseApplication + Else + On Error GoTo ERR_DEFAULT + Set document_ = application_.Documents.Add("") + Set NewDocument = document_ + End If + Exit Function + End If + +ERR_DEFAULT: + Call Report(" ") + If bCloseOnFail_ Then _ + Call ReleaseApplication +End Function + +Public Function OpenDocument(sFile$, Optional nOpenFlags As Integer = 0) As Visio.Document +' Note: Visio ReadOnly + If Not document_ Is Nothing Then _ + Exit Function + + Dim bResetApplication As Boolean + bResetApplication = application_ Is Nothing + If bResetApplication Then _ + Call DefaultApplication + + If TryAlreadyOpened(sFile) Then + Set OpenDocument = document_ + Exit Function + End If + + If Not TestFile(sFile) Then _ + GoTo SAFE_EXIT + + On Error GoTo SAFE_EXIT + Set document_ = application_.Documents.OpenEx(sFile, nOpenFlags) + On Error GoTo 0 + + If Not document_ Is Nothing Then + bOwnsDoc_ = True + Set OpenDocument = document_ + Else +SAFE_EXIT: + bOwnsDoc_ = False + If bCloseOnFail_ Then _ + Call KillApplication + If bResetApplication Then _ + Set application_ = Nothing + End If +End Function + +Public Function ReleaseApplication() + If bOwnsApp_ Then _ + Call KillApplication + Call ResetDocument +End Function + +Public Function ReleaseDocument(Optional bCloseApplication As Boolean = True, _ + Optional bSaveChanges As Boolean = False) As Boolean + ReleaseDocument = False + If document_ Is Nothing Then _ + Exit Function + If Not bOwnsDoc_ Then + Set document_ = Nothing + ReleaseDocument = True + Exit Function + End If + + On Error GoTo EXIT_FUNC + + If (bSaveChanges And document_.Path <> vbNullString) Then + Call document_.OpenStencilWindow + Call document_.Save + End If + + Dim nInitialResponse&: nInitialResponse = application_.AlertResponse + application_.AlertResponse = vbNo + Call document_.Close + application_.AlertResponse = nInitialResponse + + On Error GoTo 0 + + Set document_ = Nothing + ReleaseDocument = True + If bCloseApplication And application_.Documents.Count = 0 Then _ + Call ReleaseApplication +EXIT_FUNC: +End Function + +Public Function SaveAs(sTargetName$) As Boolean + SaveAs = False + + If document_ Is Nothing Then _ + Exit Function + + On Error GoTo RETURN_FALSE + Call document_.SaveAs(sTargetName) + On Error GoTo 0 + + SaveAs = True + Exit Function +RETURN_FALSE: +End Function + +' =========== +Private Function Report(sMsg$) + If bSilent_ Then + Debug.Print "VisioWrapper: " & sMsg + Exit Function + ElseIf reporter_ Is Nothing Then + Call MsgBox(sMsg, vbExclamation) + Else + Call reporter_.Report(sMsg, vbExclamation) + End If +End Function + +Private Function DefaultApplication() + bOwnsApp_ = False + On Error GoTo CREATE_NEW_APPL + Set application_ = GetObject(, "Visio.Application") + On Error GoTo 0 + Exit Function + +CREATE_NEW_APPL: + Set application_ = CreateObject("Visio.Application") + bOwnsApp_ = True + bCloseOnFail_ = True + application_.Visible = True +End Function + +Private Function KillApplication() + Const HIDE_SHELL = 0 + Const WAIT_RETURN = True + + On Error GoTo OFFICE_QUIT + + Dim nThreadID&, nProcID& + nThreadID = GetWindowThreadProcessId(application_.WindowHandle32, nProcID) + + Dim iShell As Object: Set iShell = VBA.CreateObject("WScript.Shell") + If iShell.Run("TaskKill /F /PID " & nProcID, HIDE_SHELL, WAIT_RETURN) = 0 Then _ + Exit Function + +OFFICE_QUIT: + Call application_.Quit +End Function + +Private Function TryAlreadyOpened(sFile$) As Boolean + TryAlreadyOpened = False + + Set document_ = FindByName(sFile) + If document_ Is Nothing Then _ + Exit Function + + bOwnsDoc_ = False + TryAlreadyOpened = True +End Function + +Private Function TestFile(sFile$) As Boolean + Const ATTRIBUTE_READONLY = 1 + TestFile = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + + If fso.GetFile(sFile).Attributes And ATTRIBUTE_READONLY Then + Call Report(" ReadOnly: " & sFile) + Exit Function + End If + If IsFileInUse(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + + TestFile = True +End Function + +Private Function FindByName(sName$) As Visio.Document + Dim aDoc As Visio.Document + For Each aDoc In application_.Documents + If aDoc.FullName = sName Then + Set FindByName = aDoc + Exit Function + End If + Next aDoc +End Function + +Private Function IsFileInUse(sFileName$) As Boolean + Dim nFile%: nFile = FreeFile + On Error Resume Next + + Open sFileName For Binary Access Read Lock Read Write As #nFile + Close #nFile + IsFileInUse = Err.Number > 0 + + On Error GoTo 0 +End Function diff --git a/api/API_WordWrapper.cls b/api/API_WordWrapper.cls new file mode 100644 index 0000000..3c7e4e4 --- /dev/null +++ b/api/API_WordWrapper.cls @@ -0,0 +1,394 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_WordWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ - MS Word ========================= +' Shared module version: 20220410 +' Tested in: TestCommons +' Depends on: +' Required reference: Scripting, Word +Option Explicit + +Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal nHwnd As Long, nProcID As Long) As Long + +Private application_ As Word.Application +Private document_ As Word.Document +Private reporter_ As Object +Private bSilent_ As Boolean + +Private isPaused_ As Boolean +Private screenUpdate_ As Boolean +Private proofing_ As Boolean + +Private bCloseOnFail_ As Boolean +Private bOwnsDoc_ As Boolean +Private bOwnsApp_ As Boolean + +Private Sub Class_Initialize() + Call ResetAll +End Sub + +Private Sub Class_Terminate() + If application_ Is Nothing Then _ + Exit Sub + ' Note: application could be owned by another Wrapper and already quit + On Error GoTo SKIP_RELEASE + If application_.Documents.Count = 0 Then _ + Call ReleaseApplication +SKIP_RELEASE: +End Sub + +Public Function ResetAll() + Call ResetDocument + + screenUpdate_ = True + proofing_ = True + bSilent_ = False +End Function + +Public Function ResetDocument() + Set application_ = Nothing + Set document_ = Nothing + bOwnsDoc_ = False + bOwnsApp_ = False + bCloseOnFail_ = False + isPaused_ = False +End Function + +' Reporter object should implement callback function Report(sMsg$, nFlags&) +Public Function SetReporter(aReporter As Object) + Set reporter_ = aReporter +End Function + +Public Property Get Document() As Word.Document + Set Document = document_ +End Property + +Public Property Get Application() As Word.Application + Set Application = application_ +End Property + +Public Property Get IsUIPaused() As Boolean + IsUIPaused = isPaused_ +End Property + +Public Function DisableMessages() + bSilent_ = True +End Function + +Public Function EnableMessages() + bSilent_ = False +End Function + +Public Function CreateApplication(Optional bIsVisible As Boolean = True) As Word.Application + Call ResetDocument + Set application_ = CreateObject("Word.Application") + bOwnsApp_ = True + application_.Visible = bIsVisible + Set CreateApplication = application_ +End Function + +Public Function SetApplication(target As Word.Application) + If ObjPtr(application_) = ObjPtr(target) Then _ + Exit Function + Call ResetDocument + Set application_ = target +End Function + +Public Function SetDocument(target As Word.Document, Optional bOwnership = False) + If ObjPtr(target) = ObjPtr(document_) Then + bOwnsDoc_ = bOwnership + Exit Function + End If + If Not target Is Nothing Then _ + Call SetApplication(target.Application) + bOwnsDoc_ = bOwnership + Set document_ = target +End Function + +Public Function PauseUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + Debug.Assert Not isPaused_ + isPaused_ = True + screenUpdate_ = application_.ScreenUpdating + proofing_ = document_.Range.NoProofing + application_.ScreenUpdating = False + document_.Range.NoProofing = True +End Function + +Public Function ResumeUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + isPaused_ = False + application_.ScreenUpdating = screenUpdate_ + document_.Range.NoProofing = proofing_ + Call application_.ScreenRefresh +End Function + +Public Function Run(sCommand$, ParamArray vArgs() As Variant) As Variant + Dim nArgCount&: nArgCount = UBound(vArgs) - LBound(vArgs) + 1 + Select Case nArgCount + Case 0: Run = application_.Run(sCommand) + Case 1: Run = application_.Run(sCommand, vArgs(0)) + Case 2: Run = application_.Run(sCommand, vArgs(0), vArgs(1)) + Case 3: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2)) + Case 4: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3)) + Case 5: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4)) + Case 6: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5)) + Case 7: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6)) + End Select +End Function + +Public Function NewDocument(Optional sTemplate$ = vbNullString, _ + Optional bDefaultIfFail As Boolean = True) As Word.Document + If Not document_ Is Nothing Then _ + Exit Function + + If application_ Is Nothing Then + bCloseOnFail_ = True + Call CreateApplication + End If + + On Error GoTo TRY_DEFAULT + + Set document_ = application_.Documents.Add(sTemplate) + bOwnsDoc_ = True + Set NewDocument = document_ + + Exit Function + +TRY_DEFAULT: + If sTemplate <> vbNullString Then + Call Report(" " & sTemplate) + If Not bDefaultIfFail Then + If bCloseOnFail_ Then _ + Call ReleaseApplication + Else + On Error GoTo ERR_DEFAULT + Set document_ = application_.Documents.Add + Set NewDocument = document_ + End If + Exit Function + End If + +ERR_DEFAULT: + Call Report(" ") + If bCloseOnFail_ Then _ + Call ReleaseApplication +End Function + +Public Function OpenDocument(sFile$, _ + Optional bReadOnly As Boolean = False, _ + Optional bTrackMRU As Boolean = False) As Word.Document + If Not document_ Is Nothing Then _ + Exit Function + + Dim bResetApplication As Boolean + bResetApplication = application_ Is Nothing + If bResetApplication Then _ + Call DefaultApplication + + If TryAlreadyOpened(sFile, bReadOnly) Then + Set OpenDocument = document_ + Exit Function + End If + + If Not TestFile(sFile, bReadOnly) Then _ + GoTo SAFE_EXIT + + On Error GoTo SAFE_EXIT + Set document_ = application_.Documents.Open(sFile, AddToRecentFiles:=bTrackMRU, ReadOnly:=bReadOnly) + On Error GoTo 0 + + If Not document_ Is Nothing Then + bOwnsDoc_ = True + Set OpenDocument = document_ + Else +SAFE_EXIT: + bOwnsDoc_ = False + If bCloseOnFail_ Then _ + Call KillApplication + If bResetApplication Then _ + Set application_ = Nothing + End If +End Function + +Public Function ReleaseApplication() + If bOwnsApp_ Then _ + Call KillApplication + Call ResetDocument +End Function + +Public Function ReleaseDocument(Optional bCloseApplication As Boolean = True, _ + Optional bSaveChanges As Boolean = False) As Boolean + ReleaseDocument = False + If document_ Is Nothing Then _ + Exit Function + If Not bOwnsDoc_ Then + Set document_ = Nothing + ReleaseDocument = True + Exit Function + End If + + On Error GoTo EXIT_FUNC + Call document_.Close(SaveChanges:=(bSaveChanges And document_.Path <> vbNullString)) + On Error GoTo 0 + + Set document_ = Nothing + ReleaseDocument = True + If (bCloseApplication And application_.Documents.Count = 0) Then _ + Call ReleaseApplication +EXIT_FUNC: +End Function + +Public Function SaveAs(sTargetName$) As Boolean + SaveAs = False + + If document_ Is Nothing Then _ + Exit Function + + On Error GoTo RETURN_FALSE + + Dim fso As New Scripting.FileSystemObject + Dim nFormat As WdSaveFormat + If GetSaveFormat(fso.GetExtensionName(sTargetName), nFormat) Then + Call document_.SaveAs2(sTargetName, FileFormat:=nFormat) + Else + Call document_.SaveAs2(sTargetName) + End If + + On Error GoTo 0 + + SaveAs = True + Exit Function +RETURN_FALSE: +End Function + +' ======= +Private Function Report(sMsg$) + If bSilent_ Then + Debug.Print "WordWrapper: " & sMsg + Exit Function + ElseIf reporter_ Is Nothing Then + Call MsgBox(sMsg, vbExclamation) + Else + Call reporter_.Report(sMsg, vbExclamation) + End If +End Function + +Private Function DefaultApplication() + bOwnsApp_ = False + On Error GoTo CREATE_NEW_APPL + Set application_ = GetObject(, "Word.Application") + On Error GoTo 0 + Exit Function + +CREATE_NEW_APPL: + Set application_ = CreateObject("Word.Application") + bOwnsApp_ = True + bCloseOnFail_ = True + application_.Visible = True +End Function + +Private Function KillApplication() + Const HIDE_SHELL = 0 + Const WAIT_RETURN = True + + On Error GoTo OFFICE_QUIT + + Dim nThreadID&, nProcID& + Call application_.Documents.Add ' Create new document for ActiveWindow to become available + nThreadID = GetWindowThreadProcessId(application_.ActiveWindow.Hwnd, nProcID) + + Dim iShell As Object: Set iShell = VBA.CreateObject("WScript.Shell") + If iShell.Run("TaskKill /F /PID " & nProcID, HIDE_SHELL, WAIT_RETURN) = 0 Then _ + Exit Function + +OFFICE_QUIT: + Call application_.Quit(SaveChanges:=False) +End Function + +Private Function TryAlreadyOpened(sFile$, bReadOnly As Boolean) As Boolean + TryAlreadyOpened = False + + Set document_ = FindByName(sFile) + If document_ Is Nothing Then _ + Exit Function + If document_.ReadOnly And Not bReadOnly Then + Set document_ = Nothing + Exit Function + End If + + bOwnsDoc_ = False + TryAlreadyOpened = True +End Function + +Private Function TestFile(sFile$, bReadOnly As Boolean) As Boolean + Const ATTRIBUTE_READONLY = 1 + TestFile = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + + If Not bReadOnly Then + If fso.GetFile(sFile).Attributes And ATTRIBUTE_READONLY Then + Call Report(" ReadOnly: " & sFile) + Exit Function + End If + If IsFileInUse(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + End If + + TestFile = True +End Function + +Private Function FindByName(sName$) As Word.Document + Dim aDoc As Word.Document + For Each aDoc In application_.Documents + If aDoc.FullName = sName Then + Set FindByName = aDoc + Exit Function + End If + Next aDoc +End Function + +Private Function IsFileInUse(sFileName$) As Boolean + Dim nFile%: nFile = FreeFile + On Error Resume Next + + Open sFileName For Binary Access Read Lock Read Write As #nFile + Close #nFile + IsFileInUse = Err.Number > 0 + + On Error GoTo 0 +End Function + +Private Function GetSaveFormat(sExtension$, ByRef nFormat As WdSaveFormat) As Boolean + GetSaveFormat = True + Select Case VBA.UCase(sExtension) + Case "DOCX": nFormat = wdFormatXMLDocument + Case "DOCM": nFormat = wdFormatXMLDocumentMacroEnabled + Case "DOTX": nFormat = wdFormatXMLTemplate + Case "DOTM": nFormat = wdFormatXMLTemplateMacroEnabled + Case "TXT": nFormat = wdFormatText + Case "PDF": nFormat = wdFormatPDF + Case Else: GetSaveFormat = False + End Select +End Function diff --git a/api/API_XLWrapper.cls b/api/API_XLWrapper.cls new file mode 100644 index 0000000..37ade8a --- /dev/null +++ b/api/API_XLWrapper.cls @@ -0,0 +1,412 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_XLWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ - MS Excel ========================= +' Shared module version: 20220421 +' Tested in: TestCommons +' Depends on: +' Required reference: Scripting, Excel +Option Explicit + +Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal nHwnd As Long, nProcID As Long) As Long + +Private application_ As Excel.Application +Private document_ As Excel.Workbook +Private reporter_ As Object +Private bSilent_ As Boolean + +Private isPaused_ As Boolean +Private calculation_ As Excel.XlCalculation + +Private bCloseOnFail_ As Boolean +Private bOwnsDoc_ As Boolean +Private bOwnsApp_ As Boolean + +Private Sub Class_Initialize() + Call ResetAll +End Sub + +Private Sub Class_Terminate() + If application_ Is Nothing Then _ + Exit Sub + ' Note: application could be owned by another Wrapper and already quit + On Error GoTo SKIP_RELEASE + If application_.Workbooks.Count = 0 Then _ + Call ReleaseApplication +SKIP_RELEASE: +End Sub + +Public Function ResetAll() + Call ResetDocument + + calculation_ = xlCalculationAutomatic + bSilent_ = False +End Function + +Public Function ResetDocument() + Set application_ = Nothing + Set document_ = Nothing + bOwnsDoc_ = False + bOwnsApp_ = False + bCloseOnFail_ = False + isPaused_ = False +End Function + +' Reporter object should implement callback function Report(sMsg$, nFlags&) +Public Function SetReporter(aReporter As Object) + Set reporter_ = aReporter +End Function + +Public Property Get Document() As Excel.Workbook + Set Document = document_ +End Property + +Public Property Get Application() As Excel.Application + Set Application = application_ +End Property + +Public Property Get IsUIPaused() As Boolean + IsUIPaused = isPaused_ +End Property + +Public Function DisableMessages() + bSilent_ = True +End Function + +Public Function EnableMessages() + bSilent_ = False +End Function + +Public Function CreateApplication(Optional bIsVisible As Boolean = True) As Excel.Application + Call ResetDocument + Set application_ = CreateObject("Excel.Application") + bOwnsApp_ = True + application_.Visible = bIsVisible + Set CreateApplication = application_ +End Function + +Public Function SetApplication(target As Excel.Application) + If ObjPtr(application_) = ObjPtr(target) Then _ + Exit Function + Call ResetDocument + Set application_ = target +End Function + +Public Function SetDocument(target As Excel.Workbook, Optional bOwnership = False) + If ObjPtr(target) = ObjPtr(document_) Then + bOwnsDoc_ = bOwnership + Exit Function + End If + If Not target Is Nothing Then _ + Call SetApplication(target.Application) + bOwnsDoc_ = bOwnership + Set document_ = target +End Function + +Public Function PauseUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + Debug.Assert Not isPaused_ + isPaused_ = True + calculation_ = application_.Calculation + application_.Calculation = xlCalculationManual + application_.ScreenUpdating = False + application_.EnableEvents = False + application_.AskToUpdateLinks = False + application_.Cursor = xlWait +End Function + +Public Function ResumeUI() + If application_ Is Nothing Then _ + Exit Function + If document_ Is Nothing Then _ + Exit Function + + isPaused_ = False + application_.Cursor = xlNormal + application_.AskToUpdateLinks = True + application_.EnableEvents = True + application_.ScreenUpdating = True + application_.Calculation = calculation_ +End Function + +Public Function Run(sFunction$, ParamArray vArgs() As Variant) As Variant + Dim sCommand$: sCommand = "'" & document_.Name & "'" & "!" & sFunction + Dim nArgCount&: nArgCount = UBound(vArgs) - LBound(vArgs) + 1 + Select Case nArgCount + Case 0: Run = application_.Run(sCommand) + Case 1: Run = application_.Run(sCommand, vArgs(0)) + Case 2: Run = application_.Run(sCommand, vArgs(0), vArgs(1)) + Case 3: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2)) + Case 4: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3)) + Case 5: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4)) + Case 6: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5)) + Case 7: Run = application_.Run(sCommand, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6)) + End Select +End Function + +Public Function NewDocument(Optional sTemplate$ = vbNullString, _ + Optional bDefaultIfFail As Boolean = True) As Excel.Workbook + If Not document_ Is Nothing Then _ + Exit Function + If application_ Is Nothing Then + bCloseOnFail_ = True + Call CreateApplication + End If + + On Error GoTo TRY_DEFAULT + + Set document_ = application_.Workbooks.Add(sTemplate) + bOwnsDoc_ = True + Set NewDocument = document_ + + Exit Function + +TRY_DEFAULT: + If sTemplate <> vbNullString Then + Call Report(" " & sTemplate) + If Not bDefaultIfFail Then + If bCloseOnFail_ Then _ + Call ReleaseApplication + Else + On Error GoTo ERR_DEFAULT + Set document_ = application_.Workbooks.Add + Set NewDocument = document_ + End If + Exit Function + End If + +ERR_DEFAULT: + Call Report(" ") + If bCloseOnFail_ Then _ + Call ReleaseApplication +End Function + +Public Function OpenDocument(sFile$, _ + Optional bReadOnly As Boolean = False, _ + Optional bTrackMRU As Boolean = False, _ + Optional bIgnoreFolder As Boolean = False) As Excel.Workbook + If Not document_ Is Nothing Then _ + Exit Function + + Dim bResetApplication As Boolean + bResetApplication = application_ Is Nothing + If bResetApplication Then _ + Call DefaultApplication + + If TryAlreadyOpened(sFile, bReadOnly, bIgnoreFolder) Then + Set OpenDocument = document_ + Exit Function + End If + + If Not TestFile(sFile, bReadOnly, bIgnoreFolder) Then _ + GoTo SAFE_EXIT + + On Error GoTo SAFE_EXIT + If sFile Like "*.csv" Then + Set document_ = application_.Workbooks.Open(sFile, addToMRU:=bTrackMRU, ReadOnly:=bReadOnly, Editable:=True, Format:=6, Delimiter:=",") + Else + Set document_ = application_.Workbooks.Open(sFile, addToMRU:=bTrackMRU, ReadOnly:=bReadOnly, Editable:=True) + End If + On Error GoTo 0 + + If Not document_ Is Nothing Then + bOwnsDoc_ = True + Set OpenDocument = document_ + Else +SAFE_EXIT: + bOwnsDoc_ = False + If bCloseOnFail_ Then _ + Call KillApplication + If bResetApplication Then _ + Set application_ = Nothing + End If +End Function + +Public Function ReleaseApplication() + If bOwnsApp_ Then _ + Call KillApplication + Call ResetDocument +End Function + +Public Function ReleaseDocument(Optional bCloseApplication As Boolean = True, _ + Optional bSaveChanges As Boolean = False) As Boolean + ReleaseDocument = False + If document_ Is Nothing Then _ + Exit Function + If Not bOwnsDoc_ Then + Set document_ = Nothing + ReleaseDocument = True + Exit Function + End If + + On Error GoTo EXIT_FUNC + Call document_.Close(SaveChanges:=(bSaveChanges And document_.Path <> vbNullString)) + On Error GoTo 0 + + Set document_ = Nothing + ReleaseDocument = True + If bCloseApplication And application_.Workbooks.Count = 0 Then _ + Call ReleaseApplication +EXIT_FUNC: +End Function + +Public Function SaveAs(sTargetName$) As Boolean + SaveAs = False + + If document_ Is Nothing Then _ + Exit Function + + Dim bDisplayAlerts As Boolean: bDisplayAlerts = application_.DisplayAlerts + application_.DisplayAlerts = False + + On Error GoTo RETURN_FALSE + + Dim fso As New Scripting.FileSystemObject + Dim nFormat As Excel.XlFileFormat + If GetSaveFormat(fso.GetExtensionName(sTargetName), nFormat) Then + Call document_.SaveAs(sTargetName, FileFormat:=nFormat, ConflictResolution:=xlLocalSessionChanges) + Else + Call document_.SaveAs(sTargetName, ConflictResolution:=xlLocalSessionChanges) + End If + + On Error GoTo 0 + + application_.DisplayAlerts = bDisplayAlerts + SaveAs = True + Exit Function + +RETURN_FALSE: + application_.DisplayAlerts = bDisplayAlerts +End Function + +' ======== +Private Function Report(sMsg$) + If bSilent_ Then + Debug.Print "XLWrapper: " & sMsg + Exit Function + ElseIf reporter_ Is Nothing Then + Call MsgBox(sMsg, vbExclamation) + Else + Call reporter_.Report(sMsg, vbExclamation) + End If +End Function + +Private Function DefaultApplication() + bOwnsApp_ = False + On Error GoTo CREATE_NEW_APPL + Set application_ = GetObject(, "Excel.Application") + On Error GoTo 0 + Exit Function + +CREATE_NEW_APPL: + Set application_ = CreateObject("Excel.Application") + bOwnsApp_ = True + bCloseOnFail_ = True + application_.Visible = True +End Function + +Private Function KillApplication() + Const HIDE_SHELL = 0 + Const WAIT_RETURN = True + + On Error GoTo OFFICE_QUIT + + Dim nThreadID&, nProcID& + nThreadID = GetWindowThreadProcessId(application_.Hwnd, nProcID) + + Dim iShell As Object: Set iShell = VBA.CreateObject("WScript.Shell") + If iShell.Run("TaskKill /F /PID " & nProcID, HIDE_SHELL, WAIT_RETURN) = 0 Then _ + Exit Function + +OFFICE_QUIT: + Call application_.Quit +End Function + +Private Function TryAlreadyOpened(sFile$, bReadOnly As Boolean, bIgnoreFolder As Boolean) As Boolean + TryAlreadyOpened = False + + Dim fso As New Scripting.FileSystemObject + Set document_ = FindByName(IIf(bIgnoreFolder, fso.GetFileName(sFile), sFile), bIgnoreFolder) + If document_ Is Nothing Then _ + Exit Function + If document_.ReadOnly And Not bReadOnly Then + Set document_ = Nothing + Exit Function + End If + + bOwnsDoc_ = False + TryAlreadyOpened = True +End Function + +Private Function TestFile(sFile$, bReadOnly As Boolean, bIgnoreFolder As Boolean) As Boolean + Const ATTRIBUTE_READONLY = 1 + TestFile = False + + Dim fso As New Scripting.FileSystemObject + If Not bIgnoreFolder And Not fso.FileExists(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + + If Not bReadOnly And fso.FileExists(sFile) Then + If fso.GetFile(sFile).Attributes And ATTRIBUTE_READONLY Then + Call Report(" ReadOnly: " & sFile) + Exit Function + End If + If Not bIgnoreFolder Then + If IsFileInUse(sFile) Then + Call Report(" : " & sFile) + Exit Function + End If + End If + End If + + TestFile = True +End Function + +Private Function FindByName(sName$, bIgnoreFolder As Boolean) As Excel.Workbook + Dim aBook As Excel.Workbook + For Each aBook In application_.Workbooks + If bIgnoreFolder And aBook.Name = sName Or _ + Not bIgnoreFolder And aBook.FullName = sName Then + Set FindByName = aBook + Exit Function + End If + Next aBook +End Function + +Private Function IsFileInUse(sFileName$) As Boolean + Dim nFile%: nFile = FreeFile + On Error Resume Next + + Open sFileName For Binary Access Read Lock Read Write As #nFile + Close #nFile + IsFileInUse = Err.Number > 0 + + On Error GoTo 0 +End Function + +Private Function GetSaveFormat(sExtension$, ByRef nFormat As Excel.XlFileFormat) As Boolean + GetSaveFormat = True + Select Case VBA.UCase(sExtension) + Case "XLSX": nFormat = xlOpenXMLWorkbook + Case "XLSM": nFormat = xlOpenXMLWorkbookMacroEnabled + Case "XLTX": nFormat = xlOpenXMLTemplate + Case "XLTM": nFormat = xlOpenXMLTemplateMacroEnabled + Case "XLAM": nFormat = xlOpenXMLAddIn + Case "TXT": nFormat = xlText + Case "CSV": nFormat = xlCSV + Case "PDF": nFormat = xlTypePDF + Case Else: GetSaveFormat = False + End Select +End Function diff --git a/api/ex_ConceptCore.bas b/api/ex_ConceptCore.bas new file mode 100644 index 0000000..9f32d94 --- /dev/null +++ b/api/ex_ConceptCore.bas @@ -0,0 +1,117 @@ +Attribute VB_Name = "ex_ConceptCore" +'================ CCL wrapper ========================= +' Shared module version: 20220713 +' Tested in: +' Depends on: +' Required reference: +Option Private Module +Option Explicit + +Private Const CONCEPT_DLL_LOCATION = "C:\Tools\dll" + +#If Win64 Then + Private Const CCL_DLL_NAME = "ConceptCore64.dll" + Private Declare PtrSafe Function ConvertToASCII Lib "ConceptCore64.dll" ( _ + ByRef vResult As Variant, ByRef vText As Variant) As Long + Private Declare PtrSafe Function ConvertToMath Lib "ConceptCore64.dll" ( _ + ByRef vResult As Variant, ByVal sText As String) As Long + Private Declare PtrSafe Function ASTasText Lib "ConceptCore64.dll" ( _ + ByRef vResult As Variant, ByVal sText As String) As Long +#Else + Private Const CCL_DLL_NAME = "ConceptCore32.dll" + Private Declare PtrSafe Function ConvertToASCII Lib "ConceptCore32.dll" ( _ + ByRef vResult As Variant, ByRef vText As Variant) As Long + Private Declare PtrSafe Function ConvertToMath Lib "ConceptCore32.dll" ( _ + ByRef vResult As Variant, ByVal sText As String) As Long + Private Declare PtrSafe Function ASTasText Lib "ConceptCore32.dll" ( _ + ByRef vResult As Variant, ByVal sText As String) As Long +#End If + +Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal sLibrary As String) As Long +Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal nLibraryHandle As Long) As Long +Private Const HRESULT_OK = 0 + +Private g_IsConceptLoaded As Boolean + +Public Function LoadConceptCore() + If g_IsConceptLoaded Then _ + Exit Function + g_IsConceptLoaded = True + + If LoadLibrary(CCL_DLL_NAME) <> 0 Then _ + Exit Function + + If LoadLibrary(CONCEPT_DLL_LOCATION & "\" & CCL_DLL_NAME) <> 0 Then _ + Exit Function + + ' TODO: remove fallback after some time + ' fallback path for earlier versions of distribution + If LoadLibrary(VBA.Environ("USERPROFILE") & "\.concept\dll\" & CCL_DLL_NAME) = 0 Then _ + Call Err.Raise(1, Description:="Could not load " & CCL_DLL_NAME) +End Function + +Public Function UnloadConceptCore() + If Not g_IsConceptLoaded Then _ + Exit Function + Dim nHandle&: nHandle = LoadLibrary(CCL_DLL_NAME) + Call FreeLibrary(nHandle) +End Function + +Public Function MathToASCII(sText$) As String + On Error GoTo HANDLE_ERROR + + LoadConceptCore + + Dim vText As Variant: vText = sText + Dim vResult As Variant + If ConvertToASCII(vResult, vText) <> HRESULT_OK Then _ + MathToASCII = "ERR: " + MathToASCII = MathToASCII & vResult + + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + MathToASCII = Err.Description + On Error GoTo 0 +End Function + +Public Function ASCIItoMath(sText$) As String + On Error GoTo HANDLE_ERROR + + LoadConceptCore + + Dim vResult As Variant + If ConvertToMath(vResult, sText) <> HRESULT_OK Then _ + ASCIItoMath = "ERR: " + ASCIItoMath = ASCIItoMath & " " & vResult + + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + ASCIItoMath = Err.Description + On Error GoTo 0 +End Function + +Public Function ASCIItoAST(sText$) As String + On Error GoTo HANDLE_ERROR + + LoadConceptCore + + Dim vResult As Variant + If ASTasText(vResult, sText) <> HRESULT_OK Then _ + ASCIItoAST = "ERR: " + ASCIItoAST = ASCIItoAST & vResult + + On Error GoTo 0 + Exit Function + +HANDLE_ERROR: + Debug.Print Err.Description + ASCIItoAST = Err.Description + On Error GoTo 0 +End Function + diff --git a/api/ex_Metadata.bas b/api/ex_Metadata.bas new file mode 100644 index 0000000..d5f44f0 --- /dev/null +++ b/api/ex_Metadata.bas @@ -0,0 +1,40 @@ +Attribute VB_Name = "ex_Metadata" +'================ ============= +' Shared module version: 20210506 +' Depends on: +' Required reference: Scripting +Option Private Module +Option Explicit + +Public Function MetadataExists(iDoc As Object, varName$) As Boolean + Dim aVar As Variant + For Each aVar In iDoc.Variables + If aVar.Name = varName Then + MetadataExists = True + Exit Function + End If + Next aVar + MetadataExists = False +End Function + +Public Function GetMetadata(iDoc As Object, varName$) As Variant + Dim aVar As Variant + For Each aVar In iDoc.Variables + If aVar.Name = varName Then + GetMetadata = aVar.Value + Exit Function + End If + Next aVar + GetMetadata = "" +End Function + +Public Function SetMetadata(iDoc As Object, varName$, val As Variant) + Dim aVar As Variant + For Each aVar In iDoc.Variables + If aVar.Name = varName Then + aVar.Value = val + Exit Function + End If + Next aVar + Call iDoc.Variables.Add(varName, val) +End Function diff --git a/api/ex_Python.bas b/api/ex_Python.bas new file mode 100644 index 0000000..93af1be --- /dev/null +++ b/api/ex_Python.bas @@ -0,0 +1,125 @@ +Attribute VB_Name = "ex_Python" +' ======== Python runner extension ======== +' Shared module version: 20240330 +' Tested in: PythonManager +' Depends on: ex_VBA, ex_WinAPI, API_Config +' Required reference: Scripting +Option Private Module +Option Explicit + +' Switch flag for Debug purposes +Private Const SHOW_PYTHON = False + +Public Const PY_KEY_INTERPRETER = "Python3" +Public Const PY_KEY_SOURCE_PATH = "PythonImports" + +Public Const PY_MODULE_TEXT = "cctext" +Public Const PY_MODULE_UTILS = "vbatopy" + +Public Const PY_DEFVALUE_INTERPRETER = "C:\Tools\Python312-venv\Scripts\python.exe" + +Public Function AccessPython() As API_Python + Static s_Python As API_Python + If s_Python Is Nothing Then + Set s_Python = New API_Python + Call s_Python.Init( _ + iPython:=ConceptConfig.GetValue(PY_KEY_INTERPRETER), _ + sModules:=OfficeActivePath & ";" & ConceptConfig.GetValue(PY_KEY_SOURCE_PATH), _ + bDoDebug:=SHOW_PYTHON) + End If + Set AccessPython = s_Python +End Function + +Public Function ConceptConfig() As API_Config + Dim iConfig As New API_Config + Set ConceptConfig = iConfig + If Not iConfig.LoadFromFile(ConceptConfigPath()) Then _ + Call LoadDefaultConfig(iConfig) +End Function + +' Runs the Python command, e.g.: Call RunPython("import bar; bar.foo()", ThisWorkbook.Path) +Public Function RunPyStandalone(sPyCommand$, Optional sWorkDir$ = vbNullString) As Long + Dim iConfig As API_Config: Set iConfig = ConceptConfig + Dim iPython$: iPython = iConfig.GetValue(PY_KEY_INTERPRETER) + Dim sSource$ + If Not sWorkDir = vbNullString Then _ + sSource = sWorkDir & ";" + sSource = sSource & iConfig.GetValue(PY_KEY_SOURCE_PATH) + If sSource <> "" Then _ + sSource = sSource & ";" + sSource = VBA.Replace(sSource, "&", "^&") + sSource = VBA.Replace(sSource, "\", "\\") + + Dim sChangeDir$: sChangeDir = GenerateCDCommandFor(iPython) + Dim sExtendedPyCommand$: sExtendedPyCommand = Fmt( _ + "{1} & ""{2}"" -B -c " & _ + """import sys, os; sys.path[0:0]=os.path.normcase(os.path.expandvars(\""{3}\"")).split(';'); " & _ + "{4}""", _ + sChangeDir, iPython, sSource, sPyCommand) + + RunPyStandalone = PyExecute(sExtendedPyCommand) +End Function + +' Runs a Python executable that has been frozen by PyInstaller and the like +' Call RunFrozenPython("C:\path\to\frozen_executable.exe", "arg1 arg2") +Public Function RunPyFrozen(iPython$, Optional sArgs$ = vbNullString) As Long + Dim sChangeDir$: sChangeDir = GenerateCDCommandFor(iPython) + Dim sPyCommand$: sPyCommand = Fmt("{1} & ""{2}"" {3}", sChangeDir, iPython, sArgs) + RunPyFrozen = PyExecute(sPyCommand) +End Function + +Public Function ConceptConfigPath() As String + ConceptConfigPath = VBA.Environ$("USERPROFILE") & "\.concept\concept-options.json" +End Function + +' ======== +Private Function PyExecute(sPyCommand$) As Long + Dim sLog$: sLog = GetLogFile() + + Dim sShellCommand$: sShellCommand = Fmt("cmd.exe /C {1} 2> ""{3}""", _ + sPyCommand, sLog) + + Dim nWindowStyle%: nWindowStyle = IIf(SHOW_PYTHON, 1, 0) + Dim iShell As Object: Set iShell = CreateObject("WScript.Shell") + Dim nExitCode&: nExitCode = iShell.Run(sShellCommand, nWindowStyle, True) + + If nExitCode <> 0 Then + Call MsgBox(ReadTextFile(sLog), vbCritical) + PyExecute = -1 + End If + + On Error Resume Next + Call Kill(sLog) + On Error GoTo 0 + + Set iShell = Nothing +End Function + +Private Function LoadDefaultConfig(ByRef iConfig As API_Config) + Call iConfig.SetValue(PY_KEY_INTERPRETER, PY_DEFVALUE_INTERPRETER) + Call iConfig.SetValue(PY_KEY_SOURCE_PATH, "") +End Function + +Private Function GenerateCDCommandFor(sFile$) As String + Dim fso As New Scripting.FileSystemObject + GenerateCDCommandFor = Fmt("{1} & cd ""{2}""", VBA.Left(sFile, 2), fso.GetParentFolderName(sFile$)) +End Function + +Private Function GetLogFile() As String + GetLogFile = Fmt("{1}\pymanager-{2}.log", VBA.Environ("Temp"), GetCurrentProcessId()) +End Function + +Private Function ReadTextFile(sFile$) As String + Dim adoStream As Object: Set adoStream = CreateObject("ADODB.Stream") + adoStream.Charset = "utf-8" + Call adoStream.Open + + On Error GoTo ERROR_FILE + Call adoStream.LoadFromFile(sFile) + On Error GoTo 0 + + ReadTextFile = adoStream.ReadText + +ERROR_FILE: + Call adoStream.Close +End Function diff --git a/api/ex_WinAPI.bas b/api/ex_WinAPI.bas new file mode 100644 index 0000000..77d4933 --- /dev/null +++ b/api/ex_WinAPI.bas @@ -0,0 +1,541 @@ +Attribute VB_Name = "ex_WinAPI" +' ================ ============= +' Shared module version: 20220812 +' Tested in: TestCommons +' Depends on: +' Required reference: Scripting, Shell32 +Option Private Module +Option Explicit + +Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal nMilliseconds As LongPtr) +Public Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long +Public Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal nHwnd As Long, nProcID As Long) As Long +Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long +Public Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" _ + (ByVal nHwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long + +Public Declare PtrSafe Function SetTimer Lib "user32" (ByVal nHwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As Long +Public Declare PtrSafe Function KillTimer Lib "user32" (ByVal nHwnd As Long, ByVal nDEvent As Long) As Long + +Private Const DATAOBJECT_BINDING$ = "new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}" ' ID DataObject + +Private Declare PtrSafe Function BringWindowToTop Lib "user32" (ByVal nHwnd As Long) As Long +Private Declare PtrSafe Function SetFocus Lib "user32" (ByVal nHwnd As Long) As Long + +Private Declare PtrSafe Function GetCommandLine Lib "kernel32" Alias "GetCommandLineW" () As LongPtr +Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpString As LongPtr) As Long +Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (iDestination As Any, iSource As Any, ByVal nLength As LongPtr) + +Public Enum TApplication + T_APP_UNDEF = 0 + [_First] = 1 + + T_APP_EXCEL = 1 + T_APP_WORD = 2 + T_APP_VISIO = 3 + + [_Last] = 3 +End Enum + +Public Function OfficeCommandLine() As String + OfficeCommandLine = String$(lstrlenW(GetCommandLine()), 0) + Call CopyMemory(ByVal StrPtr(OfficeCommandLine), ByVal GetCommandLine(), LenB(OfficeCommandLine)) +End Function + +Public Function ActivateWindow(sClass$, Optional sWindow$ = vbNullString) As Boolean + On Error Resume Next + ActivateWindow = False + Dim nWindow&: nWindow = FindWindow(sClass, sWindow) + If nWindow <= 0 Then _ + Exit Function + ActivateWindow = ActivateWindowHwnd(nWindow) +End Function + +Public Function ActivateWindowHwnd(nWindow&) As Boolean + On Error Resume Next + ActivateWindowHwnd = False + If nWindow <= 0 Then _ + Exit Function + If BringWindowToTop(nWindow) = 0 Then _ + Exit Function + + Call SetFocus(nWindow) + ActivateWindowHwnd = True +End Function + +Public Function ApplicationFromExtension(sExtension$) As TApplication + ApplicationFromExtension = T_APP_UNDEF + Select Case VBA.UCase(sExtension) + Case "XLS": ApplicationFromExtension = T_APP_EXCEL + Case "XLSX": ApplicationFromExtension = T_APP_EXCEL + Case "XLSM": ApplicationFromExtension = T_APP_EXCEL + Case "XLTM": ApplicationFromExtension = T_APP_EXCEL + Case "RTF": ApplicationFromExtension = T_APP_WORD + Case "DOC": ApplicationFromExtension = T_APP_WORD + Case "DOCX": ApplicationFromExtension = T_APP_WORD + Case "DOCM": ApplicationFromExtension = T_APP_WORD + Case "DOTM": ApplicationFromExtension = T_APP_WORD + Case "VSDX": ApplicationFromExtension = T_APP_VISIO + Case "VSDM": ApplicationFromExtension = T_APP_VISIO + Case "VSTX": ApplicationFromExtension = T_APP_VISIO + Case "VSTM": ApplicationFromExtension = T_APP_VISIO + End Select +End Function + +Public Function ApplicationTypeFor(iApp As Object) As TApplication + ApplicationTypeFor = T_APP_UNDEF + Select Case iApp.Name + Case "Microsoft Excel": ApplicationTypeFor = T_APP_EXCEL + Case "Microsoft Word": ApplicationTypeFor = T_APP_WORD + Case "Microsoft Visio": ApplicationTypeFor = T_APP_VISIO + End Select +End Function + +Public Function ApplicationWrapper(iType As TApplication) As Object + Dim iApplication As Object: Set iApplication = Application + If Application.Name = "Microsoft Visio" Then _ + Call Err.Raise(vbObjectError, Description:="Visio does not support meta wrappers") + Select Case iType + Case T_APP_EXCEL: Set ApplicationWrapper = iApplication.Run("CreateWrapperXL") + Case T_APP_WORD: Set ApplicationWrapper = iApplication.Run("CreateWrapperWord") + Case T_APP_VISIO: Set ApplicationWrapper = iApplication.Run("CreateWrapperVisio") + End Select +End Function + +' To use application wrapper copy required CreateWrapper functions to Main module and uncomment them +'Public Function CreateWrapperXL() As API_XLWrapper +' Set CreateWrapperXL = New API_XLWrapper +'End Function +' +'Public Function CreateWrapperWord() As API_WordWrapper +' Set CreateWrapperWord = New API_WordWrapper +'End Function +' +'Public Function CreateWrapperVisio() As API_VisioWrapper +' Set CreateWrapperVisio = New API_VisioWrapper +'End Function + +' Wrapper for active document +Public Function OfficeActiveWrapper() As Object + Dim activeApp As Object: Set activeApp = Application + Dim iWrapper As Object + + Select Case ApplicationTypeFor(Application) + Case T_APP_EXCEL + Set iWrapper = ApplicationWrapper(T_APP_EXCEL) + Call iWrapper.SetDocument(activeApp.ActiveWorkbook) + + Case T_APP_WORD + Set iWrapper = ApplicationWrapper(T_APP_WORD) + Call iWrapper.SetDocument(activeApp.ActiveDocument) + + Case T_APP_VISIO + Set iWrapper = ApplicationWrapper(T_APP_VISIO) + Call iWrapper.SetDocument(activeApp.ActiveDocument) + End Select + + Set OfficeActiveWrapper = iWrapper +End Function + +' Wrapper for this document +Public Function OfficeThisWrapper() As Object + Dim activeApp As Object: Set activeApp = Application + Dim iWrapper As Object + + Select Case ApplicationTypeFor(Application) + Case T_APP_EXCEL + Set iWrapper = ApplicationWrapper(T_APP_EXCEL) + Call iWrapper.SetDocument(activeApp.ThisWorkbook) + + Case T_APP_WORD + Set iWrapper = ApplicationWrapper(T_APP_WORD) + Call iWrapper.SetDocument(activeApp.ThisDocument) + + Case T_APP_VISIO + Set iWrapper = ApplicationWrapper(T_APP_VISIO) + Call iWrapper.SetDocument(activeApp.ThisDocument) + End Select + + Set OfficeThisWrapper = iWrapper +End Function + +' Path to active document folder +Public Function OfficeActivePath() As String + Dim activeApp As Object: Set activeApp = Application + Select Case ApplicationTypeFor(Application) + Case T_APP_EXCEL: OfficeActivePath = activeApp.ActiveWorkbook.Path + Case T_APP_WORD: OfficeActivePath = activeApp.ActiveDocument.Path + Case T_APP_VISIO: OfficeActivePath = activeApp.ActiveDocument.Path + End Select +End Function + +' Path to this document folder +Public Function OfficeThisPath() As String + Dim activeApp As Object: Set activeApp = Application + Select Case ApplicationTypeFor(Application) + Case T_APP_EXCEL: OfficeThisPath = activeApp.ThisWorkbook.Path + Case T_APP_WORD: OfficeThisPath = activeApp.ThisDocument.Path + Case T_APP_VISIO: OfficeThisPath = activeApp.ThisDocument.Path + End Select +End Function + +Public Function AddToClipboard(sText$) + With CreateObject(DATAOBJECT_BINDING) + Call .SetText(sText) + .PutInClipboard + End With +End Function + +Public Function RetrieveFromClipboard() As String + With CreateObject(DATAOBJECT_BINDING) + Call .GetFromClipboard + RetrieveFromClipboard = .GetText + End With +End Function + +Public Function WindowsTempFolder() As String + WindowsTempFolder = Environ("TEMP") +End Function + +Public Function EnsureFolderExists(sPath$, Optional ByRef fso As Scripting.FileSystemObject = Nothing) + If fso Is Nothing Then _ + Set fso = New Scripting.FileSystemObject + If fso.FolderExists(sPath) Then _ + Exit Function + + Dim sParent$: sParent = sPath + Do + If sParent = vbNullString Then _ + Exit Function + If VBA.Right(sParent, 1) <> "\" Then _ + Exit Do + sParent = VBA.Left(sParent, VBA.Len(sParent) - 1) + Loop + + Dim nDelim&: nDelim = VBA.InStrRev(sParent, "\") + If nDelim = 0 Then _ + Exit Function + sParent = VBA.Left(sParent, nDelim - 1) + + Call EnsureFolderExists(sParent, fso) + Call fso.CreateFolder(sPath) +End Function + +Public Function CreateTextFileUTF16(sFile$, Optional bOverwrite = False) As Boolean + CreateTextFileUTF16 = False + + Dim fso As New Scripting.FileSystemObject + If Not bOverwrite Then _ + If fso.FileExists(sFile) Then _ + Exit Function + + On Error GoTo EXIT_FALSE + Call fso.CreateTextFile(sFile, Overwrite:=bOverwrite, Unicode:=True).Close + On Error GoTo 0 + + CreateTextFileUTF16 = True + Exit Function +EXIT_FALSE: +End Function + +Public Function AppendTextToFileUTF16(sFile$, sMsg$) As Boolean + Const UTF16_MODE = TristateTrue + AppendTextToFileUTF16 = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then _ + Exit Function + + On Error GoTo RETURN_FALSE + Dim fs As Scripting.TextStream + Set fs = fso.OpenTextFile(sFile, ForAppending, Format:=UTF16_MODE) + If fs Is Nothing Then _ + Exit Function + + Call fs.WriteLine(sMsg) + Call fs.Close + + AppendTextToFileUTF16 = True + Exit Function +RETURN_FALSE: +End Function + +Public Function ReadTextFromFileUTF16(sFile$, ByRef sResult$) As Boolean + Const UTF16_MODE = TristateTrue + ReadTextFromFileUTF16 = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sFile) Then _ + Exit Function + + On Error GoTo RETURN_FALSE + Dim fs As Scripting.TextStream + Set fs = fso.OpenTextFile(sFile, ForReading, Format:=UTF16_MODE) + If fs Is Nothing Then _ + Exit Function + + sResult = fs.ReadAll + Call fs.Close + + ReadTextFromFileUTF16 = True + Exit Function +RETURN_FALSE: +End Function + +Public Function CopyFileOrFolder(sSource$, sDestination$, Optional ByRef fso As Scripting.FileSystemObject = Nothing) As Boolean + CopyFileOrFolder = False + + If fso Is Nothing Then _ + Set fso = New Scripting.FileSystemObject + + Dim bIsFile As Boolean: bIsFile = fso.FileExists(sSource) + Dim bIsFolder As Boolean: bIsFolder = fso.FolderExists(sSource) + If Not bIsFile And Not bIsFolder Then _ + Exit Function + + Call EnsureFolderExists(fso.GetParentFolderName(sDestination), fso) + On Error GoTo RETURN_FALSE + If bIsFile Then + Call fso.CopyFile(sSource, sDestination, OverWriteFiles:=True) + Else + Call fso.CopyFolder(sSource, sDestination, OverWriteFiles:=True) + End If + On Error GoTo 0 + + CopyFileOrFolder = True +RETURN_FALSE: +End Function + +Public Function MoveFileOrFolder(sSource$, sDestination$, Optional fso As Scripting.FileSystemObject = Nothing) As Boolean + MoveFileOrFolder = False + If sSource = sDestination Then _ + Exit Function + If Not CopyFileOrFolder(sSource, sDestination, fso) Then _ + Exit Function + + On Error GoTo RETURN_FALSE + If fso.FileExists(sSource) Then _ + Call fso.DeleteFile(sSource) + If fso.FolderExists(sSource) Then _ + Call fso.DeleteFolder(sSource) + On Error GoTo 0 + + MoveFileOrFolder = True + Exit Function + +RETURN_FALSE: + MoveFileOrFolder = False +End Function + +Public Function ListFilesIn(sTargetFolder$, Optional sMask$ = "*.*") As Collection + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sTargetFolder) Then _ + Exit Function + + Dim iFiles As New Collection + Dim cQueue As New Collection + Call cQueue.Add(fso.GetFolder(sTargetFolder)) + + Dim aFolder As Scripting.Folder + Dim subFolder As Scripting.Folder + Dim aFile As Scripting.File + Do While cQueue.Count > 0 + Set aFolder = cQueue(1) + Call cQueue.Remove(1) + + For Each subFolder In aFolder.SubFolders + Call cQueue.Add(subFolder) + Next subFolder + + For Each aFile In aFolder.files + If VBA.UCase(aFile.Name) Like VBA.UCase(sMask) Then _ + Call iFiles.Add(aFile.Path) + Next aFile + Loop + + Set ListFilesIn = iFiles +End Function + +Public Function ForEachFileRecursive(sTargetFolder$, oCallback As Object, sFuncName$) + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sTargetFolder) Then _ + Exit Function + + Dim cQueue As New Collection + Call cQueue.Add(fso.GetFolder(sTargetFolder)) + + Dim aFolder As Scripting.Folder + Dim subFolder As Scripting.Folder + Dim aFile As Scripting.File + Do While cQueue.Count > 0 + Set aFolder = cQueue(1) + Call cQueue.Remove(1) + + For Each subFolder In aFolder.SubFolders + Call cQueue.Add(subFolder) + Next subFolder + + For Each aFile In aFolder.files + Call CallByName(oCallback, sFuncName, VbMethod, aFile) + Next aFile + Loop +End Function + +' Callback should return TRUE to continue recursive +Public Function ForEachFolderRecursive(sTargetFolder$, oCallback As Object, sFuncName$) + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sTargetFolder) Then _ + Exit Function + + Dim cQueue As New Collection + Call cQueue.Add(fso.GetFolder(sTargetFolder)) + + Dim aFolder As Scripting.Folder + Dim subFolder As Scripting.Folder + Dim aFile As Scripting.File + Do While cQueue.Count > 0 + Set aFolder = cQueue(1) + Call cQueue.Remove(1) + + If CallByName(oCallback, sFuncName, VbMethod, aFolder) Then + For Each subFolder In aFolder.SubFolders + Call cQueue.Add(subFolder) + Next subFolder + End If + Loop +End Function + +Public Function FileToMD5(sTargetFile$) As String + FileToMD5 = ComputeFileHash(sTargetFile, "MD5CryptoServiceProvider") +End Function + +Public Function FileToSHA1(sTargetFile$) As String + FileToSHA1 = ComputeFileHash(sTargetFile, "SHA1CryptoServiceProvider") +End Function + +Public Function UnzipFile(sTargetFile$, sDestinationFolder$) As Boolean + Const OPT_DISABLE_DIALOGS = 4 + Const OPT_YES_TO_ALL = 16 + + UnzipFile = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sTargetFile) Then _ + Exit Function + + Dim sExt$: sExt = fso.GetExtensionName(sTargetFile) + Dim sZip$: sZip = sTargetFile + If sExt <> "zip" Then + sZip = Left(sTargetFile, Len(sTargetFile) - Len(sExt) - 1) & ".tmp.zip" + Call fso.GetFile(sTargetFile).Copy(sZip) + End If + + Call EnsureFolderExists(sDestinationFolder, fso) + + Dim aShell As New Shell32.Shell + Call aShell.Namespace(sDestinationFolder).CopyHere(aShell.Namespace(sZip).Items, OPT_DISABLE_DIALOGS + OPT_YES_TO_ALL) + + If sExt <> "zip" Then _ + Call fso.DeleteFile(sZip) + + UnzipFile = True +End Function + +Public Function ZipFolder(sTargetFolder$, sDestinationFile$) As Boolean + ZipFolder = False + + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sTargetFolder) Then _ + Exit Function + + Dim sZip$: sZip = sDestinationFile + Dim sExt$: sExt = fso.GetExtensionName(sDestinationFile) + If sExt <> "zip" Then _ + sZip = Left(sDestinationFile, Len(sDestinationFile) - Len(sExt) - 1) & ".zip" + + Call CreateEmptyZip(sZip) + Dim aShell As New Shell32.Shell + Call aShell.Namespace(sZip).CopyHere(aShell.Namespace(sTargetFolder).Items) + +' Wait for zipping to finish + On Error Resume Next + Dim nTimeout&: nTimeout = 90 ' seconds + Do Until aShell.Namespace(sZip).Items.Count = aShell.Namespace(sTargetFolder).Items.Count + Call Sleep(1000) + nTimeout = nTimeout - 1 + If nTimeout = 0 Then _ + Exit Function + Loop + On Error GoTo 0 + + If sExt <> "zip" Then + Call fso.CopyFile(sZip, sDestinationFile) + Call fso.DeleteFile(sZip) + End If + + ZipFolder = True +End Function + +Public Function PositionInMiddle(targetForm As Object) + Dim nTopOffset&: nTopOffset = (Application.UsableHeight / 2) - (targetForm.Height / 2) + Dim nLeftOffset&: nLeftOffset = (Application.UsableWidth / 2) - (targetForm.Width / 2) + + targetForm.StartUpPosition = 0 + targetForm.Top = Application.Top + IIf(nTopOffset > 0, nTopOffset, 0) + targetForm.Left = Application.Left + IIf(nLeftOffset > 0, nLeftOffset, 0) +End Function + +' ======== +Private Function CreateEmptyZip(sTarget$) + Dim nFileID%: nFileID = FreeFile + Open sTarget For Output As #nFileID + Print #nFileID, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0) + Close #nFileID +End Function + +Private Function ComputeFileHash(sTargetFile$, sCryptoProvider$) As String + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sTargetFile) Then + ComputeFileHash = "File not exists" + Exit Function + End If + + Dim nFileID&: nFileID = VBA.FreeFile + On Error GoTo CANNOT_OPEN + Open sTargetFile For Binary Access Read As nFileID + On Error GoTo 0 + + Dim iEncrypter As Object: Set iEncrypter = CreateObject("System.Security.Cryptography." & sCryptoProvider) + Dim iBuffer() As Byte + Dim nBlockSize&: nBlockSize = 2 ^ 16 + Dim nFileSize&: nFileSize = VBA.LOF(nFileID) + If nFileSize < nBlockSize Then _ + nBlockSize = ((nFileSize + 1024) \ 1024) * 1024 + + ReDim iBuffer(0 To nBlockSize - 1) + Dim i& + For i = 1 To nFileSize \ nBlockSize Step 1 + Get nFileID, , iBuffer + Call iEncrypter.TransformBlock(iBuffer, 0, nBlockSize, iBuffer, 0) + Next + + Get nFileID, , iBuffer + Call iEncrypter.TransformFinalBlock(iBuffer, 0, nFileSize Mod nBlockSize) + iBuffer = iEncrypter.Hash + + Close nFileID + Call iEncrypter.Clear + + Dim nPos& + Dim sHash$ + For nPos = 1 To VBA.LenB(iBuffer) + sHash = sHash & VBA.LCase(VBA.Right("0" & VBA.Hex(VBA.AscB(VBA.MidB(iBuffer, nPos, 1))), 2)) + Next nPos + + Set iEncrypter = Nothing + ComputeFileHash = sHash + Exit Function + +CANNOT_OPEN: + ComputeFileHash = "Cannot open file" +End Function diff --git a/api/z_LoadPictureAPI.bas b/api/z_LoadPictureAPI.bas new file mode 100644 index 0000000..df8ea02 --- /dev/null +++ b/api/z_LoadPictureAPI.bas @@ -0,0 +1,97 @@ +Attribute VB_Name = "z_LoadPictureAPI" +'================ GDI ============= +' Shared module version: 20210228 +' Required reference: +Option Private Module +Option Explicit + +'Declare a UDT to store a GUID for the IPicture OLE Interface +Private Type GUID + Data1 As Long + Data2 As Integer + Data3 As Integer + Data4(0 To 7) As Byte +End Type + +'Declare a UDT to store the bitmap information +Private Type PictDescriptor + size_ As LongLong + type_ As LongLong + hPic_ As LongPtr + hPal_ As LongLong +End Type + +'Declare a UDT to store the GDI+ Startup information +Private Type GdiplusStartupInput + version_ As Long + callback_ As LongPtr + suppBgThread_ As LongLong + suppExtCodecs_ As LongLong +End Type + +'Windows API calls into the GDI+ library +Private Declare PtrSafe Function GdiplusStartup Lib "GDIPlus" (pToken As LongPtr, pInputBuf As GdiplusStartupInput, Optional ByVal pOutputBuf As LongPtr = 0) As Long +Private Declare PtrSafe Function GdipCreateBitmapFromFile Lib "GDIPlus" (ByVal pFile As LongPtr, pBitmap As LongPtr) As Long +Private Declare PtrSafe Function GdipCreateHBITMAPFromBitmap Lib "GDIPlus" (ByVal pBitmap As LongPtr, hbmReturn As LongPtr, ByVal pBackground As LongPtr) As Long +Private Declare PtrSafe Function GdipDisposeImage Lib "GDIPlus" (ByVal pImage As LongPtr) As Long +Private Declare PtrSafe Function GdiplusShutdown Lib "GDIPlus" (ByVal pToken As LongPtr) As Long +Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32.dll" (PicDesc As PictDescriptor, RefIID As GUID, ByVal fPictureOwnsHandle As LongPtr, iPic As IPicture) As LongPtr + +Private Const PICTYPE_BITMAP = 1 + +' Procedure: LoadPictureGDI +' Purpose: Loads an image using GDI+ +' Returns: The image as an IPicture Object +Public Function LoadPictureGDI(ByVal sFileName$) As IPicture + 'Initialize GDI+ + Dim uGdiInput As GdiplusStartupInput + uGdiInput.version_ = 1 + Dim hGdiPlus As LongPtr + Dim lResult&: lResult = GdiplusStartup(hGdiPlus, uGdiInput) + + If lResult <> 0 Then _ + Exit Function + + Dim hGdiImage As LongPtr + If GdipCreateBitmapFromFile(StrPtr(sFileName), hGdiImage) = 0 Then + Dim hBitmap As LongPtr + Call GdipCreateHBITMAPFromBitmap(hGdiImage, hBitmap, 0) + Set LoadPictureGDI = CreateIPicture(hBitmap) + Call GdipDisposeImage(hGdiImage) + End If + + Call GdiplusShutdown(hGdiPlus) +End Function + +' Procedure: CreateIPicture +' Purpose: Converts a image handle into an IPicture object. +' Returns: The IPicture object +Private Function CreateIPicture(ByVal hPic As LongPtr) As IPicture + Dim IID_IDispatch As GUID + With IID_IDispatch + .Data1 = &H7BF80980 + .Data2 = &HBF32 + .Data3 = &H101A + .Data4(0) = &H8B + .Data4(1) = &HBB + .Data4(2) = &H0 + .Data4(3) = &HAA + .Data4(4) = &H0 + .Data4(5) = &H30 + .Data4(6) = &HC + .Data4(7) = &HAB + End With + + Dim uPicInfo As PictDescriptor + With uPicInfo + .size_ = Len(uPicInfo) + .type_ = PICTYPE_BITMAP + .hPic_ = hPic + .hPal_ = 0 + End With + + Dim iPic As IPicture + Call OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, iPic) + Set CreateIPicture = iPic +End Function + diff --git a/api/z_PastePictureAPI.bas b/api/z_PastePictureAPI.bas new file mode 100644 index 0000000..e8b21b2 --- /dev/null +++ b/api/z_PastePictureAPI.bas @@ -0,0 +1,156 @@ +Attribute VB_Name = "z_PastePictureAPI" +'================ Copy/Paste ============= +' Shared module version: 20210228 +' Required reference: OLE Automation + +' PastePicture The entry point for the routine +' CreatePicture Private function to convert a bitmap or metafile handle to an OLE reference +' ErrorDescription Get the error text for an OLE error code +Option Private Module +Option Explicit +Option Compare Text + +' Declare a UDT to store a GUID for the IPicture OLE Interface +Private Type GUID + Data1 As Long + Data2 As Integer + Data3 As Integer + Data4(0 To 7) As Byte +End Type + +' Declare a UDT to store the bitmap information +Private Type PictDescriptor + size_ As LongLong + type_ As LongLong + hPic_ As LongPtr + hPal_ As LongLong +End Type + +'Does the clipboard contain a bitmap/metafile? +Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Integer) As Long + +Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long +Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Integer) As Long +Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long + +'Convert the handle into an OLE IPicture interface. +Private Declare PtrSafe Function OleCreatePictureIndirect Lib "olepro32.dll" (pDesct As PictDescriptor, refID As GUID, ByVal nPicHandle As Long, iPic As IPicture) As Long + +'Create our own copy of the metafile, so it doesn't get wiped out by subsequent clipboard updates. +Private Declare PtrSafe Function CopyEnhMetaFile Lib "gdi32" Alias "CopyEnhMetaFileA" (ByVal hemfSrc As Long, ByVal lpszFile As String) As Long + +'Create our own copy of the bitmap, so it doesn't get wiped out by subsequent clipboard updates. +Private Declare PtrSafe Function CopyImage Lib "user32" (ByVal nHandle As Long, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long + +'The API format types +Private Const CF_BITMAP = 2 +Private Const CF_PALETTE = 9 +Private Const CF_ENHMETAFILE = 14 +Private Const IMAGE_BITMAP = 0 +Private Const LR_COPYRETURNORG = &H4 + +Public Function PastePicture(Optional nType& = xlPicture) As IPicture + 'Convert the type of picture requested from the xl constant to the API constant + Dim lPicType&: lPicType = IIf(nType = xlBitmap, CF_BITMAP, CF_ENHMETAFILE) + + 'Check if the clipboard contains the required format + Dim hPicAvail&: hPicAvail = IsClipboardFormatAvailable(lPicType) + If hPicAvail = 0 Then _ + Exit Function + + Dim cbHandle&: cbHandle = OpenClipboard(0&) + If cbHandle <= 0 Then _ + Exit Function + + 'Get a handle to the image data + Dim hPtr&: hPtr = GetClipboardData(lPicType) + + 'Create our own copy of the image on the clipboard, in the appropriate format. + Dim hCopy& + If lPicType = CF_BITMAP Then + hCopy = CopyImage(hPtr, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG) + Else + hCopy = CopyEnhMetaFile(hPtr, vbNullString) + End If + + 'Release the clipboard to other programs + cbHandle = CloseClipboard + + 'If we got a handle to the image, convert it into a Picture object and return it + If hPtr <> 0 Then _ + Set PastePicture = CreatePicture(hCopy, 0, lPicType) +End Function + +Private Function CreatePicture(ByVal hPic&, ByVal hPal&, ByVal lPicType) As IPicture + 'OLE Picture types + Const PICTYPE_BITMAP = 1 + Const PICTYPE_ENHMETAFILE = 4 + + ' Create the Interface GUID (for the IPicture interface) + Dim IID_IDispatch As GUID + With IID_IDispatch + .Data1 = &H7BF80980 + .Data2 = &HBF32 + .Data3 = &H101A + .Data4(0) = &H8B + .Data4(1) = &HBB + .Data4(2) = &H0 + .Data4(3) = &HAA + .Data4(4) = &H0 + .Data4(5) = &H30 + .Data4(6) = &HC + .Data4(7) = &HAB + End With + + ' Fill uPicInfo with necessary parts. + Dim uPicInfo As PictDescriptor + With uPicInfo + .size_ = Len(uPicInfo) ' Length of structure. + .type_ = IIf(lPicType = CF_BITMAP, PICTYPE_BITMAP, PICTYPE_ENHMETAFILE) ' Type of Picture + .hPic_ = hPic ' Handle to image. + .hPal_ = IIf(lPicType = CF_BITMAP, hPal, 0) ' Handle to palette (if bitmap). + End With + + ' Create the Picture object. + Dim r& + Dim iPic As IPicture + r = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, iPic) + + ' If an error occured, show the description + If r <> 0 Then _ + Debug.Print "Create Picture: " & ErrorDescription(r) + + ' Return the new Picture object. + Set CreatePicture = iPic +End Function + + +Private Function ErrorDescription(nErr&) As String +'OLECreatePictureIndirect return values + Const E_ABORT = &H80004004 + Const E_ACCESSDENIED = &H80070005 + Const E_FAIL = &H80004005 + Const E_HANDLE = &H80070006 + Const E_INVALIDARG = &H80070057 + Const E_NOINTERFACE = &H80004002 + Const E_NOTIMPL = &H80004001 + Const E_OUTOFMEMORY = &H8007000E + Const E_POINTER = &H80004003 + Const E_UNEXPECTED = &H8000FFFF + Const S_OK = &H0 + + Select Case nErr + Case E_ABORT: ErrorDescription = " Aborted" + Case E_ACCESSDENIED: ErrorDescription = " Access Denied" + Case E_FAIL: ErrorDescription = " General Failure" + Case E_HANDLE: ErrorDescription = " Bad/Missing Handle" + Case E_INVALIDARG: ErrorDescription = " Invalid Argument" + Case E_NOINTERFACE: ErrorDescription = " No Interface" + Case E_NOTIMPL: ErrorDescription = " Not Implemented" + Case E_OUTOFMEMORY: ErrorDescription = " Out of Memory" + Case E_POINTER: ErrorDescription = " Invalid Pointer" + Case E_UNEXPECTED: ErrorDescription = " Unknown Error" + Case S_OK: ErrorDescription = " Success!" + End Select +End Function + diff --git a/dev/API_Logger.cls b/dev/API_Logger.cls new file mode 100644 index 0000000..7b5bc81 --- /dev/null +++ b/dev/API_Logger.cls @@ -0,0 +1,96 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_Logger" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ Log functionality ============= +' Shared module version: 20210413 +' Tested in: TestCommons +' Depends on: ex_VBA +' Required reference: Scripting +' Note: log is saved as UTF-16 encoded text file +Option Explicit + +Private fileName_ As String + +Public Function Init(sName$) + If sName = vbNullString Then _ + Exit Function + fileName_ = sName + Call EnsureOutputExists +End Function + +Public Property Get LogFileName() As String + LogFileName = fileName_ +End Property + +Public Function Log(sMsg$) + If fileName_ <> vbNullString Then + Call LogIntoFile(StampTime(sMsg)) + Else + Call LogDefault(StampTime(sMsg)) + End If +End Function + +Public Function Report(sMsg$, Optional nFlags& = 0) + ' TODO: indicate flags + Call Log(sMsg) +End Function + +Public Function Clear() + If fileName_ <> vbNullString Then _ + Call CreateLogFile +End Function + +' ========== +Private Function LogIntoFile(sMsg$) + Const UTF16_MODE = TristateTrue + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(fileName_) Then _ + GoTo CANNOT_ACCESS + + Dim fs As Scripting.TextStream: Set fs = fso.OpenTextFile(fileName_, ForAppending, Format:=UTF16_MODE) + If fs Is Nothing Then _ + GoTo CANNOT_ACCESS + + Call fs.WriteLine(sMsg) + Call fs.Close + Exit Function + +CANNOT_ACCESS: + Call LogDefault(StampTime("Cannot access log")) + Call LogDefault(sMsg) +End Function + +Private Function LogDefault(sMsg$) + Debug.Print sMsg +End Function + +Private Function EnsureOutputExists() + Dim fso As New Scripting.FileSystemObject + If fso.FileExists(fileName_) Then _ + Exit Function + + Call CreateLogFile +End Function + +Private Function StampTime(sMsg$) As String + StampTime = Format(Now(), "yyyy-mm-dd hh:mm:ss") & ": " & sMsg +End Function + +Private Function CreateLogFile() + Dim fso As New Scripting.FileSystemObject + Call EnsureFolderExists(fso.GetParentFolderName(fileName_)) + Dim fs As Scripting.TextStream: Set fs = fso.CreateTextFile(fileName_, Unicode:=True) + If fs Is Nothing Then + Call LogDefault(StampTime("Cannot create log: " & fileName_)) + Else + Call fs.Close + Call Log("Log file created") + End If +End Function diff --git a/dev/API_MockInteraction.cls b/dev/API_MockInteraction.cls new file mode 100644 index 0000000..52269fe --- /dev/null +++ b/dev/API_MockInteraction.cls @@ -0,0 +1,247 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_MockInteraction" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'================ Mock ========================= +' Shared module version: 20210331 +' Depends on: DevTester, API_UserInteraction +' Required reference: +Option Explicit +Implements API_UserInteraction + +Private Enum InteractionType + T_UI_INVALID = 0 + T_UI_MESSAGE + T_UI_QUESTION + T_UI_PROMPT_INPUT + T_UI_PROMPT_FILE + T_UI_FOLLOW_HYPERLINK +End Enum + +Private Type ExpectationInfo + type_ As InteractionType + id_ As Long + response_ As String +End Type + +Private types_ As Collection ' of InteractionType +Private ids_ As Collection ' of Long +Private responses_ As Collection ' of String + +Private Sub Class_Initialize() + Call ResetCollection +End Sub + +Public Function ExpectNoInteraction() + Call ResetExpectations +End Function + +Public Function ExpectMessage(nMsg&) + Call AddExpectation(T_UI_MESSAGE, nMsg) +End Function + +Public Function ExpectQuestion(nMsg&, bAnswerYes As Boolean) + Call AddExpectation(T_UI_QUESTION, nMsg, IIf(bAnswerYes, "YES", "NO")) +End Function + +Public Function ExpectInputPrompt(sResponse$) + Call AddExpectation(T_UI_PROMPT_INPUT, sResponse:=sResponse) +End Function + +Public Function ExpectFilePrompt(sResponse$) + Call AddExpectation(T_UI_PROMPT_FILE, sResponse:=sResponse) +End Function + +Public Function ExpectFollowHyperlink(sLink$) + Call AddExpectation(T_UI_FOLLOW_HYPERLINK, sResponse:=sLink) +End Function + +Public Function ResetExpectations() As Boolean + ResetExpectations = ids_.Count = 0 + If ResetExpectations Then _ + Exit Function + + Dim sMsg$: sMsg = "Unrealized expectations: " + Dim info As ExpectationInfo + Do While ids_.Count <> 0 + sMsg = sMsg & vbNewLine & InfoToString(PopExpectation) + Loop + Call Dev_LogMsg(sMsg) +End Function + +Public Function API_UserInteraction_ShowMessage(nMsg&, ParamArray params() As Variant) + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="ShowMessage " & nMsg) + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_MESSAGE + iActual.id_ = nMsg + iActual.response_ = vbNullString + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) +End Function + +Public Function API_UserInteraction_AskQuestion(nQuestion&, ParamArray params() As Variant) As Boolean + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="AskQuestion " & nQuestion) + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_QUESTION + iActual.id_ = nQuestion + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) + API_UserInteraction_AskQuestion = iExpected.response_ = "YES" +End Function + +Public Function API_UserInteraction_PromptInput( _ + sPrompt$, _ + Optional sTitle$ = vbNullString, _ + Optional sInitial As Variant) As String + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="PromptInput") + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_PROMPT_INPUT + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) + API_UserInteraction_PromptInput = iExpected.response_ +End Function + +Public Function API_UserInteraction_PromptFileFilter( _ + sInitialPath$, sDescription$, sFilter$, _ + Optional sTitle$ = " ", _ + Optional bNewApplication As Boolean = False) As String + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="PromptFileFilter") + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_PROMPT_FILE + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) + API_UserInteraction_PromptFileFilter = iExpected.response_ +End Function + +Public Function API_UserInteraction_PromptFile(sInitialPath$, _ + Optional sTitle$ = " ", _ + Optional cDescriptions As Collection = Nothing, _ + Optional cFilters As Collection = Nothing, _ + Optional bNewApplication As Boolean = False) As String + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="PromptFile") + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_PROMPT_FILE + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) + API_UserInteraction_PromptFile = iExpected.response_ +End Function + +Public Function API_UserInteraction_PromptFolder(sInitialPath$, _ + Optional sTitle$ = " ", _ + Optional bNewApplication As Boolean = False) As String + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="PromptFile") + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_PROMPT_FILE + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) + API_UserInteraction_PromptFolder = iExpected.response_ +End Function + +Public Function API_UserInteraction_FollowHyperlink(oDocument As Object, sAddress$) + Dim iExpected As ExpectationInfo: iExpected = PopExpectation + If iExpected.type_ = T_UI_INVALID Then _ + Call Err.Raise(UNEXPECTED_INTERACTION, Description:="FollowHyperlink") + + Dim iActual As ExpectationInfo + iActual.type_ = T_UI_FOLLOW_HYPERLINK + + If Not CompareInfo(iActual, iExpected) Then _ + Call Dev_LogMsg(Fmt("Unrecognized interaction" & vbNewLine & _ + "Expected: {1}" & vbNewLine & _ + "Actual: {2}", _ + InfoToString(iExpected), InfoToString(iActual))) +End Function + +' ===== +Private Function ResetCollection() + Set ids_ = New Collection + Set types_ = New Collection + Set responses_ = New Collection +End Function + +Private Function AddExpectation(nType As InteractionType, Optional nID& = 0, Optional sResponse$ = vbNullString) + Call types_.Add(nType) + Call ids_.Add(nID) + Call responses_.Add(sResponse) +End Function + +Private Function PopExpectation() As ExpectationInfo + If ids_.Count = 0 Then + PopExpectation.id_ = T_UI_INVALID + Exit Function + End If + + PopExpectation.id_ = ids_.Item(1) + PopExpectation.type_ = types_.Item(1) + PopExpectation.response_ = responses_.Item(1) + + Call ids_.Remove(1) + Call types_.Remove(1) + Call responses_.Remove(1) +End Function + +Private Function InfoToString(target As ExpectationInfo) As String + Select Case target.type_ + Case T_UI_MESSAGE: InfoToString = "Message " & target.id_ + Case T_UI_QUESTION: InfoToString = "Question " & target.id_ + Case T_UI_PROMPT_INPUT: InfoToString = "InputBox with response " & target.response_ + Case T_UI_PROMPT_FILE: InfoToString = "FilePromp with response " & target.response_ + Case T_UI_FOLLOW_HYPERLINK: InfoToString = "Follow hyperlink " & target.response_ + End Select +End Function + +Private Function CompareInfo(val1 As ExpectationInfo, val2 As ExpectationInfo) As Boolean + CompareInfo = False + If val1.id_ <> val2.id_ Then _ + Exit Function + If val1.type_ <> val2.type_ Then _ + Exit Function + CompareInfo = True +End Function diff --git a/dev/API_TestRunner.cls b/dev/API_TestRunner.cls new file mode 100644 index 0000000..5fc2b49 --- /dev/null +++ b/dev/API_TestRunner.cls @@ -0,0 +1,119 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_TestRunner" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'================ VBA tests runner ============= +' Shared module version: 20220410 +' Depends on: DevTester, DevTools, DataTools, API_Logger, CDS_InfoTests, CDS_InfoFunction, API_Timer +' Required reference: Scripting, VBIDE, Word, Excel, Visio +Option Explicit + +Public testsDB_ As Scripting.Dictionary ' Value = Collection of tests +Private target_ As Object ' Wrapper +Private logger_ As API_Logger +Private timer_ As API_Timer + +' Sink object should implement function PushTestResult(sSuite$, sTest$, bResult As Boolean, sMsg$, dDuration as Double) +Private sink_ As Object + +Private Sub Class_Initialize() + Set timer_ = New API_Timer +End Sub + +Private Sub Class_Terminate() + Set target_ = Nothing +End Sub + +Public Function Init(targetWrapper As Object, oLog As API_Logger, Optional oSink As Object = Nothing) + Set target_ = targetWrapper + Set logger_ = oLog + Set sink_ = oSink + Set testsDB_ = New Scripting.Dictionary +End Function + +Public Function ScanTests() As Boolean + On Error GoTo RETURN_FALSE + + Call logger_.Log("Scanning for tests...") + Dim nCount&: nCount = Dev_ScanTests(target_.Document.VBProject, testsDB_) + ScanTests = True + Call logger_.Log("Found tests: " & nCount) + Exit Function + +RETURN_FALSE: + ScanTests = False + Call logger_.Log("Failed to scan tests. Error code " & Err.Number & ":" & Err.Description) +End Function + +Public Function RunAllTests() As CDS_InfoTests + If Not ScanTests Then _ + Exit Function + + Dim iResult As New CDS_InfoTests + Dim sSuite As Variant + Dim tests As Collection + For Each sSuite In testsDB_ + Set tests = testsDB_(sSuite) + Dim sTest As Variant + For Each sTest In tests + Call iResult.AddTest(RunTest(CStr(sSuite), CStr(sTest))) + Next sTest + Next sSuite + + Set RunAllTests = iResult +End Function + +Public Function RunTest(sSuite$, sTest$) As Boolean + RunTest = False + + Call timer_.Start + On Error GoTo CANNOT_RUN + Dim sMsg$: sMsg = RunInternal(sSuite, sTest) + On Error GoTo 0 + + RunTest = sMsg = "OK" + If Not sink_ Is Nothing Then _ + Call sink_.PushTestResult(sSuite, sTest, RunTest, sMsg, timer_.TimeElapsed) + Call logger_.Log(sSuite & "." & sTest & " ... " & sMsg) + + Exit Function +CANNOT_RUN: + Call logger_.Log(sSuite & "." & sTest & " ... " & "Failed to run the test") +End Function + +' ===== +Private Function RunInternal(sSuite$, sTest$) As String + If TypeOf target_.Application Is Visio.Application Then + RunInternal = RunFileIO(sSuite, sTest) + Else + RunInternal = target_.Run("DevTester.Dev_RunTest", sSuite, sTest) + End If +End Function + +Private Function RunFileIO(sSuite$, sTest$) As String + Const UTF16_MODE = TristateTrue + + Dim sFileName$: sFileName = target_.Document.Name & "_IO.txt" + Dim sPath$: sPath = target_.Document.Path & sFileName + + Dim fso As New Scripting.FileSystemObject + If fso.FileExists(sPath) Then _ + Call fso.DeleteFile(sPath) + + Call target_.Run("Dev_RunTestFileIO", sSuite, sTest, sFileName) + + If Not fso.FileExists(sPath) Then + Call logger_.Log(sSuite & "." & sTest & " ... " & "Failed to run the test") + Exit Function + End If + + Dim fs As Scripting.TextStream: Set fs = fso.OpenTextFile(sPath, ForReading, Format:=UTF16_MODE) + RunFileIO = fs.ReadAll + Call fs.Close + Call fso.DeleteFile(sPath) +End Function diff --git a/dev/CDS_InfoFunction.cls b/dev/CDS_InfoFunction.cls new file mode 100644 index 0000000..87ccc60 --- /dev/null +++ b/dev/CDS_InfoFunction.cls @@ -0,0 +1,16 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "CDS_InfoFunction" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'============ Function info ============ +' Shared module version: 20210220 +Option Explicit + +Public name_ As String +Public type_ As VBIDE.vbext_ProcKind +Public isPublic_ As Boolean diff --git a/dev/CDS_InfoTests.cls b/dev/CDS_InfoTests.cls new file mode 100644 index 0000000..afec3aa --- /dev/null +++ b/dev/CDS_InfoTests.cls @@ -0,0 +1,33 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "CDS_InfoTests" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'================ Tests summary ============ +' Shared module version: 20210306 +Option Explicit + +Public count_ As Long +Public success_ As Long +Public failed_ As Long + +Public Function AddTest(bResult As Boolean) + count_ = count_ + 1 + If bResult Then + success_ = success_ + 1 + Else + failed_ = failed_ + 1 + End If +End Function + +Public Function MergeStats(iSource As CDS_InfoTests) + If iSource Is Nothing Then _ + Exit Function + count_ = count_ + iSource.count_ + success_ = success_ + iSource.success_ + failed_ = failed_ + iSource.failed_ +End Function diff --git a/dev/DevTester.bas b/dev/DevTester.bas new file mode 100644 index 0000000..3ce9104 --- /dev/null +++ b/dev/DevTester.bas @@ -0,0 +1,679 @@ +Attribute VB_Name = "DevTester" +'================ Developer Testing mechanics ============= +' Shared module version: 20220614 +' Depends on: ex_VBA +' Required reference: Microsoft Scripting Runtime +Option Private Module +Option Explicit + +Private Const ENUM_PARAM_CLASS_MODULE = 2 +Private Const ENUM_PARAM_PROCEDURE = 0 + +Public Enum TTestErrors + ASSERT_FAIL = vbObjectError + 1 + UNEXPECTED_INTERACTION +End Enum + +Private g_TestLog As String +Private g_TestCase As String +Private g_TestClause As String + +Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal nMilliseconds As LongPtr) + +' Run this procedure to add new test suite +Public Sub RunAddTestSuite() + Dim iProject As Object: Set iProject = GetVBProject + + Dim sName$: sName = InputBox("Input suite name s_Suite") + If sName = vbNullString Then _ + Exit Sub + If Not sName Like "s_*" Then + Call MsgBox("Invalid suite name", vbExclamation) + Exit Sub + End If + + Dim aComponent As Object: Set aComponent = FindVBComponent(iProject, sName) + If Not aComponent Is Nothing Then + Call MsgBox("Test suite already exists", vbExclamation) + Exit Sub + End If + + Dim iNewComponent As Object + Set iNewComponent = iProject.VBComponents.Add(ENUM_PARAM_CLASS_MODULE) + iNewComponent.Name = sName + Call iNewComponent.CodeModule.AddFromString(DefaultTestCode) + + Dim iHelper As Object: Set iHelper = FindVBComponent(iProject, "DevHelper").CodeModule + Dim nStart&: nStart = iHelper.ProcStartLine("Dev_GetTestSuite", ENUM_PARAM_PROCEDURE) + Call iHelper.InsertLines(nStart + 3, GetTestSuiteLine(sName)) +End Sub + +Public Function Dev_LogMsg(sMsg$) + If g_TestLog <> vbNullString Then _ + g_TestLog = g_TestLog & vbNewLine + + If g_TestCase <> vbNullString Then + g_TestLog = g_TestLog & "Test case: " & g_TestCase & vbNewLine + g_TestCase = vbNullString + End If + + If g_TestClause <> vbNullString Then + g_TestLog = g_TestLog & "Test clause: " & g_TestClause & vbNewLine + g_TestClause = vbNullString + End If + + g_TestLog = g_TestLog & sMsg +End Function + +Public Function Dev_LogError(nErrID&, Optional sDescription$ = vbNullString) + Select Case nErrID + Case 0 To 512: Call Dev_LogMsg("System error " & nErrID & vbNewLine & sDescription) + Case ASSERT_FAIL: Call Dev_LogMsg("Assertion failed... Aborting test") + Case UNEXPECTED_INTERACTION: Call Dev_LogMsg("Unexpected user interaction ... Aborting test") + Case Else: Call Dev_LogMsg("Unknown error type " & nErrID & vbNewLine & sDescription) + End Select +End Function + +Public Function Dev_RunTestFileIO(sSuite$, sTest$, sOutputFile$) + Const UTF16_MODE = TristateTrue + + Dim sResult$: sResult = Dev_RunTest(sSuite, sTest) + Dim sPath$: sPath = Application.ActiveDocument.Path & sOutputFile + + Dim fso As New Scripting.FileSystemObject + Dim fs As Scripting.TextStream: Set fs = fso.OpenTextFile(sPath, ForWriting, Create:=True, Format:=UTF16_MODE) + + Call fs.Write(sResult) + Call fs.Close +End Function + +Public Function Dev_RunTest(sSuite$, sTest$) As String + Dim iSuite As Object: Set iSuite = Dev_GetTestSuite(sSuite) + If iSuite Is Nothing Then + Dev_RunTest = "Invalid test suite" + Exit Function + End If + + Call Randomize + Call ResetLog + Call ResetDescription + + On Error GoTo ON_ERROR + Call iSuite.Setup + Call CallByName(iSuite, sTest, VbMethod) + Call iSuite.Teardown + On Error GoTo 0 + + Dev_RunTest = IIf(g_TestLog = vbNullString, "OK", "FAILED" & vbNewLine & g_TestLog) + Exit Function + +ON_ERROR: + Call Dev_LogError(Err.Number, Err.Description) + Dev_RunTest = "FAILED" & vbNewLine & g_TestLog + Set iSuite = Nothing +End Function + +Public Function Dev_RunTestDebug(sSuite$, sTest$) As String + Dim iSuite As Object: Set iSuite = Dev_GetTestSuite(sSuite) + If iSuite Is Nothing Then + Dev_RunTestDebug = "Invalid test suite" + Exit Function + End If + + Call ResetLog + Call ResetDescription + + Call iSuite.Setup + Call CallByName(iSuite, sTest, VbMethod) + Call iSuite.Teardown + + Dev_RunTestDebug = IIf(g_TestLog = vbNullString, "OK", "FAILED" & vbNewLine & g_TestLog) +End Function + +' ====== Tester functions ======== +Public Function Dev_NewCase(sDescription$) + Call ResetDescription + g_TestCase = sDescription +End Function + +Public Function Dev_NewClause(sDescription$) + g_TestClause = sDescription +End Function + +Public Function Dev_ExpectObjectValid(oVal As Object, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectObjectValid = TestObjectValid(oVal) + If Not Dev_ExpectObjectValid Then _ + Call Dev_LogMsg("Expected valid object") + Call ResetClause +End Function + +Public Function Dev_ExpectObjectInvalid(oVal As Object, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectObjectInvalid = Not TestObjectValid(oVal) + If Not Dev_ExpectObjectInvalid Then _ + Call Dev_LogMsg("Expected invalid object") + Call ResetClause +End Function + +Public Function Dev_ExpectNothing(oVal As Object, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectNothing = oVal Is Nothing + If Not Dev_ExpectNothing Then _ + Call Dev_LogMsg("Expected Nothing") + Call ResetClause +End Function + +Public Function Dev_ExpectNotNothing(oVal As Object, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectNotNothing = Not oVal Is Nothing + If Not Dev_ExpectNotNothing Then _ + Call Dev_LogMsg("Expected not Nothing") + Call ResetClause +End Function + +Public Function Dev_ExpectTrue(bVal As Boolean, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectTrue = bVal = True + If Not Dev_ExpectTrue Then _ + Call Dev_LogMsg("Expected TRUE statement") + Call ResetClause +End Function + +Public Function Dev_ExpectFalse(bVal As Boolean, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectFalse = bVal = False + If Not Dev_ExpectFalse Then _ + Call Dev_LogMsg("Expected FALSE statement") + Call ResetClause +End Function + +Public Function Dev_ExpectEQ(actualValue As Variant, expectedValue As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectEQ = CompareDeep(expectedValue, actualValue) = 0 + On Error GoTo 0 + + If Not Dev_ExpectEQ Then _ + Call Dev_LogMsg(Fmt("Actual: {1} | Expectedl: {2}", actualValue, expectedValue)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectEQ = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} == {2}", actualValue, expectedValue)) +End Function + +Public Function Dev_ExpectNE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectNE = CompareDeep(val1, val2) <> 0 + On Error GoTo 0 + + If Not Dev_ExpectNE Then _ + Call Dev_LogMsg(Fmt("Unexpected equality: {1} == {2}", val1, val2)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectNE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} <> {2}", val1, val2)) +End Function + +Public Function Dev_ExpectGR(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectGR = CompareDeep(val1, val2) > 0 + On Error GoTo 0 + + If Not Dev_ExpectGR Then _ + Call Dev_LogMsg(Fmt("Expected: {1} > {2}", val1, val2)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectGR = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} > {2}", val1, val2)) +End Function + +Public Function Dev_ExpectLS(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectLS = CompareDeep(val1, val2) < 0 + On Error GoTo 0 + + If Not Dev_ExpectLS Then _ + Call Dev_LogMsg(Fmt("Expected: {1} < {2}", val1, val2)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectLS = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} < {2}", val1, val2)) +End Function + +Public Function Dev_ExpectGE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectGE = CompareDeep(val1, val2) >= 0 + On Error GoTo 0 + + If Not Dev_ExpectGE Then _ + Call Dev_LogMsg(Fmt("Expected: {1} >= {2}", val1, val2)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectGE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} >= {2}", val1, val2)) +End Function + +Public Function Dev_ExpectLE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectLE = CompareDeep(val1, val2) <= 0 + On Error GoTo 0 + + If Not Dev_ExpectLE Then _ + Call Dev_LogMsg(Fmt("Expected: {1} <= {2}", val1, val2)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectLE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} <= {2}", val1, val2)) +End Function + +Public Function Dev_ExpectAEQ(actualValue As Variant, expectedValue As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectAEQ = CompareApproximate(actualValue, expectedValue, nPrecision) = 0 + On Error GoTo 0 + + If Not Dev_ExpectAEQ Then _ + Call Dev_LogMsg(Fmt("Actual: {1} | Expectedl: {2} | Precision: {3}", actualValue, expectedValue, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectAEQ = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} == {2}, precision {3}", actualValue, expectedValue, nPrecision)) +End Function + +Public Function Dev_ExpectANE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectANE = CompareApproximate(val1, val2, nPrecision) <> 0 + On Error GoTo 0 + + If Not Dev_ExpectANE Then _ + Call Dev_LogMsg(Fmt("Unexpected equality: {1} == {2}, precision: {3}", val1, val2, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectANE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} <> {2}, precision {3}", val1, val2, nPrecision)) +End Function + +Public Function Dev_ExpectAGR(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectAGR = CompareApproximate(val1, val2, nPrecision) > 0 + On Error GoTo 0 + + If Not Dev_ExpectAGR Then _ + Call Dev_LogMsg(Fmt("Expected: {1} > {2}, precision {3}", val1, val2, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectAGR = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} > {2}, precision {3}", val1, val2, nPrecision)) +End Function + +Public Function Dev_ExpectALS(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectALS = CompareApproximate(val1, val2, nPrecision) < 0 + On Error GoTo 0 + + If Not Dev_ExpectALS Then _ + Call Dev_LogMsg(Fmt("Expected: {1} < {2}, precision {3}", val1, val2, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectALS = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} < {2}, precision {3}", val1, val2, nPrecision)) +End Function + +Public Function Dev_ExpectAGE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectAGE = CompareApproximate(val1, val2, nPrecision) >= 0 + On Error GoTo 0 + + If Not Dev_ExpectAGE Then _ + Call Dev_LogMsg(Fmt("Expected: {1} >= {2}, precision {3}", val1, val2, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectAGE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} >= {2}, precision {3}", val1, val2, nPrecision)) +End Function + +Public Function Dev_ExpectALE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + On Error GoTo INCOMPARABLE + Dev_ExpectALE = CompareApproximate(val1, val2, nPrecision) <= 0 + On Error GoTo 0 + + If Not Dev_ExpectALE Then _ + Call Dev_LogMsg(Fmt("Expected: {1} <= {2}, precision {3}", val1, val2, nPrecision)) + Call ResetClause + Exit Function + +INCOMPARABLE: + Dev_ExpectALE = False + Call Dev_LogMsg(Fmt("Invalid comparison: {1} <= {2}, precision {3}", val1, val2, nPrecision)) +End Function + +' ========= String comparisons =========== +Public Function Dev_ExpectLike(sValue$, sMask$, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + Dev_ExpectLike = sValue Like sMask + If Not Dev_ExpectLike Then _ + Call Dev_LogMsg(Fmt("Expected: {1} Like {2}", sValue, sMask)) + Call ResetClause +End Function + +Public Function Dev_ExpectNotLike(sValue$, sMask$, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + Dev_ExpectNotLike = Not sValue Like sMask + If Not Dev_ExpectNotLike Then _ + Call Dev_LogMsg(Fmt("Expected: {1} not Like {2}", sValue, sMask)) + Call ResetClause +End Function + +' ======= Error comparisons =========== +Public Function Dev_ExpectAnyError(Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + Dev_ExpectAnyError = Err.Number <> 0 + If Not Dev_ExpectAnyError Then _ + Call Dev_LogMsg("Expected any Error, but got nothing") + Call ResetClause +End Function + +Public Function Dev_ExpectError(nError&, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + Dev_ExpectError = Err.Number = nError + If Err.Number = 0 Then + Call Dev_LogMsg(Fmt("Expected Error #{1}, but got no error", nError)) + ElseIf Not Dev_ExpectError Then + Call Dev_LogMsg(Fmt("Expected Error #{1}, actual Error #{2}", nError, Err.Number)) + End If + + Call Err.Clear + Call ResetClause +End Function + +Public Function Dev_ExpectErrorMsg(sDesc$, Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + Dev_ExpectErrorMsg = Err.Description = sDesc + If Err.Number = 0 Then + Call Dev_LogMsg(Fmt("Expected Error, but got no error")) + ElseIf Not Dev_ExpectErrorMsg Then + Call Dev_LogMsg(Fmt("Expected error msg: #{1}" & vbNewLine & "Actual error message: #{2}", sDesc, Err.Description)) + End If + + Call Err.Clear + Call ResetClause +End Function + +Public Function Dev_ExpectNoError(Optional sClause$ = vbNullString) As Boolean + If sClause <> vbNullString Then _ + Call Dev_NewClause(sClause) + + Dev_ExpectNoError = Err.Number = 0 + If Not Dev_ExpectNoError Then _ + Call Dev_LogMsg(Fmt("Expected no Error, but got Error #{1}", Err.Number)) + + Call Err.Clear + Call ResetClause +End Function + +' =========== Assertion proxies ========== +Public Function Dev_AssertObjectValid(oVal As Object, Optional sClause$ = vbNullString) + If Not Dev_ExpectObjectValid(oVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertObjectInvalid(oVal As Object, Optional sClause$ = vbNullString) + If Not Dev_ExpectObjectInvalid(oVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertNothing(oVal As Object, Optional sClause$ = vbNullString) + If Not Dev_ExpectNothing(oVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertNotNothing(oVal As Object, Optional sClause$ = vbNullString) + If Not Dev_ExpectNotNothing(oVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertTrue(bVal As Boolean, Optional sClause$ = vbNullString) + If Not Dev_ExpectTrue(bVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertFalse(bVal As Boolean, Optional sClause$ = vbNullString) + If Not Dev_ExpectFalse(bVal, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertEQ(expectedValue As Variant, actualValue As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectEQ(expectedValue, actualValue, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertNE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectNE(val1, val2, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertGR(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectGR(val1, val2, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertLS(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectLS(val1, val2, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertGE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectGE(val1, val2, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertLE(val1 As Variant, val2 As Variant, Optional sClause$ = vbNullString) + If Not Dev_ExpectLE(val1, val2, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertAEQ(expectedValue As Variant, actualValue As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectAEQ(expectedValue, actualValue, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertANE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectANE(val1, val2, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertAGR(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectAGR(val1, val2, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertALS(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectALS(val1, val2, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertAGE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectAGE(val1, val2, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertALE(val1 As Variant, val2 As Variant, nPrecision&, Optional sClause$ = vbNullString) + If Not Dev_ExpectALE(val1, val2, nPrecision, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertLike(sValue$, sMask$, Optional sClause$ = vbNullString) As Boolean + If Not Dev_ExpectLike(sValue, sMask, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertNotLike(sValue$, sMask$, Optional sClause$ = vbNullString) As Boolean + If Not Dev_ExpectNotLike(sValue, sMask, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertAnyError(Optional sClause$ = vbNullString) + If Not Dev_ExpectAnyError(sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertError(nError&, Optional sClause$ = vbNullString) As Boolean + If Not Dev_ExpectError(nError, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertErrorMsg(sDesc$, Optional sClause$ = vbNullString) As Boolean + If Not Dev_ExpectErrorMsg(sDesc, sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +Public Function Dev_AssertNoError(Optional sClause$ = vbNullString) As Boolean + If Not Dev_ExpectNoError(sClause) Then _ + Call Err.Raise(ASSERT_FAIL) +End Function + +' ======== +Private Function ResetLog() + g_TestLog = vbNullString +End Function + +Private Function ResetDescription() + g_TestCase = vbNullString + g_TestClause = vbNullString +End Function + +Private Function ResetClause() + g_TestClause = vbNullString +End Function + +Private Function TestObjectValid(target As Object) As Boolean + Call Sleep(100) + + On Error Resume Next + Dim sName$: sName = target.Name + Select Case Err.Number + ' no error + Case 0: TestObjectValid = True + + ' object doesnt not support property + Case 438: TestObjectValid = True + + Case Else: TestObjectValid = False + End Select +End Function + +Private Function GetVBProject() As Object + Dim iApplication As Object: Set iApplication = Application + If Application.Name = "Microsoft Excel" Then + Set GetVBProject = iApplication.ThisWorkbook.VBProject + Else + Set GetVBProject = iApplication.VBE.ActiveVBProject + End If +End Function + +Private Function FindVBComponent(iProject As Object, sName$) As Object + Dim aComponent As Object + For Each aComponent In iProject.VBComponents + If aComponent.Name = sName Then + Set FindVBComponent = aComponent + Exit Function + End If + Next aComponent +End Function + +Private Function DefaultTestCode() As String + DefaultTestCode = _ + "Public Function Setup()" & vbNewLine & _ + " ' Mandatory setup function" & vbNewLine & _ + "End Function" & vbNewLine & _ + vbNewLine & _ + "Public Function Teardown()" & vbNewLine & _ + " ' Mandatory teardown function" & vbNewLine & _ + "End Function" & vbNewLine & _ + vbNewLine & _ + "Public Function t_Init()" & vbNewLine & _ + " On Error GoTo PROPAGATE_ERROR" & vbNewLine & _ + vbNewLine & _ + " Exit Function" & vbNewLine & _ + "PROPAGATE_ERROR:" & vbNewLine & _ + " Call Dev_LogError(Err.Number, Err.Description)" & vbNewLine & _ + "End Function" +End Function + +Private Function GetTestSuiteLine(sName$) As String + GetTestSuiteLine = _ + " Case " & """" & sName & """" & ": " & _ + "Set Dev_GetTestSuite = New " & sName +End Function diff --git a/dev/DevTesterUI.bas b/dev/DevTesterUI.bas new file mode 100644 index 0000000..4ffdc23 --- /dev/null +++ b/dev/DevTesterUI.bas @@ -0,0 +1,21 @@ +Attribute VB_Name = "DevTesterUI" +'================ Developer Testing mechanics for UI testing ============= +' Shared module version: 20210401 +' Depends on: API_MockInteraction, API_UserInteraction, z_UIMessages +' Required reference: +Option Private Module +Option Explicit + +Private g_MockUI As API_MockInteraction + +Public Function Dev_MockUI() As API_MockInteraction + If g_MockUI Is Nothing Then _ + Set g_MockUI = New API_MockInteraction + Call SetUserInteraction(g_MockUI) + Set Dev_MockUI = g_MockUI +End Function + +Public Function Dev_ResetUI() As Boolean + Dev_ResetUI = g_MockUI.ResetExpectations + Call SetUserInteraction(New API_UserInteraction) +End Function diff --git a/dev/DevTools.bas b/dev/DevTools.bas new file mode 100644 index 0000000..3674087 --- /dev/null +++ b/dev/DevTools.bas @@ -0,0 +1,194 @@ +Attribute VB_Name = "DevTools" +'================ Developer Tools ============= +' Shared module version: 20220811 +' Depends on: ex_WinAPI +' Required reference: Scripting, VBIDE +Option Private Module +Option Explicit + +Private Const TEST_SUITE_MASK = "s_*" +Private Const TEST_NAME_MASK = "t_*" +Private Const COMPILATION_DELAY As Long = 250 ' ms + +Public Function CompileVBProject(target As VBIDE.VBProject) As Boolean + Const CONTROL_ID_COMPILE = 578 + Const MSGBOX_CLASS_ID As String = "#32770" + Const MSGBOX_NAME As String = "Microsoft Visual Basic for Applications" + Const WINDOW_MESSAGE_CLOSE As Long = &H10 + + target.VBE.MainWindow.Visible = True + With target.VBComponents(1).CodeModule + Call .CodePane.Show + Call .InsertLines(1, "123") + Call .DeleteLines(1, 1) + End With + + Dim btnCompile As CommandBarControl: Set btnCompile = target.VBE.CommandBars.FindControl(ID:=CONTROL_ID_COMPILE) + If Not btnCompile.Enabled Then + CompileVBProject = False + Exit Function + End If + + Call btnCompile.Execute + + Call Sleep(COMPILATION_DELAY) + + Dim nHwnd&: nHwnd = FindWindow(MSGBOX_CLASS_ID, MSGBOX_NAME) + CompileVBProject = nHwnd = 0 + If Not CompileVBProject Then _ + Call PostMessage(nHwnd, WINDOW_MESSAGE_CLOSE, 0&, 0&) +End Function + +Public Function UpdateVersionStamp(target As VBIDE.VBProject, sVersion$) As Boolean + UpdateVersionStamp = False + + On Error Resume Next + Dim iModule As VBComponent: Set iModule = target.VBComponents("Main") + If iModule Is Nothing Then _ + Exit Function + + Dim iCode As CodeModule: Set iCode = iModule.CodeModule + + Dim nLineStart&: nLineStart = 1 + Dim nLineEnd&: nLineEnd = iCode.CountOfLines + + Dim nColumnStart&: nColumnStart = 1 + Dim nColumnEnd&: nColumnEnd = 255 + + If Not iCode.Find("Public Const PRODUCT_VERSION", nLineStart, nColumnStart, nLineEnd, nColumnEnd) Then _ + Exit Function + Call iCode.ReplaceLine(nLineEnd, "Public Const PRODUCT_VERSION = """ & sVersion & """") + + UpdateVersionStamp = True +End Function + +Public Function PrepareSkeletonFor(target As Object) + On Error Resume Next + Call target.Application.Run("'" & target.Name & "'" & "!Dev_PrepareSkeleton") +End Function + +Public Function ExtractProperties(sLines() As String, ByRef nCurrent&, ByRef oSink As Scripting.Dictionary) + ExtractProperties = False + + Do While nCurrent <= UBound(sLines, 1) + Dim sLine$: sLine = sLines(nCurrent) + nCurrent = nCurrent + 1 + If sLine Like "%%*" Then _ + Exit Do + + Dim nSeparator&: nSeparator = VBA.InStr(1, sLine, "=") + If nSeparator = 0 Then _ + GoTo NEXT_LINE + If VBA.Left(sLine, 1) = "#" Then _ + GoTo NEXT_LINE + + Dim sKey$: sKey = VBA.Trim(VBA.Left(sLine, nSeparator - 1)) + Dim sValue$: sValue = VBA.Trim(Right(sLine, VBA.Len(sLine) - nSeparator)) + If oSink.Exists(sKey) Then _ + Exit Function + Call oSink.Add(sKey, sValue) +NEXT_LINE: + Loop + + ExtractProperties = True +End Function + +Public Function Dev_CountPublicAPI(target As VBIDE.CodeModule) As Long + Dim nLine& + For nLine = 1 To target.CountOfLines Step 1 + If InStr(1, target.Lines(nLine, 1), "Public") <> 0 Then _ + Dev_CountPublicAPI = Dev_CountPublicAPI + 1 + Next nLine +End Function + +Public Function Dev_ScanFunctions(target As VBIDE.CodeModule) As Collection ' of CDS_InfoFunction + Dim cResult As New Collection + + Dim currentFunc As CDS_InfoFunction + Dim lastFunc As New CDS_InfoFunction + Dim nLine& + For nLine = 1 To target.CountOfLines Step 1 + Set currentFunc = ExtractProcedure(target, nLine) + If lastFunc.name_ <> currentFunc.name_ And currentFunc.name_ <> vbNullString Then + currentFunc.isPublic_ = IsPublicFunc(target, currentFunc) + Call cResult.Add(currentFunc) + End If + Set lastFunc = currentFunc + Next nLine + + Set Dev_ScanFunctions = cResult +End Function + +Public Function Dev_ScanTests(target As VBIDE.VBProject, ByRef cTestsDB As Scripting.Dictionary) As Long + Dim nCount&: nCount = 0 + Dim aComponent As VBIDE.VBComponent + Dim iComponents As VBIDE.VBComponents: Set iComponents = target.VBComponents + Dim tests As Collection + For Each aComponent In iComponents + If aComponent.Type <> vbext_ct_ClassModule Then _ + GoTo NEXT_COMPONENT + If Not aComponent.Name Like TEST_SUITE_MASK Then _ + GoTo NEXT_COMPONENT + + Set tests = New Collection + Dim funcs As Collection: Set funcs = Dev_ScanFunctions(aComponent.CodeModule) + Dim aFunc As CDS_InfoFunction + For Each aFunc In funcs + If aFunc.name_ Like TEST_NAME_MASK And aFunc.isPublic_ Then _ + Call tests.Add(aFunc.name_) + Next aFunc + + If tests.Count > 0 Then + Call cTestsDB.Add(aComponent.Name, tests) + nCount = nCount + tests.Count + End If +NEXT_COMPONENT: + Next aComponent + + Set iComponents = Nothing + Set aComponent = Nothing + + Dev_ScanTests = nCount +End Function + +Public Function Dev_RemoveDebugCode(target As VBIDE.VBProject) + Dim selectedComps As New Collection + Dim aComponent As VBIDE.VBComponent + For Each aComponent In target.VBComponents + If Dev_IsTestingModule(aComponent.Name) Then _ + Call selectedComps.Add(aComponent) + Next aComponent + + For Each aComponent In selectedComps + Call target.VBComponents.Remove(aComponent) + Next aComponent +End Function + +Public Function Dev_IsTestingModule(sName$) As Boolean + Dev_IsTestingModule = True + + If sName Like TEST_SUITE_MASK Then _ + Exit Function + If sName Like "DevHelper*" Then _ + Exit Function + If sName Like "DevTester*" Then _ + Exit Function + If sName Like "DevTesterUI*" Then _ + Exit Function + If sName Like "API_MockInteraction*" Then _ + Exit Function + + Dev_IsTestingModule = False +End Function + +' ===== +Private Function ExtractProcedure(target As VBIDE.CodeModule, nLine&) As CDS_InfoFunction + Set ExtractProcedure = New CDS_InfoFunction + ExtractProcedure.name_ = target.ProcOfLine(nLine, ExtractProcedure.type_) +End Function + +Private Function IsPublicFunc(target As VBIDE.CodeModule, func As CDS_InfoFunction) As Boolean + Dim nStart&: nStart = target.ProcBodyLine(func.name_, func.type_) + Dim sTxt$: sTxt = target.Lines(nStart, 1) + IsPublicFunc = InStr(1, sTxt, "Private") = 0 +End Function diff --git a/dev/ex_ConceptOrganization.bas b/dev/ex_ConceptOrganization.bas new file mode 100644 index 0000000..3f3453d --- /dev/null +++ b/dev/ex_ConceptOrganization.bas @@ -0,0 +1,84 @@ +Attribute VB_Name = "ex_ConceptOrganization" +' ======== Concept organization specific functionality ============ +' Shared module version: 20221031 +' Tested in: +' Depends on: +' Required reference: Scripting +Option Private Module +Option Explicit + +Public Enum TProjectType + T_PRJ_LEAD_ACTIVE = 1 + T_PRJ_LEAD_TENTATIVE = 2 + T_PRJ_OUTCOME = 3 + T_PRJ_INCOME = 4 +End Enum + +Public Enum TProjectCategory + T_PCAT_ERR = 0 + T_PCAT_MISC = 1 + T_PCAT_OUTCOME = 2 + T_PCAT_INCOME = 3 +End Enum + +Public Const SERVER_PATH_INCOME = "\\fs1.concept.ru\projects\01 Income-" +Public Const SERVER_PATH_OUTCOME = "\\fs1.concept.ru\projects\02 Outcome-" +Public Const SERVER_PATH_LEAD_ACTIVE = "\\fs1.concept.ru\projects\03 " +Public Const SERVER_PATH_LEAD_TENTATIVE = "\\fs1.concept.ru\projects\03 1 " + +Public Function CCListProjects(nType As TProjectType) As Scripting.Dictionary + Dim iProjects As New Scripting.Dictionary + Set CCListProjects = iProjects + + Dim sFolder$: sFolder = GetProjectsFolder(nType) + Dim fso As New Scripting.FileSystemObject + If Not fso.FolderExists(sFolder) Then _ + Exit Function + + Dim iSubFolder As Scripting.Folder + For Each iSubFolder In fso.GetFolder(sFolder).SubFolders + If CCTestProjectName(iSubFolder.Name) Then _ + Call iProjects.Add(iSubFolder.Name, ProjectTypeToCategory(nType)) + Next iSubFolder +End Function + +Public Function CCTestProjectName(sProject$) As Boolean + CCTestProjectName = sProject Like "####*" +End Function + +Public Function CCCategoryToString(nType As TProjectCategory) As String + Select Case nType + Case T_PCAT_ERR: CCCategoryToString = "ERR" + Case T_PCAT_INCOME: CCCategoryToString = "I" + Case T_PCAT_OUTCOME: CCCategoryToString = "O" + Case T_PCAT_MISC: CCCategoryToString = "-" + End Select +End Function + +Public Function CCStringToCategory(sType$) As TProjectCategory + Select Case sType + Case "I": CCStringToCategory = T_PCAT_INCOME + Case "O": CCStringToCategory = T_PCAT_OUTCOME + Case "-": CCStringToCategory = T_PCAT_MISC + Case Else: CCStringToCategory = T_PCAT_ERR + End Select +End Function + +' ============ +Private Function GetProjectsFolder(nType As TProjectType) As String + Select Case nType + Case T_PRJ_INCOME: GetProjectsFolder = SERVER_PATH_INCOME + Case T_PRJ_OUTCOME: GetProjectsFolder = SERVER_PATH_OUTCOME + Case T_PRJ_LEAD_ACTIVE: GetProjectsFolder = SERVER_PATH_LEAD_ACTIVE + Case T_PRJ_LEAD_TENTATIVE: GetProjectsFolder = SERVER_PATH_LEAD_TENTATIVE + End Select +End Function + +Private Function ProjectTypeToCategory(nType As TProjectCategory) As TProjectCategory + Select Case nType + Case T_PRJ_INCOME: ProjectTypeToCategory = T_PCAT_INCOME + Case T_PRJ_OUTCOME: ProjectTypeToCategory = T_PCAT_OUTCOME + Case T_PRJ_LEAD_ACTIVE: ProjectTypeToCategory = T_PCAT_OUTCOME + Case T_PRJ_LEAD_TENTATIVE: ProjectTypeToCategory = T_PCAT_OUTCOME + End Select +End Function diff --git a/excel/API_XLRecordsWrapper.cls b/excel/API_XLRecordsWrapper.cls new file mode 100644 index 0000000..74d5eec --- /dev/null +++ b/excel/API_XLRecordsWrapper.cls @@ -0,0 +1,49 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "API_XLRecordsWrapper" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ==== Excel ========= +' Shared module version: 20210329 +' Depends on: +' Required reference: +' Prerequisites: first column should contain data with no empty cells +Option Explicit + +Private data_ As Excel.Worksheet +Private row_ As Long +Private start_ As Long + +Public Function Init(ws As Excel.Worksheet, Optional nStart& = 2) + start_ = nStart + Set data_ = ws + Call MoveStart +End Function + +Public Function MoveStart() + row_ = start_ +End Function + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, 1) = vbNullString +End Function + +Public Function MoveNext() As Boolean + MoveNext = Not IsDone + If MoveNext Then _ + row_ = row_ + 1 +End Function + +Public Function MovePrev() As Boolean + MovePrev = row_ > start_ + If MovePrev Then _ + row_ = row_ - 1 +End Function + +Public Function Field(nColumn&) As Excel.Range + Set Field = data_.Cells(row_, nColumn) +End Function diff --git a/excel/ex_Excel.bas b/excel/ex_Excel.bas new file mode 100644 index 0000000..bc1fd95 --- /dev/null +++ b/excel/ex_Excel.bas @@ -0,0 +1,151 @@ +Attribute VB_Name = "ex_Excel" +'================ Excel ============= +' Shared module version: 20220623 +' Tested in: +' Depends on: +' Required reference: Scripting +Option Private Module +Option Explicit + +Public Const XLUTIL_GRAPH_PADDING = 0.1 +Public Const XL_DEFAULT_FIRST_ROW = 2 +Public Const XL_INVALID_ROW = -1 + +Private Const XL_INFINITY_THRESHOLD = 255 + +Public Function XLFirstEmptyCell(iRange As Excel.Range) As Long + Dim nItem&: nItem = 1 + Dim iCell As Excel.Range + For Each iCell In iRange + If iCell = vbNullString Then + XLFirstEmptyCell = nItem + Exit Function + End If + nItem = nItem + 1 + Next iCell + + XLFirstEmptyCell = -1 +End Function + +Public Function XLGetCallerCell() As Excel.Range + On Error GoTo RETURN_NOTHING + If TypeName(Application.Caller) = "Range" Then + Set XLGetCallerCell = Application.Caller + Exit Function + End If + +RETURN_NOTHING: + On Error GoTo 0 + Set XLGetCallerCell = Nothing +End Function + +Public Function XLWorksheetExists(sName$, iWhere As Excel.Workbook) As Boolean + On Error Resume Next + Dim iSheet As Excel.Worksheet + Set iSheet = iWhere.Sheets(sName) + On Error GoTo 0 + XLWorksheetExists = Not iSheet Is Nothing +End Function + +Public Function XLShowAllData(target As Excel.Worksheet, Optional bKeepRows As Boolean = False, Optional bKeepColumns As Boolean = False) + On Error Resume Next + Call target.ShowAllData + On Error GoTo 0 + + If Not bKeepRows Then _ + target.Rows.EntireRow.Hidden = False + If Not bKeepColumns Then _ + target.Columns.EntireColumn.Hidden = False +End Function + +Public Function XLUpdateHyperlink(iCellAnchor As Excel.Range, sAddress$, Optional sSubAddress$ = "") + Call iCellAnchor.Hyperlinks.Delete + If sAddress = vbNullString Then _ + Exit Function + Call iCellAnchor.Hyperlinks.Add(iCellAnchor, sAddress, sSubAddress) +End Function + +Public Function AutoScaleGraphAxis(target As Excel.Chart, tValueRange As Excel.Range) + Dim dMinValue As Double: dMinValue = WorksheetFunction.Min(tValueRange) + Dim dMaxValue As Double: dMaxValue = WorksheetFunction.Max(tValueRange) + If dMaxValue = dMinValue Then _ + Exit Function + + Dim dPrecision&: dPrecision = -(CLng(VBA.Log(dMaxValue - dMinValue) / VBA.Log(10#)) - 1) + + If VBA.Sgn(dMinValue) = VBA.Sgn(dMaxValue) Then + dMinValue = dMinValue * IIf(dMinValue > 0, 1 - XLUTIL_GRAPH_PADDING, 1 + XLUTIL_GRAPH_PADDING) + dMaxValue = dMaxValue * IIf(dMaxValue > 0, 1 + XLUTIL_GRAPH_PADDING, 1 - XLUTIL_GRAPH_PADDING) + dMinValue = WorksheetFunction.Round(dMinValue, dPrecision) + dMaxValue = WorksheetFunction.Round(dMaxValue, dPrecision) + + target.Axes(xlValue, xlPrimary).MinimumScale = dMinValue + target.Axes(xlValue, xlPrimary).MaximumScale = dMaxValue + End If + + If dPrecision > 0 Then + target.Axes(xlValue, xlPrimary).TickLabels.NumberFormat = "# ##0," & VBA.Replace(VBA.Space(dPrecision), " ", "0") + Else + target.Axes(xlValue, xlPrimary).TickLabels.NumberFormat = "# ##0" + End If +End Function + +Public Function XLFindOrCreateID(sID$, nIdColumn&, target As Excel.Worksheet, _ + Optional nFirstRow& = XL_DEFAULT_FIRST_ROW) As Long + ' Note: this function is precise but slow. Consider using Range.Find but beware of filtered cells being ignored + Dim nRow&: nRow = nFirstRow + Dim sTxt$ + Do + sTxt = target.Cells(nRow, nIdColumn) + If sTxt = vbNullString Then _ + Exit Do + If sTxt = sID Then + XLFindOrCreateID = nRow + Exit Function + End If + nRow = nRow + 1 + Loop + target.Cells(nRow, nIdColumn) = sID + XLFindOrCreateID = nRow +End Function + +Public Function XLBruteFindRow(sTarget$, nIdColumn&, wsWhere As Excel.Worksheet, _ + Optional nFirstRow = XL_DEFAULT_FIRST_ROW) As Long + XLBruteFindRow = XL_INVALID_ROW + Dim nRow&: nRow = nFirstRow + Dim sTxt$ + Do + sTxt = wsWhere.Cells(nRow, nIdColumn) + If sTxt = vbNullString Then + Exit Function + ElseIf sTxt = sTarget Then + XLBruteFindRow = nRow + Exit Function + End If + nRow = nRow + 1 + Loop +End Function + +Public Function FlipColumnsOrder(target As Excel.Worksheet, nStart&, nFinish&) + Dim nItem& + For nItem = 0 To nFinish - nStart - 1 Step 1 + Call target.Columns(nStart).Cut + Call target.Columns(nFinish - nItem + 1).Insert + Next nItem +End Function + +Public Function XLForEachNonEmptyCell(target As Excel.Range, oCallback As Object, sFuncName$) + Dim aCell As Excel.Range + Dim blankCount%: blankCount = 0 + For Each aCell In target + If aCell = vbNullString Then + blankCount = blankCount + 1 + If blankCount > XL_INFINITY_THRESHOLD Then _ + Exit Function + Else + blankCount = 0 + Call CallByName(oCallback, sFuncName, VbMethod, aCell) + End If + Next aCell +End Function + diff --git a/parsers/DetectorClassifier.cls b/parsers/DetectorClassifier.cls new file mode 100644 index 0000000..0bab3b5 --- /dev/null +++ b/parsers/DetectorClassifier.cls @@ -0,0 +1,50 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DetectorClassifier" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Classification entity detector ======= +' Shared module version: 20220615 +' Tested in: +' Depends on: +' Required reference: API_Python, ex_Python +Option Explicit + +Private tags_ As String + +Public Function Init(sTags$) + tags_ = sTags +End Function + +Public Function Test(sText$) As Boolean + If tags_ = vbNullString Then _ + Exit Function + Test = AccessPython.CallFunction(PY_MODULE_TEXT, "extract_entities", Array(sText)) <> Array() +End Function + +Public Function ExtractFragments(sText$) As PC_ParsedData + Dim iData As New PC_ParsedData + + On Error GoTo SKIP_PYTHON + Dim iResult As Variant + iResult = AccessPython.CallFunction(PY_MODULE_TEXT, "extract_entities", Array(sText)) + On Error GoTo 0 + + Dim nItem& + For nItem = LBound(iResult) To UBound(iResult) Step 1 + Dim nStart&: nStart = iResult(nItem, 0) + Dim nEnd&: nEnd = iResult(nItem, 1) + Dim nClass&: nClass = iResult(nItem, 2) + Call iData.AddItem(nStart, nEnd, nClass) + Next nItem + +SKIP_PYTHON: + Set ExtractFragments = iData +End Function + + + diff --git a/parsers/DetectorListWords.cls b/parsers/DetectorListWords.cls new file mode 100644 index 0000000..c315fde --- /dev/null +++ b/parsers/DetectorListWords.cls @@ -0,0 +1,105 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DetectorListWords" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Single word dictionary detector ======= +' Shared module version: 20220623 +' Tested in: +' Depends on: +' Required reference: ADODB, Scripting +Option Explicit + +Private data_ As Scripting.Dictionary + +Public Function Init(sDictionaryPath$) + Set data_ = New Scripting.Dictionary + + On Error Resume Next + Call LoadFrom(sDictionaryPath) + On Error GoTo 0 +End Function + +Public Function Test(sText$) As Boolean + Test = data_.Exists(VBA.LCase(sText)) +End Function + +Public Function ExtractFragments(sText$) As PC_ParsedData + Dim iData As New PC_ParsedData + + Dim nStart&: nStart = 1 + Dim nLen&: nLen = VBA.Len(sText) + Dim nCur&: nCur = nStart + Do While nCur <= nLen + If IsDelim(VBA.Mid$(sText, nCur, 1)) Then + If nCur > nStart Then _ + If Test(VBA.Mid$(sText, nStart, nCur - nStart)) Then _ + Call iData.AddItem(nStart - 1, nCur - 1) + nStart = nCur + 1 + End If + nCur = nCur + 1 + Loop + + If nCur > nStart Then _ + If Test(VBA.Mid$(sText, nStart, nCur - nStart)) Then _ + Call iData.AddItem(nStart - 1, nCur - 1) + + Set ExtractFragments = iData +End Function + +' ======== +Public Function LoadFrom(sPath$) + Dim adoStream As New ADODB.Stream + adoStream.Charset = "utf-8" + Call adoStream.Open + Call adoStream.LoadFromFile(sPath) + + Dim nLine&: nLine = 1 + Do Until adoStream.EOS + Dim sItem$: sItem = adoStream.ReadText(adReadLine) + If sItem <> "" And Not data_.Exists(sItem) Then _ + Call data_.Add(VBA.LCase(sItem), nLine) + nLine = nLine + 1 + Loop + + Call adoStream.Close +End Function + +' ========== +Private Function IsDelim(sSymbol$) As Boolean + IsDelim = True + If sSymbol = "." Then + Exit Function + ElseIf sSymbol = Chr(13) Then + Exit Function + ElseIf sSymbol = Chr(10) Then + Exit Function + ElseIf sSymbol = "," Then + Exit Function + ElseIf sSymbol = ChrW(&H2013) Then _ + Exit Function + ElseIf sSymbol = ChrW(&H2012) Then _ + Exit Function + ElseIf sSymbol = ChrW(&H2010) Then _ + Exit Function + ElseIf sSymbol = ";" Then + Exit Function + ElseIf sSymbol = " " Then + Exit Function + ElseIf sSymbol = "!" Then + Exit Function + ElseIf sSymbol = ":" Then + Exit Function + ElseIf sSymbol = "?" Then + Exit Function + ElseIf sSymbol = """" Then + Exit Function + ElseIf sSymbol = "Chr(85)" Then + Exit Function + End If + IsDelim = False +End Function diff --git a/parsers/DetectorMorpho.cls b/parsers/DetectorMorpho.cls new file mode 100644 index 0000000..0f652b1 --- /dev/null +++ b/parsers/DetectorMorpho.cls @@ -0,0 +1,47 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DetectorMorpho" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Morphology expression detector ======= +' Shared module version: 20220613 +' Tested in: +' Depends on: +' Required reference: API_Python, ex_Python +Option Explicit + +Private tags_ As String + +Public Function Init(sTags$) + tags_ = sTags +End Function + +Public Function Test(sText$) As Boolean + If tags_ = vbNullString Then _ + Exit Function + Test = AccessPython.CallFunction(PY_MODULE_TEXT, "parse", Array(sText, tags_)) <> "" +End Function + +Public Function ExtractFragments(sText$) As PC_ParsedData + Dim iData As New PC_ParsedData + + On Error GoTo SKIP_PYTHON + Dim iResult As Variant + iResult = AccessPython.CallFunction(PY_MODULE_TEXT, "match_all_morpho", Array(sText, tags_)) + On Error GoTo 0 + + Dim nItem& + For nItem = LBound(iResult) To UBound(iResult) Step 1 + Dim nStart&: nStart = iResult(nItem, 0) + Dim nEnd&: nEnd = iResult(nItem, 1) + Call iData.AddItem(nStart, nEnd) + Next nItem + +SKIP_PYTHON: + Set ExtractFragments = iData +End Function + diff --git a/parsers/DetectorRegex.cls b/parsers/DetectorRegex.cls new file mode 100644 index 0000000..9c7b247 --- /dev/null +++ b/parsers/DetectorRegex.cls @@ -0,0 +1,42 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DetectorRegex" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Regular expression based detector ======= +' Shared module version: 20220613 +' Tested in: +' Depends on: +' Required reference: VBScript_RegExp_55 +Option Explicit + +Private regex_ As RegExp + +Public Function Init(theRegex As RegExp) + Set regex_ = theRegex +End Function + +Public Function Test(sText$) As Boolean + Dim matches As Object: Set matches = regex_.Execute(sText) + If matches.Count <> 1 Then _ + Exit Function + Test = matches.Item(0).Length = VBA.Len(sText) +End Function + +Public Function ExtractFragments(sText$) As PC_ParsedData + Dim iData As New PC_ParsedData + + Dim matches As Object: Set matches = regex_.Execute(sText) + Dim nMatch& + For nMatch = 0 To matches.Count - 1 Step 1 + Dim nStart&: nStart = matches.Item(nMatch).FirstIndex + Dim nEnd&: nEnd = nStart + matches.Item(nMatch).Length + Call iData.AddItem(nStart, nEnd) + Next nMatch + + Set ExtractFragments = iData +End Function diff --git a/parsers/ExtractionOptions.cls b/parsers/ExtractionOptions.cls new file mode 100644 index 0000000..46089d2 --- /dev/null +++ b/parsers/ExtractionOptions.cls @@ -0,0 +1,44 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "ExtractionOptions" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ================ ========================= +' Shared module version: 20220614 +' Tested in: +' Depends on: ParserDeclarations +' Required reference: +Option Explicit + +Public detector_ As TDetector +Public param_ As String +Public transform_ As String +Public loadCategory_ As Long + +Private Sub Class_Initialize() + detector_ = T_DETECTOR_UNKNOWN + loadCategory_ = 0 +End Sub + +Public Function FromFlatData(iData As Variant) + detector_ = CLng(iData(1)) + param_ = iData(2) + transform_ = iData(3) + loadCategory_ = CLng(iData(4)) +End Function + +Public Function AsFlatData() As String() + Dim iData() As String + ReDim iData(1 To 4) + + iData(1) = detector_ + iData(2) = param_ + iData(3) = transform_ + iData(4) = loadCategory_ + + AsFlatData = iData +End Function diff --git a/parsers/PC_Fragment.cls b/parsers/PC_Fragment.cls new file mode 100644 index 0000000..829a53b --- /dev/null +++ b/parsers/PC_Fragment.cls @@ -0,0 +1,34 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "PC_Fragment" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== ======= +' Shared module version: 20210909 +' Tested in: +' Depends on: +' Required reference: +Option Explicit + +Public start_ As Long +Public end_ As Long +Public type_ As Long + +Public Function Init(nStart&, nEnd&, nType&) + start_ = nStart + end_ = nEnd + type_ = nType +End Function + +Public Function Clone() As PC_Fragment + Set Clone = New PC_Fragment + With Clone + .start_ = start_ + .end_ = end_ + .type_ = type_ + End With +End Function diff --git a/parsers/PC_InfoNPA.cls b/parsers/PC_InfoNPA.cls new file mode 100644 index 0000000..ab02f77 --- /dev/null +++ b/parsers/PC_InfoNPA.cls @@ -0,0 +1,58 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "PC_InfoNPA" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Legal document attributes ======= +' Shared module version: 20220614 +' Tested in: +' Depends on: +' Required reference: Scripting +Option Explicit + +Public prefix_ As String +Public date_ As String +Public number_ As String +Public docType_ As String +Public creator_ As String +Public title_ As String +Public titlePrefix_ As String +Public titleDoc_ As String + +Public isTimeBound_ As Boolean + +Private Sub Class_Initialize() + isTimeBound_ = False +End Sub + +Public Function AsCollection() As Collection + Dim iData As New Collection + Call iData.Add(prefix_) + Call iData.Add(docType_) + Call iData.Add(creator_) + Call iData.Add(number_) + Call iData.Add(date_) + Call iData.Add(title_) + Call iData.Add(titlePrefix_) + Call iData.Add(titleDoc_) + Call iData.Add(isTimeBound_) + Set AsCollection = iData +End Function + +Public Function AsDescription() As Scripting.Dictionary + Dim iData As New Scripting.Dictionary + Call iData.Add("", prefix_) + Call iData.Add(" ", docType_) + Call iData.Add(" ", creator_) + Call iData.Add("", number_) + Call iData.Add("", date_) + Call iData.Add("", title_) + Call iData.Add(" ", titlePrefix_) + Call iData.Add(" ", titleDoc_) + Call iData.Add("", isTimeBound_) + Set AsDescription = iData +End Function diff --git a/parsers/PC_ParsedData.cls b/parsers/PC_ParsedData.cls new file mode 100644 index 0000000..ed2990d --- /dev/null +++ b/parsers/PC_ParsedData.cls @@ -0,0 +1,42 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "PC_ParsedData" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== ======= +' Shared module version: 20220614 +' Tested in: +' Depends on: PC_Fragment, ParserDeclarations +' Required reference: +Option Explicit + +Public data_ As New Collection + +Public Property Get Count() As Long + Count = data_.Count +End Property + +Public Property Get IsEmpty() As Boolean + IsEmpty = Count = 0 +End Property + +Public Property Get First() As PC_Fragment + If IsEmpty Then _ + Exit Property + Set First = data_.Item(1) +End Property + +Public Property Get Last() As PC_Fragment + If IsEmpty Then _ + Exit Property + Set Last = data_.Item(data_.Count) +End Property + +Public Function AddItem(nStart&, nEnd&, Optional nType& = 0) + Dim newItem As New PC_Fragment: Call newItem.Init(nStart, nEnd, nType) + Call data_.Add(newItem) +End Function diff --git a/parsers/PC_Tools.cls b/parsers/PC_Tools.cls new file mode 100644 index 0000000..42360d5 --- /dev/null +++ b/parsers/PC_Tools.cls @@ -0,0 +1,127 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "PC_Tools" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== ======= +' Shared module version: 20220801 +' Tested in: +' Depends on: ParserDeclarations, DetectorListWords, DetectorRegex, DetectorMorpho, DetectorClassifier +' Required reference: +Option Explicit + +Public Function Detector(iType As TDetector, sParam$) As Object +' Any Detector implements methods: +' Public Function Test(sText$) As Boolean +' Public Function ExtractFragments(sText$) As PC_ParsedData + Select Case iType + Case TDetector.T_DETECTOR_ACTION: Set Detector = CachedActionDetector + Case TDetector.T_DETECTOR_LIST: Set Detector = CachedDetectorList(sParam) + Case TDetector.T_DETECTOR_REGEX: Set Detector = CachedDetectorRegex(sParam) + Case TDetector.T_DETECTOR_MORPHO: Set Detector = CachedDetectorMorpho(sParam) + Case TDetector.T_DETECTOR_DATE: + Set Detector = New DetectorRegex: Call Detector.Init(GlobalDateRegex) + Case TDetector.T_DETECTOR_NPA: + Set Detector = New DetectorRegex: Call Detector.Init(GlobalNPARegex) + Case TDetector.T_DETECTOR_BASIC_NER: Set Detector = CachedDetectorNER(sParam) + End Select + If Detector Is Nothing Then _ + Call Err.Raise(CUSTOM_ERROR_DEBUG) +End Function + +Public Function Parser(iType As TDetector, sParam$) As Object +' Any Parser implements methods: +' Public Function Test(sText$) As Boolean +' Public Function Parse(sText$) As Boolean +' Public Function GetData() As Collection +' Public Function GetDataDescription() As Scripting.Dictionary +' Public Function Transform(sText$, sParam$) As String + Select Case iType + Case TDetector.T_DETECTOR_ACTION: Set Parser = Nothing + Case TDetector.T_DETECTOR_LIST: Set Parser = Nothing + Case TDetector.T_DETECTOR_REGEX: Set Parser = Nothing + Case TDetector.T_DETECTOR_MORPHO: Set Parser = Nothing + Case TDetector.T_DETECTOR_DATE: Set Parser = CachedParserDate + Case TDetector.T_DETECTOR_NPA: Set Parser = CachedParserNPA + Case TDetector.T_DETECTOR_BASIC_NER: Set Parser = Nothing + End Select +End Function + +' ======== +Private Function CachedActionDetector() As DetectorListWords + Static s_Detector As DetectorListWords + If s_Detector Is Nothing Then + Set s_Detector = New DetectorListWords + Call s_Detector.Init(LOCAL_MODELS & "\" & MODEL_ACTION_VERBS) + End If + Set CachedActionDetector = s_Detector +End Function + +Private Function CachedDetectorList(sParam$) As DetectorListWords + Static s_Param$ + Static s_Detector As DetectorListWords + If s_Detector Is Nothing Or sParam <> s_Param Then + s_Param = sParam + Set s_Detector = New DetectorListWords + Call s_Detector.Init(sParam) + End If + Set CachedDetectorList = s_Detector +End Function + +Private Function CachedDetectorRegex(sParam$) As DetectorRegex + Static s_Regex As RegExp + Static s_Param$ + Static s_Detector As DetectorRegex + If s_Detector Is Nothing Or sParam <> s_Param Then + s_Param = sParam + Set s_Regex = New RegExp + s_Regex.Global = True + s_Regex.Pattern = sParam + + Set s_Detector = New DetectorRegex + Call s_Detector.Init(s_Regex) + End If + Set CachedDetectorRegex = s_Detector +End Function + +Private Function CachedDetectorMorpho(sParam$) As DetectorMorpho + Static s_Param$ + Static s_Detector As DetectorMorpho + If s_Detector Is Nothing Or sParam <> s_Param Then + s_Param = sParam + Set s_Detector = New DetectorMorpho + Call s_Detector.Init(sParam) + End If + Set CachedDetectorMorpho = s_Detector +End Function + +Private Function CachedParserDate() As ParserDate + Static s_Parser As ParserDate + If s_Parser Is Nothing Then + Set s_Parser = New ParserDate + End If + Set CachedParserDate = s_Parser +End Function + +Private Function CachedParserNPA() As ParserNPA + Static s_Parser As ParserNPA + If s_Parser Is Nothing Then + Set s_Parser = New ParserNPA + End If + Set CachedParserNPA = s_Parser +End Function + +Private Function CachedDetectorNER(sParam$) As DetectorClassifier + Static s_Param$ + Static s_Detector As DetectorClassifier + If s_Detector Is Nothing Or sParam <> s_Param Then + s_Param = sParam + Set s_Detector = New DetectorClassifier + Call s_Detector.Init(sParam) + End If + Set CachedDetectorNER = s_Detector +End Function diff --git a/parsers/ParserDate.cls b/parsers/ParserDate.cls new file mode 100644 index 0000000..5942c06 --- /dev/null +++ b/parsers/ParserDate.cls @@ -0,0 +1,151 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "ParserDate" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ========= Date parser ========== +' Shared module version: 20220614 +' Tested in: TestCommons +' Depends on: z_ParserRegex +' Required reference: VBScript_RegExp_55 +Option Explicit + +Private rxDate_ As RegExp + +Public day_ As Integer +Public month_ As Integer +Public year_ As Integer + +Private Sub Class_Initialize() + Set rxDate_ = New RegExp + rxDate_.Pattern = P_DATE_CAPTURE + rxDate_.Global = False +End Sub + +Public Function Init(aDay%, aMonth%, aYear%) + day_ = aDay + month_ = aMonth + year_ = aYear +End Function + +Public Function Test(target$) As Boolean + Test = rxDate_.Test(target) +End Function + +Public Function Parse(target$) As Boolean + Parse = Test(target) + If Parse Then + Dim matches As Object + Set matches = rxDate_.Execute(target) + If matches.Item(0).SubMatches(0) <> vbNullString Then + day_ = CInt(matches.Item(0).SubMatches(0)) + month_ = ConvertDateText2Int(matches.Item(0).SubMatches(1)) + year_ = CInt(matches.Item(0).SubMatches(2)) + Else + day_ = CInt(matches.Item(0).SubMatches(3)) + month_ = CInt(matches.Item(0).SubMatches(4)) + year_ = CInt(matches.Item(0).SubMatches(5)) + End If + End If +End Function + +Public Function GetData() As Collection + Set GetData = New Collection + Call GetData.Add(year_) + Call GetData.Add(month_) + Call GetData.Add(day_) +End Function + +Public Function GetDataDescription() As Scripting.Dictionary + Dim iData As New Scripting.Dictionary + Call iData.Add("", year_) + Call iData.Add("", month_) + Call iData.Add("", day_) + Set GetDataDescription = iData +End Function + +Public Function Transform(sText$, sParam$) As String + If Not Parse(sText) Then + Transform = sText + ElseIf sParam = "1" Then + Transform = AsTextString + ElseIf sParam = "2" Then + Transform = AsDigitsString + Else + Transform = sText + End If +End Function + +Public Property Get IsValidDate() As Boolean + IsValidDate = False + + If month_ > 12 Or month_ < 1 Then _ + Exit Property + If day_ > 31 Or day_ < 1 Then _ + Exit Property + + IsValidDate = IsDate(AsDigitsString) +End Property + +Public Property Get DDate() As Double + If Not IsValidDate Then _ + Exit Function + DDate = DateSerial(year_, month_, day_) +End Property + +Public Function AsDigitsString() As String + AsDigitsString = Format(day_, "00") & "." & Format(month_, "00") & "." & Format(year_, IIf(year_ > 100, "0000", "00")) +End Function + +Public Function AsTextString() As String + If Not IsValidDate Then + AsTextString = "INVALID_DATE" + Else + AsTextString = Format(day_, "00") & " " & MonthStr(month_) & " " & Year(DateSerial(year_, 1, 1)) & " " + End If +End Function + +' ============ +Private Function ConvertDateText2Int(sMonth$) As Integer + If IsNumeric(sMonth) Then + ConvertDateText2Int = CInt(sMonth) + Exit Function + End If + Select Case sMonth + Case "": ConvertDateText2Int = 1 + Case "": ConvertDateText2Int = 2 + Case "": ConvertDateText2Int = 3 + Case "": ConvertDateText2Int = 4 + Case "": ConvertDateText2Int = 5 + Case "": ConvertDateText2Int = 6 + Case "": ConvertDateText2Int = 7 + Case "": ConvertDateText2Int = 8 + Case "": ConvertDateText2Int = 9 + Case "": ConvertDateText2Int = 10 + Case "": ConvertDateText2Int = 11 + Case "": ConvertDateText2Int = 12 + Case Else: ConvertDateText2Int = 0 + End Select +End Function + +Private Function MonthStr(nMonth%) As String + Select Case nMonth + Case 1: MonthStr = "" + Case 2: MonthStr = "" + Case 3: MonthStr = "" + Case 4: MonthStr = "" + Case 5: MonthStr = "" + Case 6: MonthStr = "" + Case 7: MonthStr = "" + Case 8: MonthStr = "" + Case 9: MonthStr = "" + Case 10: MonthStr = "" + Case 11: MonthStr = "" + Case 12: MonthStr = "" + Case Else: MonthStr = "INVALID MONTH" + End Select +End Function diff --git a/parsers/ParserDeclarations.bas b/parsers/ParserDeclarations.bas new file mode 100644 index 0000000..87b62d5 --- /dev/null +++ b/parsers/ParserDeclarations.bas @@ -0,0 +1,54 @@ +Attribute VB_Name = "ParserDeclarations" +' ======== Parser declarations ======== +' Shared module version: 20220614 +' Tested in: +' Depends on: +' Required reference: +Option Private Module +Option Explicit + +Public Const LOCAL_MODELS = "C:\Tools\models" +Public Const MODEL_ACTION_VERBS = "ActionVerbs.txt" + +Public Const CUSTOM_ERROR_DEBUG = 1025 + +Public Enum TDetector + [_First] = 1 + + T_DETECTOR_UNKNOWN = 0 + T_DETECTOR_REGEX = 1 + T_DETECTOR_MORPHO = 2 + T_DETECTOR_LIST = 3 + T_DETECTOR_DATE = 4 + T_DETECTOR_ACTION = 5 + T_DETECTOR_NPA = 6 + T_DETECTOR_BASIC_NER = 7 + + [_Last] = 7 +End Enum + +Public Function DetectorFromStr(sName$) As TDetector + Select Case sName + Case "": DetectorFromStr = T_DETECTOR_DATE + Case "": DetectorFromStr = T_DETECTOR_ACTION + Case " ": DetectorFromStr = T_DETECTOR_LIST + Case "RegExp": DetectorFromStr = T_DETECTOR_REGEX + Case "": DetectorFromStr = T_DETECTOR_MORPHO + Case "": DetectorFromStr = T_DETECTOR_NPA + Case ". ": DetectorFromStr = T_DETECTOR_BASIC_NER + Case Else: DetectorFromStr = T_DETECTOR_UNKNOWN + End Select +End Function + +Public Function DetectorToStr(iType As TDetector) As String + Select Case iType + Case T_DETECTOR_UNKNOWN: DetectorToStr = "" + Case T_DETECTOR_DATE: DetectorToStr = "" + Case T_DETECTOR_ACTION: DetectorToStr = "" + Case T_DETECTOR_LIST: DetectorToStr = " " + Case T_DETECTOR_REGEX: DetectorToStr = "RegExp" + Case T_DETECTOR_MORPHO: DetectorToStr = "" + Case T_DETECTOR_NPA: DetectorToStr = "" + Case T_DETECTOR_BASIC_NER: DetectorToStr = ". " + End Select +End Function diff --git a/parsers/ParserNPA.cls b/parsers/ParserNPA.cls new file mode 100644 index 0000000..a99d1d7 --- /dev/null +++ b/parsers/ParserNPA.cls @@ -0,0 +1,179 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "ParserNPA" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' ====== Legal document title parser ======= +' Shared module version: 20220614 +' Tested in: +' Depends on: +' Required reference: z_ParserRegex, PC_InfoNPA +Option Explicit + +Private rxTitleFirst_ As RegExp +Private rxTitleLast_ As RegExp +Private rxCreatorPrefix_ As RegExp +Private rxTitle_ As RegExp +Private rxImmediate_ As RegExp + +Public data_ As New PC_InfoNPA + +Private Sub Class_Initialize() + Set rxTitleFirst_ = New RegExp + rxTitleFirst_.Pattern = P_NPA_TITLE_FIRST + rxTitleFirst_.Global = False + + Set rxTitleLast_ = New RegExp + rxTitleLast_.Pattern = P_NPA_PROPS_FIRST + rxTitleLast_.Global = False + + Set rxCreatorPrefix_ = New RegExp + rxCreatorPrefix_.Pattern = P_NPA_DOCTYPE + rxCreatorPrefix_.Global = False + + Set rxTitle_ = New RegExp + rxTitle_.Pattern = P_NPA_COMPOSITE_TITLE + rxTitle_.Global = False + + Set rxImmediate_ = New RegExp + rxImmediate_.Pattern = P_NPA_IMMEDIATE + rxImmediate_.Global = False +End Sub + +Public Function Test(target$) As Boolean + Test = rxTitleLast_.Test(target) + If Not Test Then Test = rxTitleFirst_.Test(target) +End Function + +Public Function Parse(target$) As Boolean + Set data_ = New PC_InfoNPA + Dim matches As Object + Dim isTitleLast As Boolean + isTitleLast = rxTitleLast_.Test(target) + If Not isTitleLast Then + If Not rxTitleFirst_.Test(target) Then + Parse = False + Exit Function + End If + End If + + Parse = True + If isTitleLast Then + Set matches = rxTitleLast_.Execute(target) + data_.prefix_ = matches.Item(0).SubMatches(0) + data_.date_ = matches.Item(0).SubMatches(1) + data_.number_ = Trim(matches.Item(0).SubMatches(2)) + data_.title_ = Trim(matches.Item(0).SubMatches(3)) + Else + Set matches = rxTitleFirst_.Execute(target) + data_.title_ = Trim(matches.Item(0).SubMatches(0)) + data_.prefix_ = Trim(matches.Item(0).SubMatches(1)) + data_.date_ = Trim(matches.Item(0).SubMatches(2)) + data_.number_ = Trim(matches.Item(0).SubMatches(3)) + End If + + Call ParsePrefix + Call ParseTitle +End Function + +Public Function GetData() As Collection + Set GetData = data_.AsCollection() +End Function + +Public Function GetDataDescription() As Scripting.Dictionary + Set GetDataDescription = data_.AsDescription() +End Function + +Public Function Transform(sText$, sParam$) As String + Transform = sText +End Function + +' ======= +Private Function ParsePrefix() + If data_.prefix_ = vbNullString Then _ + Exit Function + If Not rxCreatorPrefix_.Test(data_.prefix_) Then + data_.creator_ = data_.prefix_ + Exit Function + End If + + Dim matches As Object: Set matches = rxCreatorPrefix_.Execute(data_.prefix_) + data_.docType_ = CapitalizeFirstLetter(Trim(matches.Item(0).Value)) + If Len(data_.docType_) <> Len(data_.prefix_) Then _ + data_.creator_ = Trim(Right(data_.prefix_, Len(data_.prefix_) - Len(data_.docType_))) + Call FixTypeCase +End Function + +Private Function ParseTitle() + Dim theTitle$: theTitle = data_.title_ + If theTitle = vbNullString Then _ + Exit Function + + data_.isTimeBound_ = rxImmediate_.Test(theTitle) + + If Not rxTitle_.Test(theTitle) Then _ + Exit Function + + Dim matches As Object: Set matches = rxTitle_.Execute(theTitle) + data_.titlePrefix_ = Trim(matches.Item(0).SubMatches(0)) + data_.titleDoc_ = Trim(matches.Item(0).SubMatches(1)) +End Function + +Private Function FixTypeCase() + If Len(data_.docType_) = 0 Then _ + Exit Function + + Select Case Left(data_.docType_, 1) + Case "" + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + Case "" + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + Case "" + data_.docType_ = Replace(data_.docType_, " ", " ") + data_.docType_ = Replace(data_.docType_, " ", " ") + data_.docType_ = Replace(data_.docType_, " ", " ") + Case "" + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + Case "" + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + Case "" + If data_.docType_ = "" Then + data_.docType_ = "" + Else + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + End If + Case "" + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + data_.docType_ = Replace(data_.docType_, "", "") + End Select +End Function + diff --git a/parsers/z_ParserRegex.bas b/parsers/z_ParserRegex.bas new file mode 100644 index 0000000..17ae212 --- /dev/null +++ b/parsers/z_ParserRegex.bas @@ -0,0 +1,232 @@ +Attribute VB_Name = "z_ParserRegex" +' ======== Parser regex masks ======== +' Shared module version: 20210909 +' Tested in: +' Depends on: +' Required reference: +Option Private Module +Option Explicit + +Private Const P_SPACE = "(?:\s+?)" + +Private Const P_QUOTE_OPEN = "[""']" +Private Const P_QUOTE_CLOSE = "[""']" +Private Const P_ANY_SHORTEST = ".*?" +Private Const P_ANY = ".*" + +Private Const P_MONTH_RUS = "(?:|||||||||||)" +Private Const P_MONTH_DIGITS = "[0-1]?\d" +Private Const P_DAY_DIGITS = "[0-3]?\d" +Private Const P_YEAR_LONG = "[1-2]\d\d\d" +Private Const P_YEAR_SHORT = "\d\d" +Private Const P_YEAR_DIGITS = "(?:" & P_YEAR_LONG & "|" & P_YEAR_SHORT & ")" + +Private Const P_DATE_TEXT = _ + "(" & P_DAY_DIGITS & ")" & P_SPACE & _ + "(" & P_MONTH_RUS & ")" & P_SPACE & _ + "(" & P_YEAR_LONG & ")" + +Private Const P_DATE_NUMERIC = _ + "(" & P_DAY_DIGITS & ")" & "\." & _ + "(" & P_MONTH_DIGITS & ")" & "\." & _ + "(" & P_YEAR_DIGITS & ")" + +Private Const P_NPA_PREFIX = "[--][\d--A-Za-z,\-\.""'\s]+?" +Private Const P_NPA_ID_ELEMENT = "[#N]" & P_SPACE & "?" & "[\d--A-Za-z_@\.\-\\/]+" +Private Const P_NPA_ID_SEQ = P_NPA_ID_ELEMENT & "(?:," & P_SPACE & P_NPA_ID_ELEMENT & ")*" +Private Const P_NPA_INTRO = _ + "(?:" & _ + "[] " & "|" & _ + "[] " & "|" & _ + "(?:" & _ + "[][]?" & "|" & _ + "(?:" & _ + "[]\." & "|" & _ + "[](?:[]|||)" & _ + ")" & " (?:[]|||) " & "|" & _ + "(?:[]? )?" & _ + "(?:" & _ + "[]\." & "|" & _ + "[](?:[]|||)" & "|" & _ + "[]?" & "|" & _ + "[](?:[]|||) " & _ + ")" & "|" & _ + "[]?" & _ + ")" & "(?: )?" & _ + ")" + +Private Const P_MASK_DOCTYPE = _ + "(?:" & _ + "[][]" & "|" & _ + "[](?:|||) (?:||)?" & "|" & _ + "[](?:|||) [](?:|||)] (?:||)?" & "|" & _ + "[](?:||)?(?: | )?" & "|" & _ + "[](?:|||)" & "|" & _ + P_ANY & "[](?:)?" & P_ANY & "|" & _ + P_ANY & "[](?:||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:||)?" & "|" & _ + "[](?:||)?" & "|" & _ + "[](?:|||) (?:|||)" & "|" & _ + "[](?:|||)" & "|" & _ + "[](?:|||)(?: )?" & "|" & _ + "[](?:|||)" & _ + ")" + +Private Const P_NPA_TITLE_PREFIX = _ + "(?:" & _ + "[] " & "(?: )?" & "|" & _ + "[] " & "|" & _ + "[] (?: (?: )? )?" & _ + " " & _ + ")" + +' ====================================== +' ======== ========== +' ====================================== + +' ======= ========== +Public Const P_DATE_NO_CAPTURE = _ + "(?:" & _ + P_DAY_DIGITS & P_SPACE & P_MONTH_RUS & P_SPACE & P_YEAR_LONG & _ + "|" & _ + P_DAY_DIGITS & "\." & P_MONTH_DIGITS & "\." & P_YEAR_DIGITS & _ + ")" & _ + "(?:" & P_SPACE & "?" & "(?:|\.|))?" + +Public Const P_DATE_CAPTURE = _ + "^" & _ + "(?:" & _ + P_DATE_TEXT & "|" & _ + P_DATE_NUMERIC & _ + ")" & _ + "(?:" & P_SPACE & "?" & "(?:|\.|))?" & _ + "$" + +' ====== ====== +Public Const P_NPA_SCAN = _ + "(?:" & "\b[]\." & P_SPACE & ")?" & _ + "(?:" & _ + "\b " & "|" & _ + "\b " & _ + ")?" & _ + "(" & _ + P_NPA_PREFIX & P_SPACE & "" & P_SPACE & P_DATE_NO_CAPTURE & P_SPACE & "?" & _ + P_NPA_ID_SEQ & "(?:" & P_SPACE & P_QUOTE_OPEN & P_ANY & P_QUOTE_CLOSE & ")?" & _ + "|" & _ + P_ANY_SHORTEST & _ + "\(" & _ + P_ANY_SHORTEST & P_NPA_INTRO & P_SPACE & P_NPA_PREFIX & _ + "(?:" & P_SPACE & "" & ")?" & P_SPACE & P_DATE_NO_CAPTURE & _ + "(?:" & P_SPACE & "?" & "(?:" & P_NPA_ID_SEQ & "))?" & P_ANY_SHORTEST & _ + "\)" & _ + ")" + +Public Const P_NPA_PROPS_FIRST = _ + "^" & _ + "(" & P_NPA_PREFIX & ")" & _ + P_SPACE & "" & P_SPACE & "(" & P_DATE_NO_CAPTURE & ")" & _ + P_SPACE & "?" & "(" & P_NPA_ID_SEQ & ")" & _ + "(?:" & P_SPACE & P_QUOTE_OPEN & "(" & P_ANY & ")" & P_QUOTE_CLOSE & ")?" & _ + P_ANY_SHORTEST & _ + "$" + +Public Const P_NPA_TITLE_FIRST = _ + "^" & _ + "(" & P_ANY_SHORTEST & ")" & _ + "\(" & _ + P_ANY_SHORTEST & P_NPA_INTRO & P_SPACE & _ + "(" & P_NPA_PREFIX & ")" & P_SPACE & _ + "(?:" & P_SPACE & ")?" & "(" & P_DATE_NO_CAPTURE & ")" & _ + "(?:" & P_SPACE & "?" & "(" & P_NPA_ID_SEQ & "))?" & _ + P_ANY_SHORTEST & _ + "\)" & _ + P_ANY_SHORTEST & _ + "$" + +' ===== ========= +Public Const P_NPA_DOCTYPE = "^" & "(" & P_MASK_DOCTYPE & ")" + +' ======== ====== +Public Const P_NPA_COMPOSITE_TITLE = _ + "^" & _ + "(" & P_NPA_TITLE_PREFIX & ")" & _ + P_QUOTE_OPEN & "?" & _ + "(" & P_ANY_SHORTEST & ")" & _ + P_QUOTE_CLOSE & "?" & _ + "$" + +' ======== === +Public Const P_NPA_IMMEDIATE = _ + " " & _ + "(?:(?:|||) |\()" & _ + "(?: | | )?" & _ + P_YEAR_DIGITS & _ + "(?:" & _ + "(?:\-| \- | | )" & _ + P_YEAR_DIGITS & _ + ")?" & _ + " " & _ + "(?:(?:|||)?|\.||\.|)" + +' ======== Static access for compiled regexp ====== +Public Function GlobalDateRegex() As RegExp + Static s_Regex As RegExp + If s_Regex Is Nothing Then + Set s_Regex = New RegExp + s_Regex.Global = True + s_Regex.Pattern = P_DATE_NO_CAPTURE + End If + Set GlobalDateRegex = s_Regex +End Function + +Public Function GlobalNPARegex() As RegExp + Static s_Regex As RegExp + If s_Regex Is Nothing Then + Set s_Regex = New RegExp + s_Regex.Global = True + s_Regex.Pattern = P_NPA_SCAN + End If + Set GlobalNPARegex = s_Regex +End Function + +' ============= - +' (?:||) ? +' (?:||) +' ? +' (?:)?[]?(?: )? +' [] +' [] +' [] +' (?:|) [] ? +' (?:(?:, )? )? +' []? +' []? +' + +' ============ - +' +' (?: | | )? (?:||) +' +' +' +' (||) +' +' (?: )? +' +' ' - +' ' +' (?: ? )? +' +' ' = c? +' ' +' ' + +' ========= TODO +' - +' Private Const P_ANY_SPECIAL_SPACE = "[ \xA0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000\uFEFF]+" + diff --git a/samples/20150923 Таймлайнер.xlsm b/samples/20150923 Таймлайнер.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..a69254b6418c4348ab34134586e22b43d1c07cbb GIT binary patch literal 390743 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%0D|Rl>3%%!CFt<4jjp}V9mWX2RA>} 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