diff --git a/README.md b/README.md index 396c538..c357c26 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,25 @@ val tsvReader = csvReader { escapeChar = '\\' } ``` +#### Listening Interface for Skipped Row Events +```kotlin +csvReader { + excessFieldsRowBehaviour = ExcessFieldsRowBehaviour.IGNORE + insufficientFieldsRowBehaviour = InsufficientFieldsRowBehaviour.IGNORE + onSkippedEvent = skipNotification { + listeners["id"] = SkipNotify { println(it) } + } +} +``` +* skipNotification - builder function for creating notification listener +* listeners `Mutable` - map of listener function to be notified +* `SkipNotify` - functional interface template +```kotlin +fun interface SkipNotify { + fun onSkipped(row: SkippedRow) +} +``` | Option | default value | description | |--------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | logger | _no-op_ | Logger instance for logging debug information at runtime. | @@ -175,6 +193,7 @@ val tsvReader = csvReader { | ~~skipMissMatchedRow~~ | `false` | Deprecated. Replace with appropriate values in `excessFieldsRowBehaviour` and `insufficientFieldsRowBehaviour`, e.g. both set to `IGNORE`. ~~Whether to skip an invalid row. If `ignoreExcessCols` is true, only rows with less than the expected number of columns will be skipped.~~ | | excessFieldsRowBehaviour | `ERROR` | Behaviour to use when a row has more fields (columns) than expected. `ERROR` (default), `IGNORE` (skip the row) or `TRIM` (remove the excess fields at the end of the row to match the expected number of fields). | | insufficientFieldsRowBehaviour | `ERROR` | Behaviour to use when a row has fewer fields (columns) than expected. `ERROR` (default), `IGNORE` (skip the row) or `EMPTY_STRING` (replace missing fields with an empty string). | +| onSkippedEvent | `null` | A pluggable Interface on listening when skipping either an insufficient or an excess number of fields. | ### CSV Write examples diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvFileReader.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvFileReader.kt index 3577ab0..c6f73e5 100644 --- a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvFileReader.kt +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvFileReader.kt @@ -3,6 +3,8 @@ package com.github.doyaaaaaken.kotlincsv.client import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour +import com.github.doyaaaaaken.kotlincsv.event.skip.SkipType +import com.github.doyaaaaaken.kotlincsv.event.skip.SkippedRow import com.github.doyaaaaaken.kotlincsv.parser.CsvParser import com.github.doyaaaaaken.kotlincsv.util.CSVAutoRenameFailedException import com.github.doyaaaaaken.kotlincsv.util.CSVFieldNumDifferentException @@ -55,13 +57,13 @@ class CsvFileReader internal constructor( logger.info("trimming excess rows. [csv row num = ${idx + 1}, fields num = ${row.size}, fields num of row = $numFieldsInRow]") row.subList(0, numFieldsInRow) } else if (ctx.skipMissMatchedRow || ctx.excessFieldsRowBehaviour == ExcessFieldsRowBehaviour.IGNORE) { - skipMismatchedRow(idx, row, numFieldsInRow) + skipMismatchedRow(idx, row, numFieldsInRow, SkipType.ExcessFieldsRowBehaviour) } else { throw CSVFieldNumDifferentException(numFieldsInRow, row.size, idx + 1) } } else if (numFieldsInRow != row.size) { if (ctx.skipMissMatchedRow || ctx.insufficientFieldsRowBehaviour == InsufficientFieldsRowBehaviour.IGNORE) { - skipMismatchedRow(idx, row, numFieldsInRow) + skipMismatchedRow(idx, row, numFieldsInRow, SkipType.InsufficientFieldsRowBehaviour) } else if (ctx.insufficientFieldsRowBehaviour == InsufficientFieldsRowBehaviour.EMPTY_STRING) { val numOfMissingFields = numFieldsInRow - row.size row.plus(List(numOfMissingFields) { "" }) @@ -77,9 +79,13 @@ class CsvFileReader internal constructor( private fun skipMismatchedRow( idx: Int, row: List, - numFieldsInRow: Int + numFieldsInRow: Int, + skipType: SkipType ): Nothing? { - logger.info("skip miss matched row. [csv row num = ${idx + 1}, fields num = ${row.size}, fields num of first row = $numFieldsInRow]") + val message = "skip miss matched row. [csv row num = ${idx + 1}, fields num = ${row.size}, fields num of first row = $numFieldsInRow]" + val skippedRow = SkippedRow(idx, row, message, skipType) + ctx.onSkippedEvent?.notify(skippedRow) + logger.info(message) return null } diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/dsl/context/CsvReaderContext.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/dsl/context/CsvReaderContext.kt index ca520a0..21a43b2 100644 --- a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/dsl/context/CsvReaderContext.kt +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/dsl/context/CsvReaderContext.kt @@ -1,5 +1,6 @@ package com.github.doyaaaaaken.kotlincsv.dsl.context +import com.github.doyaaaaaken.kotlincsv.event.skip.INotifySkippedEvent import com.github.doyaaaaaken.kotlincsv.util.Const import com.github.doyaaaaaken.kotlincsv.util.CsvDslMarker import com.github.doyaaaaaken.kotlincsv.util.logger.Logger @@ -88,6 +89,11 @@ interface ICsvReaderContext { * If a row exceeds have the expected number of fields (columns), how, and if, the reader should proceed */ val excessFieldsRowBehaviour: ExcessFieldsRowBehaviour + + /** + * A pluggable Interface on listening when skipping either an insufficient or an excess number of fields + */ + val onSkippedEvent: INotifySkippedEvent? } enum class InsufficientFieldsRowBehaviour { @@ -142,4 +148,5 @@ class CsvReaderContext : ICsvReaderContext { override var autoRenameDuplicateHeaders: Boolean = false override var insufficientFieldsRowBehaviour: InsufficientFieldsRowBehaviour = InsufficientFieldsRowBehaviour.ERROR override var excessFieldsRowBehaviour: ExcessFieldsRowBehaviour = ExcessFieldsRowBehaviour.ERROR + override var onSkippedEvent: INotifySkippedEvent? = null } diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/CsvSkipEvent.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/CsvSkipEvent.kt new file mode 100644 index 0000000..5f8e0ea --- /dev/null +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/CsvSkipEvent.kt @@ -0,0 +1,18 @@ +package com.github.doyaaaaaken.kotlincsv.event.skip + + +fun interface SkipNotify { + fun onSkipped(row: SkippedRow) +} + +/** + * interface function that will be triggered when skipping either insufficient or excess number of fields + */ +fun interface INotifySkippedEvent { + fun notify(skippedRow: SkippedRow) +} + + +fun skipNotification(block: NotifySkipEvent.() -> Unit): INotifySkippedEvent { + return NotifySkipEvent().apply(block) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/NotifySkipEvent.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/NotifySkipEvent.kt new file mode 100644 index 0000000..b15b43e --- /dev/null +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/NotifySkipEvent.kt @@ -0,0 +1,8 @@ +package com.github.doyaaaaaken.kotlincsv.event.skip + +class NotifySkipEvent: INotifySkippedEvent { + val listeners = mutableMapOf() + override fun notify(skippedRow: SkippedRow) { + listeners.forEach { (_, value) -> value.onSkipped(skippedRow) } + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkipType.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkipType.kt new file mode 100644 index 0000000..37daf03 --- /dev/null +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkipType.kt @@ -0,0 +1,5 @@ +package com.github.doyaaaaaken.kotlincsv.event.skip +enum class SkipType { + InsufficientFieldsRowBehaviour, + ExcessFieldsRowBehaviour +} diff --git a/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkippedRow.kt b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkippedRow.kt new file mode 100644 index 0000000..0a0149b --- /dev/null +++ b/src/commonMain/kotlin/com/github/doyaaaaaken/kotlincsv/event/skip/SkippedRow.kt @@ -0,0 +1,24 @@ +package com.github.doyaaaaaken.kotlincsv.event.skip + +/** + * data class containing relevant information + */ +data class SkippedRow( + /** + * row index in the csv file + */ + val idx: Int, + /** + * row values + */ + val row: List, + /** + * reason for skipping + */ + val message: String, + /** + * type of skip + */ + val skipType: SkipType + +) diff --git a/src/jvmTest/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvReaderTest.kt b/src/jvmTest/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvReaderTest.kt index df92f47..6329755 100644 --- a/src/jvmTest/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvReaderTest.kt +++ b/src/jvmTest/kotlin/com/github/doyaaaaaken/kotlincsv/client/CsvReaderTest.kt @@ -4,6 +4,8 @@ import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvReaderContext import com.github.doyaaaaaken.kotlincsv.dsl.context.ExcessFieldsRowBehaviour import com.github.doyaaaaaken.kotlincsv.dsl.context.InsufficientFieldsRowBehaviour import com.github.doyaaaaaken.kotlincsv.dsl.csvReader +import com.github.doyaaaaaken.kotlincsv.event.skip.SkipNotify +import com.github.doyaaaaaken.kotlincsv.event.skip.skipNotification import com.github.doyaaaaaken.kotlincsv.util.CSVFieldNumDifferentException import com.github.doyaaaaaken.kotlincsv.util.CSVParseFormatException import com.github.doyaaaaaken.kotlincsv.util.Const @@ -206,6 +208,22 @@ class CsvReaderTest : WordSpec({ actual.size shouldBe 1 } } + "it should be be possible to listen to skip events for insufficient or excess fields " { + val expected = listOf(listOf("a", "b")) + val actual = + csvReader { + excessFieldsRowBehaviour = ExcessFieldsRowBehaviour.IGNORE + insufficientFieldsRowBehaviour = InsufficientFieldsRowBehaviour.IGNORE + onSkippedEvent = skipNotification { + listeners["id"] = SkipNotify { println(it) } + } + }.readAll(readTestDataFile("varying-column-lengths.csv")) + + assertSoftly { + actual shouldBe expected + actual.size shouldBe 1 + } + } "it should be be possible to replace insufficient fields with strings and skip rows with excess fields" { val expected = listOf(listOf("a", "b"), listOf("c", "")) val actual =