ZUGFeRD: Validate errors in xml and throw ValidationException
This commit is contained in:
parent
0da1eb55a9
commit
524c429de4
@ -111,6 +111,7 @@
|
|||||||
<DependentUpon>Settings.settings</DependentUpon>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="ZUGFeRDInterface\Validator.vb" />
|
||||||
<Compile Include="ZUGFeRDInterface\Version1.0\CrossIndustryDocumentType.vb" />
|
<Compile Include="ZUGFeRDInterface\Version1.0\CrossIndustryDocumentType.vb" />
|
||||||
<Compile Include="ZUGFeRDInterface.vb" />
|
<Compile Include="ZUGFeRDInterface.vb" />
|
||||||
<Compile Include="ZUGFeRDInterface\FileGroups.vb" />
|
<Compile Include="ZUGFeRDInterface\FileGroups.vb" />
|
||||||
|
|||||||
@ -15,6 +15,7 @@ Public Class ZUGFeRDInterface
|
|||||||
Private ReadOnly _logConfig As LogConfig
|
Private ReadOnly _logConfig As LogConfig
|
||||||
Private ReadOnly _logger As Logger
|
Private ReadOnly _logger As Logger
|
||||||
Private ReadOnly _Options As ZugferdOptions
|
Private ReadOnly _Options As ZugferdOptions
|
||||||
|
Private ReadOnly _Validator As Validator
|
||||||
|
|
||||||
' These constants define the specification markers for the different
|
' These constants define the specification markers for the different
|
||||||
' zugferd document schema versions. These markers need to be used to
|
' zugferd document schema versions. These markers need to be used to
|
||||||
@ -39,7 +40,6 @@ Public Class ZUGFeRDInterface
|
|||||||
UnsupportedFormat
|
UnsupportedFormat
|
||||||
FileTooBig
|
FileTooBig
|
||||||
UnknownError
|
UnknownError
|
||||||
ValidationFailed
|
|
||||||
End Enum
|
End Enum
|
||||||
|
|
||||||
Public ReadOnly Property FileGroup As FileGroups
|
Public ReadOnly Property FileGroup As FileGroups
|
||||||
@ -54,7 +54,6 @@ Public Class ZUGFeRDInterface
|
|||||||
|
|
||||||
Public Class ZugferdResult
|
Public Class ZugferdResult
|
||||||
Public Property DataFileName As String
|
Public Property DataFileName As String
|
||||||
Public Property XPathObject As XPathDocument
|
|
||||||
Public Property XElementObject As XElement
|
Public Property XElementObject As XElement
|
||||||
Public Property SchemaObject As Object
|
Public Property SchemaObject As Object
|
||||||
Public Property Specification As String
|
Public Property Specification As String
|
||||||
@ -76,6 +75,7 @@ Public Class ZUGFeRDInterface
|
|||||||
Public Sub New(pLogConfig As LogConfig, pGDPictureKey As String, Optional pOptions As ZugferdOptions = Nothing)
|
Public Sub New(pLogConfig As LogConfig, pGDPictureKey As String, Optional pOptions As ZugferdOptions = Nothing)
|
||||||
_logConfig = pLogConfig
|
_logConfig = pLogConfig
|
||||||
_logger = _logConfig.GetLogger()
|
_logger = _logConfig.GetLogger()
|
||||||
|
_Validator = New Validator(_logConfig)
|
||||||
|
|
||||||
If pOptions Is Nothing Then
|
If pOptions Is Nothing Then
|
||||||
_Options = New ZugferdOptions()
|
_Options = New ZugferdOptions()
|
||||||
@ -138,9 +138,11 @@ Public Class ZUGFeRDInterface
|
|||||||
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path)
|
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path)
|
||||||
oResult = ValidateZUGFeRDDocument(oResult)
|
oResult = ValidateZUGFeRDDocument(oResult)
|
||||||
|
|
||||||
'If IsNothing(oResult.SchemaObject) Then
|
If oResult.ValidationErrors.Any() Then
|
||||||
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
|
Throw New ValidationException() With {
|
||||||
'End If
|
.ValidationErrors = oResult.ValidationErrors
|
||||||
|
}
|
||||||
|
End If
|
||||||
|
|
||||||
Return SerializeZUGFeRDDocument(oResult)
|
Return SerializeZUGFeRDDocument(oResult)
|
||||||
End Function
|
End Function
|
||||||
@ -154,9 +156,16 @@ Public Class ZUGFeRDInterface
|
|||||||
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream)
|
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream)
|
||||||
oResult = ValidateZUGFeRDDocument(oResult)
|
oResult = ValidateZUGFeRDDocument(oResult)
|
||||||
|
|
||||||
'If IsNothing(oResult.SchemaObject) Then
|
If oResult.ValidationErrors.Any() Then
|
||||||
' Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
|
_logger.Info("Validation found [{0}] errors", oResult.ValidationErrors.Count)
|
||||||
'End If
|
oResult.ValidationErrors.ForEach(
|
||||||
|
Sub(e) _logger.Info("Field [{0}] with value [{1}] has error: [{2}]", e.ElementName, e.ElementValue, e.ErrorMessage)
|
||||||
|
)
|
||||||
|
|
||||||
|
Throw New ValidationException() With {
|
||||||
|
.ValidationErrors = oResult.ValidationErrors
|
||||||
|
}
|
||||||
|
End If
|
||||||
|
|
||||||
Return SerializeZUGFeRDDocument(oResult)
|
Return SerializeZUGFeRDDocument(oResult)
|
||||||
End Function
|
End Function
|
||||||
@ -248,22 +257,13 @@ Public Class ZUGFeRDInterface
|
|||||||
End If
|
End If
|
||||||
|
|
||||||
Try
|
Try
|
||||||
Dim oXPathObject As XPathDocument = Nothing
|
|
||||||
Using oStream As New MemoryStream(oAllowedResult.FileContents)
|
Using oStream As New MemoryStream(oAllowedResult.FileContents)
|
||||||
oXPathObject = New XPathDocument(oStream)
|
Return New ZugferdResult With {
|
||||||
|
.DataFileName = oAllowedResult.FileName,
|
||||||
|
.XElementObject = XElement.Load(oStream)
|
||||||
|
}
|
||||||
End Using
|
End Using
|
||||||
|
|
||||||
Dim oXElementObject As XElement = Nothing
|
|
||||||
Using oStream As New MemoryStream(oAllowedResult.FileContents)
|
|
||||||
oXElementObject = XElement.Load(oStream)
|
|
||||||
End Using
|
|
||||||
|
|
||||||
Return New ZugferdResult With {
|
|
||||||
.DataFileName = oAllowedResult.FileName,
|
|
||||||
.XElementObject = oXElementObject,
|
|
||||||
.XPathObject = oXPathObject
|
|
||||||
}
|
|
||||||
|
|
||||||
Catch ex As ZUGFeRDExecption
|
Catch ex As ZUGFeRDExecption
|
||||||
' Don't log ZUGFeRD Exceptions here, they should be handled by the calling code.
|
' Don't log ZUGFeRD Exceptions here, they should be handled by the calling code.
|
||||||
' It also produces misleading error messages when checking if an attachment is a zugferd file.
|
' It also produces misleading error messages when checking if an attachment is a zugferd file.
|
||||||
@ -281,93 +281,11 @@ Public Class ZUGFeRDInterface
|
|||||||
End Class
|
End Class
|
||||||
|
|
||||||
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
||||||
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()
|
Return _Validator.ValidateZUGFeRDDocument(pResult)
|
||||||
Dim oNamespaceManager As New XmlNamespaceManager(oNavigator.NameTable)
|
|
||||||
|
|
||||||
oNamespaceManager.AddNamespace("ram", "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12")
|
|
||||||
oNamespaceManager.AddNamespace("rsm", "urn:ferd:CrossIndustryDocument:invoice:1p0")
|
|
||||||
|
|
||||||
Try
|
|
||||||
Dim oDecimalNodes = pResult.XElementObject.Descendants().
|
|
||||||
Where(Function(n) n.Name.ToString.EndsWith("Amount") Or n.Name.ToString.EndsWith("Percent"))
|
|
||||||
|
|
||||||
For Each oNode As XElement In oDecimalNodes
|
|
||||||
Dim oParsedValue As Decimal = 0.0
|
|
||||||
If Decimal.TryParse(oNode.Value, oParsedValue) = False Then
|
|
||||||
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
|
||||||
.ElementName = oNode.Name.LocalName,
|
|
||||||
.ElementValue = oNode.Value,
|
|
||||||
.ErrorMessage = "Value could not be parsed as Decimal"
|
|
||||||
})
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
|
|
||||||
Catch ex As Exception
|
|
||||||
_logger.Error(ex)
|
|
||||||
End Try
|
|
||||||
|
|
||||||
' CurrencyCode Nodes
|
|
||||||
Try
|
|
||||||
Dim oCurrencyCodeNodes = pResult.XElementObject.Descendants().
|
|
||||||
Where(Function(n) n.Name.ToString.EndsWith("CurrencyCode"))
|
|
||||||
|
|
||||||
For Each oNode As XElement In oCurrencyCodeNodes
|
|
||||||
Dim oValid = ValidateCurrencyCode(oNode.Value)
|
|
||||||
If oValid = False Then
|
|
||||||
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
|
||||||
.ElementName = oNode.Name.LocalName,
|
|
||||||
.ElementValue = oNode.Value,
|
|
||||||
.ErrorMessage = "Invalid CurrencyCode. Only 3-Character codes are allowed."
|
|
||||||
})
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Catch ex As Exception
|
|
||||||
_logger.Error(ex)
|
|
||||||
End Try
|
|
||||||
|
|
||||||
' currencyID
|
|
||||||
Try
|
|
||||||
Dim oCurrencyIDNodes = pResult.XElementObject.Descendants().
|
|
||||||
Where(Function(n) n.Attributes.Any(Function(a) a.Name.LocalName = "currencyID"))
|
|
||||||
|
|
||||||
For Each oNode As XElement In oCurrencyIDNodes
|
|
||||||
Dim oCurrencyID As String = oNode.Attribute("currencyID")?.Value
|
|
||||||
|
|
||||||
' CurrencyID is optional per spec
|
|
||||||
If String.IsNullOrWhiteSpace(oCurrencyID) Then
|
|
||||||
Continue For
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim oValid = ValidateCurrencyCode(oCurrencyID)
|
|
||||||
If oValid = False Then
|
|
||||||
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
|
||||||
.ElementName = oNode.Name.LocalName,
|
|
||||||
.ElementValue = oCurrencyID,
|
|
||||||
.ErrorMessage = "Invalid currencyID. Only 3-Character codes or empty values are allowed."
|
|
||||||
})
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
|
|
||||||
Catch ex As Exception
|
|
||||||
_logger.Error(ex)
|
|
||||||
End Try
|
|
||||||
|
|
||||||
Return pResult
|
|
||||||
End Function
|
|
||||||
|
|
||||||
Private Function ValidateCurrencyCode(pValue As String) As Boolean
|
|
||||||
Dim oValueRegex As New Text.RegularExpressions.Regex("[A-Z]{3}")
|
|
||||||
|
|
||||||
If oValueRegex.IsMatch(pValue) = False Then
|
|
||||||
Return False
|
|
||||||
End If
|
|
||||||
|
|
||||||
Return True
|
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
||||||
Try
|
Try
|
||||||
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()
|
|
||||||
Dim oReader As XmlReader
|
Dim oReader As XmlReader
|
||||||
|
|
||||||
Dim oObject As Object = Nothing
|
Dim oObject As Object = Nothing
|
||||||
@ -405,8 +323,7 @@ Public Class ZUGFeRDInterface
|
|||||||
_logger.Debug("Trying Type [{0}]", oTypeName)
|
_logger.Debug("Trying Type [{0}]", oTypeName)
|
||||||
|
|
||||||
Try
|
Try
|
||||||
oReader = oNavigator.ReadSubtree()
|
oReader = pResult.XElementObject.CreateReader()
|
||||||
|
|
||||||
oObject = oSerializer.Deserialize(oReader)
|
oObject = oSerializer.Deserialize(oReader)
|
||||||
oSpecification = oType.Specification
|
oSpecification = oType.Specification
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
Public Class Exceptions
|
Imports DigitalData.Modules.Interfaces.ZUGFeRDInterface
|
||||||
|
|
||||||
|
Public Class Exceptions
|
||||||
Public Class ZUGFeRDExecption
|
Public Class ZUGFeRDExecption
|
||||||
Inherits ApplicationException
|
Inherits ApplicationException
|
||||||
|
|
||||||
@ -23,4 +25,14 @@
|
|||||||
_XmlFile = pXmlFileName
|
_XmlFile = pXmlFileName
|
||||||
End Sub
|
End Sub
|
||||||
End Class
|
End Class
|
||||||
|
|
||||||
|
Public Class ValidationException
|
||||||
|
Inherits ApplicationException
|
||||||
|
|
||||||
|
Public ValidationErrors As List(Of ZugferdValidationError)
|
||||||
|
|
||||||
|
Public Sub New()
|
||||||
|
MyBase.New("ZUGFeRD document found but validation failed!")
|
||||||
|
End Sub
|
||||||
|
End Class
|
||||||
End Class
|
End Class
|
||||||
|
|||||||
98
Interfaces/ZUGFeRDInterface/Validator.vb
Normal file
98
Interfaces/ZUGFeRDInterface/Validator.vb
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
Imports DigitalData.Modules.Interfaces.ZUGFeRDInterface
|
||||||
|
Imports DigitalData.Modules.Logging
|
||||||
|
|
||||||
|
Public Class Validator
|
||||||
|
Private ReadOnly _logConfig As LogConfig
|
||||||
|
Private ReadOnly _logger As Logger
|
||||||
|
|
||||||
|
Public Sub New(pLogConfig As LogConfig)
|
||||||
|
_logConfig = pLogConfig
|
||||||
|
_logger = pLogConfig.GetLogger()
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
|
||||||
|
ValidateDecimalNodes(pResult)
|
||||||
|
ValidateCurrencyNodes(pResult)
|
||||||
|
|
||||||
|
Return pResult
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Private Sub ValidateDecimalNodes(ByRef pResult As ZugferdResult)
|
||||||
|
Try
|
||||||
|
Dim oDecimalNodes = pResult.XElementObject.Descendants().
|
||||||
|
Where(Function(n) n.Name.ToString.EndsWith("Amount") Or n.Name.ToString.EndsWith("Percent"))
|
||||||
|
|
||||||
|
For Each oNode As XElement In oDecimalNodes
|
||||||
|
Dim oParsedValue As Decimal = 0.0
|
||||||
|
If Decimal.TryParse(oNode.Value, oParsedValue) = False Then
|
||||||
|
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
||||||
|
.ElementName = oNode.Name.LocalName,
|
||||||
|
.ElementValue = oNode.Value,
|
||||||
|
.ErrorMessage = "Value could not be parsed as Decimal"
|
||||||
|
})
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub ValidateCurrencyNodes(ByRef pResult As ZugferdResult)
|
||||||
|
' CurrencyCode Nodes
|
||||||
|
Try
|
||||||
|
Dim oCurrencyCodeNodes = pResult.XElementObject.Descendants().
|
||||||
|
Where(Function(n) n.Name.ToString.EndsWith("CurrencyCode"))
|
||||||
|
|
||||||
|
For Each oNode As XElement In oCurrencyCodeNodes
|
||||||
|
Dim oValid = ValidateCurrencyCode(oNode.Value)
|
||||||
|
If oValid = False Then
|
||||||
|
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
||||||
|
.ElementName = oNode.Name.LocalName,
|
||||||
|
.ElementValue = oNode.Value,
|
||||||
|
.ErrorMessage = "Invalid CurrencyCode. Only 3-Character codes are allowed."
|
||||||
|
})
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Catch ex As Exception
|
||||||
|
_logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' currencyID
|
||||||
|
Try
|
||||||
|
Dim oCurrencyIDNodes = pResult.XElementObject.Descendants().
|
||||||
|
Where(Function(n) n.Attributes.Any(Function(a) a.Name.LocalName = "currencyID"))
|
||||||
|
|
||||||
|
For Each oNode As XElement In oCurrencyIDNodes
|
||||||
|
Dim oCurrencyID As String = oNode.Attribute("currencyID")?.Value
|
||||||
|
|
||||||
|
' CurrencyID is optional per spec
|
||||||
|
If String.IsNullOrWhiteSpace(oCurrencyID) Then
|
||||||
|
Continue For
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim oValid = ValidateCurrencyCode(oCurrencyID)
|
||||||
|
If oValid = False Then
|
||||||
|
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
|
||||||
|
.ElementName = oNode.Name.LocalName,
|
||||||
|
.ElementValue = oCurrencyID,
|
||||||
|
.ErrorMessage = "Invalid currencyID. Only 3-Character codes or empty values are allowed."
|
||||||
|
})
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Function ValidateCurrencyCode(pValue As String) As Boolean
|
||||||
|
Dim oValueRegex As New Text.RegularExpressions.Regex("[A-Z]{3}")
|
||||||
|
|
||||||
|
If oValueRegex.IsMatch(pValue) = False Then
|
||||||
|
Return False
|
||||||
|
End If
|
||||||
|
|
||||||
|
Return True
|
||||||
|
End Function
|
||||||
|
End Class
|
||||||
@ -77,14 +77,4 @@ Public Class Exceptions
|
|||||||
MyBase.New(pInfo)
|
MyBase.New(pInfo)
|
||||||
End Sub
|
End Sub
|
||||||
End Class
|
End Class
|
||||||
|
|
||||||
Public Class ValidationException
|
|
||||||
Inherits ApplicationException
|
|
||||||
|
|
||||||
Public ValidationErrors As List(Of ZugferdValidationError)
|
|
||||||
|
|
||||||
Public Sub New()
|
|
||||||
MyBase.New("ZUGFeRD document found but validation failed!")
|
|
||||||
End Sub
|
|
||||||
End Class
|
|
||||||
End Class
|
End Class
|
||||||
|
|||||||
@ -255,6 +255,9 @@ Public Class ImportZUGFeRDFiles
|
|||||||
Try
|
Try
|
||||||
oDocument = _zugferd.ExtractZUGFeRDFileWithGDPicture(oFile.FullName)
|
oDocument = _zugferd.ExtractZUGFeRDFileWithGDPicture(oFile.FullName)
|
||||||
|
|
||||||
|
Catch ex As ValidationException
|
||||||
|
Throw ex
|
||||||
|
|
||||||
Catch ex As ZUGFeRDExecption
|
Catch ex As ZUGFeRDExecption
|
||||||
Select Case ex.ErrorType
|
Select Case ex.ErrorType
|
||||||
Case ZUGFeRDInterface.ErrorType.NoZugferd
|
Case ZUGFeRDInterface.ErrorType.NoZugferd
|
||||||
@ -267,7 +270,7 @@ Public Class ImportZUGFeRDFiles
|
|||||||
Throw New UnsupportedFerdException(ex.XmlFile)
|
Throw New UnsupportedFerdException(ex.XmlFile)
|
||||||
|
|
||||||
Case ZUGFeRDInterface.ErrorType.NoValidZugferd
|
Case ZUGFeRDInterface.ErrorType.NoValidZugferd
|
||||||
_logger.Warn("File [{0}] is an Incorrectly formatted ZUGFeRD document!", oFile.Name)
|
_logger.Info("File [{0}] is an Incorrectly formatted ZUGFeRD document!", oFile.Name)
|
||||||
Throw New InvalidFerdException()
|
Throw New InvalidFerdException()
|
||||||
|
|
||||||
Case Else
|
Case Else
|
||||||
@ -276,17 +279,6 @@ Public Class ImportZUGFeRDFiles
|
|||||||
End Select
|
End Select
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
' These validation errors check the document according to the specification.
|
|
||||||
' Things like this will be checked:
|
|
||||||
' - Currency Codes
|
|
||||||
' - Country Codes
|
|
||||||
' - DateTime Formats
|
|
||||||
If oDocument.ValidationErrors.Any() Then
|
|
||||||
Throw New ValidationException() With {
|
|
||||||
.ValidationErrors = oDocument.ValidationErrors
|
|
||||||
}
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Extract all attachments with the extensions specified in `AllowedExtensions`.
|
' Extract all attachments with the extensions specified in `AllowedExtensions`.
|
||||||
' If you need to extract and use embedded xml files, you need to filter out the zugferd-invoice.xml yourself.
|
' If you need to extract and use embedded xml files, you need to filter out the zugferd-invoice.xml yourself.
|
||||||
' Right now the zugferd-invoice.xml is filtered out because `AllowedExtensions` does not contain `xml`.
|
' Right now the zugferd-invoice.xml is filtered out because `AllowedExtensions` does not contain `xml`.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user