最近工作中在开发的一个项目需要根据数据库的数据来动态生成Word文档存放在服务端,让用户随时随地下载。
以前没有接触过Office开发,临时查了些资料,最终用COM接口完成了。项目虽然是完成了,不过我还是对期间处理Word COM接口的繁琐过程心有余悸。后期发现存在遗留问题,而且效率也不高,生成上万条的数据的时候实在是让人无语。跟朋友聊过类似的问题后,朋友提出可以用OpenXML。稍微了解一下OpenXML,于是我就动了用OpenXML的念头,稍稍的研究了一下,果然速度和效率不是吹的。
以下是我整理的一些在项目中用到过的方法。
项目中的功能是根据主模版文档来生成word,还需从子模板中复制内容到word中。总结了一下,分成四部分来操作。(实际上后三部分才使用到Openxml)
注意:(源代码管理器中开发的所有文件都会为“只读”。)
需要操作的文档都必须把“只读”属性去掉,最好是把存放模板文件的文件夹整体去掉“只读”属性。
第一部,当然是最简单的,复制模板到指定位置,操作复制后的文件。
if (File.Exists(filename)) //判断文件已存在,则删除
File.Delete(filename);
File.Copy(objTemplate, filename, true); //先复制一份模板文件
第二部分,复制子模板内容到Word文档中某个位置,位置用书签来定位。要考滤到需要复制的文档中存在一些什么元素,都需要相对应复制过来,并要考滤相关联的xml文件也需改变。此示例中的子模板存在以下元素:项目符号:WNumberingId;段落样式:ParagraphStyleId;超链接:Hyperlink;块样式:RunStyle
以下是复制子模板的内容:
/// <summary>
/// 复制模板内容到Word中书签的指定位置
///=========================================
/// </summary>
/// <param name="temps">多个子模板</param>
/// <param name="filePath">To文档径</param>
/// <param name="bookmarkName">书签名称</param>
public static void CopyTempsToFile(string[] temps, string filePath, string bookmarkName)
using (WordprocessingDocument objectiveDoc = WordprocessingDocument.Open(filePath, true))
MainDocumentPart mainPart = objectiveDoc.MainDocumentPart;
int s = mainPart.ThemePart.ImageParts.Count();
#region
var res = from t in objectiveDoc.MainDocumentPart.Document.Body.Descendants<WBookmarkStart>()
where t.Name == bookmarkName
select t;
var bookmark = res.SingleOrDefault();
if (bookmark != null)
var parent = bookmark.Parent.NextSibling<WParagraph>();
foreach (WText txt in bookmark.Parent.Descendants<WText>())
txt.Text = "";
foreach (var name in temps)
string filename = name;
System.IO.FileInfo info = new System.IO.FileInfo(filename);
if (info.IsReadOnly)
throw new ApplicationException("文件:" + name + "为只读,请修改后再执行操作。");
using (WordprocessingDocument sourceDoc = WordprocessingDocument.Open(filename, true))
Hashtable htStyles = CreateStylesPart(sourceDoc, objectiveDoc);
Hashtable htNumberingInstance = CreateNumberingPart(sourceDoc, objectiveDoc);
Hashtable htHyperlink = CreateHyperlinkPart(sourceDoc, objectiveDoc);
//复制段落到书签
WBody body = sourceDoc.MainDocumentPart.Document.Body;
int p = 0;
int tb = 0;
var ps = from t in body.ChildElements select t;
foreach (var item in ps)
if (item.GetType().Name == "Paragraph")
WParagraph p1 = body.Elements<WParagraph>().ElementAt(p);
OpenXmlElement oxePara = p1.CloneNode(true);
if (oxePara.Descendants<WNumberingId>().Count() > 0)
int numberID = oxePara.Descendants<WNumberingId>().First().Val;
if (htNumberingInstance[numberID] != null)
oxePara.Descendants<WNumberingId>().First().Val = WebHelper.SafeParse(htNumberingInstance[numberID], 0);
if (oxePara.Descendants<ParagraphStyleId>().Count() > 0)
string styleId = oxePara.Descendants<ParagraphStyleId>().First().Val;
if (htStyles[styleId] != null)
oxePara.Descendants<ParagraphStyleId>().First().Val = htStyles[styleId].ToString();
if (oxePara.Descendants<Hyperlink>().Count() > 0)
string hlnkId = oxePara.Descendants<Hyperlink>().First().Id;
if (htHyperlink[hlnkId] != null)
oxePara.Descendants<Hyperlink>().First().Id = htHyperlink[hlnkId].ToString();
if (oxePara.Descendants<RunStyle>().Count() > 0)
string rstyId = oxePara.Descendants<RunStyle>().First().Val;
if (htStyles[rstyId] != null)
oxePara.Descendants<RunStyle>().First().Val = htStyles[rstyId].ToString();
parent.InsertBeforeSelf(oxePara);
if (item.GetType().Name == "Table")
WTable tb1 = body.Elements<WTable>().ElementAt(tb);
OpenXmlElement oxeTab = tb1.CloneNode(true);
if (oxeTab.Descendants<WNumberingId>().Count() > 0)
foreach (var oexItem in oxeTab.Descendants<WNumberingId>())
int oldId = oexItem.Val;
if (htNumberingInstance[oldId] != null)
oexItem.Val = WebHelper.SafeParse(htNumberingInstance[oldId], 0);
parent.InsertBeforeSelf(oxeTab);
tb++;
catch (Exception ex)
throw new ApplicationException("出错文件:" + name + ",错误信息:" + ex.Message.ToString());
#endregion
catch (Exception)
throw;
finally
objectiveDoc.Close();
objectiveDoc.Dispose();
生成word样式的方法:
/// <summary>
/// 创建子模板的样式
///=========================================
/// </summary>
/// <param name="sourceDoc">来源文档</param>
/// <param name="objectiveDoc">目标文档</param>
/// <returns></returns>
private static Hashtable CreateStylesPart(WordprocessingDocument sourceDoc, WordprocessingDocument objectiveDoc)
MainDocumentPart mainPart = objectiveDoc.MainDocumentPart; //目标文档主体
StyleDefinitionsPart stPart = mainPart.StyleDefinitionsPart;
Styles styles2 = null;
if (stPart != null)
styles2 = stPart.Styles;
int st = mainPart.Parts.Count();
styles2 = new Styles();
styles2.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
styles2.AddNamespaceDeclaration("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
DocDefaults docDefaults2 = GenerateDocDefaults();
LatentStyles latentStyles2 = GenerateLatentStyles();
Hashtable htStyles = new Hashtable();
if (sourceDoc.MainDocumentPart.StyleDefinitionsPart != null)
#region 为目标文档添加 子模板中的 样式
int styleNum = 0;
OpenXmlElement oxeStyle = sourceDoc.MainDocumentPart.StyleDefinitionsPart.Styles.CloneNode(true);
foreach (var item in oxeStyle.Descendants<Style>())
#region 检查新的 styleId
string styleId = "a" + styleNum;
foreach (var objStyle in styles2.Descendants<Style>())
if (objStyle.StyleId == styleId)
styleNum++;
styleId = "a" + styleNum;
#endregion
string sourId = item.StyleId;
htStyles.Add(sourId, styleId);
item.StyleId = styleId;
int styNum = styles2.Elements<Style>().Count();
if (styNum > 0)
styles2.Elements<Style>().ElementAt(styNum - 1).InsertAfterSelf(item.CloneNode(true)); //下标从0开始
styles2.ChildElements[1].Append(item); //第一个Style 节点
styleNum++;
if (stPart == null)
stPart.Styles = styles2;
stPart.Styles.Append(sourceDoc.MainDocumentPart.StyleDefinitionsPart.Styles.CloneNode(true));
#endregion
return htStyles;
catch (Exception ex)
throw new ApplicationException("读取子模板样式时出错,错误信息:" + ex.Message.ToString());
生成项目符号的方法:
/// <summary>
/// 创建子模板的项目符号
///=========================================
/// </summary>
/// <param name="sourceDoc">来源文档</param>
/// <param name="objectiveDoc">目标文档</param>
/// <returns></returns>
private static Hashtable CreateNumberingPart(WordprocessingDocument sourceDoc, WordprocessingDocument objectiveDoc)
MainDocumentPart mainPart = objectiveDoc.MainDocumentPart;
WNumbering numbering2 = null;
NumberingDefinitionsPart ndPart = mainPart.NumberingDefinitionsPart;
if (ndPart != null)
numbering2 = ndPart.Numbering;
int c = mainPart.Parts.Count();
ndPart = mainPart.AddNewPart<NumberingDefinitionsPart>("rId" + c + 1);
numbering2 = new WNumbering();
numbering2.AddNamespaceDeclaration("ve", "http://schemas.openxmlformats.org/markup-compatibility/2006");
numbering2.AddNamespaceDeclaration("o", "urn:schemas-microsoft-com:office:office");
numbering2.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
numbering2.AddNamespaceDeclaration("m", "http://schemas.openxmlformats.org/officeDocument/2006/math");
numbering2.AddNamespaceDeclaration("v", "urn:schemas-microsoft-com:vml");
numbering2.AddNamespaceDeclaration("wp", "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing");
numbering2.AddNamespaceDeclaration("w10", "urn:schemas-microsoft-com:office:word");
numbering2.AddNamespaceDeclaration("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
numbering2.AddNamespaceDeclaration("wne", "http://schemas.microsoft.com/office/word/2006/wordml");
Hashtable htNumberingInstance = new Hashtable();
Hashtable htAbsNum = new Hashtable();
if (sourceDoc.MainDocumentPart.NumberingDefinitionsPart != null)
#region 新增项目符号
var numbering1 = from n in sourceDoc.MainDocumentPart.NumberingDefinitionsPart.Numbering.Elements() select n;
int npbId = 0;
int anId = 0;
int niId = 1;
int oldAbsId = 0;
if (ndPart != null)
npbId = numbering2.Elements<WNumberingPictureBullet>().Count();
anId = numbering2.Elements<WAbstractNum>().Count();
niId = numbering2.Elements<WNumberingInstance>().Count() + 1;
oldAbsId = numbering2.Descendants<WAbstractNumId>().Count(); //主模板原有的 WAbstractNumId数量
foreach (var number in numbering1)
if (number.GetType().Name == "NumberingPictureBullet")
WNumberingPictureBullet numberingPictureBullet1 = new WNumberingPictureBullet() { NumberingPictureBulletId = npbId };
foreach (var pic in number.ChildElements)
numberingPictureBullet1.Append(pic.CloneNode(true));
int picNum = numbering2.Elements<WNumberingPictureBullet>().Count();
if (picNum > 0)
numbering2.Elements<WNumberingPictureBullet>().ElementAt(picNum - 1).InsertAfterSelf(numberingPictureBullet1); //下标从0开始
numbering2.ChildElements[0].InsertBeforeSelf(numberingPictureBullet1); //第一个 NumberingPictureBullet 节点
npbId++;
if (number.GetType().Name == "AbstractNum")
WAbstractNum abstractNum1 = new WAbstractNum() { AbstractNumberId = anId };
foreach (var abs in number.ChildElements)
abstractNum1.Append(abs.CloneNode(true));
numbering2.Elements<WAbstractNum>().ElementAt(anId - 1).InsertAfterSelf(abstractNum1);
int oldID = WebHelper.SafeParse(GetXmlNodeId(number.OuterXml, "w:abstractNumId"), 0);
htAbsNum.Add(oldID, anId);
anId++;
if (number.GetType().Name == "NumberingInstance")
string strNumberingInstance = number.OuterXml;
int oldID = WebHelper.SafeParse(GetXmlNodeId(strNumberingInstance, "w:numId"), 0);
int newID = niId;
htNumberingInstance.Add(oldID, newID);
WNumberingInstance numberingInstance1 = new WNumberingInstance() { NumberID = niId };
foreach (var item in number.ChildElements)
if (item.GetType().Name == "AbstractNumId")
int absId = WebHelper.SafeParse(GetXmlNodeId(item.OuterXml, "w:val"), 0);
WAbstractNumId abstractNumId1 = new WAbstractNumId() { Val = WebHelper.SafeParse(htAbsNum[absId], 0) };
numberingInstance1.Append(abstractNumId1);
numberingInstance1.Append(item.CloneNode(true));
numbering2.Elements<WNumberingInstance>().ElementAt(niId - 2).InsertAfterSelf(numberingInstance1);
niId++;
if (ndPart == null)
ndPart.Numbering = numbering2;
ndPart.Numbering.Append(sourceDoc.MainDocumentPart.NumberingDefinitionsPart.Numbering.CloneNode(true));
#endregion
return htNumberingInstance;
catch (Exception ex)
throw new ApplicationException("读取子模板项目符号时出错,错误信息:" + ex.Message.ToString());
生成超链接的方法:
#region
/// <summary>
/// 创建子模板的超链接到父模板
///=========================================
/// </summary>
/// <param name="sourceDoc">来源文档</param>
/// <param name="objectiveDoc">目标文档</param>
/// <returns></returns>
private static Hashtable CreateHyperlinkPart(WordprocessingDocument sourceDoc, WordprocessingDocument objectiveDoc)
Hashtable ht = new Hashtable();
MainDocumentPart mainPart = objectiveDoc.MainDocumentPart; //目标文档主体
int c = mainPart.Parts.Count();
int hylnkNum = c + 1;
if (sourceDoc.MainDocumentPart.HyperlinkRelationships != null)
foreach (var item in sourceDoc.MainDocumentPart.HyperlinkRelationships)
string hylnkId = "rId" + hylnkNum;
foreach (var objHylnk in mainPart.Document.Descendants<Hyperlink>())
if (objHylnk.Id == hylnkId)
hylnkId = "rId" + hylnkNum + 1;
ht.Add(item.Id, hylnkId);
string url = item.Uri.ToString();
mainPart.AddHyperlinkRelationship(new System.Uri(url, System.UriKind.Absolute), true, hylnkId);
hylnkNum++;
return ht;
catch (Exception ex)
throw new ApplicationException("读取子模板超链时出错,错误信息:" + ex.Message.ToString());
#endregion
第三部分:替换word中的部分内容,此处需用到word中的书签功能。首先在word模板中定义好需要替换的书签,第一部复制模板的时候会自动复制过去。然后在程序中定义到要替换的内容,进行替换。
string[] tableremarks = new string[]
"name","age","sex"
string[]tablevalues = new string[]
"tracy","24","girl"
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(filepath, true))
for (int oIndex = 0; oIndex < tableremarks.Length; oIndex++)
string obDD_Name = tableremarks[oIndex];
string val = tablevalues[oIndex].ToString();
var res = from t in wordDoc.MainDocumentPart.Document.Body.Descendants<WBookmarkStart>()
where t.Name == obDD_Name
select t;
var bookmark = res.SingleOrDefault();
var pare = bookmark.Parent;
if (bookmark != null)
foreach (WText text in bookmark.NextSibling().Elements<WText>())
text.Text = val;
catch (Exception)
throw;
finally
wordDoc.Close();
wordDoc.Dispose();
第四部分:为word中的表格插入数据。
/// <summary>
/// 根据下标找到表格位置并添加数据
///=========================================
/// </summary>
/// <param name="filepath">文件路径</param>
/// <param name="dt">需插入的数据,用DataTable接收</param>
/// <param name="index">表格的下标(下标从0开始)</param>
/// <param name="rowIndex">在表格的第几行开始添加数据(下标从0开始)</param>
public static void AddDataToTableByIndex(string filepath, int tableIndex, DataTable dt, int rowIndex)
using (WordprocessingDocument doc = WordprocessingDocument.Open(filepath, true))
WTable table = doc.MainDocumentPart.Document.Body.Elements<WTable>().ElementAt(tableIndex);
WTableRow row = table.Elements<WTableRow>().ElementAt(rowIndex); //从第几行开始插入数据
#region 循环为表格添加数据
for (int i = 0; i < dt.Rows.Count; i++)
//添加行,复制第一行的元素,以免格式丢失
if (i > 0)
List<OpenXmlElement> rowElements = new List<OpenXmlElement>();
foreach (var item in row.Elements())
rowElements.Add(item.Clone() as OpenXmlElement);
WTableRow newRow = table.InsertAfter(new WTableRow(), row);
newRow.Append(rowElements);
//循环添加数据
for (int j = 0; j < dt.Columns.Count; j++)
WTableCell cell = row.Elements<WTableCell>().ElementAt(j);
AddTextToTableCell(cell, dt.Rows[i][j].ToString());
#endregion
catch (Exception)
throw;
finally
doc.Close();
doc.Dispose();
PS:插入的数据太大,有可能会出现异常,小数据量没有问题。
拒绝访问。(Exception FROM HRESULT:0x80070005(E_ACCESSDENIED))
我在项目中就出现过,找了很久没有找到原因,最后的解决方案是:
在C:\Documents and Settings\Default User\Local Settings\Application Data 位置下,建一个名为 IsolatedStorage的文件夹。
原理还没有搞清楚,个人认为,可能是因为数据量太大,需要一个虚拟存储空间吧。
Openxml很强大,需要研究的东西还很多,下面是几个比较详细的有关Openxml 的文章。
http://www.cnblogs.com/brooks-dotnet/archive/2010/02/08/1665600.html
http://blog.csdn.net/atian15/article/details/6948602
http://blog.csdn.net/april_zheng/article/details/6741143
最近工作中在开发的一个项目需要根据数据库的数据来动态生成Word文档存放在服务端,让用户随时随地下载。以前没有接触过Office开发,临时查了些资料,最终用COM接口完成了。项目虽然是完成了,不过我还是对期间处理Word COM接口的繁琐过程心有余悸。后期发现存在遗留问题,而且效率也不高,生成上万条的数据的时候实在是让人无语。跟朋友聊过类似的问题后,朋友提出可以用OpenXML。稍微了解一下Ope
打开XML SDK
Office 2019支持可用!
有一个可用于Office 2019类型的支持。 随时尝试一下,让我们知道它如何为您工作。 请提交发现的任何问题并将其标记为Office2019 。
Open XML SDK提供了用于处理Office Word,Excel和PowerPoint文档的工具。 它支持以下场景:
高性能的文字处理文档,电子表格和演示文稿。
从XML数据源填充Word文件中的内容。
将一个Word或PowerPoint文件拆分(切碎)为多个文件,然后将多个Word / PowerPoint文件组合为一个文件。
从Excel文档中提取数据。
使用正则表达式搜索和替换Word / PowerPoint中的内容。
在Word / PowerPoint中为图表更新缓存的数据和嵌入式电子表格。
文档修改,例如添加,更新和删除内容和元数据。
/// </summary>
/// <param name="fileName">保存文件路径</param>
/// <param name="exportTable">数据源</param>
private static void Exp
二、获取EXCEL的work sheet对象
Public Function xmlWorksheet(ByVal SheetName As String) As Worksheet
'Debug.Print("in xmlGetWorksheet,SheetName=" & SheetName)
Dim mySheet As IEnumerable(Of Sheet) =
OpenXmlHelper,其于OpenXml SDK2.0写的帮助类,主要实现Excel的导出、导入的功能及具体的源码Demo。实现的环境是Visual Studio 2012。
主要修复大于26列时的Bug
----Lixg 867314078@qq.com
Ecma Office Open XML(“Open XML”)是针对字处理文档、演示文稿和电子表格的国际化开放标准。(话说中文资料好少)
想看到***.xlsx文档内部是怎么存储信息是很简单的,只需要将***.xlsx后缀名改为***.zip,然后用压缩软件解压缩就得到了一堆xml文件,而各公司就是利用openxml的标准解析,
-------------------------文尾看效果---------------------
----------效果一(模板文件写入集合对象)------
----------效果二(新建文件写入集合对象)------
加入包:OpenXml
创建文件:ExcelWrite.cs
复制下面全部代码到文件 ExcelWrite.cs
using System;
DECLARE @XmlDocumentHandle int
DECLARE @XmlDocument nvarchar(1000)
SET @XmlDocument = N'
VINET
Paul Henriot
using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(@"C:\document.docx", false))
PdfConverter converter = new PdfConverter();
converter.OwnerPassword = "";
converter.PdfStandard = PdfStandard.Pdf;
byte[] outPdfBuffer = converter.ConvertToPdf(wordDocument);
using (FileStream outPdfFile = new FileStream(@"C:\document.pdf", FileMode.Create, FileAccess.Write))
outPdfFile.Write(outPdfBuffer, 0, outPdfBuffer.Length);
请注意,您需要引用以下命名空间:
using DocumentFormat.OpenXml.Packaging;
using SautinSoft.Document;
希望这能帮到您!