基于OOXML (Office Open XML)的复杂Excel文件在线输出1(动态复制表单)

toddeli 2008-01-17 10:45:41
写在前面的:如果你是OOXML的反对者,本文可能不适合你。
概要
大家一定知道微软最新的OOXML吧,虽然去年没有被设立为ISO标准,但是它的思路和做法对我们开发人员还是有很大帮助的。本问想阐述的是如何利用OOXML实现在我们项目中复杂Excel文件的在线输出。

项目需求
导出Excel文件是我们项目的重要功能,且待导出的Excel文件构成比较复杂,特别是程序要能根据用户的请求数据自动判断表单数目,动态完成Excel表单的复制和数据绑定,最终提供用户下载。
在尝试以往各种方法后,我们决定采用Excel2007的格式开发,即OOXML,众所周知OOXML是一个新的文件类型,确切的说是一个压缩包,而压缩包里面是组成该文件的XML部件文档,一旦面向XML文件了,所有问题就变得简单了。

实现步骤
1、对Office 2007格式文件包操作
由于Office 2007的文件是基于 XML 和 ZIP 归档技术创建的,所以我们可以使用任何能够处理 XML 或者 ZIP 文件的工具来访问并且修改文档内容。可使用ICSharpCode.SharpZipLib 下载地址:
http://www.icsharpcode.net/OpenSource/SharpZipLib/Download.aspx,也可以使用 System.IO.Packaging 名称空间中的类库,但需要安装Framework3.0,并引用 WindowsBase.dll。
2、完成表单复制代码

/// <summary>
/// 拷贝sheet
/// </summary>
/// <param name="fileName">excel文件</param>
/// <param name="sheetName">待拷贝的sheet名称</param>
/// <param name="fileNo">流水号,用于名称后的数字</param>
/// <returns>新产生的sheet名称</returns>
protected string CopySheet(string fileName, string sheetName, int fileNo)
{
string partName = "/xl/workbook.xml";
string relFile = "/xl/_rels/workbook.xml.rels";
//打开包==============================================
Package xlPackage = Package.Open(fileName, FileMode.Open, FileAccess.ReadWrite);

// 出异常时返回"newSheetName",则:如果在try{}已产生名字,也可以返回

string newSheetName = "";

try
{
Uri documentUri = new Uri(partName, UriKind.Relative);
PackagePart documentPart = xlPackage.GetPart(documentUri);
//读出workbook.xml=======================================
XmlDocument doc = new XmlDocument();
doc.Load(documentPart.GetStream());
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("d", doc.DocumentElement.NamespaceURI);

string searchString = string.Format("//d:sheet[@name='{0}']", sheetName);
XmlNode node = doc.SelectSingleNode(searchString, nsManager);

if (node == null)
return null; //指定的sheet不存在
else
{
string relId = node.Attributes["r:id"].Value;
string sheetId = node.Attributes["sheetId"].Value;
string name = node.Attributes["name"].Value;

XmlNode nodeSheets = doc.DocumentElement.SelectSingleNode("d:sheets", nsManager);

string relId1 = node.Attributes["r:id"].Value + "_" + fileNo.ToString();

int maxSheetID = 0;
int tempSheetID = 0;
foreach (XmlNode note in nodeSheets.ChildNodes)
{
tempSheetID = Convert.ToInt32(note.Attributes["sheetId"].Value);
if (maxSheetID < tempSheetID)
maxSheetID = tempSheetID;
}
string sheetId1 = Convert.ToString(maxSheetID + 1);

newSheetName = name + "_" + fileNo.ToString();

//构造更改所需内容,主要针对/xl/_rels/workbook.xml.rels文件
string sheetFileName;
Uri xmlUri = new Uri(relFile, UriKind.Relative);
PackagePart xmlPart = xlPackage.GetPart(xmlUri);
XmlDocument doc1 = new XmlDocument();
doc1.Load(xmlPart.GetStream());

XmlNode nodeSheet1 = SelectOneNode(doc1.DocumentElement.ChildNodes, "Id", relId);
sheetFileName = nodeSheet1.Attributes["Target"].Value; // [worksheets/sheetname.xml]
string sheetFileName1 = sheetFileName.Substring(sheetFileName.LastIndexOf('/') + 1, (sheetFileName.IndexOf('.') - sheetFileName.LastIndexOf('/') - 1)) + "_" + fileNo.ToString() + ".xml";

string xmlString = "<Relationship Id=\"" + relId1 + "\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/" + sheetFileName1.ToLower() + "\" />";

XmlNode node1 = doc1.DocumentElement;
node1.InnerXml += xmlString;

//拷贝sheet文件(若文件已存在,则抛出异常),并修改主关系文件[content_types].xml
string sheetXmlToPaste = "/xl/worksheets/" + sheetFileName1.ToLower();
CopyXmlFile(xlPackage, "/xl/" + sheetFileName, sheetXmlToPaste);

//修改workbook.xml
nodeSheets.InnerXml += "<sheet name=\"" + newSheetName + "\" sheetId=\"" + sheetId1 + "\" r:id=\"" + relId1 + "\" />";
doc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write));

//修改/xl/_rels/workbook.xml.rels文件
doc1.Save(xmlPart.GetStream(FileMode.Create, FileAccess.Write));
xlPackage.Flush();

xlPackage.Close();
return newSheetName;
}
}
catch
{
xlPackage.Close();
return newSheetName;
}
}

