I did a complete copy from the following link: https://msdn.microsoft.com/en-us/library/dd452407(v=office.12).aspx
The copy from template works fine, the FixChartData() method works fine. However, the output File does not contain any data. I do see that the contentRow contains the data via the debugger, but the excel sheet does not have the data in it when I open the file.
Very frustrating. Any help would be appreciated.
public void Create()
{
string appPath = System.IO.Path.GetDirectoryName(System.IO.Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
string templateFile = appPath + #"\Templates\ChartExample.xlsx";
string saveFile = appPath + #"\Documents\Generated.xlsx";
File.Copy(templateFile, saveFile, true);
//open copied template.
using(SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(saveFile, true))
{
//this is the workbook contains all the worksheets
WorkbookPart workbookPart = myWorkbook.WorkbookPart;
//we know that the first worksheet contains the data for the graph
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); //getting the first worksheet
//the shhet data contains the information we are looking to alter
SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
int index = 2;//Row the data for the graph starts on
//var qry = from t in db.SEL_SE_DEATHS()
FudgeData fudge = new FudgeData();
var qry = fudge.Fudged();
foreach(var item in qry)
{
int Year = item.EventYear;
int PSQ = item.PSQReviewable;
int death = item.Deaths;
Row contentRow = CreateContentRow(index, Year, PSQ, death);
index++;
//contentRow.RowIndex = (UInt32)index;
sheetData.AppendChild(contentRow);
}
//(<x:c r="A2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>2014</x:v></x:c><x:c r="B2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>21</x:v></x:c><x:c r="C2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>4</x:v></x:c>)
FixChartData(workbookPart, index);
worksheetPart.Worksheet.Save();
myWorkbook.Close();
myWorkbook.Dispose();
}
}
string[] headerColumns = new string[] { "A", "B", "C" }; //the columns being accessed
public Row CreateContentRow(int index, int year, int pSQ, int death)
{
Row r = new Row();
r.RowIndex = (UInt32)index;
//skipping the text add function
//we are createing a cell for each column (headerColumns),
//for each cell we are adding a value.
//we then append the value to the cell and append the cell to the row - wich is returned.
for(int i =0; i <headerColumns.Length; i++)
{
Cell c = new Cell();
c.CellReference = headerColumns[i] + index;
CellValue v = new CellValue();
if(i == 0)
{
v.Text = year.ToString();
}else if(i == 1)
{
v.Text = pSQ.ToString();
}else if(i == 2)
{
v.Text = death.ToString();
}
c.AppendChild(v);
r.AppendChild(c);
}
return r;
}
//Method for when the datatype is text based
public Cell CreateTextCell(string header, string text, int index)
{
//Create a new inline string cell.
Cell c = new Cell();
c.DataType = CellValues.InlineString;
c.CellReference = header + index;
//Add text to the text cell.
InlineString inlineString = new InlineString();
Text t = new Text();
t.Text = text;
inlineString.AppendChild(t);
c.AppendChild(inlineString);
return c;
}
//fix the chart Data Regions
public void FixChartData(WorkbookPart workbookPart, int totalCount)
{
var wsparts = workbookPart.WorksheetParts.ToArray();
foreach(WorksheetPart wsp in wsparts)
{
if(wsp.DrawingsPart != null)
{
ChartPart chartPart = wsp.DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
foreach (DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
{
if (formula.Text.Contains("$2"))
{
string s = formula.Text.Split('$')[1];
formula.Text += ":$" + s + "$" + totalCount;
}
}
chartPart.ChartSpace.Save();
}
}
//ChartPart chartPart = workbookPart.ChartsheetParts.First().DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
//foreach(DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
//{
// if (formula.Text.Contains("$2"))
// {
// string s = formula.Text.Split('$')[1];
// formula.Text += ":$" + s + "$" + totalCount;
// }
//}
//chartPart.ChartSpace.Save();
}
David,
I got your code to work fine. Here is a link to my Console Application.. I uploaded it to Github with some minor changes. I made 2 changes:
1) I was not able to download the samples from the link you provided. So I created a blank empty spreadsheet with Excel2016 and saved it in that directory.
2) The Fudge data was missing, so I generated some sample data via self mocked object.
The spreadsheet copies fine from the template and your code populates it with the fudge data. Here is what the final result looks like:
After downloading, you will need to make a Template and Document subdirectory. Then place my ChartExample.xslx file in the Template directory and run.
Related
I'm creating a new open xml document. When I write unique data/rows to the document I can open it a variety of programs. When I write a non-unique row and attempt to open the document in Apache OpenOffice I get an error General Error. General input/output error. Obviously this isn't very descriptive so I'm assuming I'm creating my document wrong but I'm not sure what is missing/wrong
Things I've tried:
The solution listed in the OpenOffice Documentation
Using the OpenXmlValidator
This doesn't return any errors
Opening in different software: Microsoft Office Excel Viewer and LibreOffice Calc.
The file opens in these but the machines running this code don't have this software installed
The weird fix
Rename the a.xlsx => a.zip
Extract the contents from the zip file
Zip up all the contents (using winrar and windows compressed zipped folder) named b.zip
Rename the b.zip to b.xlsx
The file now opens in OpenOffice without any error.
Doing a diff on the unzipped files shows no differences, doing a diff on a.xlsx and b.xlsx there are differences but nothing that makes sense to me
The Code:
static void Main(string[] args) {
var thing = new MyClass();
thing.GenerateDoc();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
public class MyClass {
public MyClass() { }
public void GenerateDoc() {
var xmlFileString = "Temp.xlsx";
var sheetName = "sheetName";
var OpenXMLAlwaysPrintHeader = true;
try {
bool fileExists = System.IO.File.Exists(xmlFileString);
if (!fileExists) {
// check for a blank file template and copy that if it exists
CreateSpreadsheetWorkbook(xmlFileString, sheetName);
}
fileExists = System.IO.File.Exists(xmlFileString);
if (fileExists) {
UInt32 RowIndex;
using (var doc = SpreadsheetDocument.Open(xmlFileString, true)) {
// Check to see if the sheet we are adding data to exists
var workbookPart = doc.WorkbookPart;
WorksheetPart worksheetPart = workbookPart.WorksheetParts.FirstOrDefault();
try {
worksheetPart = GetWorksheetPart(workbookPart, sheetName);
}
catch (Exception) { }
var sheet = worksheetPart.Worksheet ?? new Worksheet();
var sheetData = sheet.Elements<SheetData>().First();
var t = sheetData.Elements<Row>();
Row eHeader = null;
if (t.Count() > 0) {
eHeader = t?.First(); // This should be the first row ( the header or key of each item in the dict)
}
RowIndex = (uint)t.Count() + 1;
// Create the table of all strings if it doesnt exist
SharedStringTablePart shareStringPart;
if (doc.WorkbookPart.GetPartsOfType<SharedStringTablePart>().Count() > 0)
shareStringPart = doc.WorkbookPart.GetPartsOfType<SharedStringTablePart>().First();
else
shareStringPart = doc.WorkbookPart.AddNewPart<SharedStringTablePart>();
// Create a row for the header and the values (referring to the keys and the values in the dict)
Row header = new Row() { RowIndex = RowIndex++ };
Row data = new Row() { RowIndex = RowIndex };
// If we are not re-printing the header than the data row needs to shift up by 1
if (!OpenXMLAlwaysPrintHeader && t.Count() > 0)
data.RowIndex = --data.RowIndex;
var ColIndex = 1;
// Create the DefinedNames part which is a dictonary of "string" to a range of cells
// todo fix the next line
// This deletes all other pre-defined names since I can't figure out how to update a defined name yet
var dfns = new DefinedNames();
workbookPart.Workbook.DefinedNames = dfns;
/*
data.RowIndex = data.RowIndex - 1;
// Row 1
InsertObjectAt("Test1", data.RowIndex, ColIndex++, worksheetPart, shareStringPart);
InsertObjectAt("TestB", data.RowIndex++, ColIndex, worksheetPart, shareStringPart);
// Row 2
InsertObjectAt("Test2", data.RowIndex, --ColIndex, worksheetPart, shareStringPart);
InsertObjectAt("TestB", data.RowIndex++, ++ColIndex, worksheetPart, shareStringPart);
// Row 3
InsertObjectAt("Test1", data.RowIndex, --ColIndex, worksheetPart, shareStringPart);
InsertObjectAt("TestB1", data.RowIndex++, ++ColIndex, worksheetPart, shareStringPart);
// Row 4
InsertObjectAt("Test2", data.RowIndex, --ColIndex, worksheetPart, shareStringPart);
InsertObjectAt("TestB1", data.RowIndex++, ++ColIndex, worksheetPart, shareStringPart);
// */
sheet.SheetDimension = new SheetDimension() { Reference = "A1:B4" };
for (int i = 0; i < 2; i++) {
if (!OpenXMLAlwaysPrintHeader && t.Count() > 0) // Look up which column we want to insert our value into
{
var indexOfItem = InsertSharedStringItem("Key", shareStringPart);
var cells = eHeader.Elements<Cell>().Where(x => x.CellValue.InnerText == indexOfItem.ToString());
if (cells.Count() < 1) continue;
var cell = cells.First();
ColIndex = GetColumnIndex(cell?.CellReference).Value;
} // Otherwise we are always inserting a header so don't bother looking up where things should go
else {
//Insert for the header
InsertObjectAt(ColIndex%2 == 0 ? "TestB" : "Test1", header.RowIndex, ColIndex, worksheetPart, shareStringPart);
}
// Insert for the data
if (RowIndex == 2) {
InsertObjectAt((i % 2 == 0 ? "Test2" : "TestC"), data.RowIndex, ColIndex++, worksheetPart, shareStringPart);
}else {
InsertObjectAt((i % 2 == 0 ? "Test2" : "TestD"), data.RowIndex, ColIndex++, worksheetPart, shareStringPart);
}
/*
if (!OpenXMLAlwaysPrintHeader) // If we are not always printing a header we can create a named range for the column
CreateRange(workbookPart, "key", sheetName, data.RowIndex, ColIndex - 1);
// */
}
}
}
var validator = new OpenXmlValidator();
int count = 0;
var stringbuilder = new StringBuilder();
foreach (ValidationErrorInfo error in validator.Validate(SpreadsheetDocument.Open(xmlFileString, true))) {
stringbuilder.Append("\r\n");
count++;
stringbuilder.Append(("Error Count : " + count) + "\r\n");
stringbuilder.Append(("Description : " + error.Description) + "\r\n");
stringbuilder.Append(("Path: " + error.Path.XPath) + "\r\n");
stringbuilder.Append(("Part: " + error.Part.Uri) + "\r\n");
}
}
catch (Exception e) {
e = e;
}
}
private string GetExcelColumnName(int columnNumber) {
int dividend = columnNumber;
string columnName = String.Empty;
int modulo;
while (dividend > 0) {
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
return columnName;
}
public void CreateRange(WorkbookPart wbPart, string Name, string SheetName, uint RowIndex, int ColIndex) {
var definedNames = wbPart.Workbook.DefinedNames;
var myLocation = GetExcelColumnName(ColIndex) + RowIndex.ToString();
var Col = GetExcelColumnName(ColIndex);
var Text = string.Format("{0}!${1}${2}:${3}${4}", SheetName, Col, 2, Col, RowIndex);
var colRange = new DefinedName { Name = Name, Text = Text };
wbPart.Workbook.DefinedNames?.Append(colRange);
}
private static int? GetColumnIndex(string cellReference) {
if (string.IsNullOrEmpty(cellReference)) {
return null;
}
//remove digits
string columnReference = Regex.Replace(cellReference.ToUpper(), #"[\d]", string.Empty);
int columnNumber = -1;
int mulitplier = 1;
//working from the end of the letters take the ASCII code less 64 (so A = 1, B =2...etc)
//then multiply that number by our multiplier (which starts at 1)
//multiply our multiplier by 26 as there are 26 letters
foreach (char c in columnReference.ToCharArray().Reverse()) {
columnNumber += mulitplier * ((int)c - 64);
mulitplier = mulitplier * 26;
}
//the result is zero based so return columnnumber + 1 for a 1 based answer
//this will match Excel's COLUMN function
return columnNumber + 1;
}
private void InsertObjectAt(object item, uint RowIndex, int ColIndex, WorksheetPart worksheetPart, SharedStringTablePart sharedStringTablePart) {
if (item == null) return;
if (item is ICollection)
item = ICollectionToString(item as ICollection);
// Create the header cell
int index = InsertSharedStringItem(item.ToString(), sharedStringTablePart);
Cell c = InsertCellInWorksheet(GetExcelColumnName(ColIndex), RowIndex, worksheetPart);
c.CellValue = new CellValue(index.ToString());
c.DataType = new EnumValue<CellValues>(CellValues.SharedString);
}
private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart) {
// If the part does not contain a SharedStringTable, create one.
if (shareStringPart.SharedStringTable == null) {
shareStringPart.SharedStringTable = new SharedStringTable();
}
int i = 0;
// Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>()) {
if (item.InnerText == text) {
return i;
}
i++;
}
// The text does not exist in the part. Create the SharedStringItem and return its index.
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
shareStringPart.SharedStringTable.Save();
return i;
}
public static WorksheetPart GetWorksheetPart(WorkbookPart workbookPart, string sheetName) {
Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault(s => s.Name == sheetName);
if (sheet == default(Sheet)) {
CreateSheet(workbookPart, sheetName);
}
return workbookPart.GetPartById(sheet.Id) as WorksheetPart;
}
public static void CreateSheet(WorkbookPart workbookPart, string sheetName) {
var sheets = workbookPart.Workbook.Descendants<Sheets>().FirstOrDefault();
if (sheets == default(Sheets))
sheets = workbookPart.Workbook.AppendChild(new Sheets());
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
var sheetdata = new SheetData();
var worksheet = new Worksheet(sheetdata);
worksheetPart.Worksheet = worksheet;
var id = (UInt32)workbookPart.Workbook.Descendants<Sheet>().Count() + 1;
var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = id, Name = sheetName };
sheets.AppendChild(sheet);
workbookPart.Workbook.Save();
}
public static void CreateSpreadsheetWorkbook(string filepath, string sheetName) {
// Create a spreadsheet document by supplying the filepath.
// By default, AutoSave = true, Editable = true, and Type = xlsx.
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.
Create(filepath, SpreadsheetDocumentType.Workbook);
// Add a WorkbookPart to the document.
WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new Workbook();
// Add a WorksheetPart to the WorkbookPart.
WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
// Add Sheets to the Workbook.
Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.
AppendChild<Sheets>(new Sheets());
// Append a new worksheet and associate it with the workbook.
var id = (UInt32)workbookpart.Workbook.Descendants<Sheet>().Count() + 1;
Sheet sheet = new Sheet() {
Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = id,
Name = sheetName
};
sheets.Append(sheet);
workbookpart.Workbook.Save();
// Close the document.
spreadsheetDocument.Close();
}
private string ICollectionToString(ICollection item) {
try {
var result = string.Empty;
if (item is IDictionary) {
foreach (DictionaryEntry kvp in item as IDictionary) {
if (kvp.Value is ICollection)
result += kvp.Key + " { " + ICollectionToString(kvp.Value as ICollection) + " } ";
else
result += kvp.Key + " => " + kvp.Value + " |";
}
}
else if (item is IList) {
var serializer = new JavaScriptSerializer();
string thing = serializer.Serialize(item);
result += thing;
}
else {
// todo
}
return result;
}
catch (Exception e) {
}
return string.Empty;
}
}
// Given a column name, a row index, and a WorksheetPart, inserts a cell into the worksheet.
// If the cell already exists, returns it.
private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart) {
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>();
string cellReference = columnName + rowIndex;
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
if (sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0) {
row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
}
else {
row = new Row() { RowIndex = rowIndex, Spans = new ListValue<StringValue>() { InnerText = "1:2" } };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference.Value == columnName + rowIndex).Count() > 0) {
return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
}
else {
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell refCell = null;
foreach (Cell cell in row.Elements<Cell>()) {
if (cell.CellReference.Value.Length == cellReference.Length) {
if (string.Compare(cell.CellReference.Value, cellReference, true) > 0) {
refCell = cell;
break;
}
}
}
Cell newCell = new Cell() { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
worksheet.Save();
return newCell;
}
}
}
Running the program once will create what I believe is a valid openxml document which will open in Apache OpenOffice. Running the program twice will add two lines of which 1 is not unique to the document. This will cause the error to show up in OpenOffice, but not in the other programs (Excel Viewer/Libreoffice Calc).
Unfortunately I need to use OpenOffice as its whats installed on the computers, but I'm not sure what I am doing wrong when creating the document. Do I need to add something to the rows to indicate that it is a duplicate?
Edit: To run the code you need the DocumentFormat.OpenXML nuget package
Edit1: This only occurs when running the program twice. If I were to just append a 4 rows two of which were identical and attempt to open the file I have no issue. Note that the InsertObjectAt method also opens the document everytime (once for each cell so 4 rows by 2 cols = 8 times).
excel(XLSM) file starts with first column empty and second column with values and so on it replaces the empty column with immediate column available
XLSM FILE :Before uploading
XLSM FILE:After uploading xlsm shifts to immediate null column
how to find the range or total column without shifting
i.e:when i count column it has to display as 3(A2,B2,C2)
but it gives me total column when converting
below is the code:
private void Get_XLSM_Data(ref DataTable dt)
{
string strPath = Path.GetExtension(this.FilePath);
if (strPath != null && strPath.ToUpper() == ".XLSM")
{
using (SpreadsheetDocument spreadSheetDocument =
SpreadsheetDocument.Open(this.FilePath, true))
{
IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook
.GetFirstChild<Sheets>().Elements<Sheet>();
string relationshipId = sheets.First().Id.Value;
WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument
.WorkbookPart.GetPartById(relationshipId);
Worksheet workSheet = worksheetPart.Worksheet;
var dimensionReference = workSheet.SheetDimension.Reference;
var cellTablePart = workSheet.WorksheetPart.SingleCellTablePart;
SheetData sheetData = workSheet.GetFirstChild<SheetData>();
IEnumerable<Column> columnsDescendants = sheetData.Descendants<Column>();
IEnumerable<Row> rows = sheetData.Descendants<Row>();
var sheetIdValue = sheets.First().SheetId.Value;
// ReSharper disable once PossibleNullReferenceException
var column = workSheet.GetFirstChild<SheetData>().ChildElements.FirstOrDefault().ChildElements.Count();
if (dt.TableName == "specific table ")
{
dt.Columns.Clear();
for (int col = 1; col <= column; col++)
{
string colName = "Column" + (col);
dt.Columns.Add(colName);
}
//// START: To add Headers (First row) in data table
string[] rowData = new string[dt.Columns.Count];
int colIndex = 0;
foreach (Cell cell in rows.ElementAt(0))
{
rowData[colIndex] = GetCellValue(spreadSheetDocument, cell); colIndex++;
}
dt.Rows.Add(rowData);
//// END: To add Headers (First row) in data table
}
try
{
for (int i = 1; i < rows.Count(); i++)
{
string[] rowData = new string[dt.Columns.Count];
int col = 0;
foreach (Cell cell in rows.ElementAt(i))
{
rowData[col] = GetCellValue(spreadSheetDocument, cell); col++;
}
dt.Rows.Add(rowData);
}
}
}
}
}
public static string GetCellValue(SpreadsheetDocument document, Cell cell)
{
SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;
if (cell != null)
{
string cellValue = cell.CellValue != null ? cell.CellValue.InnerXml : String.Empty;
if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
{
cellValue = stringTablePart.SharedStringTable.ChildElements[Int32.Parse(cellValue)].InnerText;
}
else
{
if(!string.IsNullOrEmpty(cellValue))
{
//return Convert.ToString(cellValue, CultureInfo.InvariantCulture);
return double.Parse(cellValue, CultureInfo.InvariantCulture).ToString();
}
return cellValue;
}
return cellValue;
}
return String.Empty;
}
Row row = worksheetPart.Worksheet.GetFirstChild<SheetData>().Elements<Row>().FirstOrDefault();
var totalnumberOfColumns = 0;
if (row != null)
{
var spans = row.Spans != null ? row.Spans.InnerText : "";
if (spans != String.Empty)
{
//spans.Split(':')[1];
string[] columns = spans.Split(':');
startcolumnInuse = int.Parse(columns[0]);
endColumnInUse = int.Parse(columns[1]);
totalnumberOfColumns = int.Parse(columns[1]);
}
}
Below is the screen shot to find the maximum column present through span with above code i have shared
Here i have used different excel file(XLSM)
Below is the screen shot to find the maximum column present through span
with above code i have shared
Here i have used different excel file(XLSM)
I am editing uploaded excel workbooks using C# with the same logic I used to do using VBA. I am using SyncFusion to open the workbooks but however, the code below is not letting me read the whole column to apply the logic. Why?
public void AppendID(string excelFilePath, HttpResponse response)
{
using (ExcelEngine excelEngine = new ExcelEngine())
{
IApplication application = excelEngine.Excel;
application.DefaultVersion = ExcelVersion.Excel2007;
IWorkbook workbook = application.Workbooks.Open(excelFilePath);
workbook.Version = ExcelVersion.Excel97to2003;
workbook.Allow3DRangesInDataValidation = true;
//Accessing worksheet via name
IWorksheet worksheet = workbook.Worksheets[2];
When I try to define the range, the error will appear "Two names not allowed".
var prismaID = worksheet.UsedRange["C15:C"].Value;
var type = worksheet.UsedRange["F15:F"].Value;
var placements = worksheet.UsedRange["I15:I"].Value;
if (!type.Contains("PKG"))
{
placements = placements + prismaID;
}
worksheet.Range["G7"].Text = "Testing";
workbook.SaveAs(excelFilePath);
workbook.Close();
}
}
Logic:
Let's say I have three columns and how to use the following logic to manipulate usedRange cells?
ID Condition Name Output
1 Yes Sarah Sarah(1)
2 No George George
3 Yes John(3) John(3)
The logics to apply:
Move the first column 'ID' to the end of the column 'Name' but
if Column 'Condition' contains 'No'then don't move the first column
or if it contains the same 'ID' already.
Here is the VBA code:
With xlSheet
LastRow = xlSheet.UsedRange.Rows.Count
Set target = .Range(.Cells(15, 9), .Cells(LastRow, 9))
values = target.Value
Set ptype=.Range(.Cells(15,6),.Cells(LastRow,6))
pvalues=ptype.Value
For i = LBound(values, 1) To UBound(values, 1)
'if Statement for test keywords
If InStr(1,pvalues(i,1),"Package")= 0 AND InStr(1,pvalues(i,1),"Roadblock")= 0 Then
If Instr(values(I,1),.Cells(i + 15 - LBound(values, 1), 3)) = 0 Then
'If InStr(1,values(i,1),"(")=0 Then
values(i, 1) = values(i, 1) & "(" & .Cells(i + 15 - LBound(values, 1), 3) & ")"
End If
End If
Next
target.Value = values
End With
Your requirement can be achieved by appending column ID with column Name using XlsIO.
Please refer below code snippet for the same.
Code Snippet:
for(int row = 1; row<= worksheet.Columns[1].Count; row++)
{
if (worksheet[row, 2].Value == "yes" && !worksheet[row, 3].Value.EndsWith(")"))
worksheet[row, 4].Value = worksheet[row, 3].Value + "(" + worksheet[row, 1].Value + ")";
else
worksheet[row, 4].Value = worksheet[row, 3].Value;
}
We have prepared simple sample and the sample can be downloaded from the following link.
Sample Link: http://www.syncfusion.com/downloads/support/directtrac/general/ze/Sample859524528.zip
I work for Syncfusion.
So I am working with templates in excel, and I developed this logic.
I create a coupling of the first row of column names and the rows using the first cell as the key to bind the data in groups to a multi value dictionary.
I use the below function, which can be adapted to skip rows before parsing allowing you to target the proper row for binding. Book is ExcelDataReader.AsDataSet()
public static MultiValueDictionary<string, ILookup<string, string>> ParseTemplate(string Sheet, ref List<string> keys)
{
int xskip = 0;
MultiValueDictionary<string, ILookup<string, string>> mvd = new MultiValueDictionary<string, ILookup<string, string>>();
var sheetRows = Book.Tables[Sheet];
//Parse First row
var FirstRow = sheetRows.Rows[0];
for (var Columns = 0; Columns < sheetRows.Columns.Count; Columns++)
{
if (xskip == 0)
{
xskip = 1;
continue;
}
keys.Add(FirstRow[Columns].ToString());
}
//Skip First Row
xskip = 0;
//Create a binding of first row and all subsequent rows
foreach (var row in sheetRows.Select().Skip(1))
{
//Make the key the first cell of each row
var key = row[0];
List<string> rows = new List<string>();
foreach (var item in row.ItemArray)
{
if (xskip == 0)
{
xskip = 1;
continue;
}
rows.Add(item.ToString());
}
mvd.Add(key.ToString(), keys.Zip(rows, (m, n) => new { Key = m, Value = n }).ToLookup(x => x.Key, y => y.Value));
xskip = 0;
}
return mvd;
}
}
//This is example of what a function to parse this could do.
foreach(var Key in mvd.Keys)
{
var KeywithValues = mvd[Key];
foreach(ColumnName in Keys)
{
KeywithValues[ColumnName].
}
}
Hope it helps.
This question is answered on a basic level on another post: here However for my case I am not able to hard code the validation values into the sheet I am pulling them from a database based on the content of the cell and will need to do a separate validation for 4 columns on every row. Is there a way this can be achieved? Thank you in advance.
// Data Validations //
// Product Validation //
for (int i = 2; i < rowCount; i++)
{
var val = ws.DataValidations.AddListValidation(ws.Cells[i, 5].Address);
val.ShowErrorMessage = true;
val.ErrorTitle = "Entry was invalid.";
val.Error = "Please choose options from the drop down only.";
var ticketEntity = ticketQueryable.Where(o => o.TTSTicketNumber == ws.Cells[i, 3].Value.ToString()).Single<CustCurrentTicketEntity>();
var prodIds = prodExtQueryable.Where(p => p.ZoneId == ticketEntity.ZoneId && p.TicketTypeId == ticketEntity.TicketTypeId);
if (ticketEntity != null)
{
var prodIdsList = new List<int>();
foreach (var prodId in prodIds)
{
prodIdsList.Add(prodId.ProductId);
}
var ProductList = ProductCache.Instance.AllProducts.Where(p => prodIdsList.Contains(p.ProductId)).Select(p => new SelectListItem() { Value = p.ProductId.ToString(), Text = p.Name });
foreach (var Result in ProductList)
{
var product = Result.Text;
val.Formula.Values.Add(product);
}
}
}
So yes as Ernie said What I did was add a second sheet "ProductValidations" and set it to Hidden (unhide it to check that it is working). I then Load my data from the DataTable and then add some basic EPPLUS formatting. I then iterate over the Rows and Insert values into the "ProductValidations" sheet for each cell. Next I convert my column number to the correct Excel Column letter name (A, AC, BCE etc) I then create a string to pass back as an Excel formula targeting the correct range of cells in the "ProductValidations" sheet. Also to anyone having an issue downloading the Excel file from the server this guid method works just fine for me.
public ActionResult DownloadExcel(EntityReportModel erModel, string filename)
{
var dataResponse = iEntityViewService.LoadEntityView(new EntityViewInput
{
SecurityContext = SessionCache.Instance.SecurityContext,
EntityViewName = "Ticket",
Parameters = new Dictionary<string, object> {
{"MinTicketDateTime", "04/26/16"}
}
});
var table = dataResponse.DataSet.Tables[0];
filename = "TICKETS-" + DateTime.Now.ToString("yyyy-MM-dd--hh-mm-ss") + ".xlsx";
using (ExcelPackage pack = new ExcelPackage())
{
ExcelWorksheet ws = pack.Workbook.Worksheets.Add(filename);
//Add second sheet to put Validations into
ExcelWorksheet productVal = pack.Workbook.Worksheets.Add("ProductValidations");
// Hide Validation Sheet
productVal.Hidden = OfficeOpenXml.eWorkSheetHidden.Hidden;
// Load the data from the datatable
ws.Cells["A1"].LoadFromDataTable(table, true);
ws.Cells[ws.Dimension.Address].AutoFitColumns();
int columnCount = table.Columns.Count;
int rowCount = table.Rows.Count;
// Format Worksheet//
ws.Row(1).Style.Font.Bold = true;
List<string> deleteColumns = new List<string>() {
"CurrentTicketId",
};
List<string> dateColumns = new List<string>() {
"TicketDateTime",
"Updated",
"InvoiceDate"
};
ExcelRange r;
// Format Dates
for (int i = 1; i <= columnCount; i++)
{
// if cell header value matches a date column
if (dateColumns.Contains(ws.Cells[1, i].Value.ToString()))
{
r = ws.Cells[2, i, rowCount + 1, i];
r.AutoFitColumns();
r.Style.Numberformat.Format = #"mm/dd/yyyy hh:mm";
}
}
// Delete Columns
for (int i = 1; i <= columnCount; i++)
{
// if cell header value matches a delete column
if ((ws.Cells[1, i].Value != null) && deleteColumns.Contains(ws.Cells[1, i].Value.ToString()))
{
ws.DeleteColumn(i);
}
}
int col = 0;
int Prow = 0;
int valRow = 1;
// Data Validations //
// Iterate over the Rows and insert Validations
for (int i = 2; i-2 < rowCount; i++)
{
Prow = 0;
col++;
valRow++;
// Add Validations At this row in column 7 //
var ProdVal = ws.DataValidations.AddListValidation(ws.Cells[valRow, 7].Address);
ProdVal.ShowErrorMessage = true;
ProdVal.ErrorTitle = "Entry was invalid.";
ProdVal.Error = "Please choose options from the drop down only.";
var ticketEntity = ticketQueryable.Where(o => o.TTSTicketNumber == ws.Cells[i, 3].Value.ToString()).Single<CustCurrentTicketEntity>();
// Product Validation //
var prodIds = prodExtQueryable.Where(p => p.ZoneId == ticketEntity.ZoneId && p.TicketTypeId == ticketEntity.TicketTypeId);
if (ticketEntity != null)
{
var prodIdsList = new List<int>();
foreach (var prodId in prodIds)
{
prodIdsList.Add(prodId.ProductId);
}
var ProductList = ProductCache.Instance.AllProducts.Where(p => prodIdsList.Contains(p.ProductId)).Select(p => new SelectListItem() { Value = p.ProductId.ToString(), Text = p.Name });
//For Each Item in the list move the row forward and add that value to the Validation Sheet
foreach (var Result in ProductList)
{
Prow++;
var product = Result.Text;
productVal.Cells[Prow, col].Value = product;
}
// convert column name from a number to the Excel Letters i.e A, AC, BCE//
int dividend = col;
string columnName = String.Empty;
int modulo;
while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
// Pass back to sheeet as an Excel Formula to get around the 255 Char limit for Validations//
string productValidationExcelFormula = "ProductValidations!" + columnName + "1:" + columnName + Prow;
ProdVal.Formula.ExcelFormula = productValidationExcelFormula;
}
}
// Save File //
var fileStream = new MemoryStream(pack.GetAsByteArray());
string handle = Guid.NewGuid().ToString();
fileStream.Position = 0;
TempData[handle] = fileStream.ToArray();
// Note we are returning a filename as well as the handle
return new JsonResult()
{
Data = new { FileGuid = handle, FileName = filename }
};
}
}
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if (TempData[fileGuid] != null)
{
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else
{
//Log err
return new EmptyResult();
}
}
I have the following piece of code in a much larger OpenXML Excel reader. This reader grabs the information assigns to a dataset and then is displayed in a datagridview:
public static DataTable ExtractExcelSheetValuesToDataTable(string xlsxFilePath, string sheetName, int startingRow) {
DataTable dt = new DataTable();
using (SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(xlsxFilePath, true)) {
//Access the main Workbook part, which contains data
WorkbookPart workbookPart = myWorkbook.WorkbookPart;
WorksheetPart worksheetPart = null;
if (!string.IsNullOrEmpty(sheetName)) {
Sheet ss = workbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == sheetName).SingleOrDefault<Sheet>();
worksheetPart = (WorksheetPart)workbookPart.GetPartById(ss.Id);
} else {
worksheetPart = workbookPart.WorksheetParts.FirstOrDefault();
}
SharedStringTablePart stringTablePart = workbookPart.SharedStringTablePart;
if (worksheetPart != null) {
Row lastRow = worksheetPart.Worksheet.Descendants<Row>().LastOrDefault();
#region ColumnCreation
//Returns the columns - come back to this later - may be able to modify this to have
//A checkbox "Column names in first row"
Row firstRow = worksheetPart.Worksheet.Descendants<Row>().FirstOrDefault();
int columnInt = 0;
//if (firstRow != null)
//{
foreach (Cell c in firstRow.ChildElements)
{
string value = GetValue(c, stringTablePart);
dt.Columns.Add(columnInt + ": " + value);
columnInt++;
}
//}
#endregion
#region Create Rows
//if (lastRow != null)
//{
//lastRow.RowIndex;
for (int i = startingRow; i <= 150000; i++)
{
DataRow dr = dt.NewRow();
bool empty = true;
Row row = worksheetPart.Worksheet.Descendants<Row>().Where(r => i == r.RowIndex).FirstOrDefault();
int j = 0;
if (row != null)
{
foreach (Cell c in row.ChildElements)
{
//Get cell value
string value = GetValue(c, stringTablePart);
if (!string.IsNullOrEmpty(value) && value != "")
empty = false;
dr[j] = value;
j++;
if (j == dt.Columns.Count)
break;
}
if (empty)
break;
dt.Rows.Add(dr);
}
}
}
#endregion
}
// }
return dt;
}
public static string GetValue(Cell cell, SharedStringTablePart stringTablePart) {
if (cell.ChildElements.Count == 0) return null;
//get cell value
string value = cell.ElementAt(0).InnerText;//CellValue.InnerText;
//Look up real value from shared string table
if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString))
value = stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
return value;
}
public void GetSheetInfo(string fileName)
{
Sheets theSheets = null;
// Open file as read-only.
using (SpreadsheetDocument mySpreadsheet = SpreadsheetDocument.Open(fileName, false))
{
S sheets = mySpreadsheet.WorkbookPart.Workbook.Sheets;
WorkbookPart wbPart = mySpreadsheet.WorkbookPart;
theSheets = wbPart.Workbook.Sheets;
foreach (Sheet item in theSheets)
{
cmbSheetSelect.Items.Add(item.Name);
}
}
}
This has worked for basic spreadsheets but as I try to read more advanced ones I get a problem or two.
Firstly, I have a worksheet that has 5 columns:see here
However when I run my program it only returns the first 4 columns and not column E and all its data.
My second question would is it possible using that code (or a variation of it) to be able to specify the line I want the program to read as the datagridview column heading?
In case anyone needs this I found that changing:
Row firstRow = worksheetPart.Worksheet.Descendants<Row>().FirstOrDefault();
To
Row firstRow = worksheetPart.Worksheet.Descendants<Row>().ElementAtOrDefault(columnIndex)
Worked. With columnIndex being a variable I can change based on the sheet selected.