Removed BankFinder code after moving them to a separate project

This commit is contained in:
dankito 2022-02-18 22:59:39 +01:00
parent 523e538ecb
commit bdd28f2587
29 changed files with 0 additions and 1437 deletions

View File

@ -1,87 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "maven-publish"
}
ext.artifactName = "bank-finder"
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = '1.8'
}
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
// js(BOTH) {
// browser {
// commonWebpackConfig {
// cssSupport.enabled = true
// }
// }
// }
ios {
binaries {
framework {
baseName = "BankFinder"
}
}
}
// def hostOs = System.getProperty("os.name")
// def isMingwX64 = hostOs.startsWith("Windows")
// def nativeTarget
// if (hostOs == "Mac OS X") nativeTarget = macosX64('native') { binaries.executable() }
// else if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries.executable() }
// else if (isMingwX64) nativeTarget = mingwX64("native") { binaries.executable() }
// else throw new GradleException("Host OS is not supported in Kotlin/Native.")
sourceSets {
commonMain {
dependencies {
api project(":common")
}
}
commonTest {
dependencies {
implementation kotlin("test-common")
implementation kotlin("test-annotations-common")
}
}
jvmMain {
dependencies {
api "net.dankito.utils:java-utils:$javaUtilsVersion"
}
}
jvmTest {
dependencies {
}
}
iosMain {
dependencies {
}
}
iosTest {
dependencies {
}
}
}
}

View File

@ -1,44 +0,0 @@
package net.dankito.banking.bankfinder
abstract class BankFinderBase : IBankFinder {
protected abstract fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo>
abstract fun searchBankByBic(bic: String): BankInfo?
protected val cachedBanksByBic = mutableMapOf<String, BankInfo?>()
override fun findBankByNameBankCodeOrCity(query: String?): List<BankInfo> {
if (query.isNullOrBlank()) {
return getBankList()
}
if (query.toIntOrNull() != null) { // if query is an integer, then it can only be an bank code, but not a bank name or city
return findBankByBankCode(query)
}
return findBankByNameBankCodeOrCityForNonEmptyQuery(query)
}
override fun findBankByBic(bic: String): BankInfo? {
cachedBanksByBic[bic]?.let {
return it
}
val bankForBic = searchBankByBic(bic)
cachedBanksByBic[bic] = bankForBic
return bankForBic
}
protected open fun loadBankList(): List<BankInfo> {
return BankListDeserializer().loadBankList()
}
}

View File

@ -1,29 +0,0 @@
package net.dankito.banking.bankfinder
open class BankInfo(
open var name: String,
open val bankCode: String,
open val bic: String,
open val postalCode: String,
open val city: String,
open val pinTanAddress: String?,
open val pinTanVersion: String?,
open var branchesInOtherCities: List<String> = listOf() // to have only one entry per bank its branches's cities are now stored in branchesInOtherCities so that branches' cities are still searchable
) {
protected constructor() : this("", "", "", "", "", null, "") // for object deserializers
open val supportsPinTan: Boolean
get() = pinTanAddress.isNullOrEmpty() == false
open val supportsFinTs3_0: Boolean
get() = pinTanVersion == "FinTS V3.0"
override fun toString(): String {
return "$bankCode $name $city"
}
}

View File

@ -1,8 +0,0 @@
package net.dankito.banking.bankfinder
expect class BankListDeserializer() {
fun loadBankList(): List<BankInfo>
}

View File

@ -1,22 +0,0 @@
package net.dankito.banking.bankfinder
open class DetailedBankInfo(
name: String,
bankCode: String,
bic: String,
postalCode: String,
city: String,
pinTanAddress: String?,
pinTanVersion: String?,
open val checksumMethod: String,
open val oldBankCode: String?
) : BankInfo(name, bankCode, bic, postalCode, city, pinTanAddress, pinTanVersion) {
protected constructor() : this("", "", "", "", "", null, "", "", null) // for object deserializers
open val isBankCodeDeleted: Boolean
get() = oldBankCode != null // TODO: this is not in all cases true, there are banks with new bank code which haven't been deleted
}

View File

@ -1,16 +0,0 @@
package net.dankito.banking.bankfinder
interface IBankFinder {
fun getBankList(): List<BankInfo>
fun findBankByBankCode(query: String): List<BankInfo>
fun findBankByNameBankCodeOrCity(query: String?): List<BankInfo>
fun findBankByBic(bic: String): BankInfo?
fun preloadBankList()
}

View File

@ -1,70 +0,0 @@
package net.dankito.banking.bankfinder
open class InMemoryBankFinder() : BankFinderBase(), IBankFinder {
constructor(bankList: List<BankInfo>) : this() {
this.bankListField = bankList
}
protected var bankListField: List<BankInfo>? = null
override fun findBankByBankCode(query: String): List<BankInfo> {
if (query.isEmpty()) {
return getBankList()
}
return getBankList().filter { it.bankCode.startsWith(query) }
}
override fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo> {
val queryPartsLowerCase = query.toLowerCase().split(" ", "-")
return getBankList().filter { bankInfo ->
checkIfAllQueryPartsMatchBankNameBankCodeOrCity(queryPartsLowerCase, bankInfo)
}
}
protected open fun checkIfAllQueryPartsMatchBankNameBankCodeOrCity(queryPartsLowerCase: List<String>, bankInfo: BankInfo): Boolean {
for (queryPartLowerCase in queryPartsLowerCase) {
if (checkIfQueryMatchesBankNameBankCodeOrCity(bankInfo, queryPartLowerCase) == false) {
return false
}
}
return true
}
protected open fun checkIfQueryMatchesBankNameBankCodeOrCity(bankInfo: BankInfo, queryLowerCase: String): Boolean {
return bankInfo.name.toLowerCase().contains(queryLowerCase)
|| bankInfo.bankCode.startsWith(queryLowerCase)
|| bankInfo.city.toLowerCase().startsWith(queryLowerCase)
|| bankInfo.branchesInOtherCities.any { it.toLowerCase().startsWith(queryLowerCase) }
}
override fun searchBankByBic(bic: String): BankInfo? {
return getBankList().firstOrNull { it.bic == bic }
}
override fun preloadBankList() {
findBankByBankCode("")
}
override fun getBankList(): List<BankInfo> {
bankListField?.let {
return it
}
val bankList = loadBankList()
this.bankListField = bankList
return bankList
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,73 +0,0 @@
package net.dankito.banking.bankfinder
import kotlin.test.Test
import kotlin.test.assertEquals
abstract class BankFinderTestBase {
protected abstract fun createBankFinder(): IBankFinder
protected val underTest = createBankFinder()
@Test
fun findBankByBankCode_10000000() {
// when
val result = underTest.findBankByBankCode("10000000")
// then
assertEquals(1, result.size)
assertEquals("Bundesbank", result[0].name)
}
@Test
fun findBankByNameBankCodeOrCity_Starnberg_MultipleResults() {
// when
val result = underTest.findBankByNameBankCodeOrCity("starnberg")
// then
assertEquals(71, result.size)
}
@Test
fun findBankByNameBankCodeOrCity_mizUh_SingleResult() {
// when
val result = underTest.findBankByNameBankCodeOrCity("mizUh")
// then
assertEquals(1, result.size)
assertEquals("Mizuho Bank Filiale Düsseldorf", result.first().name)
}
@Test
fun findBankByNameBankCodeOrCity_MultiPartsQuery_BerlinSparkas() {
// when
val result = underTest.findBankByNameBankCodeOrCity("Berlin Sparkas")
// then
assertEquals(3, result.size)
assertEquals("Landesbank Berlin - Berliner Sparkasse", result.first().name)
}
@Test
fun findBankByNameBankCodeOrCity_MultiPartsQuery_SparkasBerlin() {
// when
val result = underTest.findBankByNameBankCodeOrCity("Sparkas Berlin")
// then
assertEquals(3, result.size)
assertEquals("Landesbank Berlin - Berliner Sparkasse", result.first().name)
}
}

View File

@ -1,10 +0,0 @@
package net.dankito.banking.bankfinder
class InMemoryBankFinderTest : BankFinderTestBase() {
override fun createBankFinder(): IBankFinder {
return InMemoryBankFinder()
}
}

View File

@ -1,43 +0,0 @@
package net.dankito.banking.bankfinder
import net.dankito.utils.multiplatform.getString
import net.dankito.utils.multiplatform.getStringOrEmpty
import net.dankito.utils.multiplatform.toList
import platform.Foundation.*
actual class BankListDeserializer {
actual fun loadBankList(): List<BankInfo> {
val bundle = NSBundle.mainBundle
bundle.pathForResource("BankList", "json")?.let { bankListJsonPath ->
NSData.dataWithContentsOfFile(bankListJsonPath, NSDataReadingMappedIfSafe, null)?.let { bankListData ->
NSJSONSerialization.JSONObjectWithData(bankListData, NSJSONReadingMutableContainers, null)?.let { bankListJson ->
(bankListJson as? NSArray)?.let {
return bankListJson.toList<NSDictionary>().map {
mapToBankInfo(it)
}
}
}
}
}
return listOf()
}
private fun mapToBankInfo(bankInfoDict: NSDictionary): BankInfo {
return BankInfo(
bankInfoDict.getStringOrEmpty("name"),
bankInfoDict.getStringOrEmpty("bankCode"),
bankInfoDict.getStringOrEmpty("bic"),
bankInfoDict.getStringOrEmpty("postalCode"),
bankInfoDict.getStringOrEmpty("city"),
bankInfoDict.getString("pinTanAddress"),
bankInfoDict.getString("pinTanVersion"),
bankInfoDict.mutableArrayValueForKey("branchesInOtherCities").mapNotNull { it as? String }
)
}
}

View File

@ -1,36 +0,0 @@
package net.dankito.banking.bankfinder
import net.dankito.utils.multiplatform.log.LoggerFactory
import net.dankito.utils.serialization.JacksonJsonSerializer
actual class BankListDeserializer {
companion object {
const val BankListFileName = "BankList.json"
private val log = LoggerFactory.getLogger(BankListDeserializer::class)
}
actual fun loadBankList(): List<BankInfo> {
try {
val bankListString = readBankListFile()
JacksonJsonSerializer().deserializeList(bankListString, BankInfo::class.java)?.let {
return it
}
} catch (e: Exception) {
log.error(e) { "Could not load bank list" }
}
return listOf()
}
fun readBankListFile(): String {
val inputStream = BankFinderBase::class.java.classLoader.getResourceAsStream(BankListFileName)
return inputStream.bufferedReader().readText()
}
}

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.dankito.banking.bankfinder">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -1,29 +0,0 @@
apply plugin: 'java-library'
apply plugin: 'kotlin'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
api project(':BankFinder')
// why can't Gradle find BankFinder? If the same applies to you, publish BankFinder to Maven Local first and uncomment this line:
// api "net.dankito.banking:BankFinder-jvm:$version"
implementation 'org.docx4j:docx4j-JAXB-ReferenceImpl:8.1.3'
testImplementation "junit:junit:4.12"
testImplementation "org.assertj:assertj-core:$assertJVersion"
testImplementation "ch.qos.logback:logback-core:$logbackVersion"
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
}

View File

@ -1,41 +0,0 @@
package net.dankito.banking.banklistcreator
import net.dankito.banking.bankfinder.BankInfo
import net.dankito.banking.banklistcreator.parser.DeutscheKreditwirtschaftBankListParser
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifier
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifierOption
import net.dankito.utils.serialization.JacksonJsonSerializer
import java.io.File
open class BankListCreator @JvmOverloads constructor(
protected open val parser: DeutscheKreditwirtschaftBankListParser = DeutscheKreditwirtschaftBankListParser(),
protected open val prettifier: BankListPrettifier = BankListPrettifier()
) {
open fun createBankListFromDeutscheKreditwirtschaftXlsxFile(deutscheKreditwirtschaftXlsxFile: File,
bankListOutputFile: File) {
val banks = parser.parse(deutscheKreditwirtschaftXlsxFile)
saveBankListAsJson(banks, bankListOutputFile)
}
open fun createDetailedAndPrettifiedBankListFromDeutscheKreditwirtschaftXlsxFile(
deutscheKreditwirtschaftXlsxFile: File, detailedBankListOutputFile: File,
prettifiedBankListOutputFile: File, prettifyOptions: List<BankListPrettifierOption>) {
val allBanks = parser.parse(deutscheKreditwirtschaftXlsxFile)
saveBankListAsJson(allBanks, detailedBankListOutputFile)
val mappedBanks = allBanks.map { BankInfo(it.name, it.bankCode, it.bic, it.postalCode, it.city, it.pinTanAddress, it.pinTanVersion) }
val prettifiedBanks = prettifier.prettify(mappedBanks, prettifyOptions)
saveBankListAsJson(prettifiedBanks, prettifiedBankListOutputFile)
}
open fun saveBankListAsJson(banks: List<BankInfo>, bankListOutputFile: File) {
JacksonJsonSerializer().serializeObject(banks, bankListOutputFile)
}
}

View File

@ -1,280 +0,0 @@
package net.dankito.banking.banklistcreator.parser
import net.dankito.banking.bankfinder.DetailedBankInfo
import net.dankito.banking.banklistcreator.parser.model.BankCodeListEntry
import net.dankito.banking.banklistcreator.parser.model.ServerAddressesListEntry
import org.docx4j.openpackaging.packages.SpreadsheetMLPackage
import org.slf4j.LoggerFactory
import org.xlsx4j.org.apache.poi.ss.usermodel.DataFormatter
import org.xlsx4j.sml.Cell
import org.xlsx4j.sml.Row
import org.xlsx4j.sml.SheetData
import java.io.File
/**
* Parses the list of German banks from Deutsche Kreditwirtschaft you can retrieve by registering here:
* https://www.hbci-zka.de/register/hersteller.htm
*/
open class DeutscheKreditwirtschaftBankListParser {
companion object {
private val log = LoggerFactory.getLogger(DeutscheKreditwirtschaftBankListParser::class.java)
}
open fun parse(bankListFile: File): List<DetailedBankInfo> {
val xlsxPkg = SpreadsheetMLPackage.load(bankListFile)
val workbookPart = xlsxPkg.getWorkbookPart()
val sheets = workbookPart.contents.sheets.sheet
val formatter = DataFormatter()
var serverAddressesList = listOf<ServerAddressesListEntry>()
var bankCodesList = listOf<BankCodeListEntry>()
for (index in 0 until sheets.size) {
log.info("\nParsing sheet ${sheets[index].name}:\n")
val sheet = workbookPart.getWorksheet(index)
val workSheetData = sheet.contents.sheetData
if (isListWithFinTsServerAddresses(workSheetData, formatter)) {
serverAddressesList = parseListWithFinTsServerAddresses(workSheetData, formatter)
}
else if (isBankCodeList(workSheetData, formatter)) {
bankCodesList = parseBankCodeList(workSheetData, formatter)
}
}
return mapBankCodeAndServerAddressesList(bankCodesList, serverAddressesList)
}
protected open fun mapBankCodeAndServerAddressesList(banks: List<BankCodeListEntry>,
serverAddresses: List<ServerAddressesListEntry>): List<DetailedBankInfo> {
val serverAddressesByBankCode = mutableMapOf<String, MutableList<ServerAddressesListEntry>>()
serverAddresses.forEach { serverAddress ->
if (serverAddressesByBankCode.containsKey(serverAddress.bankCode) == false) {
serverAddressesByBankCode.put(serverAddress.bankCode, mutableListOf(serverAddress))
}
else {
serverAddressesByBankCode[serverAddress.bankCode]!!.add(serverAddress)
}
}
return banks.map { mapToBankInfo(it, serverAddressesByBankCode as Map<String, List<ServerAddressesListEntry>>) }
}
protected open fun mapToBankInfo(bank: BankCodeListEntry,
serverAddressesByBankCode: Map<String, List<ServerAddressesListEntry>>): DetailedBankInfo {
val serverAddress = findServerAddress(bank, serverAddressesByBankCode)
return DetailedBankInfo(
bank.bankName,
bank.bankCode,
bank.bic,
bank.postalCode,
bank.city,
serverAddress?.pinTanAddress,
serverAddress?.pinTanVersion,
bank.checksumMethod,
bank.oldBankCode
)
}
protected open fun findServerAddress(bankCode: BankCodeListEntry,
serverAddressesByBankCode: Map<String, List<ServerAddressesListEntry>>
): ServerAddressesListEntry? {
serverAddressesByBankCode[bankCode.bankCode]?.let { serverAddresses ->
serverAddresses.firstOrNull { it.city == bankCode.city }?.let {
return it
}
return serverAddresses[0]
}
return null
}
protected open fun isListWithFinTsServerAddresses(workSheetData: SheetData, formatter: DataFormatter): Boolean {
return hasHeaders(workSheetData, formatter, listOf("BLZ", "PIN/TAN-Zugang URL"))
}
protected open fun parseListWithFinTsServerAddresses(workSheetData: SheetData, formatter: DataFormatter):
List<ServerAddressesListEntry> {
val entries = mutableListOf<ServerAddressesListEntry>()
val headerRow = workSheetData.row[0]
val headerNames = headerRow.c.map { getCellText(it, formatter) }
val bankNameColumnIndex = headerNames.indexOf("Institut")
val bankCodeColumnIndex = headerNames.indexOf("BLZ")
val bicColumnIndex = headerNames.indexOf("BIC")
val cityColumnIndex = headerNames.indexOf("Ort")
val pinTanAddressColumnIndex = headerNames.indexOf("PIN/TAN-Zugang URL")
val pinTanVersionColumnIndex = headerNames.indexOf("Version")
for (row in workSheetData.row.subList(1, workSheetData.row.size)) { // removes header row
parseToServerAddressesListEntry(row, formatter, bankNameColumnIndex, bankCodeColumnIndex, bicColumnIndex,
cityColumnIndex, pinTanAddressColumnIndex, pinTanVersionColumnIndex)?.let { entry ->
entries.add(entry)
}
}
return entries
}
protected open fun parseToServerAddressesListEntry(row: Row, formatter: DataFormatter, bankNameColumnIndex: Int,
bankCodeColumnIndex: Int, bicColumnIndex: Int, cityColumnIndex: Int,
pinTanAddressColumnIndex: Int, pinTanVersionColumnIndex: Int):
ServerAddressesListEntry? {
try {
if (row.c.size < pinTanVersionColumnIndex) { // a row with only the index number in first column, doesn't contain enough information for us to parse it to an ServerAddressesListEntry
return null
}
val bankCode = getCellText(row, bankCodeColumnIndex, formatter)
if (bankCode.isNotEmpty()) { // filter out empty rows
return ServerAddressesListEntry(
getCellText(row, bankNameColumnIndex, formatter),
bankCode,
getCellText(row, bicColumnIndex, formatter),
getCellText(row, cityColumnIndex, formatter),
getCellText(row, pinTanAddressColumnIndex, formatter),
getCellText(row, pinTanVersionColumnIndex, formatter)
)
}
} catch (e: Exception) {
log.error("Could not parse row ${getRowAsString(row, formatter)} to BankCodeListEntry", e)
}
return null
}
protected open fun isBankCodeList(workSheetData: SheetData, formatter: DataFormatter): Boolean {
return hasHeaders(workSheetData, formatter, listOf("Bankleitzahl", "Merkmal"))
}
protected open fun parseBankCodeList(workSheetData: SheetData, formatter: DataFormatter): List<BankCodeListEntry> {
val entries = mutableListOf<BankCodeListEntry>()
val headerRow = workSheetData.row[0]
val headerNames = headerRow.c.map { getCellText(it, formatter) }
val bankNameColumnIndex = headerNames.indexOf("Bezeichnung")
val bankCodeColumnIndex = headerNames.indexOf("Bankleitzahl")
val bicColumnIndex = headerNames.indexOf("BIC")
val postalCodeColumnIndex = headerNames.indexOf("PLZ")
val cityColumnIndex = headerNames.indexOf("Ort")
val checksumMethodColumnIndex = headerNames.indexOf("Prüfziffer-berechnungs-methode")
val bankCodeDeletedColumnIndex = headerNames.indexOf("Bankleitzahl-löschung")
val newBankCodeColumnIndex = headerNames.indexOf("Nachfolge-Bankleitzahl")
var lastParsedEntry: BankCodeListEntry? = null
for (row in workSheetData.row.subList(1, workSheetData.row.size)) {
parseToBankCodeListEntry(row, formatter, bankNameColumnIndex, bankCodeColumnIndex, bicColumnIndex,
postalCodeColumnIndex, cityColumnIndex, checksumMethodColumnIndex, bankCodeDeletedColumnIndex,
newBankCodeColumnIndex)?.let { entry ->
// if the following banks have the same BIC, the BIC is only given for the first bank -> get BIC from previous bank
if (entry.bic.isEmpty() &&
(entry.bankCode == lastParsedEntry?.bankCode || entry.bankCode == lastParsedEntry?.oldBankCode)) {
entry.bic = lastParsedEntry?.bic!!
}
entries.add(entry)
lastParsedEntry = entry
}
}
updateDeletedBanks(entries)
return entries
}
protected open fun parseToBankCodeListEntry(row: Row, formatter: DataFormatter, bankNameColumnIndex: Int,
bankCodeColumnIndex: Int, bicColumnIndex: Int, postalCodeColumnIndex: Int,
cityColumnIndex: Int, checksumMethodColumnIndex: Int,
bankCodeDeletedColumnIndex: Int, newBankCodeColumnIndex: Int): BankCodeListEntry? {
try {
val bankCode = getCellText(row, bankCodeColumnIndex, formatter)
if (bankCode.isNotEmpty()) { // filter out empty rows
var newBankCode: String? = null
val isBankCodeDeleted = getCellText(row, bankCodeDeletedColumnIndex, formatter) == "1"
val newBankCodeCellText = getCellText(row, newBankCodeColumnIndex, formatter)
if (isBankCodeDeleted && newBankCodeCellText.isNotEmpty() && newBankCodeCellText != "00000000") {
newBankCode = newBankCodeCellText
}
return BankCodeListEntry(
getCellText(row, bankNameColumnIndex, formatter),
newBankCode ?: bankCode,
getCellText(row, bicColumnIndex, formatter),
getCellText(row, postalCodeColumnIndex, formatter),
getCellText(row, cityColumnIndex, formatter),
getCellText(row, checksumMethodColumnIndex, formatter),
if (newBankCode != null) bankCode else newBankCode
)
}
} catch (e: Exception) {
log.error("Could not parse row ${getRowAsString(row, formatter)} to BankCodeListEntry", e)
}
return null
}
/**
* Deleted banks may not have a BIC. This method fixes this
*/
protected open fun updateDeletedBanks(banks: MutableList<BankCodeListEntry>) {
val banksByCode = banks.associateBy { it.bankCode }
val deletedBanks = banks.filter { it.isBankCodeDeleted }
for (deletedBank in deletedBanks) {
banksByCode[deletedBank.bankCode]?.let { newBank ->
deletedBank.bic = newBank.bic
}
}
}
protected open fun hasHeaders(workSheetData: SheetData, formatter: DataFormatter, headerNames: List<String>): Boolean {
if (workSheetData.row.isNotEmpty()) {
val headerRow = workSheetData.row[0]
val rowHeaderNames = headerRow.c.map { getCellText(it, formatter) }
return rowHeaderNames.containsAll(headerNames)
}
return false
}
protected open fun getCellText(row: Row, columnIndex: Int, formatter: DataFormatter): String {
return getCellText(row.c[columnIndex], formatter)
}
protected open fun getCellText(cell: Cell, formatter: DataFormatter): String {
if (cell.f != null) { // cell with formular
return cell.v
}
return formatter.formatCellValue(cell)
}
protected open fun getRowAsString(row: Row, formatter: DataFormatter): String {
return row.c.joinToString("\t|", "|\t", "\t|") { getCellText(it, formatter) }
}
}

View File

@ -1,22 +0,0 @@
package net.dankito.banking.banklistcreator.parser.model
open class BankCodeListEntry(
open val bankName: String,
open val bankCode: String,
open var bic: String, // TODO: make val again
open val postalCode: String,
open val city: String,
open val checksumMethod: String,
open val oldBankCode: String?
) {
open val isBankCodeDeleted: Boolean
get() = oldBankCode != null
override fun toString(): String {
return "$bankCode $bankName ($bic, $city)"
}
}

View File

@ -1,17 +0,0 @@
package net.dankito.banking.banklistcreator.parser.model
open class ServerAddressesListEntry(
open val bankName: String,
open val bankCode: String,
open val bic: String,
open val city: String,
open val pinTanAddress: String?,
open val pinTanVersion: String?
) {
override fun toString(): String {
return "$bankCode $bankName ($city, $pinTanAddress)"
}
}

View File

@ -1,148 +0,0 @@
package net.dankito.banking.banklistcreator.prettifier
import net.dankito.banking.bankfinder.BankInfo
open class BankListPrettifier {
open fun prettify(banks: List<BankInfo>, options: List<BankListPrettifierOption>): List<BankInfo> {
var prettifiedList = banks
if (options.contains(BankListPrettifierOption.RemoveInstitutionInternalBank)) {
prettifiedList = removeInstitutionInternalBank(prettifiedList)
}
if (options.contains(BankListPrettifierOption.RemoveBanksWithSameBankCodeAndPostalCode)) {
prettifiedList = removeBanksWithSameBankCodeAndPostalCode(prettifiedList)
}
if (options.contains(BankListPrettifierOption.RemoveBanksWithSameBankCodeAndCity)) {
prettifiedList = removeBanksWithSameBankCodeAndCity(prettifiedList)
}
if (options.contains(BankListPrettifierOption.MergeAllBranchesOfBankIntoOne)) {
prettifiedList = mergeAllBranchesOfBankIntoOne(prettifiedList)
}
if (options.contains(BankListPrettifierOption.MapBankNamesToWellKnownNames)) {
prettifiedList = mapBankNamesToWellKnownNames(prettifiedList)
}
return prettifiedList
}
open fun mapBankNamesToWellKnownNames(banks: List<BankInfo>): List<BankInfo> {
banks.forEach { bank ->
when {
bank.name.contains("Postbank") -> bank.name = "Postbank"
bank.name.startsWith("Deutsche Kreditbank") -> bank.name = "DKB (Deutsche Kreditbank)"
bank.name.startsWith("Deutsche Bank") || bank.name.startsWith("DB Privat- und Firmenkundenbank") -> bank.name = "Deutsche Bank"
bank.name.startsWith("Commerzbank") -> bank.name = "Commerzbank" // TODO: keep "vormals Dresdner Bank"?
}
}
return banks
}
open fun removeInstitutionInternalBank(banks: List<BankInfo>): List<BankInfo> {
return banks.filterNot {
(it.name.contains("intern", true) && it.name.contains("international", true) == false)
|| it.name.startsWith("UniCredit Bank - HVB Settlement")
}
}
open fun removeBanksWithSameBankCodeAndPostalCode(banks: List<BankInfo>): List<BankInfo> {
val groupedByBankCodeAndPostalCode = banks.groupBy { it.bankCode + "_" + it.postalCode }
val banksToRemove = groupedByBankCodeAndPostalCode.values.flatMap { banksWithSameBankCodeAndPostalCode ->
if (banksWithSameBankCodeAndPostalCode.size > 1) {
val bankWithBestName = findBankWithShortestName(banksWithSameBankCodeAndPostalCode)
val banksWithoutBankWithBestName = banksWithSameBankCodeAndPostalCode.toMutableList()
banksWithoutBankWithBestName.remove(bankWithBestName)
return@flatMap banksWithoutBankWithBestName
}
listOf<BankInfo>()
}
val prettifiedList = banks.toMutableList()
prettifiedList.removeAll(banksToRemove)
return prettifiedList
}
// TODO: there are many banks like "Volksbank Nordmünsterland -alt-" and "Volksbank Nordmünsterland (Gf P2)" where each time "-alt-" gets selected
protected open fun findBankWithShortestName(banks: List<BankInfo>): BankInfo {
var bankWithBestName = banks.first()
for (i in 1 until banks.size) {
val bank = banks[i]
if (bank.name.length < bankWithBestName.name.length) {
bankWithBestName = bank
}
}
return bankWithBestName
}
open fun removeBanksWithSameBankCodeAndCity(banks: List<BankInfo>): List<BankInfo> {
val groupedByBankCodeAndCity = banks.groupBy { it.bankCode + "_" + it.city }
val banksToRemove = groupedByBankCodeAndCity.values.flatMap { banksWithSameBankCodeAndCity ->
if (banksWithSameBankCodeAndCity.size > 1) {
val banksToRemove = banksWithSameBankCodeAndCity.toMutableList()
banksToRemove.remove(banksWithSameBankCodeAndCity.first())
return@flatMap banksToRemove
}
listOf<BankInfo>()
}
val prettifiedList = banks.toMutableList()
prettifiedList.removeAll(banksToRemove)
return prettifiedList
}
open fun mergeAllBranchesOfBankIntoOne(banks: List<BankInfo>): List<BankInfo> {
val groupedByBankCodeAndPinTanAddress = banks.groupBy { it.bankCode + "_" + it.pinTanAddress }
val banksToRemove = groupedByBankCodeAndPinTanAddress.values.flatMap { banksWithSameBankCodeAndPinTanAddress ->
if (banksWithSameBankCodeAndPinTanAddress.size > 1) {
val banksToRemove = banksWithSameBankCodeAndPinTanAddress.toMutableList()
val mainBranch = findMainBranch(banksWithSameBankCodeAndPinTanAddress)
banksToRemove.remove(banksWithSameBankCodeAndPinTanAddress.first())
mainBranch.branchesInOtherCities = banksToRemove.map { it.city }.toSet().toList()
return@flatMap banksToRemove
}
listOf<BankInfo>()
}
val prettifiedList = banks.toMutableList()
prettifiedList.removeAll(banksToRemove)
return prettifiedList
}
private fun findMainBranch(banksWithSameBankCodeAndPinTanAddress: List<BankInfo>): BankInfo {
// banksWithSameBankCodeAndPinTanAddress.forEach { bank ->
// if (bank.name.contains(bank.city)) {
// println("${bank.name}: Picked ${bank.city} from ${banksWithSameBankCodeAndPinTanAddress.map { it.city }}")
// return bank
// }
// }
// in most cases this turned out to be the best one
return banksWithSameBankCodeAndPinTanAddress.first()
}
}

View File

@ -1,31 +0,0 @@
package net.dankito.banking.banklistcreator.prettifier
enum class BankListPrettifierOption {
/**
* Maps e.g. 'DB Privat- und Firmenkundenbank ...' to 'Deutsche Bank' or 'Deutsche Kreditbank' to 'DKB'
*/
MapBankNamesToWellKnownNames,
/**
* Often the same bank is contained multiple times but with a '(Gf 2)' etc. suffix in name. Filters these out that have the same bank code and postal code.
*/
RemoveBanksWithSameBankCodeAndPostalCode,
/**
* Often the same bank is contained multiple times but with a '(Gf 2)' etc. suffix in name. Filters these out that have the same bank code and city.
*/
RemoveBanksWithSameBankCodeAndCity,
/**
* By default each branch has its own BankInfo. With this option only main branch keeps its BankInfo and all branches get added to branchesInOtherCities.
*/
MergeAllBranchesOfBankIntoOne,
/**
* Removes banks like 'Deutsche Bank (Gf intern)' or 'UniCredit Bank - HVB Settlement EAC01' etc.
*/
RemoveInstitutionInternalBank
}

View File

@ -1,27 +0,0 @@
package net.dankito.banking.banklistcreator
import net.dankito.banking.banklistcreator.prettifier.BankListPrettifierOption
import org.junit.Test
import org.junit.Ignore
import java.io.File
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
class BankListCreatorTest {
private val underTest = BankListCreator()
@Test
fun createBankListJson() {
// TODO: set path to bank list file from Deutsche Kreditwirtschaft in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
underTest.createDetailedAndPrettifiedBankListFromDeutscheKreditwirtschaftXlsxFile(
TestConfig.DeutscheKreditwirtschaftBankListXlsxFile,
File("../BankFinder/src/commonMain/resources/DetailedBankList.json"),
File("../BankFinder/src/commonMain/resources/BankList.json"),
BankListPrettifierOption.values().toList()
)
}
}

View File

@ -1,15 +0,0 @@
package net.dankito.banking.banklistcreator
import java.io.File
open class TestConfig {
companion object {
// TODO: set path to bank list file from Deutsche Kreditwirtschaft here
val DeutscheKreditwirtschaftBankListXlsxFile = File("")
}
}

View File

@ -1,36 +0,0 @@
package net.dankito.banking.banklistcreator.parser
import net.dankito.banking.banklistcreator.TestConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.Ignore
import org.junit.Test
import java.io.File
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
class DeutscheKreditwirtschaftBankListParserTest {
private val underTest = DeutscheKreditwirtschaftBankListParser()
@Test
fun parse() {
// when
// TODO: set path to bank list file from Deutsche Kreditwirtschaft in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
val result = underTest.parse(TestConfig.DeutscheKreditwirtschaftBankListXlsxFile)
// then
assertThat(result).hasSize(16282)
result.forEach { bankInfo ->
assertThat(bankInfo.name).isNotEmpty()
assertThat(bankInfo.bankCode).isNotEmpty()
// assertThat(bankInfo.bic).isNotEmpty() // TODO: is there a way to find BICs for all banks?
assertThat(bankInfo.postalCode).isNotEmpty()
assertThat(bankInfo.city).isNotEmpty()
assertThat(bankInfo.checksumMethod).isNotEmpty()
}
}
}

View File

@ -1,46 +0,0 @@
package net.dankito.banking.banklistcreator.prettifier
import net.dankito.banking.banklistcreator.TestConfig
import net.dankito.banking.banklistcreator.parser.DeutscheKreditwirtschaftBankListParser
import org.assertj.core.api.Assertions.assertThat
import org.junit.Ignore
import org.junit.Test
@Ignore // not an automatic test, set your path to your bank list file in TestConfig.DeutscheKreditwirtschaftBankListXlsxFile
class BankListPrettifierTest {
private val underTest = BankListPrettifier()
private val allBanks = DeutscheKreditwirtschaftBankListParser().parse(TestConfig.DeutscheKreditwirtschaftBankListXlsxFile)
@Test
fun mapBankNamesToWellKnownNames() {
// when
val result = underTest.mapBankNamesToWellKnownNames(allBanks)
// then
assertThat(result).hasSize(allBanks.size)
val resultingBanksNames = result.map { it.name }
assertThat(resultingBanksNames.filter { it.contains("DB Privat- und Firmenkundenbank") }).isEmpty()
}
@Test
fun removeBanksWithSameBankCodeAndPostalCode() {
// when
val result = underTest.removeBanksWithSameBankCodeAndPostalCode(allBanks)
// then
assertThat(result).hasSizeLessThan(allBanks.size)
val resultingBanksNames = result.map { it.name }
assertThat(resultingBanksNames.filter { it.contains("PSD Bank München (Gf P2)") }).isEmpty()
}
}

View File

@ -1,24 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
the key "bySecond" into the logger context. This value will be
available to all subsequent configuration elements. -->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<!-- Raise log level here if you don't want to see noisy log output -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -1,26 +0,0 @@
apply plugin: 'java-library'
apply plugin: 'kotlin'
ext.artifactName = "lucene-bank-finder"
sourceCompatibility = "7"
targetCompatibility = "7"
dependencies {
api project(":BankFinder")
implementation "net.dankito.search:lucene-4-utils:$luceneUtilsVersion"
// TODO: enable to reference BankFinder tests again to run LuceneBankFinderTest
// testImplementation project(path: ':BankFinder', configuration: 'testOutput')
testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
testImplementation "org.assertj:assertj-core:$assertJVersion"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
}

View File

@ -1,215 +0,0 @@
package net.dankito.banking.bankfinder
import net.dankito.utils.hashing.HashAlgorithm
import net.dankito.utils.hashing.HashService
import net.dankito.utils.io.FileUtils
import net.dankito.utils.lucene.index.DocumentsWriter
import net.dankito.utils.lucene.index.FieldBuilder
import net.dankito.utils.lucene.mapper.PropertyDescription
import net.dankito.utils.lucene.mapper.PropertyType
import net.dankito.utils.lucene.search.FieldMapper
import net.dankito.utils.lucene.search.MappedSearchConfig
import net.dankito.utils.lucene.search.QueryBuilder
import net.dankito.utils.lucene.search.Searcher
import org.apache.lucene.document.Document
import org.apache.lucene.search.Query
import org.slf4j.LoggerFactory
import java.io.File
open class LuceneBankFinder(indexFolder: File) : BankFinderBase(), IBankFinder {
companion object {
const val IndexedBankListFileHashIdFieldName = "IndexedBankListFileHashId"
const val IndexedBankListFileHashIdFieldValue = "IndexedBankListFileHashValue"
const val IndexedBankListFileHashFieldName = "IndexedBankListFileHash"
const val BankInfoNameFieldName = "name"
const val BankInfoBankCodeFieldName = "bank_code"
const val BankInfoBicFieldName = "bic"
const val BankInfoCityIndexedFieldName = "city_indexed"
const val BankInfoCityStoredFieldName = "city_stored"
const val BankInfoPostalCodeFieldName = "postal_code"
const val BankInfoPinTanServerAddressFieldName = "pin_tan_server_address"
const val BankInfoPinTanVersionFieldName = "pin_tan_version"
val bankInfoProperties = listOf(
PropertyDescription(PropertyType.String, BankInfoNameFieldName, BankInfo::name),
PropertyDescription(PropertyType.String, BankInfoBankCodeFieldName, BankInfo::bankCode),
PropertyDescription(PropertyType.String, BankInfoBicFieldName, BankInfo::bic),
PropertyDescription(PropertyType.String, BankInfoPostalCodeFieldName, BankInfo::postalCode),
PropertyDescription(PropertyType.String, BankInfoCityStoredFieldName, BankInfo::city),
PropertyDescription(PropertyType.NullableString, BankInfoPinTanServerAddressFieldName, BankInfo::pinTanAddress),
PropertyDescription(PropertyType.NullableString, BankInfoPinTanVersionFieldName, BankInfo::pinTanVersion)
)
private val log = LoggerFactory.getLogger(LuceneBankFinder::class.java)
}
protected val indexDir = File(indexFolder, "banklist")
protected val fileUtils = FileUtils()
protected val hashService = HashService(fileUtils)
protected val fields = FieldBuilder()
protected val queries = QueryBuilder()
protected val mapper = FieldMapper()
protected val searcher = Searcher(indexDir)
protected var bankFinderWhileUpdatingIndex: IBankFinder? = null
override fun findBankByBankCode(query: String): List<BankInfo> {
bankFinderWhileUpdatingIndex?.let {
return it.findBankByBankCode(query)
}
if (query.isBlank()) {
return getBankList()
}
val luceneQuery = queries.startsWith(BankInfoBankCodeFieldName, query)
return getBanksFromQuery(luceneQuery)
}
override fun findBankByNameBankCodeOrCityForNonEmptyQuery(query: String): List<BankInfo> {
bankFinderWhileUpdatingIndex?.let {
return it.findBankByNameBankCodeOrCity(query)
}
val luceneQuery = queries.createQueriesForSingleTerms(query.toLowerCase()) { singleTerm ->
listOf(
queries.fulltextQuery(BankInfoNameFieldName, singleTerm),
queries.startsWith(BankInfoCityIndexedFieldName, singleTerm),
queries.startsWith(BankInfoBankCodeFieldName, singleTerm)
// TODO: add query for branchesInOtherCities
)
}
return getBanksFromQuery(luceneQuery)
}
override fun searchBankByBic(bic: String): BankInfo? {
(bankFinderWhileUpdatingIndex as? BankFinderBase)?.let {
return it.searchBankByBic(bic)
}
return getBanksFromQuery(queries.exact(BankInfoBicFieldName, bic)).firstOrNull()
}
override fun getBankList(): List<BankInfo> {
bankFinderWhileUpdatingIndex?.let {
return it.getBankList()
}
return getBanksFromQuery(queries.allDocumentsThatHaveField(BankInfoNameFieldName))
}
protected open fun getBanksFromQuery(query: Query): List<BankInfo> {
// there are more than 16.000 banks in bank list -> 10.000 is too few
return searcher.searchAndMapLazily(MappedSearchConfig(query, BankInfo::class.java, bankInfoProperties, 100_000))
}
override fun preloadBankList() {
val hashSearchResult = searcher.search(
queries.exact(IndexedBankListFileHashIdFieldName, IndexedBankListFileHashIdFieldValue, false))
val lastIndexedBankListFileHash = hashSearchResult.hits.firstOrNull()?.let {
mapper.string(it, IndexedBankListFileHashFieldName)
}
if (lastIndexedBankListFileHash == null) {
updateIndex()
}
else {
val currentBankListFileHash = calculateCurrentBankListFileHash()
if (currentBankListFileHash != lastIndexedBankListFileHash) {
updateIndex(currentBankListFileHash)
}
}
}
protected open fun updateIndex() {
updateIndex(calculateCurrentBankListFileHash())
}
protected open fun updateIndex(bankListFileHash: String) {
try {
val banks = loadBankList()
// while indexing - which takes a long time on Android - use InMemoryBankFinder so that user sees at least some search results even though it's slower
bankFinderWhileUpdatingIndex = InMemoryBankFinder(banks)
fileUtils.deleteFolderRecursively(indexDir) // delete current index
indexDir.mkdirs()
writeBanksToIndex(banks, bankListFileHash)
bankFinderWhileUpdatingIndex = null // now use LuceneBankFinder again for searching
} catch (e: Exception) {
log.error("Could not update index", e)
}
}
protected open fun writeBanksToIndex(banks: List<BankInfo>, bankListFileHash: String) {
DocumentsWriter(indexDir).use { writer ->
writer.saveDocuments(banks.map {
createDocumentForBank(it, writer)
})
writer.updateDocument(
IndexedBankListFileHashIdFieldName, IndexedBankListFileHashIdFieldValue,
fields.storedField(IndexedBankListFileHashFieldName, bankListFileHash))
writer.optimizeIndex()
}
}
protected open fun createDocumentForBank(bank: BankInfo, writer: DocumentsWriter): Document {
val indexableFields = mutableListOf(
fields.fullTextSearchField(BankInfoNameFieldName, bank.name, true),
fields.keywordField(BankInfoBankCodeFieldName, bank.bankCode, true),
fields.keywordField(BankInfoBicFieldName, bank.bic, true),
fields.fullTextSearchField(BankInfoCityIndexedFieldName, bank.city, true),
fields.storedField(BankInfoCityStoredFieldName, bank.city),
fields.storedField(BankInfoPostalCodeFieldName, bank.postalCode),
fields.nullableStoredField(BankInfoPinTanServerAddressFieldName, bank.pinTanAddress),
fields.nullableStoredField(BankInfoPinTanVersionFieldName, bank.pinTanVersion)
// TODO: index branchesInOtherCities
)
bank.branchesInOtherCities.forEach { branchCity ->
indexableFields.add(fields.storedField(BankInfoCityStoredFieldName, branchCity))
}
return writer.createDocumentForNonNullFields(indexableFields)
}
protected open fun calculateCurrentBankListFileHash(): String {
return calculateHash(BankListDeserializer().readBankListFile())
}
protected open fun calculateHash(stringToHash: String): String {
return hashService.hashString(HashAlgorithm.SHA512, stringToHash)
}
}

View File

@ -1,33 +0,0 @@
// TODO: enable to reference BankFinder tests again to run LuceneBankFinderTest
//package net.dankito.banking.bankfinder
//
//import net.dankito.utils.io.FileUtils
//import org.junit.jupiter.api.AfterAll
//import java.io.File
//
//
//class LuceneBankFinderTest : BankFinderTestBase() {
//
// companion object {
// private val IndexFolder = File("testData", "index")
//
//
// @AfterAll
// @JvmStatic
// fun deleteIndex() {
// FileUtils().deleteFolderRecursively(IndexFolder.parentFile)
// }
// }
//
//
// override fun createBankFinder(): IBankFinder {
// return LuceneBankFinder(IndexFolder)
// }
//
//
// init {
// underTest.preloadBankList()
// }
//
//}