使用OpenXML库替换docx文档(Word文档)中的特定字段

在批量生成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文档)中特定字段的替换问题啦。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值