You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:17:02