[研究][ASP.NET]使用 OdsReaderWriter + DotNetZip 1.15.0 (Ionic.Zip) 匯出寫入 .ods
2021-02-09
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 + ASP.NET + WebForm + C# + NuGet 安裝 NPOI 2.5.2
暫存目錄要設定程式或 IIS_IUSRS 或 IUSR 可以寫入。
( 如果是 deploy 發佈到 ASP.NET + IIS 網站, IIS_IUSRS 可以寫入即可)
( 如果是 deploy 發佈到 IIS +某種 CGI 網站, IUSR 需可以寫入)
Web.Config
<?xml version="1.0" encoding="utf-8"?>
<!--
如需如何設定 ASP.NET 應用程式的詳細資訊,請前往
https://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<connectionStrings>
<add name="TestDBConnectionString" connectionString="Data Source=.;Initial Catalog=TestDB;User ID=sa;Password=P@ssw0rd"
providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.7.2"/>
<httpRuntime targetFramework="4.7.2"/>
</system.web>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs"
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701"/>
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+"/>
</compilers>
</system.codedom>
<appSettings>
<add key="TempFolder" value="D:\WWW\Temp" />
</appSettings>
</configuration>
|
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNetZip" version="1.15.0" targetFramework="net472" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="2.0.1" targetFramework="net472" />
</packages>
|
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ExportOdsByOdsReaderWriterDotNetZipTest.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:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TestDBConnectionString %>" SelectCommand="SELECT * FROM [Table1]"></asp:SqlDataSource> <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="id" DataSourceID="SqlDataSource1"> <Columns> <asp:BoundField DataField="id" HeaderText="id" InsertVisible="False" ReadOnly="True" SortExpression="id" /> <asp:BoundField DataField="FieldText" HeaderText="FieldText" SortExpression="FieldText" /> <asp:BoundField DataField="FieldDateTime" HeaderText="FieldDateTime" SortExpression="FieldDateTime" /> <asp:BoundField DataField="FieldInt" HeaderText="FieldInt" SortExpression="FieldInt" /> <asp:CheckBoxField DataField="FieldBit" HeaderText="FieldBit" SortExpression="FieldBit" /> </Columns> </asp:GridView> <asp:Button ID="Button_Export_Ods_By_OdsReaderWriter_DotNetZip" runat="server" OnClick="Button_Export_Ods_By_OdsReaderWriter_DotNetZip_Click" Text="匯出(.ods)" /><br /> <asp:Label ID="Label_MSG1" runat="server"></asp:Label> </div> </form> </body> </html> |
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.IO; using System.Linq; using System.Web; using System.Web.Configuration; using System.Web.UI; using System.Web.UI.WebControls; using OdsReadWrite; // 要 using namespace ,才能存取到 OdsReadWriter 這個 class namespace ExportOdsByOdsReaderWriterDotNetZipTest { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Button_Export_Ods_By_OdsReaderWriter_DotNetZip_Click(object sender, EventArgs e) { /* OdsReaderWriter.cs 和 template.ods 取自下面網站 How to Read and Write ODF/ODS Files (OpenDocument Spreadsheets) https://www.codeproject.com/Articles/38425/How-to-Read-and-Write-ODF-ODS-Files-OpenDocument-2 template.ods 請自己用 Excel、OpenOffice 或 LibreOffice 的 Calc 建立一個空的即可。 NuGet 要安裝 Ionic.Zip 套件。(Ionic.Zip 已經淘汰,請改用DotNetZip) */ string mainFileName = "匯出檔案名稱"; string fd = (string)ConfigurationManager.AppSettings["TempFolder"] + @"\"; if (!System.IO.Directory.Exists(fd)) { System.IO.Directory.CreateDirectory(fd); } string dateTimeString = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-fff"); // 若檔案名稱有中文字或特殊符號,Response.AddHeader() 輸出檔案名稱要編碼 string outFileName = HttpUtility.UrlEncode( mainFileName, System.Text.Encoding.UTF8) + "-" + dateTimeString + ".ods"; // XLWorkbook.SaveAs() 輸出檔案名稱不要用 HttpUtility.UrlEncode 編碼 // Response.TransmitFile() 輸出檔案名稱不要用 HttpUtility.UrlEncode 編碼 string dirFileName = fd + mainFileName + "-" + dateTimeString + ".ods"; try { Response.Clear(); Response.Buffer = true; Response.Charset = ""; #region Export ODS // [EXCEL] Excel打開是亂碼?快速找回資料的最好方法! // http://blog.e-happy.com.tw/excel-excel%E6%89%93%E9%96%8B%E6%98%AF%E4%BA%82%E7%A2%BC%EF%BC%9F%E5%BF%AB%E9%80%9F%E6%89%BE%E5%9B%9E%E8%B3%87%E6%96%99%E7%9A%84%E6%9C%80%E5%A5%BD%E6%96%B9%E6%B3%95%EF%BC%81/ // Excel開啟CSV時的中文編碼問題補遺 //https://blog.darkthread.net/blog/csv-encoding-again/ Response.ContentType = "application/vnd.oasis.opendocument.spreadsheet"; // ODF/.ods Response.AddHeader("content-disposition", "attachment;filename=" + outFileName); using (MemoryStream MyMemoryStream = new MemoryStream()) { StreamWriter odsFileStreamWriter = new StreamWriter(MyMemoryStream); StreamReader odsFileStreamReader = new StreamReader(new MemoryStream()); string dataText = ""; DataSet ds = new DataSet("DS1"); DataTable dt = ds.Tables.Add("工作表1"); // 定義欄位名稱列 string[] columnNameArray = { "id", "FieldText", "FieldDateTime", "FieldInt", "FieldBit" }; foreach (string columnName in columnNameArray) { DataColumn dataColumnName = new DataColumn(); dataColumnName.DataType = System.Type.GetType("System.String"); dataColumnName.ColumnName = columnName; //fNameColumn.DefaultValue = "Fname"; dt.Columns.Add(dataColumnName); } //dt.Columns.Add(new DataColumn("FieldText", typeof(string))); // 把欄位名稱列寫入「工作表1」 DataRow dataColumnNameRow = dt.NewRow(); int ii = 0; foreach (string columnName in columnNameArray) { dataColumnNameRow[ii] = columnName; ii++; } dt.Rows.Add(dataColumnNameRow); // 讀取資料,資料寫入「工作表1」 string queryString = @" --DECLARE @FieldText nvarchar(50) --SET @FieldText=N'abc' SELECT * FROM [TestDB].[dbo].[Table1] "; using (SqlConnection connection = new SqlConnection( WebConfigurationManager.ConnectionStrings["TestDBConnectionString"].ConnectionString)) { SqlCommand command = new SqlCommand(queryString, connection); //command.Parameters.Clear(); //command.Parameters.AddWithValue("@FieldText", queryFieldText); connection.Open(); try { SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { //Console.WriteLine(String.Format("{0}", reader[0])); DataRow datarow = dt.NewRow(); for (int i = 0; i < reader.FieldCount; i++) { dataText = reader[i].ToString(); // 逸出字元 | Microsoft Docs // https://docs.microsoft.com/zh-tw/dotnet/standard/base-types/regular-expression-language-quick-reference // 換掉特殊字元 dataText = dataText.Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(",", ",").Replace("'", "′").Replace(@"""", "”"); dataText = dataText.Replace("\a", "").Replace("\b", "").Replace("\t", "").Replace("\v", "").Replace("\f", "").Replace("\\", ""); datarow[i] = dataText; } dt.Rows.Add(datarow); } //GridView1.DataSourceID = ""; //GridView1.DataSource = ds; // Fail, ds 是 null 嗎? //GridView1.DataBind(); OdsReaderWriter odsFile = new OdsReaderWriter(); //odsFile.WriteOdsFile(ds, @"C:\TEMP\1.ods"); odsFile.WriteOdsFile(ds, dirFileName); // Fail, ds 是 null 嗎? Response.TransmitFile(dirFileName); Response.End(); Label_MSG1.ForeColor = System.Drawing.Color.Green; Label_MSG1.Text = "匯出成功。"; // 上面 Response 輸出檔案,所以下面 JavaScript alert 是不會跳出的 //Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('匯出成功。');</script>"); } catch (Exception ex) { string errMsg = "#3: 不明例外。"; Label_MSG1.ForeColor = System.Drawing.Color.Red; if (ex != null) { errMsg = "#3: " + ex.Message.ToString(); } Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('" + errMsg + "');</script>"); } } } #endregion Export ODS } catch (Exception ex) { Label_MSG1.ForeColor = System.Drawing.Color.Red; string exMsg = "不明錯誤。"; if (ex != null) { exMsg = ex.ToString(); } Label_MSG1.Text = exMsg; Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('匯出失敗。');</script>"); // Exception Message 內容可能導致 JavaScript alert 無法顯示,所以下面這行可能無法跳出對話盒視窗。 Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('" + exMsg + "');</script>"); } } // ---------- } } |
OdsReaderWriter.cs
要做點修改 ZipFile templateFile 那行
https://www.codeproject.com/Articles/38425/How-to-Read-and-Write-ODF-ODS-Files-OpenDocument-2
using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Web;
using System.Xml;
// Ionic.Zip 已經淘汰,請改用 DotNetZip
// 不管 NuGet 安裝 DotNetZip 或 Ionic.Zip,Using 都是 Ionic.Zip
using Ionic.Zip;
namespace OdsReadWrite
{
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");
}
}
}
|
(完)
沒有留言:
張貼留言