Skip to content

Commit a5c87f3

Browse files
committed
fix: handle escaped closing brace in template placeholder parsing
When a template placeholder contains an escaped closing brace like {foo\}, the parser would throw StringIndexOutOfBoundsException because it attempted to use suffixIndex=-1 in a substring operation. Added a bounds check after the inner while loop to break out when no valid (non-escaped) closing brace is found.
1 parent fbec1ed commit a5c87f3

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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.temp.issue462;
21+
22+
import java.io.File;
23+
import java.io.FileOutputStream;
24+
import java.io.IOException;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
import org.apache.fesod.sheet.FesodSheet;
29+
import org.apache.fesod.sheet.util.TestFileUtil;
30+
import org.apache.poi.ss.usermodel.Cell;
31+
import org.apache.poi.ss.usermodel.Row;
32+
import org.apache.poi.ss.usermodel.Sheet;
33+
import org.apache.poi.ss.usermodel.Workbook;
34+
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
35+
import org.junit.jupiter.api.Assertions;
36+
import org.junit.jupiter.api.BeforeAll;
37+
import org.junit.jupiter.api.Test;
38+
39+
/**
40+
* Test case for issue #462: ArrayIndexOutOfBoundsException when placeholder content is '{foo\}'
41+
*
42+
* When a placeholder has an escaped closing brace like {foo\}, the parser should handle it
43+
* gracefully without throwing an exception.
44+
*/
45+
public class EscapedPlaceholderTest {
46+
47+
private static final String TEMPLATE_PATH = TestFileUtil.getPath() + "temp/issue462" + File.separator;
48+
49+
@BeforeAll
50+
public static void createTemplates() throws IOException {
51+
// Create template with escaped placeholder {foo\}
52+
createTemplate("escaped_suffix.xlsx", "{foo\\}");
53+
54+
// Create template with escaped prefix \{foo}
55+
createTemplate("escaped_prefix.xlsx", "\\{foo}");
56+
57+
// Create template with normal placeholder for comparison
58+
createTemplate("normal.xlsx", "{foo}");
59+
60+
// Create template with mixed content
61+
createTemplate("mixed.xlsx", "prefix {foo\\} suffix");
62+
}
63+
64+
private static void createTemplate(String fileName, String content) throws IOException {
65+
File templateFile = new File(TEMPLATE_PATH + fileName);
66+
try (Workbook workbook = new XSSFWorkbook();
67+
FileOutputStream fos = new FileOutputStream(templateFile)) {
68+
Sheet sheet = workbook.createSheet("Sheet1");
69+
Row row = sheet.createRow(0);
70+
Cell cell = row.createCell(0);
71+
cell.setCellValue(content);
72+
workbook.write(fos);
73+
}
74+
}
75+
76+
/**
77+
* Test that template with {foo\} (escaped closing brace) does not throw exception.
78+
* The escaped brace means there's no valid closing brace, so this should be treated as literal text.
79+
*/
80+
@Test
81+
public void testEscapedSuffixPlaceholder() {
82+
String templateFileName = TEMPLATE_PATH + "escaped_suffix.xlsx";
83+
String outputFileName = TEMPLATE_PATH + "output_escaped_suffix.xlsx";
84+
85+
Map<String, Object> data = new HashMap<>();
86+
data.put("foo", "replaced_value");
87+
88+
// This should not throw an exception
89+
Assertions.assertDoesNotThrow(() -> {
90+
FesodSheet.write(new File(outputFileName))
91+
.withTemplate(new File(templateFileName))
92+
.sheet()
93+
.doFill(data);
94+
});
95+
96+
// Verify output file exists and can be read
97+
List<Object> result = FesodSheet.read(new File(outputFileName))
98+
.sheet()
99+
.headRowNumber(0)
100+
.doReadSync();
101+
Assertions.assertFalse(result.isEmpty());
102+
}
103+
104+
/**
105+
* Test that template with \{foo} (escaped opening brace) is treated as literal text.
106+
*/
107+
@Test
108+
public void testEscapedPrefixPlaceholder() {
109+
String templateFileName = TEMPLATE_PATH + "escaped_prefix.xlsx";
110+
String outputFileName = TEMPLATE_PATH + "output_escaped_prefix.xlsx";
111+
112+
Map<String, Object> data = new HashMap<>();
113+
data.put("foo", "replaced_value");
114+
115+
// This should not throw an exception
116+
Assertions.assertDoesNotThrow(() -> {
117+
FesodSheet.write(new File(outputFileName))
118+
.withTemplate(new File(templateFileName))
119+
.sheet()
120+
.doFill(data);
121+
});
122+
123+
// Verify the output
124+
List<Object> result = FesodSheet.read(new File(outputFileName))
125+
.sheet()
126+
.headRowNumber(0)
127+
.doReadSync();
128+
Assertions.assertFalse(result.isEmpty());
129+
}
130+
131+
/**
132+
* Test that normal placeholder {foo} still works correctly.
133+
*/
134+
@Test
135+
public void testNormalPlaceholder() {
136+
String templateFileName = TEMPLATE_PATH + "normal.xlsx";
137+
String outputFileName = TEMPLATE_PATH + "output_normal.xlsx";
138+
139+
Map<String, Object> data = new HashMap<>();
140+
data.put("foo", "replaced_value");
141+
142+
FesodSheet.write(new File(outputFileName))
143+
.withTemplate(new File(templateFileName))
144+
.sheet()
145+
.doFill(data);
146+
147+
// Verify the placeholder was replaced
148+
List<Object> result = FesodSheet.read(new File(outputFileName))
149+
.sheet()
150+
.headRowNumber(0)
151+
.doReadSync();
152+
Assertions.assertFalse(result.isEmpty());
153+
154+
@SuppressWarnings("unchecked")
155+
Map<String, String> row = (Map<String, String>) result.get(0);
156+
Assertions.assertEquals("replaced_value", row.get(0));
157+
}
158+
159+
/**
160+
* Test mixed content with escaped placeholder in the middle.
161+
*/
162+
@Test
163+
public void testMixedContentWithEscapedPlaceholder() {
164+
String templateFileName = TEMPLATE_PATH + "mixed.xlsx";
165+
String outputFileName = TEMPLATE_PATH + "output_mixed.xlsx";
166+
167+
Map<String, Object> data = new HashMap<>();
168+
data.put("foo", "replaced_value");
169+
170+
// This should not throw an exception
171+
Assertions.assertDoesNotThrow(() -> {
172+
FesodSheet.write(new File(outputFileName))
173+
.withTemplate(new File(templateFileName))
174+
.sheet()
175+
.doFill(data);
176+
});
177+
}
178+
}

fesod-sheet/src/main/java/org/apache/fesod/sheet/write/executor/ExcelWriteFillExecutor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ private String prepareData(
559559
suffixIndex = -1;
560560
}
561561
}
562+
if (suffixIndex < 0) {
563+
break out;
564+
}
562565
if (analysisCell == null) {
563566
analysisCell = initAnalysisCell(rowIndex, columnIndex);
564567
}

0 commit comments

Comments
 (0)