在批量生成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;
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>();
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("首席针头"))
{
//找到特定字段所在的第一个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)
{
var texts = run.Elements<Text>();
foreach (var mytext in texts)
{
mytext.Text = "";
}
}
sel_pt++;
}
//重新遍历一遍,替换目标字段
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("首席针头", "吃席针头");
}
}
}
}
}
//保存文档
wordDoc.Save();
//释放资源
wordDoc.Dispose();
到此,我们就完美解决了docx文档(Word文档)中特定字段的替换问题啦。