From d000bc932c023415221b7896efbf88e33cf4dabc Mon Sep 17 00:00:00 2001 From: closset703 Date: Thu, 13 Aug 2026 15:39:50 +0900 Subject: [PATCH] fix: write xml:space="preserve" for inline strings Excel trims leading and trailing whitespace from cells written with inlineString(int, int, String), because the element is emitted without xml:space="preserve". StringCache.write() and RichText.Run.write() already emit the attribute, so the same value is preserved or trimmed depending on which string API the caller picks. The added test asserts the attribute on all three string paths (shared, inline, rich). Excel's trimming cannot be reproduced in a unit test, so it verifies the emitted XML instead; it fails without the one-line change in Cell.write() and passes with it. Fixes #624 Co-Authored-By: Claude Opus 5 (1M context) --- .../main/java/org/dhatim/fastexcel/Cell.java | 2 +- .../org/dhatim/fastexcel/CorrectnessTest.java | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Cell.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Cell.java index 37f1ec2c..89ee26f4 100644 --- a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Cell.java +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Cell.java @@ -62,7 +62,7 @@ void write(Writer w, int r, int c) throws IOException { } else if (value instanceof RichText) { ((RichText) value).write(w); } else if (value instanceof String) { - w.append("").appendEscaped((String) value).append(""); + w.append("").appendEscaped((String) value).append(""); } else if (value != null) { w.append(""); if (value instanceof CachedString) { diff --git a/fastexcel-writer/src/test/java/org/dhatim/fastexcel/CorrectnessTest.java b/fastexcel-writer/src/test/java/org/dhatim/fastexcel/CorrectnessTest.java index 1ec32dad..c637cc52 100644 --- a/fastexcel-writer/src/test/java/org/dhatim/fastexcel/CorrectnessTest.java +++ b/fastexcel-writer/src/test/java/org/dhatim/fastexcel/CorrectnessTest.java @@ -17,17 +17,23 @@ import org.apache.commons.io.output.NullOutputStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.*; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import java.util.function.Consumer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import static org.assertj.core.api.Assertions.assertThat; import static org.dhatim.fastexcel.CellAddress.convertNumToColString; @@ -770,4 +776,40 @@ void testFormatCodeWithSpecialCharacters() throws Exception { // If we reach here without exception, the XML was valid } + @Test + void stringCellsKeepEdgeWhitespace(@TempDir Path directory) throws Exception { + Path file = directory.resolve("whitespace.xlsx"); + writeWorkbook(wb -> { + Worksheet ws = wb.newWorksheet("Sheet 1"); + ws.value(0, 0, " shared "); + ws.inlineString(1, 0, " inline "); + ws.inlineString(2, 0, RichText.builder().run(" rich ").end().build()); + }, file.toString()); + + try (ZipFile workbook = new ZipFile(file.toFile())) { + String sharedStrings = readPart(workbook, "xl/sharedStrings.xml"); + String sheet = readPart(workbook, "xl/worksheets/sheet1.xml"); + + // Every string path must mark its text as whitespace-significant, otherwise + // Excel trims leading and trailing whitespace when it reads the cell. + assertThat(sharedStrings).contains(" shared "); + assertThat(sheet).contains(" inline "); + assertThat(sheet).contains(" rich "); + } + } + + private static String readPart(ZipFile workbook, String name) throws IOException { + ZipEntry entry = workbook.getEntry(name); + assertThat(entry).as("%s should be present", name).isNotNull(); + try (InputStream in = workbook.getInputStream(entry)) { + ByteArrayOutputStream contents = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int read; + while ((read = in.read(buffer)) != -1) { + contents.write(buffer, 0, read); + } + return new String(contents.toByteArray(), StandardCharsets.UTF_8); + } + } + }