diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/XlsxEscapeUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/XlsxEscapeUtils.java index 64f42d4ec..6c0ff6fb0 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/XlsxEscapeUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/XlsxEscapeUtils.java @@ -21,6 +21,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.fesod.sheet.write.handler.impl.EscapeHexCellWriteHandler; /** * The {@code _xHHHH_} escapes that xlsx uses for characters XML 1.0 forbids, defined by section 3.18.9 of the Office @@ -37,11 +38,11 @@ * {@code _x005F_} is the escape for the underscore. Decoding it yields the literal back, not {@code A}. *

* The write half of the same convention lives in - * {@link org.apache.fesod.sheet.write.handler.EscapeHexCellWriteHandler EscapeHexCellWriteHandler}, which produces + * {@link EscapeHexCellWriteHandler EscapeHexCellWriteHandler}, which produces * that {@code _x005F_x} form. Both sides read {@code _xHHHH_} the same way, so a change to what counts as an escape * belongs in both. * - * @see org.apache.fesod.sheet.write.handler.EscapeHexCellWriteHandler + * @see EscapeHexCellWriteHandler */ public class XlsxEscapeUtils { diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/DefaultWriteHandlerLoader.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/DefaultWriteHandlerLoader.java index 4621e3976..924282ca0 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/DefaultWriteHandlerLoader.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/DefaultWriteHandlerLoader.java @@ -30,6 +30,7 @@ import org.apache.fesod.sheet.support.ExcelTypeEnum; import org.apache.fesod.sheet.write.handler.impl.DefaultRowWriteHandler; import org.apache.fesod.sheet.write.handler.impl.DimensionWorkbookWriteHandler; +import org.apache.fesod.sheet.write.handler.impl.EscapeHexCellWriteHandler; import org.apache.fesod.sheet.write.handler.impl.FillStyleCellWriteHandler; import org.apache.fesod.sheet.write.handler.impl.WriteSheetWorkbookWriteHandler; import org.apache.fesod.sheet.write.style.DefaultStyle; @@ -60,6 +61,7 @@ public static List loadDefaultHandler(Boolean useDefaultStyle, Exc case XLSX: handlerList.add(new DimensionWorkbookWriteHandler()); handlerList.add(new DefaultRowWriteHandler()); + handlerList.add(new EscapeHexCellWriteHandler()); handlerList.add(new FillStyleCellWriteHandler()); handlerList.add(new WriteSheetWorkbookWriteHandler()); if (useDefaultStyle) { diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandler.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/impl/EscapeHexCellWriteHandler.java similarity index 86% rename from fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandler.java rename to fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/impl/EscapeHexCellWriteHandler.java index b2ec70d50..20c748592 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandler.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/handler/impl/EscapeHexCellWriteHandler.java @@ -17,11 +17,13 @@ * under the License. */ -package org.apache.fesod.sheet.write.handler; +package org.apache.fesod.sheet.write.handler.impl; import org.apache.fesod.sheet.enums.CellDataTypeEnum; +import org.apache.fesod.sheet.event.NotRepeatExecutor; import org.apache.fesod.sheet.metadata.Head; import org.apache.fesod.sheet.metadata.data.WriteCellData; +import org.apache.fesod.sheet.write.handler.CellWriteHandler; import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder; import org.apache.fesod.sheet.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.Cell; @@ -36,18 +38,18 @@ * To store the literal _xHHHH_ sequence without it being decoded by POI, we need to escape the initial underscore by * replacing _x with _x005F_x. *

- * This handler is not registered by default. Without it the writer stores {@code _xHHHH_}-shaped text exactly as - * typed, and any reader that follows the convention - Fesod, POI or Excel - decodes it back to the character it - * names, so the literal does not survive a round trip. Register it on the write to keep such text intact. - *

* The read half of the same convention lives in * {@link org.apache.fesod.sheet.util.XlsxEscapeUtils#utfDecode(String) XlsxEscapeUtils.utfDecode}, which undoes what * this handler writes. Both sides read {@code _xHHHH_} the same way, so a change to what counts as an escape belongs * in both. + *

+ * This handler implements {@link NotRepeatExecutor} so that it is only executed once. {@link #uniqueValue()} returns + * the fully qualified class name, which never collides with handlers from other classes even across different class + * loaders. * * @see org.apache.fesod.sheet.util.XlsxEscapeUtils#utfDecode(String) */ -public class EscapeHexCellWriteHandler implements CellWriteHandler { +public class EscapeHexCellWriteHandler implements CellWriteHandler, NotRepeatExecutor { // ASCII hex digits only. Not Character.digit(c, 16), which also accepts non-ASCII // digits such as U+0663 that OOXML never uses. @@ -64,6 +66,17 @@ public class EscapeHexCellWriteHandler implements CellWriteHandler { private static final int PREFIX_LENGTH = PREFIX.length(); private static final int HEX_DIGIT_COUNT = 4; + /** + * Returns the fully qualified class name as the unique identity of this handler. + *

+ * This handler only needs to execute once. The fully qualified class name serves as the unique identity, + * which never collides with handlers from other classes even across different class loaders. + */ + @Override + public String uniqueValue() { + return this.getClass().getName(); + } + @Override public void afterCellDataConverted( WriteSheetHolder writeSheetHolder, diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/readwrite/HexEscapeRoundTripTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/readwrite/HexEscapeRoundTripTest.java deleted file mode 100644 index fba19522e..000000000 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/readwrite/HexEscapeRoundTripTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.fesod.sheet.readwrite; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import org.apache.fesod.sheet.FesodSheet; -import org.apache.fesod.sheet.testkit.Tags; -import org.apache.fesod.sheet.testkit.base.AbstractExcelTest; -import org.apache.fesod.sheet.testkit.enums.ExcelFormat; -import org.apache.fesod.sheet.testkit.listeners.CollectingReadListener; -import org.apache.fesod.sheet.testkit.models.SimpleData; -import org.apache.fesod.sheet.write.handler.EscapeHexCellWriteHandler; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -/** - * Regression test for issue #696: the {@code _xHHHH_} - * escapes were undone only for cells backed by {@code sharedStrings.xml}, so an inline - * string - what the default writer emits - reached the caller with the raw escape. - */ -@Tag(Tags.ROUND_TRIP) -class HexEscapeRoundTripTest extends AbstractExcelTest { - - /** - * The handler is what puts a real escape in the cell, storing the literal as {@code Product_x005F_x0002_Code}. - * Taking it from the writer's own output instead would tie the expectation to a writer default, not to the - * reader under test. - */ - @Test - void escapedOnWrite_readsBackAsTheLiteral() throws IOException { - SimpleData data = new SimpleData(); - data.setName("Product_x0002_Code"); - File file = createTempFile("hex-escape", ExcelFormat.XLSX); - FesodSheet.write(file, SimpleData.class) - .registerWriteHandler(new EscapeHexCellWriteHandler()) - .sheet() - .doWrite(Collections.singletonList(data)); - - CollectingReadListener listener = new CollectingReadListener<>(); - FesodSheet.read(file, SimpleData.class, listener).sheet().doRead(); - List rows = listener.getRows(); - - Assertions.assertEquals(1, rows.size()); - Assertions.assertEquals("Product_x0002_Code", rows.get(0).getName()); - } -} diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerRoundTripTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerRoundTripTest.java index 6227dca34..b3ac634cd 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerRoundTripTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerRoundTripTest.java @@ -31,12 +31,16 @@ import org.apache.fesod.sheet.testkit.Tags; import org.apache.fesod.sheet.testkit.base.AbstractExcelTest; import org.apache.fesod.sheet.testkit.enums.ExcelFormat; +import org.apache.fesod.sheet.testkit.listeners.CollectingReadListener; +import org.apache.fesod.sheet.testkit.models.SimpleData; import org.apache.fesod.sheet.testkit.params.ExcelFormatSource; +import org.apache.fesod.sheet.write.handler.impl.EscapeHexCellWriteHandler; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.xssf.streaming.SXSSFCell; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @Tag(Tags.ROUND_TRIP) @@ -49,8 +53,8 @@ private File writeEscapedWorkbook(ExcelFormat format) throws IOException { FesodSheet.write(file) .excelType(format.toExcelTypeEnum()) - .head(Collections.singletonList(Collections.singletonList("value"))) .registerWriteHandler(new EscapeHexCellWriteHandler()) + .head(Collections.singletonList(Collections.singletonList("value"))) .sheet("escape") .doWrite(rows); return file; @@ -81,4 +85,22 @@ void registeredOnAWrite_keepsLiteralHexSequencesIntactAcrossFormats(ExcelFormat File file = writeEscapedWorkbook(format); Assertions.assertEquals("_xB9f0_ and _x1234_", readBackFirstDataValue(file, format)); } + + @Test + void escapedOnWrite_readsBackAsTheLiteral() throws IOException { + SimpleData data = new SimpleData(); + data.setName("Product_x0002_Code"); + File file = createTempFile("hex-escape", ExcelFormat.XLSX); + FesodSheet.write(file, SimpleData.class) + .registerWriteHandler(new EscapeHexCellWriteHandler()) + .sheet() + .doWrite(Collections.singletonList(data)); + + CollectingReadListener listener = new CollectingReadListener<>(); + FesodSheet.read(file, SimpleData.class, listener).sheet().doRead(); + List rows = listener.getRows(); + + Assertions.assertEquals(1, rows.size()); + Assertions.assertEquals("Product_x0002_Code", rows.get(0).getName()); + } } diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerTest.java index f751594e7..7a5c1b947 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/write/handler/EscapeHexCellWriteHandlerTest.java @@ -22,6 +22,7 @@ import org.apache.fesod.sheet.enums.CellDataTypeEnum; import org.apache.fesod.sheet.metadata.data.WriteCellData; import org.apache.fesod.sheet.testkit.Tags; +import org.apache.fesod.sheet.write.handler.impl.EscapeHexCellWriteHandler; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.xssf.streaming.SXSSFCell; import org.junit.jupiter.api.Assertions;