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

commonMain.korlibs.io.serialization.csv.CSV.kt Maven / Gradle / Ivy

There is a newer version: 6.0.0-alpha2
Show newest version
@file:Suppress("PackageDirectoryMismatch")

package korlibs.io.serialization.csv

import korlibs.util.*

class CSV(val lines: List>, val names: List? = null) : Collection {
    val namesToIndex: Map = names?.withIndex()?.associate { it.value to it.index } ?: emptyMap()
    val linesWithNames = if (names != null) listOf(names, *lines.toTypedArray()) else lines
    val records: List = lines.map { Record(it) }

    override val size: Int get() = records.size
    operator fun get(index: Int) = records[index]

    override fun iterator(): Iterator = records.iterator()
    override fun contains(element: Record): Boolean = records.contains(element)
    override fun containsAll(elements: Collection): Boolean = records.containsAll(elements)
    override fun isEmpty(): Boolean = records.isEmpty()

    inner class Record(val cells: List) {
        operator fun get(index: Int): String = getOrNull(index) ?: error("Can't find element at $index")
        operator fun get(name: String): String = getOrNull(name) ?: error("Can't find element '$name'")

        fun getOrNull(index: Int): String? = cells.getOrNull(index)
        fun getOrNull(name: String): String? = namesToIndex[name]?.let { cells[it] }

        fun toMap(): Map = if (names != null) cells.zip(names).associate { it.first to it.second } else cells.mapIndexed { index, s -> index to s }.associate { "${it.first}" to it.second }
        override fun toString(): String = if (names != null) "${toMap()}" else "$cells"
    }

    fun toString(separator: Char): String = linesWithNames.joinToString("\n") { serializeLine(it, separator) }
    override fun toString(): String = toString(DEFAULT_SEPARATOR)

    companion object {
        const val DEFAULT_SEPARATOR = ','

        internal fun serializeElement(value: String, separator: Char): String {
            if (!value.contains('"') && !value.contains('\n') && !value.contains(separator)) return value
            val out = StringBuilder(value.length)
            for (n in 0 until value.length) {
                out.append(value[n])
            }
            return out.toString()
        }

        fun serializeLine(values: List, separator: Char = DEFAULT_SEPARATOR): String {
            return values.joinToString("$separator") { serializeElement(it, separator) }
        }

        fun parseLine(line: String, separator: Char = DEFAULT_SEPARATOR): List = parseLine(SimpleStrReader(line), separator)

        fun parseLine(line: SimpleStrReader, separator: Char = DEFAULT_SEPARATOR): List {
            val out = arrayListOf()
            val str = StringBuilder()
            while (line.hasMore) {
                val c = line.readChar()
                when (c) {
                    // Quoted string
                    '"' -> {
                        loop@while (line.hasMore) {
                            val c2 = line.readChar()
                            when (c2) {
                                '"' -> {
                                    if (line.peekChar() == '"') {
                                        line.readChar()
                                        str.append('"')
                                    } else {
                                        break@loop
                                    }
                                }
                                else -> str.append(c2)
                            }
                        }
                    }
                    // Line break
                    '\n' -> {
                        break
                    }
                    // Empty string
                    separator -> {
                        out.add(str.toString())
                        str.clear()
                    }
                    // Normal string
                    else -> {
                        str.append(c)
                    }
                }
            }
            out.add(str.toString())
            str.clear()
            return out
        }

        fun parse(s: SimpleStrReader, separator: Char = DEFAULT_SEPARATOR, headerNames: Boolean = true): CSV {
            val lines = arrayListOf>()
            while (s.hasMore) {
                lines.add(parseLine(s, separator))
            }
            return if (headerNames) CSV(lines.drop(1), lines[0]) else CSV(lines, null)
        }

        fun parse(str: String, separator: Char = DEFAULT_SEPARATOR, headerNames: Boolean = true): CSV = parse(SimpleStrReader(str), separator, headerNames)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy