Skip to content

Commit b1b96ec

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 b1b96ec

2 files changed

Lines changed: 182 additions & 0 deletions

File tree

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

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)