在批量生成Word文档的应用中,最常见的需求莫过于替换掉文档中的特定字段以生成新的文档。利用OpenXML库可轻松实现这一需求。
仅限文本内容的不完善版本
首先放出最简单然而有bug的版本:
using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; //使用OpenXml SDK 2.5打开Word文档,并将里面的目标字段替换成指定的值 WordprocessingDocument wordDoc = WordprocessingDocument.Open("word.docx", true); var body = wordDoc.MainDocumentPart!.Document.Body; var paras = body!.Elements<Paragraph>(); foreach (var para in paras) {
var runs = para.Elements<Run>(); foreach (var run in runs) {
var texts = run.Elements<Text>(); foreach (var text in texts) {
if (text.Text.Contains("首席针头")) {
text.Text = text.Text.Replace("首席针头", "吃席针头"); } } } } //保存文档 wordDoc.Save(); //释放资源 wordDoc.Dispose();
讯享网
该版本的原理是遍历word文档中的每个段落,搜索段落中的每个文字字段对象,如果找到匹配的值就将其替换成目标值。
该操作存在问题在于有时候看起来连在一起的文字对象是存储在不同的文字字段对象(run)中的(分开的原因大概率是格式不统一,但有时看着格式完全一样也会被分开,魔性),比如下面的文档:

