I have a requirement to update a few columns on every row in an Excel sheet that will be uploaded from a web browser (I need to decrypt the values that are in the sheet from those columns and replace them with the true values). After the replacement happens, I will just push the updated file back down to the client.
I've worked with NPOI in the past but I'm wondering if there's a better solution out there now to accomplish this. I did a little digging before making this post and found ExcelDataReader but I couldn't find an easy way to do anything other than read Excel files using that library and my requirements are to do an update to the file.
Any suggestions would be greatly appreciated.
Here's a real simple class using the EPPlus library. It updates an Excel file by reversing the text in the first column of every row. You can test it using any Excel spreadsheet with text in the first column.
Replace ReverseText with whatever it is that decrypts the value.
public class ExcelUpdater
{
public void UpdateExcel(string pathToFile)
{
using (var package = new ExcelPackage(new FileInfo(pathToFile)))
{
var worksheet = package.Workbook.Worksheets.First();
var lastRow = worksheet.Dimension.End.Row;
for (var row = 1; row <= lastRow; row++)
{
worksheet.Cells[row, 1].Value = ReverseText(worksheet.Cells[row, 1].Text);
}
package.Save();
}
}
private string ReverseText(string value)
{
return new string(value.Reverse().ToArray());
}
}
Related
I am using Office.Interop.Excel to read data from Excel using C# ASP.Net & Dotnet 6.
I can read the Data and everything seems to be working fine.
But I have a challenge here.
The excel which I am reading data from would be updated every second.
But I am seeing an error while trying to open it and update random data.
The error says that the file is locked for editing.
Please have a look at the code below:
public double GetGoldPrice()
{
string filename = #"D:\Test.xlsx";
int row = 1;
int column = 1;
Application excelApplication = new Application();
Workbook excelWorkBook = excelApplication.Workbooks.Open(filename);
string workbookName = excelWorkBook.Name;
int worksheetcount = excelWorkBook.Worksheets.Count;
if (worksheetcount > 0)
{
Worksheet worksheet = (Worksheet)excelWorkBook.Worksheets[1];
string firstworksheetname = worksheet.Name;
var data = ((Microsoft.Office.Interop.Excel.Range) worksheet.Cells[row, column]).Value;
excelApplication.Quit();
return data;
}
else
{
Console.WriteLine("No worksheets available");
excelApplication.Quit();
return 0;
}
}
My end goal is to get live data from Excel whenever I fire the function.
The Excel would be open and can be editing any time.
Please help!
You said your file is xlsx so you would be better not using Interop but Open XML SDK 2.5. Then you can open the file in read only mode:
using (SpreadsheetDocument spreadsheetDocument =
SpreadsheetDocument.Open(fileName, false))
{
// Code removed here.
}
Check here to get familiar with Open XML SDK
using the example here How to Copy a Worksheet within a Workbook
I have successfully been able to clone/copy sheets in my excel file, however when I open the excel the 2nd sheet is the active(visible) sheet. I haven't been able to locate a property that could do thins.....Is there any way to specify what sheet is active?
I've tried to force it by opening and editing the first sheet in the file thinking it was the last edited sheet that was active but that didn't work either.
any help would be great. TIA
update: looking at the workbook.xml created when renaming the .xlsx to .zip I came accross the 'activeTab' property. made a quick change to my code and seems to work just fine
public void SetFirstSheetInFocus(String xlsxFile)
{
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(xlsxFile, true))
{
//Get a reference to access the main Workbook part, which contains all references
WorkbookPart _workbookPart = spreadSheet.WorkbookPart;
if (_workbookPart != null)
{
WorkbookView _workbookView = spreadSheet.WorkbookPart.Workbook.BookViews.ChildElements.First<WorkbookView>();
if (_workbookView != null)
{
_workbookView.ActiveTab = 0; // 0 for first or whatever tab you want to use
}
// Save the workbook.
_workbookPart.Workbook.Save();
}
}
}
If the name of your sheet is in the variable
sheetName
you can set the sheet with that name active like this:
using (var spreadsheetDoc = SpreadsheetDocument.Open(emptyHIPTemplatePath, true /* isEditable */, new OpenSettings { AutoSave = false }))
{
var workbookPart = spreadsheetDoc.WorkbookPart;
var workBook = spreadsheetDoc.WorkbookPart.Workbook;
var sheet = workBook.Descendants<Sheet>().FirstOrDefault(s => s.Name == sheetName);
var sheetIndex = workBook.Descendants<Sheet>().ToList().IndexOf(sheet);
var workBookView = workBook.Descendants<WorkbookView>().First();
workBookView.ActiveTab = Convert.ToUInt32(sheetIndex);
...
workBook.Save();
}
From Vincent Tan's book:
The SheetId property doesn't determine the order. The order of
appending the Sheet classes to the Sheets class, does.
When you add a sheet, it gets the next index, but a single sheet does not have an index. OpenXML gives it an index when you are done adding sheets. Again, from Vincent Tan's book:
Let's say you have 3 worksheets named Sheet1, Sheet2 and Sheet3.
However, when you appended the corresponding Sheet classes, you did it
as Sheet2, Sheet3 and Sheet1, in that order.
I am working on a Windows Phone 8 app to READ/WRITE Excel files. I asked a question here about this and the comment provided and many other links led me to OpenXml.
All of this got me good on how to create an Excel file and how to launch it. But now I am stuck at very basic of these all i.e. How to read an existing Excel file (probably created outside using MS Excel) cell-by-cell i.e. I want to access each cells and their values through my code. In the openXML thing I did this:
Stream localFile = App.GetResourceStream(new Uri("/ReadExcel;component/jai.xlsx"
,UriKind.Relative)).Stream;
MemoryStream ms = new MemoryStream();
localFile.CopyTo(ms);
DocumentFormat.OpenXml.Packaging.SpreadsheetDocument spreadsheetDoc =
DocumentFormat.OpenXml.Packaging.SpreadsheetDocument.Open(localFile, true);
{
var a = spreadsheetDoc.Package;
// Do work here
}
But it gives me error:
The type 'System.IO.Packaging.Package' is defined in an assembly that is not
referenced. You must add a reference to assembly 'WindowsBase, Version=4.0.0.0
So basically I am stuck at this WindowsBase.dll. I tried all various ways to import an assembly i.e. unblock and all, but nothing works.
So all I want to do is to programmatically access the content of an existing Excel file in my code cell-by-cell.
Please help or suggest whether it is even possible as of now in WP8.
I used the following method to read cells from an xlsx Excel file on Windows Phone 8:
Add the Microsoft Compression library to your project using NuGet
Adapt the code sample from the developer network to your needs - it shows how to read cells from an Excel file (and it needs the Compression lib)
Since I already extended the code a bit to handle empty columns and empty files properly you can also use my code:
public class ExcelReader
{
List<string> _sharedStrings;
List<Dictionary<string, string>> _derivedData;
public List<Dictionary<string, string>> DerivedData
{
get
{
return _derivedData;
}
}
List<string> _header;
public List<string> Headers { get { return _header; } }
// e.g. cellID = H2 - only works with up to 26 cells
private int GetColumnIndex(string cellID)
{
return cellID[0] - 'A';
}
public void StartReadFile(Stream input)
{
ZipArchive z = new ZipArchive(input, ZipArchiveMode.Read);
var worksheet = z.GetEntry("xl/worksheets/sheet1.xml");
var sharedString = z.GetEntry("xl/sharedStrings.xml");
// get shared string
_sharedStrings = new List<string>();
// if there is no content the sharedStrings will be null
if (sharedString != null)
{
using (var sr = sharedString.Open())
{
XDocument xdoc = XDocument.Load(sr);
_sharedStrings =
(
from e in xdoc.Root.Elements()
select e.Elements().First().Value
).ToList();
}
}
// get header
using (var sr = worksheet.Open())
{
XDocument xdoc = XDocument.Load(sr);
// get element to first sheet data
XNamespace xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
XElement sheetData = xdoc.Root.Element(xmlns + "sheetData");
_header = new List<string>();
_derivedData = new List<Dictionary<string, string>>();
// worksheet empty?
if (!sheetData.Elements().Any())
return;
// build header first
var firstRow = sheetData.Elements().First();
// full of c
foreach (var c in firstRow.Elements())
{
// the c element, if have attribute t, will need to consult sharedStrings
string val = c.Elements().First().Value;
if (c.Attribute("t") != null)
{
_header.Add(_sharedStrings[Convert.ToInt32(val)]);
} else
{
_header.Add(val);
}
}
// build content now
foreach (var row in sheetData.Elements())
{
// skip row 1
if (row.Attribute("r").Value == "1")
continue;
Dictionary<string, string> rowData = new Dictionary<string, string>();
// the "c" elements each represent a column
foreach (var c in row.Elements())
{
var cellID = c.Attribute("r").Value; // e.g. H2
// each "c" element has a "v" element representing the value
string val = c.Elements().First().Value;
// a string? look up in shared string file
if (c.Attribute("t") != null)
{
rowData.Add(_header[GetColumnIndex(cellID)], _sharedStrings[Convert.ToInt32(val)]);
} else
{
// number
rowData.Add(_header[GetColumnIndex(cellID)], val);
}
}
_derivedData.Add(rowData);
}
}
}
}
This works for simple Excel files having one work sheet and some text and number cells. It assumes there is a header row.
Usage is as follows:
var excelReader = new ExcelReader();
excelReader.StartReadFile(excelStream);
After reading excelReader.Headers contains the header names, excelReader.DerivedData contains the rows. Each row is a Dictionary having the header as key and the data as value. Empty cells won't be in there.
Hope this gets you started.
Unfortunately, it is not possible to use the official OpenXML SDK by Microsoft. The reason is exactly the exception you already ran into. WP8 does not have the System.IO.Packaging namespace available which is required to extract/compress the zip-based xlsx file format. Adding WindowsBase.dll won't work either because it is not compiled for WP8.
After googling for quite some time in the last two years about this the only 3 solutions that I know are (despite developing Excel support from zero by your own :) ):
Use the Ag.OpenXML open source project which you can find on http://agopenxml.codeplex.com/ . The source repository contains an implementation to write an Excel file (the downloadable package only contains Word export). I use this in my WP8 app for quite some time and it works well despite the lack of a lot of features. Unfortunately, this package is not maintained anymore since 2011. However, it might be a good start for you.
Use the commercial libraries of ComponentOne https://www.componentone.com/SuperProducts/StudioWindowsPhone/
Use the commercial libraries of Syncfusion http://www.syncfusion.com/products/windows-phone
I have an DataTable I need to put into Excel 2007 format and save it as an excel file(.xlsx) 2007.
Can anyone help me to achieve this?
You can use an OLEDB data provider and just treat Excel as another ADO.NET data source in order to loop through your DataTable rows and insert them into the Excel spreadsheet. Here's a Microsoft KB article that walks you through a lot of the details.
http://support.microsoft.com/kb/316934/en-us
The big thing to keep in mind is that you can create workbooks and sheets within the workbook, and you can reference existing sheets by appending a '$' at the end of the name. If you omit the '$' at the end of the sheet name, the OLEDB provider will assume that it's a new sheet and will try to create it.
The dollar sign following the
worksheet name is an indication that
the table exists. If you are creating
a new table, as discussed in the
Create New Workbooks and Tables
section of this article, do not use
the dollar sign.
You can create and spreadsheet in 2003 (.xls) or 2007 format (xlsx), and that's defined on your connection string -- you specify the file that you're going to write to, and just specify the extension. Make sure you use the right OLEDB provider version.
If you want to create a 2003 (.xls) version, you use this connection string:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Book1.xls;Extended Properties="Excel 8.0;HDR=YES
If you want to create a 2007 (.xlsx) version, you use this connection string:
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Book1.xlsx;Extended Properties="Excel 12.0;HDR=YES
You may have to download the ACE provider from Microsoft in order to create XLSX files. You can find it here.
I usually use the XLS provider, so I haven't worked with the XLSX provider as much.
Hope this helps. Let me know if you have other questions.
I wrote the following code for the company some time back. It takes Enumerable of any class type and exports all its (get)properties to Excel and also open Excel. You should be able to do something similar for a DataTable. Remember you need to add reference to Microsoft.Office.Interop.Excel
public static void ExportToExcel<T>(IEnumerable<T> exportData)
{
Excel.ApplicationClass excel = new Excel.ApplicationClass();
Excel.Workbook workbook = excel.Application.Workbooks.Add(true);
PropertyInfo[] pInfos = typeof(T).GetProperties();
if (pInfos != null && pInfos.Count() > 0)
{
int iCol = 0;
int iRow = 0;
foreach (PropertyInfo eachPInfo in pInfos.Where(W => W.CanRead == true))
{
// Add column headings...
iCol++;
excel.Cells[1, iCol] = eachPInfo.Name;
}
foreach (T item in exportData)
{
iRow++;
// add each row's cell data...
iCol = 0;
foreach (PropertyInfo eachPInfo in pInfos.Where(W => W.CanRead == true))
{
iCol++;
excel.Cells[iRow + 1, iCol] = eachPInfo.GetValue(item, null);
}
}
// Global missing reference for objects we are not defining...
object missing = System.Reflection.Missing.Value;
// If wanting to Save the workbook...
string filePath = System.IO.Path.GetTempPath() + DateTime.Now.Ticks.ToString() + ".xlsm";
workbook.SaveAs(filePath, Excel.XlFileFormat.xlOpenXMLWorkbookMacroEnabled, missing, missing, false, false, Excel.XlSaveAsAccessMode.xlNoChange, missing, missing, missing, missing, missing);
// If wanting to make Excel visible and activate the worksheet...
excel.Visible = true;
Excel.Worksheet worksheet = (Excel.Worksheet)excel.ActiveSheet;
excel.Rows.EntireRow.AutoFit();
excel.Columns.EntireColumn.AutoFit();
((Excel._Worksheet)worksheet).Activate();
}
}
I have an DataTable I need to put into Excel 2007 format and save it
as an excel file(.xlsx) 2007.
Can anyone help me to achieve this?
You just need to add my free C# class to your project, and one line of code.
Full details (with free downloadable source code, and an example project) here:
Mikes Knowledge Base - Export to Excel
My library uses the free Microsoft OpenXML libraries (also provided in my downloads) to write the file, so you don't have to use the heavyweight VSTO libraries, or have Excel installed on your server.
Also, it creates a real .xlsx file, rather than some other methods which write a stream of data to a comma-separated text file, but name it as a .xls file.
By the way, I had loads of difficulties writing to Excel files using OLEDB, not least because I was running Windows 7 64-bit, with Office 2007 (which is 32-bit) and the Microsoft ACE provider has to be the 64-bit edition... but you can't install this, if you have the 32-bit version of Office installed.
So, you have to uninstall Office, install the ACE driver, and then re-install Office.
But even then, I gave up using OLEDB.. it just wasn't stable enough.
Found this in some old code I did like 5 years ago that should work...
public static void DataTableToExcel(DataTable tbl)
{
HttpContext context = HttpContext.Current;
context.Response.Clear();
foreach (DataColumn c in tbl.Columns)
{
context.Response.Write(c.ColumnName + ";");
}
context.Response.Write(Environment.NewLine);
foreach (DataRow r in tbl.Rows)
{
for (int i = 0; i < tbl.Columns.Count; i++)
{
context.Response.Write(r[i].ToString().Replace(";", string.Empty) + ";");
}
context.Response.Write(Environment.NewLine);
}
context.Response.ContentType = "text/csv";
context.Response.AppendHeader("Content-Disposition",
"attachment; filename=export.csv");
context.Response.End();
}
This will output from ASP.NET a response with a CSV file that Excel 2007 can open. If you want you can change the extension to mimic excel and it should work just by replacing the following lines:
context.Response.ContentType = "application/vnd.ms-excel";
context.Response.AppendHeader("Content-Disposition",
"attachment; filename=export.xlsx");
A CSV is the easiest way if you don't need to do anything complex. If you do require it to truly be a Excel 2007 file in the native format, you will need to use an Office library to build it or convert it from the CSV and then serve/save it.
This link might also be useful:
How to avoid the Excel prompt window when exporting data to Excel 2007
Saw that someone else posted a "save to csv" option. While that didn't seem to be the answer the OP was looking for, here is my version that includes the table's headers
public static String ToCsv(DataTable dt, bool addHeaders)
{
var sb = new StringBuilder();
//Add Header Header
if (addHeaders)
{
for (var x = 0; x < dt.Columns.Count; x++)
{
if (x != 0) sb.Append(",");
sb.Append(dt.Columns[x].ColumnName);
}
sb.AppendLine();
}
//Add Rows
foreach (DataRow row in dt.Rows)
{
for (var x = 0; x < dt.Columns.Count; x++)
{
if (x != 0) sb.Append(",");
sb.Append(row[dt.Columns[x]]);
}
sb.AppendLine();
}
return sb.ToString();
}
I'm trying to add comments to cells in an Excel 2007 spreadsheet. I'm using the OpenXml SDK 2.0 to do so.
My use case is this:
I've created a template Excel file that I copy and use that as my starting point, rather than create an OpenXML document from scratch. My template file has a comment in cell A1 so that Excel has already created a WorksheetCommentPart for me.
Now my problem is that when I add Comment nodes to the Comments part the spreadsheet doesn't load and Excel asks if I want to recover.
What really bothers me is that my original comment in A1 is still there, but any comments I added programmatically are gone!
Here's the code I'm working with:
using (MemoryStream spreadsheetStream = new MemoryStream())
{
GetGradebookSpreadsheetTemplate(spreadsheetStream);
using (SpreadsheetDocument spDoc = SpreadsheetDocument.Open(spreadsheetStream, true))
{
WorkbookPart wbPart = spDoc.WorkbookPart;
WorksheetPart wsPart = wbPart.WorksheetParts.First();
SheetData sheet = wsPart.Worksheet.GetFirstChild<SheetData>();
Comments comments = wsPart.WorksheetCommentsPart.Comments;
comments.Descendants<Author>().First().Text = string.Format("{0}, {1}", instructor.LastName, instructor.FirstName);
comments.Descendants<Text>().First().Text = string.Format("{0}, {1}", instructor.LastName, instructor.FirstName);
List<DefinedName> definedNames = new List<DefinedName>();
definedNames.Add(CreateDefinedName("COLWeb_Gradebook", sheet.NamespaceURI, "Gradebook", "1", "A"));
uint index = 4;
foreach (User u in users)
CreateUserDataRow(index++, definedNames, comments.CommentList, sheet, u, coursesForUsers[u], assignments, submissions[u]);
Cell lastCell = sheet.Descendants<Cell>().Last();
OpenXmlElement dimensionsElement = wsPart.Worksheet.Elements().Where(x => x.LocalName == "dimension").First();
dimensionsElement.SetAttribute(new OpenXmlAttribute("ref", null, "A1:" + lastCell.CellReference));
comments.Save();
wsPart.Worksheet.Save();
wbPart.Workbook.Save();
}
return spreadsheetStream.ToArray();
}
And "CreateUserDataRow" creates a new row, but the relevant part is (where "comment" is my comment string and "c" is my Cell that I want to create the comment about):
if (!string.IsNullOrEmpty(comment))
{
List<OpenXmlElement> runs = new List<OpenXmlElement>();
foreach (string row in comment.Split(new string[] { "<p>", "</p>" }, StringSplitOptions.RemoveEmptyEntries))
{
string trimmed = row.Trim();
if (!string.IsNullOrEmpty(trimmed))
{
string escaped = System.Security.SecurityElement.Escape(trimmed);
runs.Add(new Run(new RunProperties(), new Text(escaped)));
}
}
Comment commentCell = new Comment();
commentCell.Reference = c.CellReference;
commentCell.AuthorId = 0;
commentCell.AppendChild(new CommentText(runs));
comments.AppendChild(commentCell);
}
Now as far as my eye can see, and KDiff3 for that matter, my files are pretty much identical to the files that would be output if I were to open Excel and put the comments into the cells by hand in Excel.
Does anyone have a good example of attaching a comment to a cell with OpenXml? Is there something I should know about maybe a relationship? Does it have something to do with using an Excel file that I created and then I'm using as a template (maybe some dimensions aren't set)?
Thanks for any help I can get.
Unfortunately, it is not that simple.
Cell comments also have a graphics object which is in a VML drawing part. VML is a cryptic legacy specification and is not in the approved ECMA standard. You can find documentation on it in Microsoft's Open XML documents, but it is not pretty. Hopefully Microsoft will address this in Excel 14 by adding full cell comment support as well as support for controls which are also written to VML.
Having said that, I have not used the Open XML SDK and I cannot say whether or not it is possible to add comments with it. I just thought this might help get you pointed in the right direction.
There appears to be a code sample at http://openxmldeveloper.org/forums/thread/7396.aspx which shows how to create the requisite VML Drawing. I'm trying this code now, and seem to have progressed, though I'm still getting some sort of unspecified error in which Excel decides to drop the comments before opening...