Bootstrap Blazor中实现富文本框Editor组件的内容导出为docx格式的Word文档
Bootstrap Blazor中的富文本框 Editor 组件,功能强大,能够在其中创建文本、表格和图片等,进而可以作为一个简单的Word编辑器使用,但是不支持导出。具体用法详见官网:SiteTitle。下面用BB的项目模板进行演示,模板下载地址:SiteTitle。
1、安装所需的库并引用
a、BootstrapBlazor.SummerNote(9.0.4):富文本框 Editor 组件是对Summernotote组件的二次封装,依赖于这个库;注意在 App.razor 中引入jQuery的js文件:
<script src="_content/BootstrapBlazor.SummerNote/js/jquery-3.6.0.min.js"></script>
b、HtmlToOpenXml.dll(3.2.5):用于将HTML转为docx格式的Word对应的XML。
c、DocumentFormat.OpenXml(3.3.0):用于将b中的XML转换并保存为Word文档。
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml;
using HtmlToOpenXml;
2、简单写一个测试页
a、RichText.razor文件,包含一个 Editor 组件和一个导出按钮:
@page "/richText"
@attribute [TabItemOption(Text = "RichText")]<div style="position: relative;width: 100%;height: 600px;"><div><Button Text="导出富文本内容" IsAsync="true" OnClick="@OnClickToExportRichText"></Button></div><div style="margin-top: 10px;"><Editor IsEditor="true" Height="400" ShowSubmit="false" PlaceHolder="请填充内容" @bind-Value="@TextValue" /></div>
</div>
b、RichText.razor.cs文件:
using BootstrapBlazor.Components;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml;
using HtmlToOpenXml;
using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis;namespace BootstrapBlazorApp.Server.Components.Pages
{public partial class RichText{// BB的下载服务[Inject][NotNull]private DownloadService _downloadService { get; set; }// 双向绑定的 Editor 组件的内容private string? TextValue { get; set; }private async Task OnClickToExportRichText(){try{if (string.IsNullOrWhiteSpace(TextValue)){throw new ArgumentNullException(nameof(TextValue));}// 将富文本的代码块封装到html的body中string htmlContent = $"<html><body>{TextValue}</body></html>";// 用于存文件流using Stream stream = new MemoryStream();// 创建一个新的Word文档using (WordprocessingDocument wordDoc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)){// 添加主文档部件MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();// 使用HtmlConverter将HTML转换为XMLHtmlConverter converter = new HtmlConverter(mainPart);// 解析body中的内容,也就是 Editor 组件中的HTML代码段await converter.ParseBody(htmlContent);// 保存文档流mainPart.Document.Save();}// 指针位置0stream.Position = 0;// 流式下载docx文件DownloadOption option = new DownloadOption();option.FileStream = stream;option.FileName = $"富文本{DateTime.Now.ToString("yyyyMMddHHmmss")}.docx";await _downloadService.DownloadFromStreamAsync(option);}catch (Exception ex){System.Console.WriteLine(ex.Message);}}}
}
c、页面
3、原理
Editor 组件的原理是将其里面的内容转化为HTML代码片段,因此实际上的导出思路是将HTML转为Word。
通过单击 Editor 中的源代码查看按钮,我们可以看到上面一行文字被转化为了下面一行HTML代码段:
但是不能直接从HTML转为Word,Word内部对应(接收)的是一种特殊格式的XML,故需要经过一次中转:HTML——>XML——>Word。
4、测试
往 Editor 组件框中粘贴一些文本,Ctrl+A全选文本改为Arial样式(这个样式与原本文更贴近):
然后单击【导出富文本内容】按钮进行导出,是一个docx格式的Word文档,内容如下:
可以看到,字体、颜色、格式等样式基本都被保留下来了,效果还是不错的。
5、扩展
上述的方式能够将 Editor 组件中的内容添加到docx格式的Word文档中,但是缺少Header和Footer。有时我们想要往特定的有头有尾的文档中填充内容,比如有下面的Header和Footer的docx格式的Word模板文档:
在wwwroot中添加模板文件:
注入环境变量:
/// <summary>
/// Web环境
/// </summary>
[Inject]
[NotNull]
private IWebHostEnvironment _env { get; set; }
这时只需要将导出方法改为如下:
private async Task OnClickToExportRichText()
{try{if (string.IsNullOrWhiteSpace(TextValue)){throw new ArgumentNullException(nameof(TextValue));}// 将富文本的代码块封装到html的body中string htmlContent = $"<html><body>{TextValue}</body></html>";// wwwroot中模板的相对路径和绝对路径string docxPath = "Template/头尾模板 - 复制.docx";string tempPath = Path.Combine(_env.WebRootPath, docxPath);// 打开现有的模板文档using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(tempPath, true)){// 添加主文档部件MainDocumentPart mainPart = wordDoc.MainDocumentPart;// 使用HtmlConverter将HTML转换为XMLHtmlConverter converter = new HtmlConverter(mainPart);// 解析body中的内容,也就是 Editor 组件中的HTML代码段await converter.ParseBody(htmlContent);// 保存文档mainPart.Document.Save();}// Url下载docx文件DownloadOption option = new DownloadOption();option.Url = docxPath;option.FileName = $"富文本{DateTime.Now.ToString("yyyyMMddHHmmss")}.docx";await _downloadService.DownloadFromUrlAsync(option);}catch (Exception ex){System.Console.WriteLine(ex.Message);}
}
这种方式是直接在模板文档中进行插入操作,会改变原本模板文件,而且每次导出都会在上一次导出的基础上进行Append,故不推荐。
当然也可以使用 Body 的 RemoveAllChildren 方法清空子元素:
mainPart.Document.Body.RemoveAllChildren<DocumentFormat.OpenXml.Wordprocessing.Paragraph>();
如果不想改变原模板文件,则需要每次复制一个模板文件出来,用于接收数据。这样又会导致文件累积,生成的文件越来越多,因此需要在下载后进行删除。
另一种方式是使用DocX库(免费的),可以进行流操作,减少文件操作。需要安装DocX(4.0.25105.5786)库并引用:
using Xceed.Words.NET;
修改导出方法如下:
private async Task OnClickToExportRichText()
{try{if (string.IsNullOrWhiteSpace(TextValue)){throw new ArgumentNullException(nameof(TextValue));}// 将富文本的代码块封装到html的body中string htmlContent = $"<html><body>{TextValue}</body></html>";// 用于存文件流using Stream stream = new MemoryStream();// 创建一个新的Word文档using (WordprocessingDocument wordDoc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document)){// 添加主文档部件MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();// 使用HtmlConverter将HTML转换为XMLHtmlConverter converter = new HtmlConverter(mainPart);// 解析body中的内容,也就是 Editor 组件中的HTML代码段await converter.ParseBody(htmlContent);// 保存文档流mainPart.Document.Save();}// 指针位置0stream.Position = 0;// 加载上面的文档流DocX docx1 = DocX.Load(stream);// 加载头尾模板文件string tempPath = Path.Combine(_env.WebRootPath, "Template/头尾模板.docx");DocX docx = DocX.Load(tempPath);// 将 Editor 中的内容插入到模板文档中docx.InsertDocument(docx1, true, false);// 另存为流using var ms = new MemoryStream();docx.SaveAs(ms);ms.Position = 0;// 流式下载docx文件DownloadOption option = new DownloadOption();option.FileStream = ms;option.FileName = $"富文本{DateTime.Now.ToString("yyyyMMddHHmmss")}.docx";await _downloadService.DownloadFromStreamAsync(option);}catch (Exception ex){System.Console.WriteLine(ex.Message);}
}
最终,两种方式导出的效果一致,如下图: