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

如何使用Apache POI获取并修改Word文档文本框内的XWPFParagraph段落

Apache POI 4.1.2 读取与回写Word文本框内段落解决方案

你提取到文本框段落之后无法回写,本质是因为之前的读取方法没有让拿到的XWPFParagraph和原文档的底层XML节点做绑定,修改的是独立对象,自然不会同步到原文档。下面给出可直接读写、修改自动生效的实现方案:


实现步骤

1. 定义绑定上下文的文本框段落结构

我们需要同时保存提取到的段落和它所属的文本框内容节点,方便后续如果需要新增/替换段落时操作父容器:

import org.apache.poi.xwpf.usermodel.*
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTxbxContent

// 存储文本框段落和它的父容器
data class TextBoxParagraph(
    val paragraph: XWPFParagraph,
    val parentTxbxContent: CTTxbxContent
)

2. 扩展XWPFParagraph提取内部所有文本框段落

这个方法会提取段落内嵌的两种格式文本框(新版drawing型、旧版pict型)中的所有段落,且生成的XWPFParagraph直接绑定原文档的XML节点,修改内容会自动同步:

import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPict
import com.microsoft.schemas.vml.CTShape

fun XWPFParagraph.extractTextBoxParagraphs(): List<TextBoxParagraph> {
    val result = mutableListOf<TextBoxParagraph>()
    runs.forEach { run ->
        val ctRun = run.getCTR()
        // 处理mc:AlternateContent下的所有分支
        ctRun.getAlternateContentList().forEach { alternateContent ->
            // 处理Choice分支下的drawing型文本框
            alternateContent.getChoiceList().forEach { choice ->
                choice.getDrawingList().forEach { drawing ->
                    drawing.getAnchorList().forEach { anchor ->
                        val graphicData = anchor.graphic.graphicData
                        val txbxNodes = graphicData.selectNodes("*/*/wps:txbx/w:txbxContent")
                        txbxNodes.forEach { node ->
                            if (node is CTTxbxContent) {
                                val paras = node.pList.map { ctP ->
                                    XWPFParagraph(ctP, this.document)
                                }
                                result.addAll(paras.map { TextBoxParagraph(it, node) })
                            }
                        }
                    }
                }
            }
            // 处理Fallback分支下的pict型旧文本框
            alternateContent.getFallbackList().forEach { fallback ->
                fallback.getPictList().forEach { pict ->
                    val txbxNodes = pict.selectNodes("v:shape/v:textbox/w:txbxContent")
                    txbxNodes.forEach { node ->
                        if (node is CTTxbxContent) {
                            val paras = node.pList.map { ctP ->
                                XWPFParagraph(ctP, this.document)
                            }
                            result.addAll(paras.map { TextBoxParagraph(it, node) })
                        }
                    }
                }
            }
        }
    }
    return result
}

3. 集成到原有全局段落提取逻辑

把文本框段落提取逻辑加到你之前的全局段落遍历代码里即可:

val allBodyElements = bodyElements
    .plus(headerList.flatMap { it.bodyElements })
    .plus(footerList.flatMap { it.bodyElements })

val allParagraphs = allBodyElements.flatMap {
    when (it) {
        is XWPFParagraph -> {
            // 普通段落 + 该段落内嵌的所有文本框段落
            listOf(it) + it.extractTextBoxParagraphs().map { tbp -> tbp.paragraph }
        }
        is XWPFTable -> {
            it.rows
                .flatMap { row -> row.tableCells }
                .flatMap { cell -> 
                    // 表格单元格内的普通段落 + 单元格段落内嵌的文本框段落
                    cell.paragraphs + cell.paragraphs.flatMap { p -> 
                        p.extractTextBoxParagraphs().map { tbp -> tbp.paragraph }
                    }
                }
        }
        else -> emptyList()
    }
}

修改后保存说明

  • 如果你只是修改已有文本框段落的内容、样式,不需要做额外的保存操作:因为上面方法提取到的XWPFParagraph绑定的就是原文档的XML节点,修改会直接同步到底层结构,最后调用document.write(输出流)保存整个文档时就会自动生效。
  • 如果你需要替换/新增文本框内的段落,可以通过TextBoxParagraph里的parentTxbxContent对象操作:
// 示例:替换文本框内索引为0的段落
val textBoxPara: TextBoxParagraph = // 你提取到的文本框段落对象
val document: XWPFDocument = // 你的文档对象
// 新建段落
val newCTP = CTTxbxContent.Factory.newInstance().addNewP()
val newParagraph = XWPFParagraph(newCTP, document)
newParagraph.createRun().setText("新的文本框内容")

// 替换原有段落
val parent = textBoxPara.parentTxbxContent
parent.removeP(0) // 移除索引为0的原有段落
parent.addP(0, newCTP) // 插入新段落到索引0位置

注意事项

  1. Apache POI 4.1.2对WPS格式文本框的样式支持有限,如果修改后出现样式异常,可以手动给XWPFParagraph对应的CTP节点复制原有段落的样式属性即可。
  2. 该方案仅支持docx格式文档,不支持doc格式。

内容的提问来源于stack exchange,提问作者Ranil Wijeyratne

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 21:39:05