“首席针头”这个词组就被分开成“首席”和“针头”两个不同的文字字段对象进行存储。在这种情况下遍历对象时就无法实现特定字段的匹配。
仅限文本内容的完善版本
要解决这个问题也不难,特定字段可能被分开,我们就将段落里面的所有字段记录下来,然后合并起来看看是否包含该字段。如果是,则检测特定字段在哪几个run中,然后将这几个run合并起来,最后再进行特定字段的替换即可。代码如下:
讯享网using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using System; using static System.Net.Mime.MediaTypeNames; using System.Text; using Text = DocumentFormat.OpenXml.Wordprocessing.Text; //合并目标范围内的run void mergeRuns(IEnumerable<Run> runs, string[] copy_text) {
//找到特定字段所在的第一个run的位置 int start = 0; while (true) {
string sub_str = ""; for (int i = start; i < runs.Count(); i++) {
sub_str += copy_text[i]; } if (!sub_str.Contains("首席针头")) {
start--; break; } else {
start++; } } //找到特定字段所在的最后一个run的位置 string inner_str = "";//范围内的字符串 int end = runs.Count(); while (true) {
string sub_str = ""; for (int i = start; i < end; i++) {
sub_str += copy_text[i]; } if (!sub_str.Contains("首席针头")) {
end++; break; } else {
inner_str = sub_str; end--; } } //将范围内的run合并在一起 int sel_pt = 0; foreach (var run in runs) {
if (sel_pt == start) {
var texts = run.Elements<Text>(); //将run里面的文字改为inner_str的内容 int num = 0; foreach (var mytext in texts) {
if (num == 0) {
mytext.Text = inner_str; } else {
mytext.Text = ""; } num++; } } else if (sel_pt > start && sel_pt < end)//将多余的runs清空 {
var texts = run.Elements<Text>(); foreach (var mytext in texts) {
mytext.Text = ""; } } sel_pt++; } } void replaceTextInParas(IEnumerable<Paragraph> paras) {
//遍历文本的所有段落 foreach (var para in paras) {
while (true) {
var runs = para.Elements<Run>(); string[] copy_text = new string[runs.Count()]; int pt = 0; foreach (var run in runs) {
var texts = run.Elements<Text>(); //第一遍先遍历,把run内部的目标字段替换掉,并构建数组记录下所有的run //创建长度和texts个数一样的数组,用于记录每个text的内容 foreach (var text in texts) {
if (text.Text.Contains("首席针头")) {
text.Text = text.Text.Replace("首席针头", "吃席针头"); } copy_text[pt] += text.Text; } pt++; } //将字符串拼接在一块,看看是否存在目标字段 string str = string.Join("", copy_text); //如果存在目标字段,则将范围内的run合并在一起,然后再替换一次,直到不存在目标字段 if (str.Contains("首席针头")) {
mergeRuns(runs, copy_text); } else {
break; } } } } //文档读取 WordprocessingDocument wordDoc = WordprocessingDocument.Open("word.docx", true); //获取文档的主体 var body = wordDoc.MainDocumentPart!.Document.Body; //获取文档的所有段落 var paras = body!.Elements<Paragraph>(); //替换文档段落中的目标字段 replaceTextInParas(paras); //保存文档 wordDoc.Save(); //释放资源 wordDoc.Dispose();
文本内容+表格内容的完善版本
当然,该版本仅限于文本内容,对于表格中的内容是无法替换的,原因在于我们在遍历替换的时候并没有遍历表格中的段落。遍历表格中的单元格段落写起来很简单,完整代码如下:
using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using System; using static System.Net.Mime.MediaTypeNames; using System.Text; using Text = DocumentFormat.OpenXml.Wordprocessing.Text; //合并目标范围内的run void mergeRuns(IEnumerable<Run> runs, string[] copy_text) {
//找到特定字段所在的第一个run的位置 int start = 0; while (true) {
string sub_str = ""; for (int i = start; i < runs.Count(); i++) {
sub_str += copy_text[i]; } if (!sub_str.Contains("首席针头")) {
start--; break; } else {
start++; } } //找到特定字段所在的最后一个run的位置 string inner_str = "";//范围内的字符串 int end = runs.Count(); while (true) {
string sub_str = ""; for (int i = start; i < end; i++) {
sub_str += copy_text[i]; } if (!sub_str.Contains("首席针头")) {
end++; break; } else {
inner_str = sub_str; end--; } } //将范围内的run合并在一起 int sel_pt = 0; foreach (var run in runs) {
if (sel_pt == start) {
var texts = run.Elements<Text>(); //将run里面的文字改为inner_str的内容 int num = 0; foreach (var mytext in texts) {
if (num == 0) {
mytext.Text = inner_str; } else {
mytext.Text = ""; } num++; } } else if (sel_pt > start && sel_pt < end)//将多余的runs清空 {
var texts = run.Elements<Text>(); foreach (var mytext in texts) {
mytext.Text = ""; } } sel_pt++; } } void replaceTextInParas(IEnumerable<Paragraph> paras) {
//遍历文本的所有段落 foreach (var para in paras) {
while (true) {
var runs = para.Elements<Run>(); string[] copy_text = new string[runs.Count()]; int pt = 0; foreach (var run in runs) {
var texts = run.Elements<Text>(); //第一遍先遍历,把run内部的目标字段替换掉,并构建数组记录下所有的run //创建长度和texts个数一样的数组,用于记录每个text的内容 foreach (var text in texts) {
if (text.Text.Contains("首席针头")) {
text.Text = text.Text.Replace("首席针头", "吃席针头"); } copy_text[pt] += text.Text; } pt++; } //将字符串拼接在一块,看看是否存在目标字段 string str = string.Join("", copy_text); //如果存在目标字段,则将范围内的run合并在一起,然后再替换一次,直到不存在目标字段 if (str.Contains("首席针头")) {
mergeRuns(runs, copy_text); } else {
break; } } } } //文档读取 WordprocessingDocument wordDoc = WordprocessingDocument.Open("word.docx", true); //获取文档的主体 var body = wordDoc.MainDocumentPart!.Document.Body; //获取文档的所有段落 var paras = body!.Elements<Paragraph>(); //替换文档段落中的目标字段 replaceTextInParas(paras); //获取文档的所有表格 var tables = body!.Elements<Table>(); //遍历表格 foreach (var table in tables) {
//获取表格的所有行 var rows = table.Elements<TableRow>(); //遍历行 foreach (var row in rows) {
//获取行的所有单元格 var cells = row.Elements<TableCell>(); //遍历单元格 foreach (var cell in cells) {
//获取单元格的所有段落 var cell_paras = cell.Elements<Paragraph>(); //替换单元格段落中的目标字段 replaceTextInParas(cell_paras); } } } //保存文档 wordDoc.Save(); //释放资源 wordDoc.Dispose();
到此,我们就完美解决了docx文档(Word文档)中特定字段的替换问题啦。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/29830.html