Skip to content

Commit ea0150c

Browse files
KopilovKopilov
authored andcommitted
Excel IO improvements
* Numeric header support * Save date and datetime * Allow using opened Workbook in-memory
1 parent c43fe4b commit ea0150c

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ commonsCsv = "1.8"
1212
commonsCompress = "1.21"
1313
klaxon = "5.5"
1414
fuel = "2.3.1"
15-
poi = "5.2.0"
15+
poi = "5.2.2"
1616
kotlinDatetime = "0.3.1"
1717

1818
junit = "4.13.2"

src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/xlsx.kt

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public fun DataFrame.Companion.readExcel(
8282
return wb.use { readExcel(it, sheetName, columns, rowsCount) }
8383
}
8484

85-
internal fun DataFrame.Companion.readExcel(
85+
public fun DataFrame.Companion.readExcel(
8686
wb: Workbook,
8787
sheetName: String? = null,
8888
columns: String? = null,
@@ -91,7 +91,14 @@ internal fun DataFrame.Companion.readExcel(
9191
val sheet: Sheet = sheetName
9292
?.let { wb.getSheet(it) ?: error("Sheet with name $sheetName not found") }
9393
?: wb.getSheetAt(0)
94+
return readExcel(sheet, columns, rowsCount)
95+
}
9496

97+
public fun DataFrame.Companion.readExcel(
98+
sheet: Sheet,
99+
columns: String? = null,
100+
rowsCount: Int? = null
101+
): AnyFrame {
95102
val columnIndexes = if (columns != null) {
96103
columns.split(",").flatMap {
97104
if (it.contains(":")) {
@@ -108,7 +115,12 @@ internal fun DataFrame.Companion.readExcel(
108115
val headerRow = sheet.getRow(0)
109116
val valueRows = sheet.drop(1).let { if (rowsCount != null) it.take(rowsCount) else it }
110117
val columns = columnIndexes.map { index ->
111-
val name = headerRow.getCell(index)?.stringCellValue ?: CellReference.convertNumToColString(index)
118+
val headerCell = headerRow.getCell(index)
119+
val name = if (headerCell?.cellType == CellType.NUMERIC) {
120+
headerCell.numericCellValue.toString() // Support numeric-named columns
121+
} else {
122+
headerCell?.stringCellValue ?: CellReference.convertNumToColString(index) // Use Excel column names if no data
123+
}
112124
val values = valueRows.map {
113125
val cell: Cell? = it.getCell(index)
114126
when (cell?.cellType) {
@@ -171,6 +183,17 @@ public fun <T> DataFrame<T>.writeExcel(
171183
factory: () -> Workbook
172184
) {
173185
val wb: Workbook = factory()
186+
writeExcel(wb, columnsSelector, sheetName, writeHeader)
187+
wb.write(outputStream)
188+
wb.close()
189+
}
190+
191+
public fun <T> DataFrame<T>.writeExcel(
192+
wb: Workbook,
193+
columnsSelector: ColumnsSelector<T, *> = { all() },
194+
sheetName: String? = null,
195+
writeHeader: Boolean = true
196+
): Sheet {
174197
val sheet = if (sheetName != null) {
175198
wb.createSheet(sheetName)
176199
} else {
@@ -189,6 +212,12 @@ public fun <T> DataFrame<T>.writeExcel(
189212
i++
190213
}
191214

215+
val createHelper = wb.creationHelper
216+
val cellStyleDate = wb.createCellStyle()
217+
val cellStyleDateTime = wb.createCellStyle()
218+
cellStyleDate.dataFormat = createHelper.createDataFormat().getFormat("dd.mm.yyyy")
219+
cellStyleDateTime.dataFormat = createHelper.createDataFormat().getFormat("dd.mm.yyyy hh:mm:ss")
220+
192221
columns.forEachRow {
193222
val row = sheet.createRow(i)
194223
it.values().forEachIndexed { index, any ->
@@ -198,12 +227,21 @@ public fun <T> DataFrame<T>.writeExcel(
198227
if (any != null) {
199228
val cell = row.createCell(index)
200229
cell.setCellValueByGuessedType(any)
230+
231+
when (any) {
232+
is LocalDate, is kotlinx.datetime.LocalDate -> {
233+
cell.cellStyle = cellStyleDate
234+
}
235+
is LocalDateTime, is kotlinx.datetime.LocalDateTime, is Calendar, is Date -> {
236+
cell.cellStyle = cellStyleDateTime
237+
}
238+
else -> {}
239+
}
201240
}
202241
}
203242
i++
204243
}
205-
wb.write(outputStream)
206-
wb.close()
244+
return sheet
207245
}
208246

209247
private fun Cell.setCellValueByGuessedType(any: Any) {

src/test/kotlin/org/jetbrains/kotlinx/dataframe/io/XlsxTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,12 @@ class XlsxTest {
6868
val df = DataFrame.read(testResource("datetime.xlsx"))
6969
df["time"].type() shouldBe typeOf<LocalDateTime>()
7070
}
71+
72+
@Test
73+
fun `write date time`() {
74+
val df = DataFrame.read(testResource("datetime.xlsx"))
75+
val temp = Files.createTempFile("excel", ".xlsx").toFile()
76+
df.writeExcel(temp)
77+
DataFrame.readExcel(temp) shouldBe df
78+
}
7179
}

0 commit comments

Comments
 (0)