From 0d915683b8ee100cda8a3a5d007086c43b753473 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:59:49 +0100 Subject: [PATCH 01/22] move class WVP to new namespace Parsers --- source/CLI.vb | 2 +- source/Controllers/WaveController.vb | 18 +++++++++--------- source/{FileFormats => Parsers}/WVP.vb | 2 +- source/Wave.vb | 2 +- source/Wave.vbproj | 3 ++- tests/TestWVP.vb | 26 +++++++++++++------------- 6 files changed, 27 insertions(+), 26 deletions(-) rename source/{FileFormats => Parsers}/WVP.vb (97%) diff --git a/source/CLI.vb b/source/CLI.vb index ec6e3ff0..6689ecb7 100644 --- a/source/CLI.vb +++ b/source/CLI.vb @@ -152,7 +152,7 @@ Friend Class CLI Throw New NotImplementedException("TEN files are currently not supported in the CLI!") Case TimeSeriesFile.FileExtensions.WVP - Dim wvp As New Fileformats.WVP(file_in) + Dim wvp As New Parsers.WVP(file_in) Dim wvpSeries As List(Of TimeSeries) = wvp.Process() Log.AddLogEntry(Log.levels.info, $"Imported {wvpSeries.Count} time series") tsList.AddRange(wvpSeries) diff --git a/source/Controllers/WaveController.vb b/source/Controllers/WaveController.vb index f7d5c705..abbe5403 100644 --- a/source/Controllers/WaveController.vb +++ b/source/Controllers/WaveController.vb @@ -561,15 +561,15 @@ Friend Class WaveController End If tsList.Add(ts) Next - Call Fileformats.WVP.writeFile(tsList, dlg.FileName, - saveRelativePaths:=dlg.SaveRelativePaths, - saveTitle:=dlg.SaveTitle, - saveUnit:=dlg.SaveUnit, - saveInterpretation:=dlg.SaveInterpretation, - saveColor:=dlg.SaveColor, - saveLineStyle:=dlg.SaveLineStyle, - saveLineWidth:=dlg.SaveLineWidth, - savePointsVisibility:=dlg.SavePointsVisibility + Call Parsers.WVP.writeFile(tsList, dlg.FileName, + saveRelativePaths:=dlg.SaveRelativePaths, + saveTitle:=dlg.SaveTitle, + saveUnit:=dlg.SaveUnit, + saveInterpretation:=dlg.SaveInterpretation, + saveColor:=dlg.SaveColor, + saveLineStyle:=dlg.SaveLineStyle, + saveLineWidth:=dlg.SaveLineWidth, + savePointsVisibility:=dlg.SavePointsVisibility ) MsgBox($"Wave project file {dlg.FileName} saved.", MsgBoxStyle.Information) End If diff --git a/source/FileFormats/WVP.vb b/source/Parsers/WVP.vb similarity index 97% rename from source/FileFormats/WVP.vb rename to source/Parsers/WVP.vb index 97b6a831..25130cb0 100644 --- a/source/FileFormats/WVP.vb +++ b/source/Parsers/WVP.vb @@ -17,7 +17,7 @@ ' Imports System.Text.RegularExpressions -Namespace Fileformats +Namespace Parsers ''' ''' Class for importing a wave project file diff --git a/source/Wave.vb b/source/Wave.vb index 127fe8dc..67fc9d79 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -200,7 +200,7 @@ Public Class Wave Call Log.AddLogEntry(Log.levels.info, $"Loading Wave project file '{projectfile}'...") - Dim wvp As New Fileformats.WVP(projectfile) + Dim wvp As New Parsers.WVP(projectfile) tsList = wvp.Process() Call Log.AddLogEntry(Log.levels.info, $"Imported {tsList.Count} timeseries") diff --git a/source/Wave.vbproj b/source/Wave.vbproj index 86c75f00..bfa712e2 100644 --- a/source/Wave.vbproj +++ b/source/Wave.vbproj @@ -348,7 +348,6 @@ - @@ -389,6 +388,7 @@ Settings.settings True + MainWindow.vb @@ -643,6 +643,7 @@ 4.6.1 + copy $(ProjectDir)..\COPYING $(TargetDir)COPYING diff --git a/tests/TestWVP.vb b/tests/TestWVP.vb index f6a9a53d..b5ced450 100644 --- a/tests/TestWVP.vb +++ b/tests/TestWVP.vb @@ -34,7 +34,7 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting } For Each file As String In filesWVP - Dim wvp As New Fileformats.WVP(file) + Dim wvp As New Parsers.WVP(file) Next End Sub @@ -46,7 +46,7 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting Dim fileWVP As String = IO.Path.Combine(TestData.getTestDataDir(), "WVP", "test_displayoptions.wvp") - Dim wvp As New Fileformats.WVP(fileWVP) + Dim wvp As New Parsers.WVP(fileWVP) Dim tsList As List(Of TimeSeries) = wvp.Process() Assert.AreEqual("AA _1AB", tsList(0).Title) @@ -71,20 +71,20 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting 'read time series using a WVP file Dim fileIn As String = IO.Path.Combine(TestData.getTestDataDir(), "WVP", "test_displayoptions.wvp") - Dim wvp As New Fileformats.WVP(fileIn) + Dim wvp As New Parsers.WVP(fileIn) Dim tsList As List(Of TimeSeries) = wvp.Process() 'write the time series to a WVP file Dim fileOut As String = IO.Path.Combine(TestData.getTestDataDir(), "WVP", "test_displayoptions_export.wvp") - Call Fileformats.WVP.writeFile(tsList, fileOut, - saveRelativePaths:=True, - saveTitle:=True, - saveUnit:=True, - saveInterpretation:=True, - saveColor:=True, - saveLineStyle:=True, - saveLineWidth:=True, - savePointsVisibility:=True + Call Parsers.WVP.writeFile(tsList, fileOut, + saveRelativePaths:=True, + saveTitle:=True, + saveUnit:=True, + saveInterpretation:=True, + saveColor:=True, + saveLineStyle:=True, + saveLineWidth:=True, + savePointsVisibility:=True ) 'check the file contents of the written WVP file @@ -123,7 +123,7 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting 'read time series using a WVP file Dim file As String = IO.Path.Combine(TestData.getTestDataDir(), "WVP", "test_preserve_order.wvp") - Dim wvp As New Fileformats.WVP(file) + Dim wvp As New Parsers.WVP(file) Dim tsList As List(Of TimeSeries) = wvp.Process() 'check the order of series From 47447d0c51480b6c8dce23522d8ffc13940ffa44 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:25:09 +0100 Subject: [PATCH 02/22] introduce a Parser base class which is inherited by WVP --- source/Parsers/Parser.vb | 214 +++++++++++++++++++++++++++++++++++++++ source/Parsers/WVP.vb | 196 ++++------------------------------- source/Wave.vb | 4 +- source/Wave.vbproj | 1 + 4 files changed, 233 insertions(+), 182 deletions(-) create mode 100644 source/Parsers/Parser.vb diff --git a/source/Parsers/Parser.vb b/source/Parsers/Parser.vb new file mode 100644 index 00000000..dcf8bcac --- /dev/null +++ b/source/Parsers/Parser.vb @@ -0,0 +1,214 @@ +'BlueM.Wave +'Copyright (C) BlueM Dev Group +' +' +'This program is free software: you can redistribute it and/or modify +'it under the terms of the GNU Lesser General Public License as published by +'the Free Software Foundation, either version 3 of the License, or +'(at your option) any later version. +' +'This program is distributed in the hope that it will be useful, +'but WITHOUT ANY WARRANTY; without even the implied warranty of +'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +'GNU Lesser General Public License for more details. +' +'You should have received a copy of the GNU Lesser General Public License +'along with this program. If not, see . +' +Namespace Parsers + + ''' + ''' Base class for parsing file import instructions + ''' + Public MustInherit Class Parser + + ''' + ''' Holds reference to a file to be imported, file import settings and the series to be imported from that file with their options + ''' + Protected Structure FileReference + Dim path As String 'path to file + Dim series As Dictionary(Of String, SeriesOptions) 'series name -> options + Dim settings As Dictionary(Of String, String) 'file import settings as key value pairs, e.g. separator, date format, etc. + End Structure + + ''' + ''' Holds options for importing a series, e.g. display options, custom title and unit, etc. + ''' + Protected Structure SeriesOptions + Dim title As String + Dim unit As String + Dim color As String + Dim linestyle As String + Dim linewidth As String + Dim interpretation As String + Dim showpoints As String + End Structure + + ''' + ''' The path to the input file + ''' + Protected InputFile As String + + ''' + ''' The input text to be parsed + ''' + Protected InputText As String + + Protected FileReferences As New List(Of FileReference) + + ''' + ''' Instantiates a new Parser instance with either a file or text input and calls the Parse method to parse the input + ''' + ''' The path to the input file + ''' The input text + Public Sub New(Optional inputfile As String = Nothing, Optional inputtext As String = Nothing) + If IsNothing(inputfile) And IsNothing(inputtext) Then + Throw New ArgumentException("Either a file or text input must be provided!") + ElseIf Not IsNothing(inputfile) And Not IsNothing(inputtext) Then + Throw New ArgumentException("Only one of file or text input can be provided!") + End If + Me.InputFile = inputfile + If Not IsNothing(inputfile) Then + Me.InputText = IO.File.ReadAllText(inputfile, Text.Encoding.UTF8) + Else + Me.InputText = inputtext + End If + Call Me.Parse() + End Sub + + ''' + ''' Parses the input and stores the results in the FileReferences list + ''' + Protected MustOverride Sub Parse() + + ''' + ''' Processes the FileReferences list by reading the specified series from the files and returning them as a list + ''' + ''' a list of time series + Public Function Process() As List(Of TimeSeries) + + Dim tsList As New List(Of TimeSeries) + + 'loop over file list + For Each fileRef As FileReference In FileReferences + + Log.AddLogEntry(Log.levels.info, $"Reading file {fileRef.path} ...") + + 'get an instance of the file + Dim fileInstance As TimeSeriesFile = TimeSeriesFile.getInstance(fileRef.path) + + 'apply custom file import settings + If fileRef.settings.Count > 0 Then + For Each key As String In fileRef.settings.Keys + Dim value As String = fileRef.settings(key) + Try + Select Case key.ToLower() + Case "iscolumnseparated" + fileInstance.IsColumnSeparated = If(value.ToLower() = "true", True, False) + Case "separator" + fileInstance.Separator = New Character(value) + Case "dateformat" + fileInstance.Dateformat = value + Case "decimalseparator" + fileInstance.DecimalSeparator = New Character(value) + Case "ilineheadings" + fileInstance.iLineHeadings = Convert.ToInt32(value) + Case "ilineunits" + fileInstance.iLineUnits = Convert.ToInt32(value) + Case "ilinedata" + fileInstance.iLineData = Convert.ToInt32(value) + Case "useunits" + fileInstance.UseUnits = If(value.ToLower() = "true", True, False) + Case "columnwidth" + fileInstance.ColumnWidth = Convert.ToInt32(value) + Case "datetimecolumnindex" + fileInstance.DateTimeColumnIndex = Convert.ToInt32(value) + Case Else + Log.AddLogEntry(Log.levels.warning, $"Setting '{key}' was not recognized and was ignored!") + End Select + Catch ex As Exception + Log.AddLogEntry(Log.levels.warning, $"Setting '{key}' with value '{value}' could not be parsed and was ignored!") + End Try + Next + 'reread columns with new settings + fileInstance.readSeriesInfo() + End If + + 'select series for importing + If fileRef.series.Count = 0 Then + 'read all series contained in the file + Call fileInstance.selectAllSeries() + Else + 'loop over series names + Dim seriesNames As List(Of String) = fileRef.series.Keys.ToList() + Dim seriesNotFound As New List(Of String) + For Each seriesName As String In seriesNames + Dim found As Boolean = fileInstance.selectSeries(seriesName) + If Not found Then + seriesNotFound.Add(seriesName) + Log.AddLogEntry(Log.levels.error, $"Series {seriesName} not found in file, skipping series!") + End If + Next + 'remove series that were not found from the list + For Each seriesName As String In seriesNotFound + seriesNames.Remove(seriesName) + Next + 'if no series remain to be imported, abort reading the file altogether + If seriesNames.Count = 0 Then + Log.AddLogEntry(Log.levels.error, "No series left to import, skipping file!") + Continue For + End If + + End If + + 'read the file + fileInstance.readFile() + + 'Log + Call Log.AddLogEntry(Log.levels.info, $"File '{fileRef.path}' imported successfully!") + + 'store the series + For Each ts As TimeSeries In fileInstance.TimeSeries.Values + 'set series options + If fileRef.series.ContainsKey(ts.Title) Then + Dim options As SeriesOptions = fileRef.series(ts.Title) + 'options affecting the time series itself + If Not IsNothing(options.title) Then + ts.Title = options.title + End If + If Not IsNothing(options.unit) Then + ts.Unit = options.unit + End If + If Not IsNothing(options.interpretation) Then + If Not [Enum].IsDefined(GetType(TimeSeries.InterpretationEnum), options.interpretation) Then + Log.AddLogEntry(levels.warning, $"Interpretation {options.interpretation} is not recognized!") + Else + ts.Interpretation = [Enum].Parse(GetType(TimeSeries.InterpretationEnum), options.interpretation) + End If + End If + 'display options + If Not IsNothing(options.color) Then + ts.DisplayOptions.SetColor(options.color) + End If + If Not IsNothing(options.linestyle) Then + ts.DisplayOptions.SetLineStyle(options.linestyle) + End If + If Not IsNothing(options.linewidth) Then + ts.DisplayOptions.SetLineWidth(options.linewidth) + End If + If Not IsNothing(options.showpoints) Then + ts.DisplayOptions.SetShowPoints(options.showpoints) + End If + End If + 'store the time series in the list + tsList.Add(ts) + Next + Next + + Return tsList + + End Function + + End Class + +End Namespace \ No newline at end of file diff --git a/source/Parsers/WVP.vb b/source/Parsers/WVP.vb index 25130cb0..bcf475d2 100644 --- a/source/Parsers/WVP.vb +++ b/source/Parsers/WVP.vb @@ -20,51 +20,26 @@ Imports System.Text.RegularExpressions Namespace Parsers ''' - ''' Class for importing a wave project file + ''' Class for parsing a wave project file ''' https://wiki.bluemodel.org/index.php/Wave_project_file ''' Public Class WVP - - Private projectfile As String - - Private Structure FileEntry - Dim path As String 'path to file - Dim series As Dictionary(Of String, SeriesOptions) 'series name -> options - Dim settings As Dictionary(Of String, String) 'file import settings as key value pairs, e.g. separator, date format, etc. - End Structure - - Private Structure SeriesOptions - Dim title As String - Dim unit As String - Dim color As String - Dim linestyle As String - Dim linewidth As String - Dim interpretation As String - Dim showpoints As String - End Structure - - Private fileEntries As New List(Of FileEntry) + Inherits Parser Public Sub New(file As String) - - Me.projectfile = file - - Call Me.Parse() - + MyBase.New(inputfile:=file) End Sub ''' ''' Parses the project file ''' - Private Sub Parse() + Protected Overrides Sub Parse() - Dim fstr As IO.FileStream - Dim strRead As IO.StreamReader Dim line, parts(), file, seriesName As String - Log.AddLogEntry(Log.levels.info, $"Parsing Wave project file {projectfile} ...") + Log.AddLogEntry(Log.levels.info, $"Parsing Wave project file {MyBase.InputFile} ...") - 'read project file + 'parse Wave project file 'file format (all whitespace is optional): ' @@ -76,20 +51,15 @@ Namespace Parsers ' series=series4 ' series=series5 ' - fstr = New IO.FileStream(projectfile, IO.FileMode.Open) - strRead = New IO.StreamReader(fstr, Text.Encoding.UTF8) - file = "" - line = strRead.ReadLine() - While Not IsNothing(line) + For Each line In Me.inputText.Split({vbCrLf, vbLf}, StringSplitOptions.RemoveEmptyEntries) line = line.Trim() 'get rid of whitespace If line.StartsWith("#") Then 'skip comments - line = strRead.ReadLine() - Continue While + Continue For End If If line.ToLower().StartsWith("file=") Then @@ -97,10 +67,10 @@ Namespace Parsers file = line.Split("=".ToCharArray(), 2)(1).Trim() If Not IO.Path.IsPathRooted(file) Then 'it's a relative path: construct the full path relative to the project file - file = IO.Path.GetFullPath(IO.Path.Combine(IO.Path.GetDirectoryName(projectfile), file)) + file = IO.Path.GetFullPath(IO.Path.Combine(IO.Path.GetDirectoryName(MyBase.InputFile), file)) End If 'store file entry - fileEntries.Add(New FileEntry With { + FileReferences.Add(New FileReference With { .path = file, .series = New Dictionary(Of String, SeriesOptions)(), .settings = New Dictionary(Of String, String)() @@ -173,9 +143,9 @@ Namespace Parsers End If End If 'add series to last entry of filelist - If fileEntries.Count > 0 Then - If Not fileEntries.Last().series.ContainsKey(seriesName) Then - fileEntries.Last().series.Add(seriesName, options) + If FileReferences.Count > 0 Then + If Not FileReferences.Last().series.ContainsKey(seriesName) Then + FileReferences.Last().series.Add(seriesName, options) Else 'duplicate series Log.AddLogEntry(Log.levels.warning, $"Series {seriesName} is specified more than once, only the first mention will be processed!") @@ -195,9 +165,9 @@ Namespace Parsers key = parts(0).Trim() value = parts(1).Trim() 'add settings to last entry of file list - If fileEntries.Count > 0 Then - If Not fileEntries.Last().settings.ContainsKey(key) Then - fileEntries.Last().settings.Add(key, value) + If FileReferences.Count > 0 Then + If Not FileReferences.Last().settings.ContainsKey(key) Then + FileReferences.Last().settings.Add(key, value) Else 'duplicate setting Log.AddLogEntry(Log.levels.warning, $"File import setting {key} is specified more than once, only the first mention will be processed!") @@ -209,141 +179,9 @@ Namespace Parsers Else 'ignore any other lines End If - line = strRead.ReadLine() - End While - - strRead.Close() - fstr.Close() - - End Sub - - ''' - ''' Reads all timeseries from all files as specified in the project file - ''' - ''' the list of time series - Public Function Process() As List(Of TimeSeries) - - Dim tsList As New List(Of TimeSeries) - - 'loop over file list - For Each fileEntry As FileEntry In fileEntries - - Log.AddLogEntry(Log.levels.info, $"Reading file {fileEntry.path} ...") - - 'get an instance of the file - Dim fileInstance As TimeSeriesFile = TimeSeriesFile.getInstance(fileEntry.path) - - 'apply custom file import settings - If fileEntry.settings.Count > 0 Then - For Each key As String In fileEntry.settings.Keys - Dim value As String = fileEntry.settings(key) - Try - Select Case key.ToLower() - Case "iscolumnseparated" - fileInstance.IsColumnSeparated = If(value.ToLower() = "true", True, False) - Case "separator" - fileInstance.Separator = New Character(value) - Case "dateformat" - fileInstance.Dateformat = value - Case "decimalseparator" - fileInstance.DecimalSeparator = New Character(value) - Case "ilineheadings" - fileInstance.iLineHeadings = Convert.ToInt32(value) - Case "ilineunits" - fileInstance.iLineUnits = Convert.ToInt32(value) - Case "ilinedata" - fileInstance.iLineData = Convert.ToInt32(value) - Case "useunits" - fileInstance.UseUnits = If(value.ToLower() = "true", True, False) - Case "columnwidth" - fileInstance.ColumnWidth = Convert.ToInt32(value) - Case "datetimecolumnindex" - fileInstance.DateTimeColumnIndex = Convert.ToInt32(value) - Case Else - Log.AddLogEntry(Log.levels.warning, $"Setting '{key}' was not recognized and was ignored!") - End Select - Catch ex As Exception - Log.AddLogEntry(Log.levels.warning, $"Setting '{key}' with value '{value}' could not be parsed and was ignored!") - End Try - Next - 'reread columns with new settings - fileInstance.readSeriesInfo() - End If - - 'select series for importing - If fileEntry.series.Count = 0 Then - 'read all series contained in the file - Call fileInstance.selectAllSeries() - Else - 'loop over series names - Dim seriesNames As List(Of String) = fileEntry.series.Keys.ToList() - Dim seriesNotFound As New List(Of String) - For Each seriesName As String In seriesNames - Dim found As Boolean = fileInstance.selectSeries(seriesName) - If Not found Then - seriesNotFound.Add(seriesName) - Log.AddLogEntry(Log.levels.error, $"Series {seriesName} not found in file, skipping series!") - End If - Next - 'remove series that were not found from the list - For Each seriesName As String In seriesNotFound - seriesNames.Remove(seriesName) - Next - 'if no series remain to be imported, abort reading the file altogether - If seriesNames.Count = 0 Then - Log.AddLogEntry(Log.levels.error, "No series left to import, skipping file!") - Continue For - End If - - End If - - 'read the file - fileInstance.readFile() - - 'Log - Call Log.AddLogEntry(Log.levels.info, $"File '{fileEntry.path}' imported successfully!") - - 'store the series - For Each ts As TimeSeries In fileInstance.TimeSeries.Values - 'set series options - If fileEntry.series.ContainsKey(ts.Title) Then - Dim options As SeriesOptions = fileEntry.series(ts.Title) - 'options affecting the time series itself - If Not IsNothing(options.title) Then - ts.Title = options.title - End If - If Not IsNothing(options.unit) Then - ts.Unit = options.unit - End If - If Not IsNothing(options.interpretation) Then - If Not [Enum].IsDefined(GetType(TimeSeries.InterpretationEnum), options.interpretation) Then - Log.AddLogEntry(levels.warning, $"Interpretation {options.interpretation} is not recognized!") - Else - ts.Interpretation = [Enum].Parse(GetType(TimeSeries.InterpretationEnum), options.interpretation) - End If - End If - 'display options - If Not IsNothing(options.color) Then - ts.DisplayOptions.SetColor(options.color) - End If - If Not IsNothing(options.linestyle) Then - ts.DisplayOptions.SetLineStyle(options.linestyle) - End If - If Not IsNothing(options.linewidth) Then - ts.DisplayOptions.SetLineWidth(options.linewidth) - End If - If Not IsNothing(options.showpoints) Then - ts.DisplayOptions.SetShowPoints(options.showpoints) - End If - End If - 'store the time series in the list - tsList.Add(ts) - Next Next - Return tsList - - End Function + End Sub ''' ''' Write a Wave project file diff --git a/source/Wave.vb b/source/Wave.vb index 67fc9d79..ee01cfff 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -196,12 +196,10 @@ Public Class Wave Private Sub Load_WVP(projectfile As String) Try - Dim tsList As List(Of TimeSeries) - Call Log.AddLogEntry(Log.levels.info, $"Loading Wave project file '{projectfile}'...") Dim wvp As New Parsers.WVP(projectfile) - tsList = wvp.Process() + Dim tsList As List(Of TimeSeries) = wvp.Process() Call Log.AddLogEntry(Log.levels.info, $"Imported {tsList.Count} timeseries") diff --git a/source/Wave.vbproj b/source/Wave.vbproj index bfa712e2..f3280213 100644 --- a/source/Wave.vbproj +++ b/source/Wave.vbproj @@ -388,6 +388,7 @@ Settings.settings True + From 254033a319a6939c4970d0ea85b6097ff033b043 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:47:52 +0100 Subject: [PATCH 03/22] Make FileReference a proper class and add docstrings --- source/Parsers/Parser.vb | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/source/Parsers/Parser.vb b/source/Parsers/Parser.vb index dcf8bcac..b97b4d25 100644 --- a/source/Parsers/Parser.vb +++ b/source/Parsers/Parser.vb @@ -23,13 +23,33 @@ Namespace Parsers Public MustInherit Class Parser ''' - ''' Holds reference to a file to be imported, file import settings and the series to be imported from that file with their options + ''' A reference to a file to be imported with all information needed for importing series from that file ''' - Protected Structure FileReference - Dim path As String 'path to file - Dim series As Dictionary(Of String, SeriesOptions) 'series name -> options - Dim settings As Dictionary(Of String, String) 'file import settings as key value pairs, e.g. separator, date format, etc. - End Structure + Protected Class FileReference + + ''' + ''' The path to the file to be imported + ''' + Public path As String + + ''' + ''' The series to be imported from the file with their options, where the key is the name of the series as it appears in the file and the value is a SeriesOptions object containing options for importing that series + ''' An empty dictionary means that all series contained in the file should be imported with default options + ''' + Public series As Dictionary(Of String, SeriesOptions) + + ''' + ''' File import settings as key value pairs, e.g. separator, date format, etc. + ''' An empty dictionary means that default settings should be used for importing the file. + ''' + Public settings As Dictionary(Of String, String) + + Public Sub New() + Me.series = New Dictionary(Of String, SeriesOptions) + Me.settings = New Dictionary(Of String, String) + End Sub + + End Class ''' ''' Holds options for importing a series, e.g. display options, custom title and unit, etc. From b70a36c8dcda2d7d3e397ad412a01ad4ed42faf7 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:48:40 +0100 Subject: [PATCH 04/22] Add TalsimClipboard Parser class --- source/Parsers/TalsimClipboard.vb | 211 ++++++++++++++++++++++++++++++ source/Wave.vb | 173 +++--------------------- source/Wave.vbproj | 1 + 3 files changed, 227 insertions(+), 158 deletions(-) create mode 100644 source/Parsers/TalsimClipboard.vb diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb new file mode 100644 index 00000000..0ca019a0 --- /dev/null +++ b/source/Parsers/TalsimClipboard.vb @@ -0,0 +1,211 @@ +'BlueM.Wave +'Copyright (C) BlueM Dev Group +' +' +'This program is free software: you can redistribute it and/or modify +'it under the terms of the GNU Lesser General Public License as published by +'the Free Software Foundation, either version 3 of the License, or +'(at your option) any later version. +' +'This program is distributed in the hope that it will be useful, +'but WITHOUT ANY WARRANTY; without even the implied warranty of +'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +'GNU Lesser General Public License for more details. +' +'You should have received a copy of the GNU Lesser General Public License +'along with this program. If not, see . +' +Imports System.Text.RegularExpressions + +Namespace Parsers + + ''' + ''' Class for parsing Talsim clipboard content + ''' + Public Class TalsimClipboard + Inherits Parser + + Public Sub New(clipboardtext As String) + MyBase.New(inputtext:=clipboardtext) + End Sub + + ''' + ''' Parses the clipboard content + ''' + Protected Overrides Sub Parse() + + 'Examples: + + '[SETTINGS] + 'Count = 1 + '[Zeitreihe1] + 'SydroTyp=SydroErgZre + 'ZRFormat=4 + 'ID=362 + 'Extension=.WEL + 'Kennung=S000 + 'KennungLang={S000} {1AB, HYO} Ablauf_1 + 'Zustand=1AB + 'Datei=D:\Talsim-NG\customers\WVER\projectData\felix\dataBase\Felix_data\00000362.WEL + 'GeaendertAm= + 'Modell=TALSIM + 'Herkunft=simuliert + 'Interpretation=2 + 'SimVariante=Test Langzeit/HWMerkmal_HWGK_v02 + 'Simulation=Test Langzeit + 'Einheit=m3/s + 'EndZeitreihe + + '[SETTINGS] + 'Count=2 + '[Zeitreihe1] + 'SydroTyp=SydroBinZre + 'ZRFormat=99 + 'ID=1041 + 'Extension=.BIN + 'Kennung=Sce10, E038, C38 (TA_Tekeze TK 04B) + 'KennungLang=Sce10, E038, C38 (TA_Tekeze TK 04B), m3/s + 'Datei=C:\Talsim-NG\customers\Nile\projectData\hubert\dataBase\hubert_zre\00001041.BIN + 'Einheit=m3/s + 'Modell=TALSIM + 'Interpretation=1 + 'EndZeitreihe + '[Zeitreihe2] + 'SydroTyp=SydroBinZre + 'ZRFormat=99 + 'ID=1042 + 'Extension=.BIN + 'Kennung=Sce10, E039, C39 (TA_TK5) + 'KennungLang=Sce10, E039, C39 (TA_TK5), m3/s + 'Datei=C:\Talsim-NG\customers\Nile\projectData\hubert\dataBase\hubert_zre\00001042.BIN + 'Einheit=m3/s + 'Modell=TALSIM + 'Interpretation=1 + 'EndZeitreihe + + 'parse clipboard contents + Dim m As Match + Dim i_series As Integer + Dim parts() As String + Dim zreblock As Boolean + Dim data As New List(Of Dictionary(Of String, String)) '[{zreparams1},{zreparams2},...] + Dim file, name As String + + zreblock = False + For Each line As String In InputText.Split(eol) + + line = line.Trim() + + m = Regex.Match(line, "\[Zeitreihe(\d+)\]") + If m.Success Then + i_series = m.Groups(1).Value + data.Add(New Dictionary(Of String, String)) + zreblock = True + End If + + If zreblock Then + If line.Contains("=") Then + parts = line.Split("=") + data(i_series - 1).Add(parts(0), parts(1)) + ElseIf line = "EndZeitreihe" Then + zreblock = False + Continue For + End If + End If + + Next + + If data.Count = 0 Then + Throw New Exception("No series could be parsed from TALSIM clipboard content!") + End If + + 'initiate parsing of series + For Each params As Dictionary(Of String, String) In data + + ' check all required parameters are present + Dim expectedKeys As New List(Of String) From { + "Datei", + "ZRFormat", + "Kennung", + "Interpretation" + } + For Each key As String In expectedKeys + If Not params.ContainsKey(key) Then + Throw New Exception($"Missing required entry '{key}' in clipboard content!") + End If + Next + + file = params("Datei") + + Select Case params("ZRFormat") + Case "4" 'WEL file + + If Not params.ContainsKey("Zustand") Then + Throw New Exception("Missing required entry 'Zustand' in clipboard content!") + End If + + 'build series name + If params("Kennung") = "ZPG" Then + 'handle control groups + name = "KGRP_" & params("Zustand") + Else + name = params("Kennung").PadRight(4, " ") & "_" & params("Zustand") + End If + + If Not IO.File.Exists(file) Then + 'A WEL/WBL file may be zipped within a WLZIP file, so try extracting it from there + If Fileformats.WEL.extractFromWLZIP(file) Then + 'TODO #136: we should ideally clean up the extracted file later + End If + End If + + 'convert interpretation value to string + Dim interpretationValue As Integer = Integer.Parse(params("Interpretation")) + Dim interpretationString As String = [Enum].GetName(GetType(TimeSeries.InterpretationEnum), interpretationValue) + + Dim fileRef As New FileReference() With { + .path = file, + .series = New Dictionary(Of String, SeriesOptions)() From { + {name, New SeriesOptions() With {.interpretation = interpretationString}} + } + } + FileReferences.Add(fileRef) + + Case "99" 'BIN file + + If Not params.ContainsKey("Einheit") Then + Throw New Exception("Missing required entry 'Einheit' in clipboard content!") + End If + + name = params("Kennung") + + 'convert interpretation value to string + Dim interpretationValue As Integer = Integer.Parse(params("Interpretation")) + Dim interpretationString As String = [Enum].GetName(GetType(TimeSeries.InterpretationEnum), interpretationValue) + + Dim fileRef As New FileReference() With { + .path = file, + .series = New Dictionary(Of String, SeriesOptions)() From { + { + IO.Path.GetFileName(file), New SeriesOptions() With { + .title = name, + .unit = params("Einheit"), + .interpretation = interpretationString + } + } + } + } + FileReferences.Add(fileRef) + + Case Else + Throw New Exception($"Unsupported value {params("ZRFormat")} for ZRFormat!") + + End Select + + Next + + End Sub + + End Class + +End Namespace \ No newline at end of file diff --git a/source/Wave.vb b/source/Wave.vb index ee01cfff..a7110afc 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -283,170 +283,27 @@ Public Class Wave ''' Private Sub LoadFromClipboard_TALSIM(clipboardtext As String) - 'Examples: - - '[SETTINGS] - 'Count = 1 - '[Zeitreihe1] - 'SydroTyp=SydroErgZre - 'ZRFormat=4 - 'ID=362 - 'Extension=.WEL - 'Kennung=S000 - 'KennungLang={S000} {1AB, HYO} Ablauf_1 - 'Zustand=1AB - 'Datei=D:\Talsim-NG\customers\WVER\projectData\felix\dataBase\Felix_data\00000362.WEL - 'GeaendertAm= - 'Modell=TALSIM - 'Herkunft=simuliert - 'Interpretation=2 - 'SimVariante=Test Langzeit/HWMerkmal_HWGK_v02 - 'Simulation=Test Langzeit - 'Einheit=m3/s - 'EndZeitreihe - - '[SETTINGS] - 'Count=2 - '[Zeitreihe1] - 'SydroTyp=SydroBinZre - 'ZRFormat=99 - 'ID=1041 - 'Extension=.BIN - 'Kennung=Sce10, E038, C38 (TA_Tekeze TK 04B) - 'KennungLang=Sce10, E038, C38 (TA_Tekeze TK 04B), m3/s - 'Datei=C:\Talsim-NG\customers\Nile\projectData\hubert\dataBase\hubert_zre\00001041.BIN - 'Einheit=m3/s - 'Modell=TALSIM - 'Interpretation=1 - 'EndZeitreihe - '[Zeitreihe2] - 'SydroTyp=SydroBinZre - 'ZRFormat=99 - 'ID=1042 - 'Extension=.BIN - 'Kennung=Sce10, E039, C39 (TA_TK5) - 'KennungLang=Sce10, E039, C39 (TA_TK5), m3/s - 'Datei=C:\Talsim-NG\customers\Nile\projectData\hubert\dataBase\hubert_zre\00001042.BIN - 'Einheit=m3/s - 'Modell=TALSIM - 'Interpretation=1 - 'EndZeitreihe - - 'parse clipboard contents - Dim m As Match - Dim i_series As Integer - Dim parts() As String - Dim zreblock As Boolean - Dim data As New List(Of Dictionary(Of String, String)) '[{zreparams1},{zreparams2},...] - Dim file, name As String - Dim fileInstance As TimeSeriesFile - Dim ts As TimeSeries - - zreblock = False - For Each line As String In clipboardtext.Split(eol) - line = line.Trim() - - m = Regex.Match(line, "\[Zeitreihe(\d+)\]") - If m.Success Then - i_series = m.Groups(1).Value - data.Add(New Dictionary(Of String, String)) - zreblock = True - End If - - If zreblock Then - If line.Contains("=") Then - parts = line.Split("=") - data(i_series - 1).Add(parts(0), parts(1)) - ElseIf line = "EndZeitreihe" Then - zreblock = False - Continue For - End If - End If + Try + Call Log.AddLogEntry(Log.levels.info, $"Parsing Talsim clipboard content...") - Next + Dim wvp As New Parsers.TalsimClipboard(clipboardtext) + Dim tsList As List(Of TimeSeries) = wvp.Process() - If data.Count = 0 Then - Throw New Exception("No series could be parsed from TALSIM clipboard content!") - End If + Call Log.AddLogEntry(Log.levels.info, $"Imported {tsList.Count} timeseries") - 'initiate loading of series - For Each params As Dictionary(Of String, String) In data - - ' check all required parameters are present - Dim expectedKeys As New List(Of String) From { - "Datei", - "ZRFormat", - "Kennung", - "Interpretation" - } - For Each key As String In expectedKeys - If Not params.ContainsKey(key) Then - Throw New Exception($"Missing required entry '{key}' in clipboard content!") - End If + 'import the series + Call Log.AddLogEntry(Log.levels.info, "Loading series in chart...") + For Each ts As TimeSeries In tsList + Call Me.Import_Series(ts) Next - file = params("Datei") - - Select Case params("ZRFormat") - Case "4" 'WEL file - - If Not params.ContainsKey("Zustand") Then - Throw New Exception("Missing required entry 'Zustand' in clipboard content!") - End If - - 'build series name - If params("Kennung") = "ZPG" Then - 'handle control groups - name = "KGRP_" & params("Zustand") - Else - name = params("Kennung").PadRight(4, " ") & "_" & params("Zustand") - End If - - 'read file - Log.AddLogEntry(Log.levels.info, $"Loading file {file} ...") - fileInstance = TimeSeriesFile.getInstance(file) - - 'read series from file - ts = fileInstance.getTimeSeries(name) - - 'set interpretation - ts.Interpretation = params("Interpretation") - - 'import series - Call Me.Import_Series(ts) - - Case "99" 'BIN file - - If Not params.ContainsKey("Einheit") Then - Throw New Exception("Missing required entry 'Einheit' in clipboard content!") - End If - - name = params("Kennung") - - 'read file - Log.AddLogEntry(Log.levels.info, $"Loading file {file} ...") - fileInstance = TimeSeriesFile.getInstance(file) - - 'read series from file - fileInstance.readFile() - ts = fileInstance.TimeSeries.First.Value - - 'add metadata - ts.Title = name - ts.Unit = params("Einheit") - - 'set interpretation - ts.Interpretation = params("Interpretation") - - 'import series - Call Me.Import_Series(ts) - - Case Else - Throw New Exception($"Unsupported value {params("ZRFormat")} for ZRFormat!") - - End Select + 'Log + Call Log.AddLogEntry(Log.levels.info, $"Talsim clipboard content parsed successfully!") - Next + Catch ex As Exception + MsgBox("Error while processing Talsim clipboard content:" & eol & ex.Message, MsgBoxStyle.Critical) + Call Log.AddLogEntry(Log.levels.error, "Error while processing Talsim clipboard content:" & eol & ex.Message) + End Try End Sub diff --git a/source/Wave.vbproj b/source/Wave.vbproj index f3280213..15813c08 100644 --- a/source/Wave.vbproj +++ b/source/Wave.vbproj @@ -389,6 +389,7 @@ True + From 739862a40b014f602e8340e0fde34f3cfaf90430 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:01:27 +0100 Subject: [PATCH 05/22] add a Parser.verifyFormat method --- source/Parsers/Parser.vb | 5 +++++ source/Parsers/TalsimClipboard.vb | 10 ++++++++++ source/Wave.vb | 31 +++++++++++++------------------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/source/Parsers/Parser.vb b/source/Parsers/Parser.vb index b97b4d25..b171db8b 100644 --- a/source/Parsers/Parser.vb +++ b/source/Parsers/Parser.vb @@ -96,6 +96,11 @@ Namespace Parsers Call Me.Parse() End Sub + Public Shared Function verifyFormat() As Boolean + 'default implementation always returns true, can be overridden in subclasses to implement specific format verification + Return True + End Function + ''' ''' Parses the input and stores the results in the FileReferences list ''' diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index 0ca019a0..a17bb950 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -29,6 +29,16 @@ Namespace Parsers MyBase.New(inputtext:=clipboardtext) End Sub + Public Overloads Shared Function verifyFormat(clipboardtext As String) As Boolean + 'check if content contains expected header + If clipboardtext.Contains("SydroTyp=SydroErgZre") Or + clipboardtext.Contains("SydroTyp=SydroBinZre") Then + Return True + Else + Return False + End If + End Function + ''' ''' Parses the clipboard content ''' diff --git a/source/Wave.vb b/source/Wave.vb index a7110afc..df376953 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -238,30 +238,25 @@ Public Class Wave Dim clipboardtext As String clipboardtext = Clipboard.GetText(TextDataFormat.Text) - If clipboardtext.Contains("SydroTyp=SydroErgZre") Or - clipboardtext.Contains("SydroTyp=SydroBinZre") Then - 'it's a clipboard entry from TALSIM! - + If Parsers.TalsimClipboard.verifyFormat(clipboardtext) Then 'ask the user for confirmation dlgres = MessageBox.Show($"TALSIM clipboard content detected!{eol}Load series in Wave?", "Load from clipboard", MessageBoxButtons.YesNo, MessageBoxIcon.Question) - If Not dlgres = Windows.Forms.DialogResult.Yes Then - Exit Sub + If dlgres = Windows.Forms.DialogResult.Yes Then + Call Me.LoadFromClipboard_TALSIM(clipboardtext) End If - Call Me.LoadFromClipboard_TALSIM(clipboardtext) Else - 'ask the user whether to attempt plain text import + 'ask the user whether to attempt CSV import dlgres = MessageBox.Show("Attempt to load clipboard text content in Wave as CSV data?", "Load from clipboard", MessageBoxButtons.YesNo, MessageBoxIcon.Question) - If Not dlgres = Windows.Forms.DialogResult.Yes Then - Exit Sub + If dlgres = Windows.Forms.DialogResult.Yes Then + 'save as temp text file and then load file + Dim tmpfile As String = IO.Path.GetTempFileName() + Using writer As New IO.StreamWriter(tmpfile, False, Helpers.DefaultEncoding) + writer.Write(clipboardtext) + End Using + Call Me.Import_File(tmpfile) + 'delete temp file after import + IO.File.Delete(tmpfile) End If - 'save as temp text file and then load file - Dim tmpfile As String = IO.Path.GetTempFileName() - Using writer As New IO.StreamWriter(tmpfile, False, Helpers.DefaultEncoding) - writer.Write(clipboardtext) - End Using - Call Me.Import_File(tmpfile) - 'delete temp file after import - IO.File.Delete(tmpfile) End If Else MessageBox.Show("No usable clipboard content detected!", "Load from clipboard", MessageBoxButtons.OK, MessageBoxIcon.Error) From 974172cb5d890b521038cefc01be2d995e989661 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:15:07 +0100 Subject: [PATCH 06/22] remove call to WEL.extractFromWLZIP from TimeSeriesFile, as it is now handled by TalsimClipboard --- source/Classes/TimeSeriesFile.vb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/source/Classes/TimeSeriesFile.vb b/source/Classes/TimeSeriesFile.vb index 480b17d5..ef841db9 100644 --- a/source/Classes/TimeSeriesFile.vb +++ b/source/Classes/TimeSeriesFile.vb @@ -664,14 +664,7 @@ Public MustInherit Class TimeSeriesFile 'Check whether the file exists If Not IO.File.Exists(file) Then - 'A WEL/WBL file may be zipped within a WLZIP file, so try extracting it from there - If fileExt = FileExtensions.WEL Or fileExt = FileExtensions.WBL Then - If Not Fileformats.WEL.extractFromWLZIP(file) Then - Throw New IO.FileNotFoundException($"File '{file}' not found!") - End If - Else - Throw New IO.FileNotFoundException($"File '{file}' not found!") - End If + Throw New IO.FileNotFoundException($"File '{file}' not found!") End If 'set default From b74dc4f5b5d9bc02a067f72f5a92bb7aa104f4f1 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:48:08 +0100 Subject: [PATCH 07/22] overload Parser.Process in TalsimClipboard so that we can delete any WEL files that we extract from WLZIP files after they have been processed #136 --- source/Parsers/TalsimClipboard.vb | 39 +++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index a17bb950..bf5ab30f 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -162,13 +162,6 @@ Namespace Parsers name = params("Kennung").PadRight(4, " ") & "_" & params("Zustand") End If - If Not IO.File.Exists(file) Then - 'A WEL/WBL file may be zipped within a WLZIP file, so try extracting it from there - If Fileformats.WEL.extractFromWLZIP(file) Then - 'TODO #136: we should ideally clean up the extracted file later - End If - End If - 'convert interpretation value to string Dim interpretationValue As Integer = Integer.Parse(params("Interpretation")) Dim interpretationString As String = [Enum].GetName(GetType(TimeSeries.InterpretationEnum), interpretationValue) @@ -216,6 +209,38 @@ Namespace Parsers End Sub + Public Overloads Function Process() As List(Of TimeSeries) + + 'check for WEL/WBL that do not exist and try to extract them from WLZIP files if possible + Dim extractedFiles As New List(Of String) + For Each fileRef As FileReference In FileReferences + If Not IO.File.Exists(fileRef.path) Then + Dim fileExt As String = IO.Path.GetExtension(fileRef.path).ToUpper() + If fileExt = TimeSeriesFile.FileExtensions.WEL Or fileExt = TimeSeriesFile.FileExtensions.WBL Then + If Fileformats.WEL.extractFromWLZIP(fileRef.path) Then + 'remember the file so that we can delete it later + extractedFiles.Add(fileRef.path) + End If + End If + End If + Next + + 'process the files as usual + Dim tsList As List(Of TimeSeries) = MyBase.Process() + + 'delete any extracted files + For Each file As String In extractedFiles + Try + IO.File.Delete(file) + Catch ex As Exception + 'ignore any errors during deletion + End Try + Next + + Return tsList + + End Function + End Class End Namespace \ No newline at end of file From a4ad6813c36ff1d8bf1de3659e5450ec52d3a64b Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:17:58 +0100 Subject: [PATCH 08/22] move Talsim-specific tests to a separate class and add a new test TestWLZIPFromClipboard --- source/Wave.vb | 2 +- tests/TestImport.vb | 22 ------------ tests/TestTalsim.vb | 79 +++++++++++++++++++++++++++++++++++++++++ tests/Wave.Tests.vbproj | 1 + 4 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 tests/TestTalsim.vb diff --git a/source/Wave.vb b/source/Wave.vb index df376953..e513210a 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -276,7 +276,7 @@ Public Class Wave ''' ''' text content of the clipboard ''' - Private Sub LoadFromClipboard_TALSIM(clipboardtext As String) + Public Sub LoadFromClipboard_TALSIM(clipboardtext As String) Try Call Log.AddLogEntry(Log.levels.info, $"Parsing Talsim clipboard content...") diff --git a/tests/TestImport.vb b/tests/TestImport.vb index 67c49151..a03c04ab 100644 --- a/tests/TestImport.vb +++ b/tests/TestImport.vb @@ -24,28 +24,6 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting Public Class TestImport - ''' - ''' Tests finding a WEL file within a WLZIP file and extracting it - ''' - - Public Sub TestWLZIP() - - Dim file_wel As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "TALSIM.WEL") - - 'delete existing file before testing - If IO.File.Exists(file_wel) Then - IO.File.Delete(file_wel) - End If - - 'attempt to extract from WLZIP - Dim success As Boolean = Fileformats.WEL.extractFromWLZIP(file_wel) - - Assert.IsTrue(success) - Assert.IsTrue(IO.File.Exists(file_wel)) - - End Sub - - ''' ''' Tests importing all supported time series file formats ''' diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb new file mode 100644 index 00000000..4d1981bd --- /dev/null +++ b/tests/TestTalsim.vb @@ -0,0 +1,79 @@ +'BlueM.Wave +'Copyright (C) BlueM Dev Group +' +' +'This program is free software: you can redistribute it and/or modify +'it under the terms of the GNU Lesser General Public License as published by +'the Free Software Foundation, either version 3 of the License, or +'(at your option) any later version. +' +'This program is distributed in the hope that it will be useful, +'but WITHOUT ANY WARRANTY; without even the implied warranty of +'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +'GNU Lesser General Public License for more details. +' +'You should have received a copy of the GNU Lesser General Public License +'along with this program. If not, see . +' +Imports System.Text +Imports Microsoft.VisualStudio.TestTools.UnitTesting + +''' +''' Tests Talsim-specific functionality +''' + +Public Class TestTalsim + + ''' + ''' Tests finding a WEL file within a WLZIP file and extracting it + ''' + + Public Sub TestWLZIP() + + Dim file_wel As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "TALSIM.WEL") + + 'delete existing file before testing + If IO.File.Exists(file_wel) Then + IO.File.Delete(file_wel) + End If + + 'attempt to extract from WLZIP + Dim success As Boolean = Fileformats.WEL.extractFromWLZIP(file_wel) + + Assert.IsTrue(success) + Assert.IsTrue(IO.File.Exists(file_wel)) + + 'clean up by deleting the extracted file + IO.File.Delete(file_wel) + + End Sub + + + ''' + ''' Tests loading a WEL file within a WLZIP file when processing Talsim clipboard data + ''' + + Public Sub TestWLZIPFromTalsimClipboard() + + Dim file_clipboard As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "Clipboard_Talsim_WLZIP.txt") + Dim file_wel As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "TALSIM.WEL") + + 'delete existing file before testing + If IO.File.Exists(file_wel) Then + IO.File.Delete(file_wel) + End If + + Dim clipboardData As String = IO.File.ReadAllText(file_clipboard) + + Dim wave As New BlueM.Wave.Wave() + wave.LoadFromClipboard_TALSIM(clipboardData) + + 'check that time series was loaded into app + Assert.IsTrue(wave.TimeSeries.Count > 0) + + 'check that extracted file was deleted + Assert.IsFalse(IO.File.Exists(file_wel)) + + End Sub + +End Class \ No newline at end of file diff --git a/tests/Wave.Tests.vbproj b/tests/Wave.Tests.vbproj index 75eff3b0..c69e658c 100644 --- a/tests/Wave.Tests.vbproj +++ b/tests/Wave.Tests.vbproj @@ -92,6 +92,7 @@ + From 602727c2cb7f203283ee6069b64e4b3f8b4e6f11 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:06:09 +0100 Subject: [PATCH 09/22] move extractFromWLZIP method to TalsimClipboard --- source/FileFormats/WEL.vb | 50 ----------------------------- source/Parsers/TalsimClipboard.vb | 52 ++++++++++++++++++++++++++++++- tests/TestTalsim.vb | 2 +- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/source/FileFormats/WEL.vb b/source/FileFormats/WEL.vb index e8080b3c..2dd1edc3 100644 --- a/source/FileFormats/WEL.vb +++ b/source/FileFormats/WEL.vb @@ -310,56 +310,6 @@ Namespace Fileformats End Function - ''' - ''' Attempts to extract a specified WEL file from a WLZIP file of the same name - ''' - ''' path to WEL file - ''' True if successful - ''' TALSIM specific - Public Shared Function extractFromWLZIP(file As String) As Boolean - - Dim file_wlzip As String - Dim filename As String - Dim zipEntryFound As Boolean = False - Dim success As Boolean = False - - 'determine WLZIP filename for files ending with .WEL (may also be e.g. .KTR.WEL, .CHLO.WEL, etc.) - Dim m As Match = Regex.Match(file, "^(.+?)(\.[a-z]+)?\.WEL$", RegexOptions.IgnoreCase) - If m.Success Then - file_wlzip = $"{m.Groups(1)}.WLZIP" - - If IO.File.Exists(file_wlzip) Then - - Log.AddLogEntry(Log.levels.info, $"Looking for file in {file_wlzip} ...") - filename = IO.Path.GetFileName(file) - - Dim zip As IO.Compression.ZipArchive = IO.Compression.ZipFile.OpenRead(file_wlzip) - - For Each entry As IO.Compression.ZipArchiveEntry In zip.Entries - If entry.Name.ToLower() = filename.ToLower() Then - zipEntryFound = True - 'extract file from zip archive - Log.AddLogEntry(Log.levels.info, $"Extracting file from {file_wlzip} ...") - Dim fs As New IO.FileStream(file, FileMode.CreateNew) - entry.Open().CopyTo(fs) - fs.Flush() - fs.Close() - success = True - Exit For - End If - Next - - If Not zipEntryFound Then - Log.AddLogEntry(Log.levels.error, $"File {filename} not found in {file_wlzip}!") - End If - - End If - End If - - Return success - - End Function - #End Region 'Methoden End Class diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index bf5ab30f..82f83d40 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -217,7 +217,7 @@ Namespace Parsers If Not IO.File.Exists(fileRef.path) Then Dim fileExt As String = IO.Path.GetExtension(fileRef.path).ToUpper() If fileExt = TimeSeriesFile.FileExtensions.WEL Or fileExt = TimeSeriesFile.FileExtensions.WBL Then - If Fileformats.WEL.extractFromWLZIP(fileRef.path) Then + If TalsimClipboard.extractFromWLZIP(fileRef.path) Then 'remember the file so that we can delete it later extractedFiles.Add(fileRef.path) End If @@ -241,6 +241,56 @@ Namespace Parsers End Function + ''' + ''' Attempts to extract a specified WEL file from a WLZIP file of the same name + ''' + ''' path to WEL file + ''' True if successful + ''' TALSIM specific + Public Shared Function extractFromWLZIP(file As String) As Boolean + + Dim file_wlzip As String + Dim filename As String + Dim zipEntryFound As Boolean = False + Dim success As Boolean = False + + 'determine WLZIP filename for files ending with .WEL (may also be e.g. .KTR.WEL, .CHLO.WEL, etc.) + Dim m As Match = Regex.Match(file, "^(.+?)(\.[a-z]+)?\.WEL$", RegexOptions.IgnoreCase) + If m.Success Then + file_wlzip = $"{m.Groups(1)}.WLZIP" + + If IO.File.Exists(file_wlzip) Then + + Log.AddLogEntry(Log.levels.info, $"Looking for file in {file_wlzip} ...") + filename = IO.Path.GetFileName(file) + + Dim zip As IO.Compression.ZipArchive = IO.Compression.ZipFile.OpenRead(file_wlzip) + + For Each entry As IO.Compression.ZipArchiveEntry In zip.Entries + If entry.Name.ToLower() = filename.ToLower() Then + zipEntryFound = True + 'extract file from zip archive + Log.AddLogEntry(Log.levels.info, $"Extracting file from {file_wlzip} ...") + Dim fs As New IO.FileStream(file, IO.FileMode.CreateNew) + entry.Open().CopyTo(fs) + fs.Flush() + fs.Close() + success = True + Exit For + End If + Next + + If Not zipEntryFound Then + Log.AddLogEntry(Log.levels.error, $"File {filename} not found in {file_wlzip}!") + End If + + End If + End If + + Return success + + End Function + End Class End Namespace \ No newline at end of file diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb index 4d1981bd..5e0b1ae1 100644 --- a/tests/TestTalsim.vb +++ b/tests/TestTalsim.vb @@ -38,7 +38,7 @@ Public Class TestTalsim End If 'attempt to extract from WLZIP - Dim success As Boolean = Fileformats.WEL.extractFromWLZIP(file_wel) + Dim success As Boolean = Parsers.TalsimClipboard.extractFromWLZIP(file_wel) Assert.IsTrue(success) Assert.IsTrue(IO.File.Exists(file_wel)) From c130e17fe51a2a170aed24dec1a3a1b0a96cc6ae Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:45:04 +0100 Subject: [PATCH 10/22] update changelog --- source/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/CHANGELOG.md b/source/CHANGELOG.md index d0b24602..4154da71 100644 --- a/source/CHANGELOG.md +++ b/source/CHANGELOG.md @@ -3,7 +3,14 @@ BlueM.Wave Release Notes Version X.X.X ------------- +FIXED: +* WEL files extracted from WLZIP archives when processing Talsim clipboard data are now deleted after use #136 + +CHANGED: +* Automatic extraction of WEL files from WLZIP archives now only occurs when processing Talsim clipboard data + API-CHANGES: +* New namespace `Parsers` containing classes for parsing file import instructions such as WVP files and Talsim clipboard content * Renamed `TimeSeriesFile.Write_File()` functions to `writeFile()` for consistency Version 2.16.0 From 1811845f8fd96e9388ed8b731324edcbb9d3f1e1 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:05:35 +0100 Subject: [PATCH 11/22] simplify by reusing TimeSeriesDisplayOptions class --- source/Parsers/Parser.vb | 31 ++++++++++--------------------- source/Parsers/WVP.vb | 22 +++++++--------------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/source/Parsers/Parser.vb b/source/Parsers/Parser.vb index b171db8b..5b704e1f 100644 --- a/source/Parsers/Parser.vb +++ b/source/Parsers/Parser.vb @@ -54,15 +54,15 @@ Namespace Parsers ''' ''' Holds options for importing a series, e.g. display options, custom title and unit, etc. ''' - Protected Structure SeriesOptions - Dim title As String - Dim unit As String - Dim color As String - Dim linestyle As String - Dim linewidth As String - Dim interpretation As String - Dim showpoints As String - End Structure + Protected Class SeriesOptions + Public title As String + Public unit As String + Public interpretation As String + Public displayOptions As TimeSeriesDisplayOptions + Public Sub New() + Me.displayOptions = New TimeSeriesDisplayOptions() + End Sub + End Class ''' ''' The path to the input file @@ -212,18 +212,7 @@ Namespace Parsers End If End If 'display options - If Not IsNothing(options.color) Then - ts.DisplayOptions.SetColor(options.color) - End If - If Not IsNothing(options.linestyle) Then - ts.DisplayOptions.SetLineStyle(options.linestyle) - End If - If Not IsNothing(options.linewidth) Then - ts.DisplayOptions.SetLineWidth(options.linewidth) - End If - If Not IsNothing(options.showpoints) Then - ts.DisplayOptions.SetShowPoints(options.showpoints) - End If + ts.DisplayOptions = options.displayOptions End If 'store the time series in the list tsList.Add(ts) diff --git a/source/Parsers/WVP.vb b/source/Parsers/WVP.vb index bcf475d2..38970734 100644 --- a/source/Parsers/WVP.vb +++ b/source/Parsers/WVP.vb @@ -98,15 +98,7 @@ Namespace Parsers If m.Success Then seriesName = m.Groups("name").Value.Trim() 'check for additional series options - 'by default, series options are nothing - Dim options As New SeriesOptions With { - .title = Nothing, - .unit = Nothing, - .color = Nothing, - .linestyle = Nothing, - .linewidth = Nothing, - .interpretation = Nothing - } + Dim options As New SeriesOptions() If m.Groups("options").Success Then 'parse series options Dim optionString As String = m.Groups("optionstring").Value.Trim() @@ -126,16 +118,16 @@ Namespace Parsers options.title = value Case "unit" options.unit = value + Case "interpretation" + options.interpretation = value Case "color" - options.color = value + options.displayOptions.SetColor(value) Case "linestyle" - options.linestyle = value + options.displayOptions.SetLineStyle(value) Case "linewidth" - options.linewidth = value - Case "interpretation" - options.interpretation = value + options.displayOptions.SetLineWidth(value) Case "showpoints" - options.showpoints = value + options.displayOptions.SetShowPoints(value) Case Else Log.AddLogEntry(levels.warning, $"Series import option keyword {keyword} not recognized!") End Select From a77110d19918a2294bcd89d5467cf2f743945546 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:20:04 +0100 Subject: [PATCH 12/22] centralize parsing of interpretation from string and integer --- source/Helpers.vb | 30 ++++++++++++++++++++++++++++++ source/Parsers/Parser.vb | 10 +++------- source/Parsers/TalsimClipboard.vb | 23 +++++++++++------------ source/Parsers/WVP.vb | 2 +- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/source/Helpers.vb b/source/Helpers.vb index fad32966..9f1e30b9 100644 --- a/source/Helpers.vb +++ b/source/Helpers.vb @@ -123,6 +123,36 @@ Public Module Helpers End Function + ''' + ''' Parses a string to a TimeSeries.InterpretationEnum value. + ''' If the string does not match any of the enum names, TimeSeries.InterpretationEnum.Undefined is returned and a warning is logged. + ''' + ''' The string to be parsed + ''' The corresponding TimeSeries.InterpretationEnum value + Public Function ParseInterpretation(interpretationString As String) As TimeSeries.InterpretationEnum + If Not [Enum].IsDefined(GetType(TimeSeries.InterpretationEnum), interpretationString) Then + Log.AddLogEntry(levels.warning, $"Interpretation {interpretationString} is not recognized!") + Return TimeSeries.InterpretationEnum.Undefined + Else + Return [Enum].Parse(GetType(TimeSeries.InterpretationEnum), interpretationString) + End If + End Function + + ''' + ''' Parses an integer to a TimeSeries.InterpretationEnum value. + ''' If the integer does not match any of the enum values, TimeSeries.InterpretationEnum.Undefined is returned and a warning is logged. + ''' + ''' The integer value to be parsed + ''' The corresponding TimeSeries.InterpretationEnum value + Public Function ParseInterpretation(interpretationValue As Integer) As TimeSeries.InterpretationEnum + If Not [Enum].IsDefined(GetType(TimeSeries.InterpretationEnum), interpretationValue) Then + Log.AddLogEntry(levels.warning, $"Interpretation {interpretationValue} is not recognized!") + Return TimeSeries.InterpretationEnum.Undefined + Else + Return [Enum].Parse(GetType(TimeSeries.InterpretationEnum), interpretationValue) + End If + End Function + ''' ''' Returns a specified color palette ''' diff --git a/source/Parsers/Parser.vb b/source/Parsers/Parser.vb index 5b704e1f..301c3fb3 100644 --- a/source/Parsers/Parser.vb +++ b/source/Parsers/Parser.vb @@ -57,7 +57,7 @@ Namespace Parsers Protected Class SeriesOptions Public title As String Public unit As String - Public interpretation As String + Public interpretation As TimeSeries.InterpretationEnum? Public displayOptions As TimeSeriesDisplayOptions Public Sub New() Me.displayOptions = New TimeSeriesDisplayOptions() @@ -204,12 +204,8 @@ Namespace Parsers If Not IsNothing(options.unit) Then ts.Unit = options.unit End If - If Not IsNothing(options.interpretation) Then - If Not [Enum].IsDefined(GetType(TimeSeries.InterpretationEnum), options.interpretation) Then - Log.AddLogEntry(levels.warning, $"Interpretation {options.interpretation} is not recognized!") - Else - ts.Interpretation = [Enum].Parse(GetType(TimeSeries.InterpretationEnum), options.interpretation) - End If + If options.interpretation.HasValue Then + ts.Interpretation = options.interpretation End If 'display options ts.DisplayOptions = options.displayOptions diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index 82f83d40..c3f1577f 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -162,14 +162,15 @@ Namespace Parsers name = params("Kennung").PadRight(4, " ") & "_" & params("Zustand") End If - 'convert interpretation value to string - Dim interpretationValue As Integer = Integer.Parse(params("Interpretation")) - Dim interpretationString As String = [Enum].GetName(GetType(TimeSeries.InterpretationEnum), interpretationValue) + Dim options As New SeriesOptions() With { + .title = name, + .interpretation = Helpers.ParseInterpretation(Integer.Parse(params("Interpretation"))) + } Dim fileRef As New FileReference() With { .path = file, .series = New Dictionary(Of String, SeriesOptions)() From { - {name, New SeriesOptions() With {.interpretation = interpretationString}} + {name, options} } } FileReferences.Add(fileRef) @@ -182,19 +183,17 @@ Namespace Parsers name = params("Kennung") - 'convert interpretation value to string - Dim interpretationValue As Integer = Integer.Parse(params("Interpretation")) - Dim interpretationString As String = [Enum].GetName(GetType(TimeSeries.InterpretationEnum), interpretationValue) + Dim options As New SeriesOptions() With { + .title = name, + .unit = params("Einheit"), + .interpretation = Helpers.ParseInterpretation(Integer.Parse(params("Interpretation"))) + } Dim fileRef As New FileReference() With { .path = file, .series = New Dictionary(Of String, SeriesOptions)() From { { - IO.Path.GetFileName(file), New SeriesOptions() With { - .title = name, - .unit = params("Einheit"), - .interpretation = interpretationString - } + IO.Path.GetFileName(file), options } } } diff --git a/source/Parsers/WVP.vb b/source/Parsers/WVP.vb index 38970734..ec7103de 100644 --- a/source/Parsers/WVP.vb +++ b/source/Parsers/WVP.vb @@ -119,7 +119,7 @@ Namespace Parsers Case "unit" options.unit = value Case "interpretation" - options.interpretation = value + options.interpretation = Helpers.ParseInterpretation(value) Case "color" options.displayOptions.SetColor(value) Case "linestyle" From 430913e00dca2e7600a63f7ac50f9729c77b040e Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:57:20 +0100 Subject: [PATCH 13/22] check for optional "Einheit" entry --- source/Parsers/TalsimClipboard.vb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index c3f1577f..55e85f95 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -166,6 +166,9 @@ Namespace Parsers .title = name, .interpretation = Helpers.ParseInterpretation(Integer.Parse(params("Interpretation"))) } + If params.ContainsKey("Einheit") Then + options.unit = params("Einheit") + End If Dim fileRef As New FileReference() With { .path = file, From a1d4562cc7167a4ca6454b4c6cd49834ced123e4 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:57:41 +0100 Subject: [PATCH 14/22] add more tests for Talsim clipboard --- tests/TestTalsim.vb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb index 5e0b1ae1..18e7e9a4 100644 --- a/tests/TestTalsim.vb +++ b/tests/TestTalsim.vb @@ -24,6 +24,25 @@ Imports Microsoft.VisualStudio.TestTools.UnitTesting Public Class TestTalsim + + + + + + + + Public Sub TestTestTalsimClipboard(clipboardFile As String) + + Dim clipboardData As String = IO.File.ReadAllText(IO.Path.Combine(TestData.getTestDataDir(), clipboardFile)) + + Dim wave As New BlueM.Wave.Wave() + wave.LoadFromClipboard_TALSIM(clipboardData) + + 'check that time series were imported + Assert.IsTrue(wave.TimeSeries.Count > 0) + + End Sub + ''' ''' Tests finding a WEL file within a WLZIP file and extracting it ''' @@ -68,7 +87,7 @@ Public Class TestTalsim Dim wave As New BlueM.Wave.Wave() wave.LoadFromClipboard_TALSIM(clipboardData) - 'check that time series was loaded into app + 'check that time series was imported Assert.IsTrue(wave.TimeSeries.Count > 0) 'check that extracted file was deleted From f4d855ad1c6b9d30babd756194d735339ba7679b Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:06:32 +0100 Subject: [PATCH 15/22] add docstring and warning log entry on failure to delete extracted file #136 --- source/Parsers/TalsimClipboard.vb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index 55e85f95..cd639b61 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -211,6 +211,12 @@ Namespace Parsers End Sub + ''' + ''' Wraps the base Process() method to check for WEL/WBL files that do not exist + ''' and attempt to extract them from WLZIP files if possible before processing, + ''' and then deletes any extracted files after processing + ''' + ''' A list of processed time series Public Overloads Function Process() As List(Of TimeSeries) 'check for WEL/WBL that do not exist and try to extract them from WLZIP files if possible @@ -235,7 +241,7 @@ Namespace Parsers Try IO.File.Delete(file) Catch ex As Exception - 'ignore any errors during deletion + Log.AddLogEntry(Log.levels.warning, $"Could not delete extracted file {file}: {ex.Message}") End Try Next From a80c94c0bb0807bf4c71eaa06b00925099be636e Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:06:46 +0100 Subject: [PATCH 16/22] update changelog --- source/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/source/CHANGELOG.md b/source/CHANGELOG.md index 4154da71..c870175c 100644 --- a/source/CHANGELOG.md +++ b/source/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGED: API-CHANGES: * New namespace `Parsers` containing classes for parsing file import instructions such as WVP files and Talsim clipboard content +* New helper method `Helpers.ParseInterpretation()` for parsing interpretation from strings and integers * Renamed `TimeSeriesFile.Write_File()` functions to `writeFile()` for consistency Version 2.16.0 From 95fc2bce92ebb4a7ede9aa4f8eea79821dd7bf54 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:11:18 +0100 Subject: [PATCH 17/22] rename test method --- tests/TestTalsim.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb index 18e7e9a4..97db44fc 100644 --- a/tests/TestTalsim.vb +++ b/tests/TestTalsim.vb @@ -31,7 +31,7 @@ Public Class TestTalsim - Public Sub TestTestTalsimClipboard(clipboardFile As String) + Public Sub TestTalsimClipboard(clipboardFile As String) Dim clipboardData As String = IO.File.ReadAllText(IO.Path.Combine(TestData.getTestDataDir(), clipboardFile)) From db4bff0b1075d4c833c5559973d54dde5726cf8f Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:34:04 +0100 Subject: [PATCH 18/22] upload test results even if tests fail --- .github/workflows/run-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 234ad500..e0fb6d7f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -37,6 +37,7 @@ jobs: dotnet test BlueM.Wave\tests\bin\x64\Debug\Wave.Tests.dll --settings BlueM.Wave\tests\tests.runsettings - name: Upload test results + if: always() uses: actions/upload-artifact@v6 with: name: test-results From e17dd37073d07edcade91db6c61a891496d8b6bc Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:38:47 +0100 Subject: [PATCH 19/22] more robust splitting of lines in clipboard text --- source/Parsers/TalsimClipboard.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Parsers/TalsimClipboard.vb b/source/Parsers/TalsimClipboard.vb index cd639b61..7e5b15ff 100644 --- a/source/Parsers/TalsimClipboard.vb +++ b/source/Parsers/TalsimClipboard.vb @@ -102,7 +102,7 @@ Namespace Parsers Dim file, name As String zreblock = False - For Each line As String In InputText.Split(eol) + For Each line As String In InputText.Split(New String() {vbCr, vbLf}, StringSplitOptions.RemoveEmptyEntries) line = line.Trim() From 81c8ad2a9aa6cb77f13585fd416d0116b6053db9 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:39:09 +0100 Subject: [PATCH 20/22] make LoadFromClipboard_TALSIM private again --- source/Wave.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Wave.vb b/source/Wave.vb index e513210a..df376953 100644 --- a/source/Wave.vb +++ b/source/Wave.vb @@ -276,7 +276,7 @@ Public Class Wave ''' ''' text content of the clipboard ''' - Public Sub LoadFromClipboard_TALSIM(clipboardtext As String) + Private Sub LoadFromClipboard_TALSIM(clipboardtext As String) Try Call Log.AddLogEntry(Log.levels.info, $"Parsing Talsim clipboard content...") From 31a1cc7f9e3cc03c4dbf9e577420490abfdc39e4 Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:44:00 +0100 Subject: [PATCH 21/22] do not instantiate a Wave instance when testing as that can cause a messagebox to appear and cause the test to hang --- tests/TestTalsim.vb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb index 97db44fc..5bb81b81 100644 --- a/tests/TestTalsim.vb +++ b/tests/TestTalsim.vb @@ -35,11 +35,11 @@ Public Class TestTalsim Dim clipboardData As String = IO.File.ReadAllText(IO.Path.Combine(TestData.getTestDataDir(), clipboardFile)) - Dim wave As New BlueM.Wave.Wave() - wave.LoadFromClipboard_TALSIM(clipboardData) + Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) + Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() - 'check that time series were imported - Assert.IsTrue(wave.TimeSeries.Count > 0) + 'check that time series were created + Assert.IsTrue(tsList.Count > 0) End Sub @@ -84,11 +84,11 @@ Public Class TestTalsim Dim clipboardData As String = IO.File.ReadAllText(file_clipboard) - Dim wave As New BlueM.Wave.Wave() - wave.LoadFromClipboard_TALSIM(clipboardData) + Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) + Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() - 'check that time series was imported - Assert.IsTrue(wave.TimeSeries.Count > 0) + 'check that time series were created + Assert.IsTrue(tsList.Count > 0) 'check that extracted file was deleted Assert.IsFalse(IO.File.Exists(file_wel)) From b5d3cf9a776cbca2f0305b3cee58095a549b4cca Mon Sep 17 00:00:00 2001 From: jamaa <90166+jamaa@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:44:34 +0100 Subject: [PATCH 22/22] change workdir in Talsim tests to allow for use of relative paths in test clipboard files --- tests/TestData.vb | 21 ++++++++++----- tests/TestTalsim.vb | 62 ++++++++++++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/tests/TestData.vb b/tests/TestData.vb index de6ab3d4..fcd1cbdd 100644 --- a/tests/TestData.vb +++ b/tests/TestData.vb @@ -20,19 +20,26 @@ ''' Module TestData + ''' + ''' Returns the absolute path to the parent directory of the repository + ''' by going up 5 levels from the application directory + ''' + Friend Function getParentDir() As String + Dim appdir As String = My.Application.Info.DirectoryPath() 'e.g. BlueM.Wave\tests\bin\x64\Debug + Dim rootdir As String = appdir + For i As Integer = 1 To 5 + rootdir = IO.Directory.GetParent(rootdir).FullName + Next + Return rootdir + End Function + ''' ''' Returns the absolute path to the test data directory BlueM.Datasets\Wave ''' which is expected to be in the same directory as BlueM.Wave ''' - ''' Friend Function getTestDataDir() As String Try - Dim appdir As String = My.Application.Info.DirectoryPath() 'e.g. BlueM.Wave\tests\bin\x64\Debug - Dim testdatadir As String = appdir - For i As Integer = 1 To 5 - testdatadir = IO.Directory.GetParent(testdatadir).FullName - Next - testdatadir = IO.Path.Combine(testdatadir, "BlueM.Datasets", "Wave") + Dim testdatadir As String = IO.Path.Combine(getParentDir(), "BlueM.Datasets", "Wave") If Not IO.Directory.Exists(testdatadir) Then Throw New AssertInconclusiveException($"Directory {testdatadir} does not exist.") End If diff --git a/tests/TestTalsim.vb b/tests/TestTalsim.vb index 5bb81b81..c9dc1a7e 100644 --- a/tests/TestTalsim.vb +++ b/tests/TestTalsim.vb @@ -33,13 +33,26 @@ Public Class TestTalsim Public Sub TestTalsimClipboard(clipboardFile As String) - Dim clipboardData As String = IO.File.ReadAllText(IO.Path.Combine(TestData.getTestDataDir(), clipboardFile)) + Dim workdir = IO.Directory.GetCurrentDirectory() + Try + 'set current directory to test assemply path to ensure any relative paths in the clipboard data are correct + IO.Directory.SetCurrentDirectory(My.Application.Info.DirectoryPath()) - Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) - Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() + Dim clipboardData As String = IO.File.ReadAllText(IO.Path.Combine(TestData.getTestDataDir(), clipboardFile)) - 'check that time series were created - Assert.IsTrue(tsList.Count > 0) + Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) + Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() + + 'check that time series were created + Assert.IsTrue(tsList.Count > 0) + + Catch ex As Exception + Assert.Fail("Exception occurred: " & ex.Message) + + Finally + 'restore original working directory + IO.Directory.SetCurrentDirectory(workdir) + End Try End Sub @@ -74,24 +87,37 @@ Public Class TestTalsim Public Sub TestWLZIPFromTalsimClipboard() - Dim file_clipboard As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "Clipboard_Talsim_WLZIP.txt") - Dim file_wel As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "TALSIM.WEL") + Dim workdir = IO.Directory.GetCurrentDirectory() + Try + 'set current directory to test assemply path to ensure any relative paths in the clipboard data are correct + IO.Directory.SetCurrentDirectory(My.Application.Info.DirectoryPath()) - 'delete existing file before testing - If IO.File.Exists(file_wel) Then - IO.File.Delete(file_wel) - End If + Dim file_clipboard As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "Clipboard_Talsim_WLZIP.txt") + Dim file_wel As String = IO.Path.Combine(TestData.getTestDataDir(), "Talsim", "TALSIM.WEL") + + 'delete existing file before testing + If IO.File.Exists(file_wel) Then + IO.File.Delete(file_wel) + End If + + Dim clipboardData As String = IO.File.ReadAllText(file_clipboard) + + Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) + Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() - Dim clipboardData As String = IO.File.ReadAllText(file_clipboard) + 'check that time series were created + Assert.IsTrue(tsList.Count > 0) - Dim talsimclipboard As New Parsers.TalsimClipboard(clipboardData) - Dim tsList As List(Of TimeSeries) = talsimclipboard.Process() + 'check that extracted file was deleted + Assert.IsFalse(IO.File.Exists(file_wel)) - 'check that time series were created - Assert.IsTrue(tsList.Count > 0) + Catch ex As Exception + Assert.Fail("Exception occurred: " & ex.Message) - 'check that extracted file was deleted - Assert.IsFalse(IO.File.Exists(file_wel)) + Finally + 'restore original working directory + IO.Directory.SetCurrentDirectory(workdir) + End Try End Sub