[研究][C#]用OdsReaderWrite匯出(寫入)、匯入(讀取) ODF/.ods 檔案 (使用 Ionic.Zip、DotNetZip)
2020-09-292020-12-15 更新
2021-01-03 更新
ODF release dates:
- ODF 1.0 May 2005
- ODF 1.1 February 2007
- ODF 1.2 September 2012
- ODF 1.3 January 2020
https://wiki.documentfoundation.org/Comparison_of_ODF_software
How to Read and Write ODF/ODS Files (OpenDocument Spreadsheets)
28 Jul 2011
https://www.codeproject.com/Articles/38425/How-to-Read-and-Write-ODF-ODS-Files-OpenDocument-2
所以 odsreadwrite.zip 可能只支援到 ODF, ODS 1.1 版
Visual Studio 2019 v15.6.0
OdsReaderWriter.cs 和 template.ods 取自上面網站
template.ods 放到 bin 目錄。
NuGet 要安裝 Ionic.Zip 套件。(Ionic.Zip 已經淘汰,請改用DotNetZip)
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ODSExport.Default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <form id="form1" runat="server"> <div> <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" /> </div> </form> </body> </html> |
Default.aspx.cs
using OdsReadWriteNameSpace; // 要 using namespace ,才能存取到 OdsReadWriter 這個 class,不是直接 using 這個 OdsReadWriter Class using System; using System.Data; using System.Web.UI.WebControls; namespace ODSExport { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { DataSet ds = new DataSet("DS1"); //DataTable dt = new DataTable(); DataTable dt = ds.Tables.Add("DS1DT"); DataColumn fNameColumn = new DataColumn(); fNameColumn.DataType = System.Type.GetType("System.String"); fNameColumn.ColumnName = "Fname"; fNameColumn.DefaultValue = "Fname"; dt.Columns.Add(fNameColumn); DataColumn lNameColumn = new DataColumn(); lNameColumn.DataType = System.Type.GetType("System.String"); lNameColumn.ColumnName = "Lname"; lNameColumn.DefaultValue = "Lname"; dt.Columns.Add(lNameColumn); DataRow dr = dt.NewRow(); dr["Fname"] = "tt0"; dr["Lname"] = "tt1"; dt.Rows.Add(dr); DataRow dr2 = dt.NewRow(); dr2["Fname"] = "aa1"; dr2["Lname"] = "aa2"; dt.Rows.Add(dr2); OdsReaderWriter odsFile = new OdsReaderWriter(); odsFile.WriteOdsFile(ds, @"C:\TEMP\1.ods"); } } } |
OdsReaderWriter.cs
using System; using System.Data; using System.Globalization; using System.IO; using System.Reflection; using System.Xml; using Ionic.Zip; namespace OdsReadWriteNameSpace { internal sealed class OdsReaderWriter { // Namespaces. We need this to initialize XmlNamespaceManager so that we can search XmlDocument. private static string[,] namespaces = new string[,] { {"table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0"}, {"office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0"}, {"style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0"}, {"text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0"}, {"draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"}, {"fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"}, {"dc", "http://purl.org/dc/elements/1.1/"}, {"meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"}, {"number", "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"}, {"presentation", "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"}, {"svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"}, {"chart", "urn:oasis:names:tc:opendocument:xmlns:chart:1.0"}, {"dr3d", "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"}, {"math", "http://www.w3.org/1998/Math/MathML"}, {"form", "urn:oasis:names:tc:opendocument:xmlns:form:1.0"}, {"script", "urn:oasis:names:tc:opendocument:xmlns:script:1.0"}, {"ooo", "http://openoffice.org/2004/office"}, {"ooow", "http://openoffice.org/2004/writer"}, {"oooc", "http://openoffice.org/2004/calc"}, {"dom", "http://www.w3.org/2001/xml-events"}, {"xforms", "http://www.w3.org/2002/xforms"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"rpt", "http://openoffice.org/2005/report"}, {"of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2"}, {"rdfa", "http://docs.oasis-open.org/opendocument/meta/rdfa#"}, {"config", "urn:oasis:names:tc:opendocument:xmlns:config:1.0"} }; // Read zip stream (.ods file is zip file). private ZipFile GetZipFile(Stream stream) { return ZipFile.Read(stream); } // Read zip file (.ods file is zip file). private ZipFile GetZipFile(string inputFilePath) { return ZipFile.Read(inputFilePath); } private XmlDocument GetContentXmlFile(ZipFile zipFile) { // Get file(in zip archive) that contains data ("content.xml"). ZipEntry contentZipEntry = zipFile["content.xml"]; // Extract that file to MemoryStream. Stream contentStream = new MemoryStream(); contentZipEntry.Extract(contentStream); contentStream.Seek(0, SeekOrigin.Begin); // Create XmlDocument from MemoryStream (MemoryStream contains content.xml). XmlDocument contentXml = new XmlDocument(); contentXml.Load(contentStream); return contentXml; } private XmlNamespaceManager InitializeXmlNamespaceManager(XmlDocument xmlDocument) { XmlNamespaceManager nmsManager = new XmlNamespaceManager(xmlDocument.NameTable); for (int i = 0; i < namespaces.GetLength(0); i++) nmsManager.AddNamespace(namespaces[i, 0], namespaces[i, 1]); return nmsManager; } /// <summary> /// Read .ods file and store it in DataSet. /// </summary> /// <param name="inputFilePath">Path to the .ods file.</param> /// <returns>DataSet that represents .ods file.</returns> public DataSet ReadOdsFile(string inputFilePath) { ZipFile odsZipFile = this.GetZipFile(inputFilePath); // Get content.xml file XmlDocument contentXml = this.GetContentXmlFile(odsZipFile); // Initialize XmlNamespaceManager XmlNamespaceManager nmsManager = this.InitializeXmlNamespaceManager(contentXml); DataSet odsFile = new DataSet(Path.GetFileName(inputFilePath)); foreach (XmlNode tableNode in this.GetTableNodes(contentXml, nmsManager)) odsFile.Tables.Add(this.GetSheet(tableNode, nmsManager)); return odsFile; } // In ODF sheet is stored in table:table node private XmlNodeList GetTableNodes(XmlDocument contentXmlDocument, XmlNamespaceManager nmsManager) { return contentXmlDocument.SelectNodes("/office:document-content/office:body/office:spreadsheet/table:table", nmsManager); } private DataTable GetSheet(XmlNode tableNode, XmlNamespaceManager nmsManager) { DataTable sheet = new DataTable(tableNode.Attributes["table:name"].Value); XmlNodeList rowNodes = tableNode.SelectNodes("table:table-row", nmsManager); int rowIndex = 0; foreach (XmlNode rowNode in rowNodes) this.GetRow(rowNode, sheet, nmsManager, ref rowIndex); return sheet; } private void GetRow(XmlNode rowNode, DataTable sheet, XmlNamespaceManager nmsManager, ref int rowIndex) { XmlAttribute rowsRepeated = rowNode.Attributes["table:number-rows-repeated"]; if (rowsRepeated == null || Convert.ToInt32(rowsRepeated.Value, CultureInfo.InvariantCulture) == 1) { while (sheet.Rows.Count < rowIndex) sheet.Rows.Add(sheet.NewRow()); DataRow row = sheet.NewRow(); XmlNodeList cellNodes = rowNode.SelectNodes("table:table-cell", nmsManager); int cellIndex = 0; foreach (XmlNode cellNode in cellNodes) this.GetCell(cellNode, row, nmsManager, ref cellIndex); sheet.Rows.Add(row); rowIndex++; } else { rowIndex += Convert.ToInt32(rowsRepeated.Value, CultureInfo.InvariantCulture); } // sheet must have at least one cell if (sheet.Rows.Count == 0) { sheet.Rows.Add(sheet.NewRow()); sheet.Columns.Add(); } } private void GetCell(XmlNode cellNode, DataRow row, XmlNamespaceManager nmsManager, ref int cellIndex) { XmlAttribute cellRepeated = cellNode.Attributes["table:number-columns-repeated"]; if (cellRepeated == null) { DataTable sheet = row.Table; while (sheet.Columns.Count <= cellIndex) sheet.Columns.Add(); row[cellIndex] = this.ReadCellValue(cellNode); cellIndex++; } else { cellIndex += Convert.ToInt32(cellRepeated.Value, CultureInfo.InvariantCulture); } } private string ReadCellValue(XmlNode cell) { XmlAttribute cellVal = cell.Attributes["office:value"]; if (cellVal == null) return String.IsNullOrEmpty(cell.InnerText) ? null : cell.InnerText; else return cellVal.Value; } /// <summary> /// Writes DataSet as .ods file. /// </summary> /// <param name="odsFile">DataSet that represent .ods file.</param> /// <param name="outputFilePath">The name of the file to save to.</param> public void WriteOdsFile(DataSet odsFile, string outputFilePath) { //ZipFile templateFile = this.GetZipFile(Assembly.GetExecutingAssembly().GetManifestResourceStream("OdsReadWrite.template.ods")); //ZipFile templateFile = this.GetZipFile(File.OpenRead(@"C:\CODE\ODSExport\ODSExport\bin\template.ods")); //ZipFile templateFile = this.GetZipFile(File.OpenRead(Server.MapPath("/") + "template.ods"); ZipFile templateFile = this.GetZipFile(File.OpenRead(HttpContext.Current.Server.MapPath("/template.ods"))); XmlDocument contentXml = this.GetContentXmlFile(templateFile); XmlNamespaceManager nmsManager = this.InitializeXmlNamespaceManager(contentXml); XmlNode sheetsRootNode = this.GetSheetsRootNodeAndRemoveChildrens(contentXml, nmsManager); foreach (DataTable sheet in odsFile.Tables) this.SaveSheet(sheet, sheetsRootNode); this.SaveContentXml(templateFile, contentXml); templateFile.Save(outputFilePath); } private XmlNode GetSheetsRootNodeAndRemoveChildrens(XmlDocument contentXml, XmlNamespaceManager nmsManager) { XmlNodeList tableNodes = this.GetTableNodes(contentXml, nmsManager); XmlNode sheetsRootNode = tableNodes.Item(0).ParentNode; // remove sheets from template file foreach (XmlNode tableNode in tableNodes) sheetsRootNode.RemoveChild(tableNode); return sheetsRootNode; } private void SaveSheet(DataTable sheet, XmlNode sheetsRootNode) { XmlDocument ownerDocument = sheetsRootNode.OwnerDocument; XmlNode sheetNode = ownerDocument.CreateElement("table:table", this.GetNamespaceUri("table")); XmlAttribute sheetName = ownerDocument.CreateAttribute("table:name", this.GetNamespaceUri("table")); sheetName.Value = sheet.TableName; sheetNode.Attributes.Append(sheetName); this.SaveColumnDefinition(sheet, sheetNode, ownerDocument); this.SaveRows(sheet, sheetNode, ownerDocument); sheetsRootNode.AppendChild(sheetNode); } private void SaveColumnDefinition(DataTable sheet, XmlNode sheetNode, XmlDocument ownerDocument) { XmlNode columnDefinition = ownerDocument.CreateElement("table:table-column", this.GetNamespaceUri("table")); XmlAttribute columnsCount = ownerDocument.CreateAttribute("table:number-columns-repeated", this.GetNamespaceUri("table")); columnsCount.Value = sheet.Columns.Count.ToString(CultureInfo.InvariantCulture); columnDefinition.Attributes.Append(columnsCount); sheetNode.AppendChild(columnDefinition); } private void SaveRows(DataTable sheet, XmlNode sheetNode, XmlDocument ownerDocument) { DataRowCollection rows = sheet.Rows; for (int i = 0; i < rows.Count; i++) { XmlNode rowNode = ownerDocument.CreateElement("table:table-row", this.GetNamespaceUri("table")); this.SaveCell(rows[i], rowNode, ownerDocument); sheetNode.AppendChild(rowNode); } } private void SaveCell(DataRow row, XmlNode rowNode, XmlDocument ownerDocument) { object[] cells = row.ItemArray; for (int i = 0; i < cells.Length; i++) { XmlElement cellNode = ownerDocument.CreateElement("table:table-cell", this.GetNamespaceUri("table")); if (row[i] != DBNull.Value) { // We save values as text (string) XmlAttribute valueType = ownerDocument.CreateAttribute("office:value-type", this.GetNamespaceUri("office")); valueType.Value = "string"; cellNode.Attributes.Append(valueType); XmlElement cellValue = ownerDocument.CreateElement("text:p", this.GetNamespaceUri("text")); cellValue.InnerText = row[i].ToString(); cellNode.AppendChild(cellValue); } rowNode.AppendChild(cellNode); } } private void SaveContentXml(ZipFile templateFile, XmlDocument contentXml) { templateFile.RemoveEntry("content.xml"); MemoryStream memStream = new MemoryStream(); contentXml.Save(memStream); memStream.Seek(0, SeekOrigin.Begin); templateFile.AddEntry("content.xml", memStream); } private string GetNamespaceUri(string prefix) { for (int i = 0; i < namespaces.GetLength(0); i++) { if (namespaces[i, 0] == prefix) return namespaces[i, 1]; } throw new InvalidOperationException("Can't find that namespace URI"); } } } |
********************************************************************************
如果產生的 .ods 檔案無法用 Excel 開啟,而出現:
這個活頁簿無法由 Microsoft Excel 開啟或修復,因其已損毀。
可能是資料包含 .ods 檔案無法允許的字元 (一般是不可見字元),但是該字元在 C# 的 string 變數和 SQL Server 的 nvarchar 欄位是允許使用的。最好 String.Replace( originString, ""); 把特殊字元移除。
.NET 中的逸出字元
https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/character-escapes-in-regular-expressions
String.Replace 方法
********************************************************************************
[研究]Ionic.Zip 1.9.1.8 即將淘汰,替代套件 DotNetZip >= 1.9.1.8
實際測試,NuGet 把 Ionic.Zip 1.9.1.8移除,安裝 DotNetZip 1.13.8,程式仍可正常使用。
using Ionic.Zip; // Ionic.Zip 被 DotNetZip 取代,NuGet 安裝 DotNetZip,但是 using 還是用舊名稱 Ionic.Zip |
********************************************************************************
注意,此測試寫入 C:\temp 目錄,如果在 Visual Studio 中執行,因為你是 administrator 或 administrators 群組中人,故有權寫入;如果程式用於 IIS 網站,此目錄必須開放讓 IIS_IUSRS 使用者有寫入權限。
(完)
相關
[研究][ASP.NET]使用 OdsReaderWriter + DotNetZip 1.15.0 (Ionic.Zip) 匯出寫入 .ods
[研究][ASP.NET]使用 OdsReaderWriter + DotNetZip 1.15.0 (Ionic.Zip) 匯出寫入 .ods
回覆刪除https://shaurong.blogspot.com/2021/02/aspnet-odsreaderwriter-dotnetzip-1150_9.html
作者已經移除這則留言。
回覆刪除