Java邮件合并程序编译报错:Illegal start of expression 求助
邮件合并Java程序编译错误排查与修复
问题背景
我正在开发一个小型Java应用,实现邮件合并功能:通过CSV/Microsoft Excel文件中的数据,替换Microsoft Word文档中的标记,并为每行数据生成独立的DOCX文件。但编译Java文件时遇到以下错误:
编译错误信息
MailMerge.java:81: error: illegal start of expression private void applyLines(Data dataIn, XWPFDocument doc) throws XmlException, IOException { ^ MailMerge.java:82: error: illegal start of expression private void applyLines(List<String> headers, List<String> data, XWPFDocument doc) throws XmlException, IOException { ^ MailMerge.java:172: error: illegal start of expression private static void appendBody(CTBody src, String append, boolean first) throws XmlException { ^ 3 error
待编译的Java代码
package org.dstadler.poi.mailmerge; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.logging.Logger; import com.google.common.base.Preconditions; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlOptions; import org.dstadler.commons.logging.jdk.LoggerFactory; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody; /** * Simple application which performs a "mail-merge" of a Microsoft Word template * document which contains replacement templates in the form of ${name}, ${first-name}, ... * and an Microsoft Excel spreadsheet which contains a list of entries that are merged in. * * Call this application with parameters <word-template> <excel/csv-template> <output-file> * * The resulting document has all resulting documents concatenated. */ public class MailMerge { private static final Logger log = LoggerFactory.make(); public static void main(String[] args) throws Exception { LoggerFactory.initLogging(); if(args.length != 3) { throw new IllegalArgumentException("Usage: MailMerge <word-template> <excel/csv-template> <output-file>"); } File wordTemplate = new File(args[0]); File excelFile = new File(args[1]); String outputFile = args[2]; if(!wordTemplate.exists() || !wordTemplate.isFile()) { throw new IllegalArgumentException("Could not read Microsoft Word template " + wordTemplate); } if(!excelFile.exists() || !excelFile.isFile()) { throw new IllegalArgumentException("Could not read data file " + excelFile); } new MailMerge().merge(wordTemplate, excelFile, outputFile); } private void merge(File wordTemplate, File dataFile, String outputFile) throws Exception { log.info("Merging data from " + wordTemplate + " and " + dataFile + " into " + outputFile); // read the data-rows from the CSV or XLS(X) file Data data = new Data(); data.read(dataFile); // now open the word file and apply the changes try (InputStream is = new FileInputStream(wordTemplate)) { try (XWPFDocument doc = new XWPFDocument(is)) { // apply the lines and concatenate the results into the document applyLines(data, doc); List<String> headers = data.getHeaders(); for (List<String> dataRow : data.getData()) { // now open the word file and apply the changes try (InputStream is = new FileInputStream(wordTemplate)) { try (XWPFDocument doc = new XWPFDocument(is)) { log.info("Writing overall result to " + outputFile); try (OutputStream out = new FileOutputStream(outputFile)) { doc.write(out); applyLines(headers, dataRow, doc); String newOutputFileName = FilenameUtils.removeExtension(outputFile) + "_" + dataRow.get(0) + ".docx"; log.info("Writing overall result to " + newOutputFileName); try (OutputStream out = new FileOutputStream(newOutputFileName)) { doc.write(out); } } } } } private void applyLines(Data dataIn, XWPFDocument doc) throws XmlException, IOException { private void applyLines(List<String> headers, List<String> data, XWPFDocument doc) throws XmlException, IOException { // small hack to not having to rework the commandline parsing just now String includeIndicator = System.getProperty("org.dstadler.poi.mailmerge.includeindicator"); CTBody body = doc.getDocument().getBody(); // read the current full Body text String srcString = body.xmlText(); // apply the replacements line-by-line boolean first = true; List<String> headers = dataIn.getHeaders(); for(List<String> data : dataIn.getData()) { log.info("Applying to template: " + data); // if the option is set ignore lines which do not have the indicator set if(includeIndicator != null) { int indicatorPos = headers.indexOf(includeIndicator); Preconditions.checkState(indicatorPos >= 0, "An include-indicator is set via system properties as %s, but there is no such column, had: %s", includeIndicator, headers); if(!StringUtils.equalsAnyIgnoreCase(data.get(indicatorPos), "1", "true")) { log.info("Skipping line " + data + " because include-indicator was not set"); continue; } } log.info("Applying to template: " + data); String replaced = srcString; for(int fieldNr = 0;fieldNr < headers.size();fieldNr++) { String header = headers.get(fieldNr); String value = data.get(fieldNr); // if the option is set ignore lines which do not have the indicator set if(includeIndicator != null) { int indicatorPos = headers.indexOf(includeIndicator); Preconditions.checkState(indicatorPos >= 0, "An include-indicator is set via system properties as %s, but there is no such column, had: %s", includeIndicator, headers); // ignore columns without headers as we cannot match them if(header == null) { continue; } if(!StringUtils.equalsAnyIgnoreCase(data.get(indicatorPos), "1", "true")) { log.info("Skipping line " + data + " because include-indicator was not set"); } } // use empty string for data-cells that have no value if(value == null) { value = ""; } String replaced = srcString; for(int fieldNr = 0;fieldNr < headers.size();fieldNr++) { String header = headers.get(fieldNr); String value = data.get(fieldNr); replaced = replaced.replace("${" + header + "}", value); // ignore columns without headers as we cannot match them if(header == null) { continue; } // check for missed replacements or formatting which interferes if(replaced.contains("${")) { log.warning("Still found template-marker after doing replacement: " + StringUtils.abbreviate(StringUtils.substring(replaced, replaced.indexOf("${")), 200)); // use empty string for data-cells that have no value if(value == null) { value = ""; } appendBody(body, replaced, first); replaced = replaced.replace("${" + header + "}", value.toUpperCase()); } first = false; // check for missed replacements or formatting which interferes if(replaced.contains("${")) { log.warning("Still found template-marker after doing replacement: " + StringUtils.abbreviate(StringUtils.substring(replaced, replaced.indexOf("${")), 200)); } appendBody(body, replaced, first); first = false; } private static void appendBody(CTBody src, String append, boolean first) throws XmlException { XmlOptions optionsOuter = new XmlOptions(); optionsOuter.setSaveOuter(); String srcString = src.xmlText(); String prefix = srcString.substring(0,srcString.indexOf(">")+1); final String mainPart; // exclude template itself in first appending if(first) { mainPart = ""; } else { mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<")); } String suffix = srcString.substring( srcString.lastIndexOf("<") ); String addPart = append.substring(append.indexOf(">") + 1, append.lastIndexOf("<")); CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+suffix); src.set(makeBody); } }
程序预期功能
- 读取CSV或XLSX文件中的数据;
- 打开Word模板文件,用CSV/XLSX数据替换模板中的标记;
- 为CSV/XLSX中的每行数据生成新的DOCX文件。
错误根源与修复建议
1. 核心编译错误原因:代码块未正确闭合
merge()方法中嵌套的try-with-resources块、for循环均未正确添加闭合的},导致后续的applyLines()方法定义被嵌套在merge()方法内部。Java语法不允许方法嵌套定义,因此触发"illegal start of expression"错误。
2. 次要问题:方法定义与逻辑混乱
- 连续定义两个
applyLines()方法,第一个方法无方法体直接定义第二个,属于语法错误; applyLines()方法内部存在重复代码块(如重复的includeIndicator判断、变量定义),变量作用域冲突(重复定义headers、replaced)。
具体修复步骤
步骤1:修复merge()方法的代码闭合问题
调整逻辑为每行数据单独加载模板并生成文件,同时闭合所有代码块:
private void merge(File wordTemplate, File dataFile, String outputFile) throws Exception { log.info("Merging data from " + wordTemplate + " and " + dataFile + " into individual files"); Data data = new Data(); data.read(dataFile); List<String> headers = data.getHeaders(); for (List<String> dataRow : data.getData()) { // 为每行数据重新加载模板 try (InputStream is = new FileInputStream(wordTemplate)) { try (XWPFDocument doc = new XWPFDocument(is)) { applyLines(headers, dataRow, doc); String newOutputFileName = FilenameUtils.removeExtension(outputFile) + "_" + dataRow.get(0) + ".docx"; log.info("Writing result to " + newOutputFileName); try (OutputStream out = new FileOutputStream(newOutputFileName)) { doc.write(out); } } } } }
步骤2:修复applyLines()方法的重载定义
实现两个重载方法的逻辑,避免语法错误:
// 处理整个数据集的重载方法 private void applyLines(Data dataIn, XWPFDocument doc) throws XmlException, IOException { List<String> headers = dataIn.getHeaders(); for(List<String> data : dataIn.getData()) { applyLines(headers, data, doc); } } // 处理单条数据的核心方法 private void applyLines(List<String> headers, List<String> data, XWPFDocument doc) throws XmlException, IOException { String includeIndicator = System.getProperty("org.dstadler.poi.mailmerge.includeindicator"); // 处理包含指示器逻辑 if(includeIndicator != null) { int indicatorPos = headers.indexOf(includeIndicator); Preconditions.checkState(indicatorPos >= 0, "An include-indicator is set via system properties as %s, but there is no such column, had: %s", includeIndicator, headers); if(!StringUtils.equalsAnyIgnoreCase(data.get(indicatorPos), "1", "true")) { log.info("Skipping line " + data + " because include-indicator was not set"); return; } } // 执行模板替换 CTBody body = doc.getDocument().getBody(); String srcString = body.xmlText(); String replaced = srcString; for(int fieldNr = 0;fieldNr < headers.size();fieldNr++) { String header = headers.get(fieldNr); if(header == null) { continue; } String value = data.get(fieldNr) == null ? "" : data.get(fieldNr); replaced = replaced.replace("${" + header + "}", value); } // 检查未替换的标记 if(replaced.contains("${")) { log.warning("Still found template-marker after doing replacement: " + StringUtils.abbreviate(StringUtils.substring(replaced, replaced.indexOf("${")), 200)); } // 替换文档内容 CTBody newBody = CTBody.Factory.parse(replaced); body.set(newBody); }
步骤3:清理冗余代码
移除applyLines()方法内部重复的判断逻辑、变量定义,确保代码逻辑连贯。
内容的提问来源于stack exchange,提问作者kheder47
相关产品推荐
相关产品推荐

