Implemented parsing

This commit is contained in:
dankl 2019-10-12 18:16:35 +02:00 committed by dankito
parent 4b731d83c3
commit 975a84cded
4 changed files with 151 additions and 15 deletions

View File

@ -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"
}
}

View File

@ -13,6 +13,8 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId {
UserParameters("HIUPA"),
AccountInfo("HIUPD")
AccountInfo("HIUPD"),
AccountTransactionsMt940("HIKAZ")
}

View File

@ -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
}
}

View File

@ -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)