最近工作中在开发的一个项目需要根据数据库的数据来动态生成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; 希望这能帮到您!