Imports System.Reflection Imports System.Text.RegularExpressions Imports DigitalData.Modules.Logging Public Class PropertyValues Private _logger As Logger Private _logConfig As LogConfig Private _indexPattern = "\((\d+)\)" Private _indexRegex As New Regex(_indexPattern) Public Sub New(LogConfig As LogConfig) _logConfig = LogConfig _logger = LogConfig.GetLogger() End Sub Public Class CheckPropertyValuesResult Public MissingProperties As New List(Of MissingProperty) Public ValidProperties As New List(Of ValidProperty) End Class Public Class ValidProperty Public MessageId As String Public TableName As String Public TableColumn As String Public IsRequired As Boolean Public GroupCounter As Integer = -1 Public Description As String Public Value As String Public XMLPath As String Public ItemType As Integer = 0 End Class Public Class MissingProperty Public Description As String Public XMLPath As String Public Overrides Function ToString() As String Return XMLPath End Function End Class Public Function CheckPropertyValues(pDocument As Object, PropertyMap As Dictionary(Of String, XmlItemProperty), MessageId As String) As CheckPropertyValuesResult Dim oGlobalGroupCounter = 0 Dim oMissingProperties As New List(Of String) Dim oResult As New CheckPropertyValuesResult() ' PropertyMap items with `IsGrouped = False` are handled normally Dim oDefaultProperties As Dictionary(Of String, XmlItemProperty) = PropertyMap. Where(Function(Item) Item.Value.IsGrouped = False). ToDictionary(Function(Item) Item.Key, Function(Item) Item.Value) _logger.Debug("Found {0} ungrouped properties.", oDefaultProperties.Count) ' PropertyMap items with `IsGrouped = True` are grouped by group scope Dim oGroupedProperties = PropertyMap. Where(Function(Item) Item.Value.IsGrouped = True). ToLookup(Function(Item) Item.Value.GroupScope, ' Lookup key is group scope Function(Item) Item) _logger.Debug($"Found [{PropertyMap.Count - oDefaultProperties.Count}] properties grouped in [{oGroupedProperties.Count}] group(s)") ' Iterate through groups to get group scope and group items For Each oGroup In oGroupedProperties Dim oGroupScope As String = oGroup.Key Dim oPropertyList As New Dictionary(Of XmlItemProperty, List(Of Object)) Dim oRowCount = 0 _logger.Debug($"Fetching Property values for group [{oGroupScope}].") ' get properties as a nested object, see `oPropertyList` For Each oProperty As KeyValuePair(Of String, XmlItemProperty) In oGroup Dim oPropertyValues As List(Of Object) _logger.Debug($"Fetching value for itemSpecification [{oProperty.Value.TableColumn}].") Try oPropertyValues = GetPropValue(pDocument, oProperty.Key) Catch ex As Exception _logger.Warn($"{MessageId} - Unknown error occurred while fetching property/TColumn [{0}] in group [{1}]:", oProperty.Value.TableColumn, oGroupScope) _logger.Error(ex) oPropertyValues = New List(Of Object) End Try ' check the first batch of values to determine the row count If oRowCount = 0 Then '08.04.2025 MS Added as Workaround for Positions Or oGroupScope = "POSITIONS" ! oRowCount = oPropertyValues.Count End If ' Flatten result value oPropertyValues = GetFinalPropValue(oPropertyValues) ' Add to list oPropertyList.Add(oProperty.Value, oPropertyValues) Next ' Structure of oPropertyList ' [ # Propertyname # Row 1 # Row 2 ' PositionsMenge: [BilledQuantity1, BilledQuantity2, ...], ' PositionsSteuersatz: [ApplicablePercent1, ApplicablePercent2, ...], ' ... ' ] For oRowIndex = 0 To oRowCount - 1 _logger.Debug("Processing row {0}", oRowIndex) For Each oColumn As KeyValuePair(Of XmlItemProperty, List(Of Object)) In oPropertyList Dim oTableName As String = oColumn.Key.TableName Dim oTableColumn As String = oColumn.Key.TableColumn Dim oIsRequired As Boolean = oColumn.Key.IsRequired Dim oPropertyDescription As String = oColumn.Key.Description Dim oPropertyPath As String = oColumn.Key.XMLPath Dim oItemType As Integer = oColumn.Key.ItemType Dim oRowCounter = oRowIndex + oGlobalGroupCounter + 1 ' Returns nothing if oColumn.Value contains an empty list Dim oPropertyValue = oColumn.Value.ElementAtOrDefault(oRowIndex) _logger.Debug("Processing itemColumn *TableColumn* [{0}].", oTableColumn) If oTableColumn = "INVOICE_SELLER_EMAIL" Then Console.WriteLine("INVOICE_SELLER_EMAIL") ElseIf oTableColumn = "INVOICE_POSITION_ARTICLE" Then Console.WriteLine("INVOICE_POSITION_ARTICLE") End If If IsNothing(oPropertyValue) OrElse String.IsNullOrEmpty(oPropertyValue) Then If oColumn.Key.IsRequired Then _logger.Warn($"{MessageId} # oPropertyValue for column [{oTableColumn}] is empty or not found but is required. Continuing with Empty String.") Dim oMissingProperty = New MissingProperty() With { .Description = oPropertyDescription, .XMLPath = oPropertyPath } oResult.MissingProperties.Add(oMissingProperty) Else _logger.Debug($"{MessageId} # oPropertyValue for column [{oTableColumn}] is empty or not found. Continuing with Empty String.") End If oPropertyValue = String.Empty End If If (oPropertyValue IsNot Nothing) Then Dim logValue As String = oPropertyValue.ToString() If logValue.Length > 50 Then _logger.Debug("Item [{0}] has value '{1}...'", oTableColumn, logValue.Substring(1, 50)) Else _logger.Debug("Item [{0}] has value '{1}'", oTableColumn, oPropertyValue) End If End If oResult.ValidProperties.Add(New ValidProperty() With { .MessageId = MessageId, .Description = oPropertyDescription, .Value = oPropertyValue, .GroupCounter = oRowCounter, .TableName = oTableName, .TableColumn = oTableColumn, .IsRequired = oIsRequired, .XMLPath = oPropertyPath, .ItemType = oItemType }) Next Next oGlobalGroupCounter += oRowCount Next ' Iterate through default properties For Each oItem As KeyValuePair(Of String, XmlItemProperty) In oDefaultProperties Dim oPropertyValueList As List(Of Object) Dim oTableColumn As String = oItem.Value.TableColumn Dim oPropertyDescription As String = oItem.Value.Description Dim oPropertyPath As String = oItem.Value.XMLPath Dim oPropertyValue As Object = Nothing Dim oTableName = oItem.Value.TableName Dim oIsRequired = oItem.Value.IsRequired Dim oDescription = oItem.Value.Description Dim oItemType = oItem.Value.ItemType Try oPropertyValueList = GetPropValue(pDocument, oItem.Key) Catch ex As Exception _logger.Warn("{2} # Unknown error occurred while fetching specification [{0}] in group [{1}]:", oPropertyDescription, oItem.Value.GroupScope, MessageId) _logger.Warn("ERROR-MESSAGE [{0}]", ex.Message) _logger.Error(ex) oPropertyValueList = New List(Of Object) End Try Try If IsNothing(oPropertyValueList) Then oPropertyValue = Nothing ElseIf TypeOf oPropertyValueList Is List(Of Object) Then Select Case oPropertyValueList.Count Case 0 oPropertyValue = Nothing Case Else Dim oList As List(Of Object) = DirectCast(oPropertyValueList, List(Of Object)) oPropertyValue = oList.Item(0) ' This should hopefully show config errors If TypeOf oPropertyValue Is List(Of Object) Then _logger.Warn("Item with specification [{0}] may be configured incorrectly", oPropertyDescription) oPropertyValue = Nothing End If End Select End If Catch ex As Exception _logger.Warn("Unknown error occurred while processing specification [{0}]:", oPropertyDescription) _logger.Error(ex) oPropertyValue = Nothing End Try If IsNothing(oPropertyValue) OrElse String.IsNullOrEmpty(oPropertyValue) Then If oItem.Value.IsRequired Then _logger.Warn("{0} # Specification [{1}] is empty, but marked as required! Skipping.", MessageId, oPropertyDescription) Dim oMissingProperty = New MissingProperty With { .Description = oPropertyDescription, .XMLPath = oPropertyPath } oResult.MissingProperties.Add(oMissingProperty) Continue For Else _logger.Debug("{0} # oPropertyValue for specification [{1}] is empty or not found. Skipping.", MessageId, oPropertyDescription) Continue For End If End If ' Statt dem Zahlenwert des Enums, wollen wir die Währunsgbezeichnung If oTableColumn = "INVOICE_CURRENCY" Then oPropertyValue = oPropertyValue.ToString() End If oResult.ValidProperties.Add(New ValidProperty() With { .MessageId = MessageId, .Description = oDescription, .Value = oPropertyValue, .TableName = oTableName, .TableColumn = oTableColumn, .IsRequired = oIsRequired, .XMLPath = oPropertyPath, .ItemType = oItemType }) Next Return oResult End Function Public Function GetPropValue(Obj As Object, PropertyName As String) As List(Of Object) Dim oNameParts As String() = PropertyName.Split("."c) If IsNothing(Obj) Then _logger.Debug("`Obj` is Nothing. Exiting.") Return New List(Of Object) End If If oNameParts.Length = 1 Then Dim oPropInfo As PropertyInfo = Obj.GetType().GetProperty(PropertyName) If IsNothing(oPropInfo) Then _logger.Debug("Property [{0}] does not exist(1).", PropertyName) Return New List(Of Object) Else Dim oPropValue = oPropInfo.GetValue(Obj, Nothing) Return New List(Of Object) From {oPropValue} End If End If For Each oPart As String In oNameParts Dim oType As Type = Obj.GetType() Dim oPartName = oPart Dim oIndex As Integer = Nothing Dim oHasIndex As Boolean = HasIndex(oPartName) If oHasIndex Then oPartName = StripIndex(oPart) oIndex = GetIndex(oPart) End If Dim oInfo As PropertyInfo = oType.GetProperty(oPartName) If IsNothing(oInfo) OrElse IsNothing(oInfo.GetValue(Obj, Nothing)) Then _logger.Debug("Property [{0}] does not exist(2).", oPartName) Return New List(Of Object) End If Obj = oInfo.GetValue(Obj, Nothing) ' TODO: This code should check for array properties by itself ' and should not rely on the user to 'If oInfo.PropertyType.IsArray Then ' Obj = Obj(0) 'End If If oHasIndex Then Obj = Obj(0) End If If IsArray(Obj) And Not oHasIndex And oPart <> "Value" Then Dim oCurrentPart As String = oPart Dim oSplitString As String() = New String() {oCurrentPart & "."} Dim oPathFragments = PropertyName.Split(oSplitString, StringSplitOptions.None) Dim oResults As New List(Of Object) ' if path has no more subitems, return an empty list If oPathFragments.Length = 1 Then Return oResults End If For Each oArrayItem In Obj Dim oResult As List(Of Object) = GetPropValue(oArrayItem, oPathFragments(1)) If Not IsNothing(oResult) Then oResults.Add(oResult) End If Next Return oResults End If Next Return New List(Of Object) From {Obj} End Function Public Function GetFinalPropValue(List As List(Of Object)) As List(Of Object) Dim oResult As New List(Of Object) For Each Item In List Dim oItemValue = DoGetFinalPropValue(Item) If Not IsNothing(oItemValue) Then oResult.Add(oItemValue) End If Next Return oResult End Function Private Function DoGetFinalPropValue(Value As Object) As String If TypeOf Value Is List(Of Object) Then Dim oList = DirectCast(Value, List(Of Object)) Dim oCount = oList.Count Select Case oCount Case 0 Return Nothing Case 1 Dim firstElement As Object firstElement = oList.FirstOrDefault() If firstElement IsNot Nothing AndAlso IsArray(firstElement) Then ' Attachments sind Byte-Arrays und müssen umgewandelt werden Return Convert.ToBase64String(firstElement) Else Return DoGetFinalPropValue(oList.First()) End If Case Else Return DoGetFinalPropValue(oList.First()) End Select Return DoGetFinalPropValue(Value) Else ' Nothing kann auch der Default-Wert der Variablen bedeuten. Also auch 0.00 If IsNothing(Value) Then Return String.Empty Else Return Value.ToString End If End If End Function Private Function GetIndex(Prop As String) As Integer If Regex.IsMatch(Prop, _indexPattern) Then Dim oMatch = _indexRegex.Match(Prop) Dim oGroup = oMatch.Groups.Item(1) Dim oValue = oGroup.Value Return Integer.Parse(oValue) End If Return Nothing End Function Private Function StripIndex(Prop As String) As String Return Regex.Replace(Prop, _indexPattern, "") End Function Private Function HasIndex(Prop As String) As Boolean Return Regex.IsMatch(Prop, _indexPattern) End Function End Class