All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.Csv.kt Maven / Gradle / Ivy

/** Copyright 2023 Halfbit GmbH, Sergej Shafarenka */
package de.halfbit.csv

import de.halfbit.csv.BaseCsv.Row

/**
 * Object with comma-separated values stored as list of [Row]'s. Each row is a list
 * of strings. Empty values are empty strings.
 *
 * If your CSV-data has header row, use [BaseCsv] instead of this base type.
 */
public interface BaseCsv {
    public val allRows: List

    // Multiline issue: https://stackoverflow.com/questions/2668678/importing-csv-with-line-breaks-in-excel-2007
    public fun toCsvText(
        newLine: NewLine = NewLine.LF,
        escapeWhitespaces: Boolean = false,
    ): String = buildString {
        allRows.forEach { row ->
            row.forEachIndexed { index, value ->
                val escapedValue = value.escapeCsvValue(escapeWhitespaces)
                append(escapedValue)
                if (index < row.lastIndex) {
                    append(',')
                }
            }
            append(newLine.value)
        }
    }

    public interface Row : List {
        public fun replaceValue(valueIndex: Int, newValue: String): Row
    }
}

/**
 * CSV-object  by with a mandatory header row. It has more convenient methods
 * for working with columns by their names.
 */
public interface Csv : BaseCsv {
    public val header: HeaderRow
    public val data: List

    public interface HeaderRow : Row {
        public fun indexOfColumn(name: String): Int
    }

    public interface DataRow : Row {
        public fun value(columnName: String): String
        public fun replaceValue(columnName: String, newValue: String): DataRow
    }

    public companion object {
        public fun parserText(csvText: String): Csv = parseCsv(csvText)

        public fun fromLists(allRows: List>): BaseCsv {
            return BaseCsv(allRows.map { DefaultRow(it) })
        }

        public fun fromLists(header: List, data: List>): Csv {
            val headerRow = DefaultHeaderRow(header)
            return Csv(
                header = headerRow,
                data = data.map { DefaultDataRow(it, headerRow) },
            )
        }
    }
}

public enum class NewLine(
    public val value: String,
) {
    LF("\n"),
    CRLF("\r\n")
}

// https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules
private fun String.escapeCsvValue(
    escapeWhitespaces: Boolean,
): String =
    when {
        isEmpty() -> "\"\""
        contains(",") || contains("\n") || (escapeWhitespaces && contains(" ")) -> {
            val escapedQuoted = replace("\"", "\"\"")
            "\"${escapedQuoted}\""
        }
        else -> this
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy