Skip to content

Commit 58699ec

Browse files
authored
Merge branch 'main' into fix/decode-inline-string-utf-escapes
2 parents 717ab4d + afff2b5 commit 58699ec

19 files changed

Lines changed: 1209 additions & 405 deletions

File tree

.mvn/wrapper/maven-wrapper.jar

58.5 KB
Binary file not shown.

.mvn/wrapper/maven-wrapper.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# "License"); you may not use this file except in compliance
77
# with the License. You may obtain a copy of the License at
88
#
9-
# http://www.apache.org/licenses/LICENSE-2.0
9+
# https://www.apache.org/licenses/LICENSE-2.0
1010
#
1111
# Unless required by applicable law or agreed to in writing,
1212
# software distributed under the License is distributed on an
@@ -15,4 +15,4 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
18-
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
18+
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

fesod-sheet/src/main/java/org/apache/fesod/sheet/FesodSheet.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public static ExcelReaderSheetBuilder readSheet(String sheetName) {
314314
* @return Excel sheet reader builder.
315315
*/
316316
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName) {
317-
return readSheet(sheetNo, sheetName, null);
317+
return readSheet(sheetNo, sheetName, null, null);
318318
}
319319

320320
/**
@@ -326,25 +326,36 @@ public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetNam
326326
* @return
327327
*/
328328
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName, Integer numRows) {
329-
return new ExcelReaderSheetBuilder()
330-
.sheetNoIfNotNull(sheetNo)
331-
.sheetNameIfNotNull(sheetName)
332-
.numRowsIfNotNull(numRows);
329+
return readSheet(sheetNo, sheetName, numRows, null);
333330
}
334331

335332
/**
336333
* Build excel the 'readSheet' targeting specific column indexes.
337334
*
338335
* @param sheetNo Index of sheet, 0 base.
336+
* @param sheetName The name of sheet.
337+
* @param columnIndexes Specific columns to read (e.g., [0, 2] for Column A and C).
338+
* @return Excel sheet reader builder.
339+
*/
340+
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName, List<Integer> columnIndexes) {
341+
return readSheet(sheetNo, sheetName, null, columnIndexes);
342+
}
343+
344+
/**
345+
* Build excel the 'ReadSheet'.
346+
*
347+
* @param sheetNo Index of sheet, 0 base.
348+
* @param sheetName The name of sheet.
349+
* @param numRows The number of rows to read, the default is all, start with 0.
339350
* @param columnIndexes Specific columns to read (e.g., [0, 2] for Column A and C).
340351
* @return Excel sheet reader builder.
341352
*/
342-
public static ExcelReaderSheetBuilder readSheetWithColumns(
353+
public static ExcelReaderSheetBuilder readSheet(
343354
Integer sheetNo, String sheetName, Integer numRows, List<Integer> columnIndexes) {
344355
return new ExcelReaderSheetBuilder()
345356
.sheetNoIfNotNull(sheetNo)
346357
.sheetNameIfNotNull(sheetName)
347358
.numRowsIfNotNull(numRows)
348-
.includeColumnIndexes(columnIndexes);
359+
.includeColumnIndexesIfNotNull(columnIndexes);
349360
}
350361
}

fesod-sheet/src/test/java/org/apache/fesod/sheet/FesodSheetTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ void testReadSheet_withColumnIndexes_shouldConfigureAll() {
297297

298298
List<Integer> targetColumns = Arrays.asList(0, 2);
299299

300-
ExcelReaderSheetBuilder builder = FesodSheet.readSheetWithColumns(0, "Sheet1", 100, targetColumns);
300+
ExcelReaderSheetBuilder builder = FesodSheet.readSheet(0, "Sheet1", 100, targetColumns);
301301
ReadSheet configuredSheet = builder.build();
302302
List<Map<Integer, String>> readResults = FesodSheet.read(tempFile)
303303
.sheet(0)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fesod.sheet.write.handler;
21+
22+
import java.io.BufferedReader;
23+
import java.io.File;
24+
import java.io.IOException;
25+
import java.nio.charset.StandardCharsets;
26+
import java.nio.file.Files;
27+
import java.util.ArrayList;
28+
import java.util.Collections;
29+
import java.util.List;
30+
import org.apache.fesod.sheet.FesodSheet;
31+
import org.apache.fesod.sheet.testkit.Tags;
32+
import org.apache.fesod.sheet.testkit.base.AbstractExcelTest;
33+
import org.apache.fesod.sheet.testkit.enums.ExcelFormat;
34+
import org.apache.fesod.sheet.testkit.params.ExcelFormatSource;
35+
import org.apache.poi.ss.usermodel.Workbook;
36+
import org.apache.poi.ss.usermodel.WorkbookFactory;
37+
import org.apache.poi.xssf.streaming.SXSSFCell;
38+
import org.junit.jupiter.api.Assertions;
39+
import org.junit.jupiter.api.Tag;
40+
import org.junit.jupiter.params.ParameterizedTest;
41+
42+
@Tag(Tags.ROUND_TRIP)
43+
class EscapeHexCellWriteHandlerRoundTripTest extends AbstractExcelTest {
44+
45+
private File writeEscapedWorkbook(ExcelFormat format) throws IOException {
46+
File file = createTempFile("escape-hex", format);
47+
List<List<String>> rows = new ArrayList<>();
48+
rows.add(Collections.singletonList("_xB9f0_ and _x1234_"));
49+
50+
FesodSheet.write(file)
51+
.excelType(format.toExcelTypeEnum())
52+
.head(Collections.singletonList(Collections.singletonList("value")))
53+
.registerWriteHandler(new EscapeHexCellWriteHandler())
54+
.sheet("escape")
55+
.doWrite(rows);
56+
return file;
57+
}
58+
59+
private String readBackFirstDataValue(File file, ExcelFormat format) throws IOException {
60+
if (format == ExcelFormat.CSV) {
61+
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
62+
reader.readLine(); // header
63+
return reader.readLine();
64+
}
65+
}
66+
try (Workbook workbook = WorkbookFactory.create(file)) {
67+
return workbook.getSheetAt(0).getRow(1).getCell(0).getStringCellValue();
68+
}
69+
}
70+
71+
/**
72+
* Writes a file with the handler registered and reads it back: the caller must see the literal they typed.
73+
*
74+
* <p>All three formats expect the same value, for different reasons. On XLSX the handler escapes the sequence
75+
* and POI's reader decodes that escape away again. On XLS and CSV the handler never fires, since it only
76+
* touches {@link SXSSFCell}, so there was nothing to undo.
77+
*/
78+
@ParameterizedTest(name = "[{index}] {0} round-trips the literal hex sequence")
79+
@ExcelFormatSource
80+
void registeredOnAWrite_keepsLiteralHexSequencesIntactAcrossFormats(ExcelFormat format) throws IOException {
81+
File file = writeEscapedWorkbook(format);
82+
Assertions.assertEquals("_xB9f0_ and _x1234_", readBackFirstDataValue(file, format));
83+
}
84+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fesod.sheet.write.handler;
21+
22+
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
23+
import org.apache.fesod.sheet.metadata.data.WriteCellData;
24+
import org.apache.fesod.sheet.testkit.Tags;
25+
import org.apache.poi.hssf.usermodel.HSSFCell;
26+
import org.apache.poi.xssf.streaming.SXSSFCell;
27+
import org.junit.jupiter.api.Assertions;
28+
import org.junit.jupiter.api.Tag;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.CsvSource;
32+
import org.junit.jupiter.params.provider.ValueSource;
33+
import org.mockito.Mockito;
34+
35+
@Tag(Tags.UNIT)
36+
class EscapeHexCellWriteHandlerTest {
37+
38+
private final EscapeHexCellWriteHandler handler = new EscapeHexCellWriteHandler();
39+
40+
/**
41+
* The handler only checks that the cell is an {@link SXSSFCell} and never reads from it, so a mock is all it
42+
* needs.
43+
*/
44+
private final SXSSFCell cell = Mockito.mock(SXSSFCell.class);
45+
46+
/**
47+
* Runs the handler over a string cell and returns the value it left behind.
48+
*/
49+
private String escape(String input) {
50+
WriteCellData<?> cellData = new WriteCellData<>(input);
51+
handler.afterCellDataConverted(null, null, cellData, cell, null, 0, Boolean.FALSE);
52+
return cellData.getStringValue();
53+
}
54+
55+
@ParameterizedTest(name = "[{index}] {0} -> {1}")
56+
@CsvSource(
57+
delimiter = '|',
58+
value = {
59+
"_xB9f0_|_x005F_xB9f0_",
60+
"abc_x0041_|abc_x005F_x0041_",
61+
"_x0041__x0042_|_x005F_x0041__x005F_x0042_",
62+
"_xB9f0_ and _x1234_ and _xABCD_|_x005F_xB9f0_ and _x005F_x1234_ and _x005F_xABCD_",
63+
// 3 below check for partially valid cases - 1st format is valid, 2nd is invalid.
64+
"_x1234_ _xGHIJ_|_x005F_x1234_ _xGHIJ_",
65+
"_x0041__x12|_x005F_x0041__x12",
66+
"_x0041__x12345|_x005F_x0041__x12345",
67+
})
68+
void afterCellDataConverted_escapesEveryValidHexPattern(String input, String expected) {
69+
Assertions.assertEquals(expected, escape(input));
70+
}
71+
72+
@ParameterizedTest(name = "[{index}] {0} is left alone")
73+
@ValueSource(
74+
strings = {
75+
"normalString",
76+
"_x12345_", // seventh character is not underscore
77+
"_x0041", // one character short of a complete pattern
78+
"_x00G1_", // a non-hex character
79+
"_x_x0041", // an unterminated pattern
80+
"", // empty input must not trip the scan
81+
"_x00é1_", // a non-ASCII character
82+
"_X1234_", // uppercase X
83+
})
84+
void afterCellDataConverted_leavesInvalidPatternsUntouched(String input) {
85+
Assertions.assertEquals(input, escape(input));
86+
}
87+
88+
/**
89+
* Escaping is not idempotent: an already-escaped literal is escaped again
90+
*/
91+
@Test
92+
void afterCellDataConverted_escapesAnAlreadyEscapedSequenceAgain() {
93+
Assertions.assertEquals("_x005F_x005F_x0041_", escape("_x005F_x0041_"));
94+
}
95+
96+
@Test
97+
void afterCellDataConverted_ignoresNonStringCellData() {
98+
WriteCellData<?> cellData = new WriteCellData<>(CellDataTypeEnum.ERROR, "_x0041_");
99+
100+
handler.afterCellDataConverted(null, null, cellData, cell, null, 0, Boolean.FALSE);
101+
102+
Assertions.assertEquals("_x0041_", cellData.getStringValue());
103+
}
104+
105+
@Test
106+
void afterCellDataConverted_ignoresNonSxssfCells() {
107+
WriteCellData<?> cellData = new WriteCellData<>("_x0041_");
108+
109+
handler.afterCellDataConverted(null, null, cellData, Mockito.mock(HSSFCell.class), null, 0, Boolean.FALSE);
110+
111+
Assertions.assertEquals("_x0041_", cellData.getStringValue());
112+
}
113+
114+
@Test
115+
void afterCellDataConverted_toleratesNullCellDataAndNullStringValue() {
116+
WriteCellData<?> emptyStringData = new WriteCellData<>(CellDataTypeEnum.STRING);
117+
118+
Assertions.assertDoesNotThrow(
119+
() -> handler.afterCellDataConverted(null, null, null, cell, null, 0, Boolean.FALSE));
120+
Assertions.assertDoesNotThrow(
121+
() -> handler.afterCellDataConverted(null, null, emptyStringData, cell, null, 0, Boolean.FALSE));
122+
Assertions.assertNull(emptyStringData.getStringValue());
123+
}
124+
}

0 commit comments

Comments
 (0)