internal void CopyXmlFile(Package xlPackage, string sheetXmlToCopy, string sheetXmlToPaste)
{
Uri sheetUri = new Uri(sheetXmlToCopy, UriKind.Relative);
PackagePart sheetPart = xlPackage.GetPart(sheetUri);
XmlDocument doc = new XmlDocument();
doc.Load(sheetPart.GetStream());
Uri xmlUri = new Uri(sheetXmlToPaste, UriKind.Relative);
if (xlPackage.PartExists(xmlUri))
{
xlPackage.Close();
throw new InvalidOperationException("XML part is existing.");
}
PackagePart xmlPart = xlPackage.CreatePart(xmlUri, @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");

using (Stream outputStream = xmlPart.GetStream(FileMode.Create, FileAccess.Write))
{
using (StreamWriter writer = new StreamWriter(outputStream))
{
writer.Write(doc.InnerXml);
writer.Close();
}
}
//修改主关系文件[content_types].xml
string schemaRelationships = @"http://schemas.openxmlformats.org/officeDocument/2006/relationships";
PackageRelationship rel = xlPackage.CreateRelationship(xmlUri, TargetMode.Internal, schemaRelationships + "/worksheet");
xlPackage.Flush();
}


代码中CopySheet和CopyXMLFile为主要函数,负责完成表单的复制和对应XML文件的修改,后面还有三个辅助函数。
...全文
831 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
lhh19879 2011-11-05
  • 打赏
  • 举报
回复
好文章,正好需要这个
clarke563 2009-06-29
  • 打赏
  • 举报
回复
非常感謝樓主,解決了我很大的問題.謝謝!
toddeli 2008-01-17
  • 打赏
  • 举报
回复
这个东东就是用来实现在线导出excel文件的。你可以去看看现有的一些做法,然后比较一下。
Demo我还在整理中,会尽快提供的!
生财 2008-01-17
  • 打赏
  • 举报
回复
啊那这个东东很有用吗,我很菜滴,能不能给个DEMO啊
toddeli 2008-01-17
  • 打赏
  • 举报
回复
居然没有修改帖子和回复的权限,晕!

推荐大家几个好的地方,用来实现对Excel 2007文件的操作:
1、微软的MSDN,http://msdn2.microsoft.com/en-us/library/bb508943.aspx,上面有详细的讲解,而且还有很多例子下载;
2、国外已经有大侠完成了功能封装,而且开源的,参见:http://www.codeplex.com/ExcelPackage,当时我们也想直接使用这个包,但是唯独缺少我们最主要的功能:表单的复制,所以就自己研究,写了复制的方法;
3、如果大家有需求,我还会把微软的包做一下整理,然后进行简单封装;
4、除此之外,我们在本项目中还实现了数据的批量赋值(写好数据和excel中XML文件的映射文件)和在线转换低版本(Com实现),我会陆续整理。
toddeli 2008-01-17
  • 打赏
  • 举报
回复
开会了,待续。。。
toddeli 2008-01-17
  • 打赏
  • 举报
回复
下面是辅助函数:
public XmlNode SelectOneNode(XmlNodeList nodes, string name)
{
foreach (XmlNode n in nodes)
{
if (n.Name == name)
return n;
}

return null;
}
public XmlNode SelectOneNode(XmlNodeList nodes, string name, string value)
{
foreach (XmlNode n in nodes)
{
if (n.Attributes[name].Value == value)
return n;
}

return null;
}
internal void AddNode(Package xlPackage, string relFile, string xmlString, string singleNode)
{
Uri xmlUri = new Uri(relFile, UriKind.Relative);
PackagePart xmlPart = xlPackage.GetPart(xmlUri);
XmlDocument doc = new XmlDocument();
doc.Load(xmlPart.GetStream());
XmlNode node = doc.DocumentElement;
node.InnerXml += xmlString;
doc.Save(xmlPart.GetStream(FileMode.Create, FileAccess.Write));
xlPackage.Flush();
}

62,052

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