Implemented parsing
This commit is contained in:
parent
4b731d83c3
commit
975a84cded
|
@ -1,14 +1,31 @@
|
|||
package net.dankito.fints.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
open class AccountTransaction(
|
||||
var referenceNumber: String = "",
|
||||
var bezugsReferenceNumber: String? = null,
|
||||
var accountIdentification: String = "",
|
||||
var statementNumber: String = "",
|
||||
var openingBalance: String = "",
|
||||
var statement: String = "",
|
||||
var accountOwner: String = "",
|
||||
var closingBalance: String = ""
|
||||
val amount: BigDecimal,
|
||||
val currency: String,
|
||||
val usage: String,
|
||||
val bookingDate: Date,
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankCode: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
val bookingText: String?,
|
||||
val valueDate: Date?,
|
||||
val openingBalance: BigDecimal?,
|
||||
val closingBalance: BigDecimal?
|
||||
// TODO: may also add other values from parsed usage lines
|
||||
) {
|
||||
|
||||
// for object deserializers
|
||||
private constructor() : this(0.toBigDecimal(),"", "", Date(), null, null, null, null, null, null, null)
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "${DateFormat.getDateInstance(DateFormat.MEDIUM).format(bookingDate)} $amount $otherPartyName: $usage"
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,8 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId {
|
|||
|
||||
UserParameters("HIUPA"),
|
||||
|
||||
AccountInfo("HIUPD")
|
||||
AccountInfo("HIUPD"),
|
||||
|
||||
AccountTransactionsMt940("HIKAZ")
|
||||
|
||||
}
|
|
@ -10,12 +10,24 @@ import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinsti
|
|||
import net.dankito.fints.messages.datenelementgruppen.implementierte.signatur.Sicherheitsprofil
|
||||
import net.dankito.fints.messages.segmente.id.MessageSegmentId
|
||||
import net.dankito.fints.response.segments.*
|
||||
import net.dankito.fints.transactions.IAccountTransactionsParser
|
||||
import net.dankito.fints.transactions.Mt940AccountTransactionsParser
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
open class ResponseParser {
|
||||
open class ResponseParser @JvmOverloads constructor(
|
||||
protected val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser()
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val BinaryDataHeaderPatternString = "@\\d+@"
|
||||
|
||||
val BinaryDataHeaderPattern = Pattern.compile(BinaryDataHeaderPatternString)
|
||||
|
||||
val EncryptionDataSegmentHeaderPattern = Pattern.compile("${MessageSegmentId.EncryptionData.id}:\\d{1,3}:\\d{1,3}\\+")
|
||||
|
||||
private val log = LoggerFactory.getLogger(ResponseParser::class.java)
|
||||
}
|
||||
|
||||
|
@ -67,6 +79,8 @@ open class ResponseParser {
|
|||
InstituteSegmentId.UserParameters.id -> parseUserParameters(segment, dataElementGroups)
|
||||
InstituteSegmentId.AccountInfo.id -> parseAccountInfo(segment, dataElementGroups)
|
||||
|
||||
InstituteSegmentId.AccountTransactionsMt940.id -> parseMt940AccountTransactions(segment, dataElementGroups)
|
||||
|
||||
else -> UnparsedSegment(segment)
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +165,16 @@ open class ResponseParser {
|
|||
}
|
||||
|
||||
|
||||
protected open fun parseMt940AccountTransactions(segment: String, dataElementGroups: List<String>): ReceivedAccountTransactions {
|
||||
val bookedTransactionsString = extractBinaryData(dataElementGroups[1])
|
||||
|
||||
// TODO: implement parsing MT942
|
||||
val unbookedTransactionsString = if (dataElementGroups.size > 2) extractBinaryData(dataElementGroups[2]) else null
|
||||
|
||||
return ReceivedAccountTransactions(mt940Parser.parseTransactions(bookedTransactionsString), listOf(), segment)
|
||||
}
|
||||
|
||||
|
||||
protected open fun parseBankDetails(dataElementsGroup: String): Kreditinstitutskennung {
|
||||
val detailsStrings = getDataElements(dataElementsGroup)
|
||||
|
||||
|
@ -216,14 +240,83 @@ open class ResponseParser {
|
|||
* Also binary data shouldn't be taken into account (TODO: really?).
|
||||
*/
|
||||
protected open fun splitIntoPartsAndUnmask(dataString: String, separator: String): List<String> {
|
||||
val separatorMask = Separators.MaskingCharacter + separator
|
||||
val maskedSymbolsGuard = Separators.MaskingCharacter + "§"
|
||||
val binaryDataRanges = mutableListOf<IntRange>()
|
||||
val binaryDataMatcher = BinaryDataHeaderPattern.matcher(dataString)
|
||||
|
||||
val maskedDataString = dataString.replace(separatorMask, maskedSymbolsGuard)
|
||||
while (binaryDataMatcher.find()) {
|
||||
if (isEncryptionDataSegment(dataString, binaryDataMatcher) == false) {
|
||||
val startIndex = binaryDataMatcher.end()
|
||||
val length = binaryDataMatcher.group().replace("@", "").toInt()
|
||||
|
||||
val elements = maskedDataString.split(separator)
|
||||
binaryDataRanges.add(IntRange(startIndex, startIndex + length - 1))
|
||||
}
|
||||
}
|
||||
|
||||
return elements.map { it.replace(maskedSymbolsGuard, separator) }
|
||||
val separatorIndices = allIndicesOf(dataString, separator)
|
||||
.filter { isCharacterMasked(it, dataString) == false }
|
||||
.filter { isInRange(it, binaryDataRanges) == false }
|
||||
|
||||
var startIndex = 0
|
||||
val elements = separatorIndices.map { endIndex ->
|
||||
val element = dataString.substring(startIndex, endIndex)
|
||||
startIndex = endIndex + 1
|
||||
element
|
||||
}.toMutableList()
|
||||
|
||||
if (startIndex < dataString.length) {
|
||||
elements.add(dataString.substring(startIndex))
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
protected open fun isEncryptionDataSegment(dataString: String, binaryDataMatcher: Matcher): Boolean {
|
||||
val binaryDataHeaderStartIndex = binaryDataMatcher.start()
|
||||
|
||||
if (binaryDataHeaderStartIndex > 15) {
|
||||
val encryptionDataSegmentMatcher = EncryptionDataSegmentHeaderPattern.matcher(dataString)
|
||||
|
||||
if (encryptionDataSegmentMatcher.find(binaryDataHeaderStartIndex - 15)) {
|
||||
return encryptionDataSegmentMatcher.start() < binaryDataHeaderStartIndex
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun isCharacterMasked(characterIndex: Int, wholeString: String): Boolean {
|
||||
if (characterIndex > 0) {
|
||||
val previousChar = wholeString[characterIndex - 1]
|
||||
|
||||
return previousChar.toString() == Separators.MaskingCharacter
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun isInRange(index: Int, ranges: List<IntRange>): Boolean {
|
||||
for (range in ranges) {
|
||||
if (range.contains(index)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun allIndicesOf(string: String, toFind: String): List<Int> {
|
||||
val indices = mutableListOf<Int>()
|
||||
var index = -1
|
||||
|
||||
do {
|
||||
index = string.indexOf(toFind, index + 1)
|
||||
|
||||
if (index > -1) {
|
||||
indices.add(index)
|
||||
}
|
||||
} while (index > -1)
|
||||
|
||||
return indices
|
||||
}
|
||||
|
||||
protected open fun parseInt(string: String): Int {
|
||||
|
@ -253,4 +346,16 @@ open class ResponseParser {
|
|||
return false
|
||||
}
|
||||
|
||||
protected open fun extractBinaryData(binaryData: String): String {
|
||||
if (binaryData.startsWith('@')) {
|
||||
val headerEndIndex = binaryData.indexOf('@', 2)
|
||||
|
||||
if (headerEndIndex > -1) {
|
||||
return binaryData.substring(headerEndIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return binaryData
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.dankito.fints.response.segments
|
||||
|
||||
import net.dankito.fints.model.AccountTransaction
|
||||
|
||||
|
||||
open class ReceivedAccountTransactions(
|
||||
val bookedTransactions: List<AccountTransaction>,
|
||||
val unbookedTransactions: List<Any>, // TODO
|
||||
segmentString: String
|
||||
|
||||
)
|
||||
: ReceivedSegment(segmentString)
|
Loading…
Reference in New Issue