Iterate Through rows in Excel - c#

I am attempting to get all the numbers from the cells in column A from an excel spreadsheet, but I am only getting my header line returned. What am I doing wrong?
static void Main(string[] args)
{
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\Documents\ANIs.xlsx");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("$A1");
object rawValue = a1.Value;
string Text = a1.Text;
foreach (Range item in a1.Cells)
{
Console.WriteLine("{1}", rawValue, Text);
}
Console.Read();
}

You are getting only the cell A1, you can get all the cells in column A by using :
Range firstCol = workSheet.Range("A:A");

Because your worksheet.get_Range is only returning the one cell. You need to specify the lower-right cell also to get multiple cells, like this:
worksheet.get_Range("$A1", "$D9");
Check out MSDN

Related

Find linked formula values from worksheets and replace with actual cell value

In a OOXML spreadsheet .xlsx you can through a linking formula fecth values from another spreadsheet and have them in your worksheet as values, that will always be updated when those values in another spreadsheet are updated.
I am using Open Xml SDK and I basically want to do what this does: https://www.e-iceblue.com/Tutorials/Spire.XLS/Spire.XLS-Program-Guide/Formula/Remove-Formulas-from-Cells-but-Keep-Values-in-Excel-in-C.html
How do I:
Find a value that has formula linking value to a cell in another spreadsheet
Replace the formula value with the actual cell value
Do this foreach cell in each worksheet in a spreadsheet
I have tried this so far: https://learn.microsoft.com/en-us/office/open-xml/how-to-retrieve-the-values-of-cells-in-a-spreadsheet
But I am recieving a NullRefereceneException each time the cell does not contain a formula or just any value. I have tried try-catch and several other ways to escape this exception, but it is not working.
But back to the challenge as outlined above; can anyone help me out?
Basic stuff such as using SOME DIRECTIVE, foreach loop, Open(), Save() I know how to do.
This worked for me:
public void Remove_CellReferences(string filepath)
{
using (SpreadsheetDocument spreadsheet = SpreadsheetDocument.Open(filepath, true))
{
// Delete all cell references in worksheet
List<WorksheetPart> worksheetparts = spreadsheet.WorkbookPart.WorksheetParts.ToList();
foreach (WorksheetPart part in worksheetparts)
{
Worksheet worksheet = part.Worksheet;
var rows = worksheet.GetFirstChild<SheetData>().Elements<Row>(); // Find all rows
foreach (var row in rows)
{
var cells = row.Elements<Cell>();
foreach (Cell cell in cells)
{
if (cell.CellFormula != null)
{
string formula = cell.CellFormula.InnerText;
if (formula.Length > 0)
{
string hit = formula.Substring(0, 1); // Transfer first 1 characters to string
if (hit == "[")
{
CellValue cellvalue = cell.CellValue; // Save current cell value
cell.CellFormula = null; // Remove RTD formula
// If cellvalue does not have a real value
if (cellvalue.Text == "#N/A")
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue("Invalid data removed");
}
else
{
cell.CellValue = cellvalue; // Insert saved cell value
}
}
}
}
}
}
}
// Delete all external link references
List<ExternalWorkbookPart> extwbParts = spreadsheet.WorkbookPart.ExternalWorkbookParts.ToList();
if (extwbParts.Count > 0)
{
foreach (ExternalWorkbookPart extpart in extwbParts)
{
var elements = extpart.ExternalLink.ChildElements.ToList();
foreach (var element in elements)
{
if (element.LocalName == "externalBook")
{
spreadsheet.WorkbookPart.DeletePart(extpart);
}
}
}
}
// Delete calculation chain
CalculationChainPart calc = spreadsheet.WorkbookPart.CalculationChainPart;
spreadsheet.WorkbookPart.DeletePart(calc);
}
}

Populate an excel sheet column with directory files using OpenXML

I have an excel workbook with two WorkSheets, "Tourist Information" and "Documents". In the "Documents" sheet, I have to fill the "Scanned Document" column with all the file names found in a directory. I don't have to fill any other column except Scanned Document column. I am unable to fill the excel sheet with file names which start from cell reference C3. Could you please help me to populate the column with file names.
"Documents" Sheet is:
My code is:
//Open the Excel file in Read Mode using OpenXML
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(#"C:\TouristRecord.xlsx", true))
{
WorksheetPart documents = GetWorksheetPart(doc.WorkbookPart, "Documents");
Worksheet documentsWorksheet = documents.Worksheet;
IEnumerable<Row> documentsRows = documentsWorksheet.GetFirstChild<SheetData>().Descendants<Row>();
//Loop through the Worksheet rows
foreach (var files in Directory.GetFiles(#"C:\DocumentsFolder"))
{
foreach (Row row in documentsRows)
{
// I am unable to write logic to update the excel sheet value here.
}
}
doc.Save();
}
And GetWorksheetPart method is :
public WorksheetPart GetWorksheetPart(WorkbookPart workbookPart, string sheetName)
{
string relId = workbookPart.Workbook.Descendants<Sheet>().First(s => sheetName.Equals(s.Name)).Id;
return (WorksheetPart)workbookPart.GetPartById(relId);
}
To add a cell to C3 you will need to create a new Cell object, assign it a cell reference of C3, set its value and then add it to the Row that represents row 3 on the sheet. We can wrap that logic into a method like this:
private void AddCellToRow(Row row, string value, string cellReference)
{
//the cell might already exist, if it does we should use it.
Cell cell = row.Descendants<Cell>().FirstOrDefault(c => c.CellReference == cellReference);
if (cell == null)
{
cell = new Cell();
cell.CellReference = cellReference;
}
cell.CellValue = new CellValue(value);
cell.DataType = CellValues.String;
row.Append(cell);
}
If we assume that the current worksheet has a contiguous set of rows then the logic of what to write is pretty straightforward:
Iterate each row in the document
Check if the row index is greater than 2 (as you want to start writing from 3 onwards). If it is:
Grab the 3rd Cell or create it if it doesn't exist.
add the nth element of your file list to the Cell.
Increment n
Iterate the remaining files in your file list (as you may have more files than rows in the original document). For each one:
add a new Row
add a new Cell to the Row with the file name as the cell's value.
Putting that into code you end up with:
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(#"C:\TouristRecord.xlsx", true))
{
WorksheetPart documents = GetWorksheetPart(doc.WorkbookPart, "Documents");
//get the she sheetdata as that's where we need to add rows
SheetData sheetData = documents.Worksheet.GetFirstChild<SheetData>();
IEnumerable<Row> documentsRows = sheetData.Descendants<Row>();
//get all of the files into an array
var filenames = Directory.GetFiles(#"C:\DocumentsFolder");
if (filenames.Length > 0)
{
int currentFileIndex = 0;
// keep the row index in case the rowindex property is null anywhere
// the spec allows for it to be null, in which case the row
// index is one more than the previous row (or 1 if this is the first row)
uint currentRowIndex = 1;
foreach (var documentRow in documentsRows)
{
if (documentRow.RowIndex.HasValue)
{
currentRowIndex = documentRow.RowIndex.Value;
}
else
{
currentRowIndex++;
}
if (currentRowIndex <= 2)
{
//this is row 1 or 2 so we can ignore it
continue;
}
AddCellToRow(documentRow, filenames[currentFileIndex], "C" + currentRowIndex);
currentFileIndex++;
if (filenames.Length <= currentFileIndex)
{
// there are no more files so we can stop
break;
}
}
// now output any files we haven't already output. These will need a new row as there isn't one
// in the document as yet.
for (int i = currentFileIndex; i < filenames.Length; i++)
{
//there are more files than there were rows in the directory, add more rows
Row row = new Row();
currentRowIndex++;
row.RowIndex = currentRowIndex;
AddCellToRow(row, filenames[i], "C" + currentRowIndex);
sheetData.Append(row);
}
}
}
There's an assumption above that the current worksheet has a contiguous set of rows. This might not always be true as the spec allows for empty rows to not be written to the XML. In that case, you could end up with gaps in your output. Imagine the original file has data in rows 1, 2 and 5; in that scenario the foreach would cause you to skip writing to rows 3 and 4. This can be solved by checking the currentRowIndex inside the loop and adding a new Row for any gaps that may occur. I haven't added that code as it's a complication that detracts from the fundamentals of the answer.

Creating an Excel report based on template

I have an Excel template where the format for table header, data section and table footer are specified. These sections might have images, merged cells, etc.
The cells where data needs to be mapped are named cells.
I tried using the EPPlus library for generating the report based on the Excel template.
I used the following snippet to copy the range of cells
var worksheet = destExcelPackage.Workbook.Worksheets.Add("Sheet 1");
var sourceRange = sourceExcelPackage.Workbook.Worksheets.First().Cells["B6:P11"];
sourceRange.Copy(worksheet.Cells["A1"]);
But this didn't make the column widths equal to the source. I had to set the column width to the source width as
var startCol = sourceRange.Start.Column;
var endCol = sourceRange.End.Column;
for (int j = startCol, destCol = 1; j <= endCol; j++, destCol++)
{
worksheet.Column(destCol).Width = sourceExcelPackage.Workbook.Worksheets.First().Column(j).Width;
}
I have the following questions:
Is there a better way to set the column width equal to the source?
The copied cells had an image, but it didn't get copy to the new sheet. How to get the image copied?
How to identify the named cells in the Excel sheet so that I can set value to the cell from some data source?
I have found a way to achieve points 2 and 3 above.
It appears that if the picture is named, it is easy to read it.
So for #2
private static void CopyImage(ExcelPackage sourceExcelPackage, ExcelWorksheet destWorksheet)
{
var image = GetImage("Pic01", sourceExcelPackage);
ExcelPicture pic = destWorksheet.Drawings.AddPicture("Pic01", image.Image);
pic.From.Column = image.From.Column;
pic.From.Row = image.From.Row;
pic.To.Column = image.To.Column;
pic.To.Row = image.To.Row;
var destRow = 1;
var destCol = 1;
pic.SetPosition(destRow, Pixel2MTU(image.From.RowOff), destCol, Pixel2MTU(image.From.ColumnOff));
pic.EditAs = eEditAs.TwoCell;
pic.AdjustPositionAndSize();
}
private static ExcelPicture GetImage(string pictureName, ExcelPackage excelFile)
{
var sheet = excelFile.Workbook.Worksheets.First();
var pic = sheet.Drawings[pictureName] as ExcelPicture;
return pic;
}
private static int Pixel2MTU(int fromRowOff)
{
return fromRowOff / ExcelDrawing.EMU_PER_PIXEL;
}
And for #3
var cell = sourceExcelPackage.Workbook.Names?.Where(item => item.Name==headerName).FirstOrDefault();
Will return the cell which is named as headerName.

How do I copy an excel range to Text (.txt) file, unformatted so that all cells form one single string and are not separate items? C#

I get a range of all "Good" cells in Column B of my excel sheet, the find the corresponding cells in the "D" Column and create a range of those cells. I want to convert all those cells to one single string and paste that to my notepad file, so that there are no spaces between each cell's strings and they are displayed on a single line.
Right now my code reads each cell item as its own entity and prints them on separate lines. I want to be able to iterate over one single string, so I would like them to all form one whole string.
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
Microsoft.Office.Interop.Excel.Workbook excelWorkbook = excelApp.Workbooks.Open(comboBox2.Text);
Excel.Worksheet xlWorkSheet =
(Excel.Worksheet)excelWorkbook.Sheets[sheetSpaces];
excelApp.Visible = false;
excelApp.ScreenUpdating = false;
excelApp.DisplayAlerts = false;
Excel.Range last = xlWorkSheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
int lastUsedRow = last.Row;
string lastCell = "B" + lastUsedRow.ToString();
Excel.Range range = xlWorkSheet.get_Range("B1", lastCell);
foreach (Excel.Range item in range.Cells)
{
string text = (string)item.Text;
if (text == "Good")
{
//get address of all Good items
string textx = (string)item.Address;
//change address of Good items to corresponing address in D column
string textxcorrect = textx.Replace("B", "D");
//get rid of "$" for address
var cellAddress = textxcorrect.Replace("$", "");
//create range for addresses with the new D column addresses
Excel.Range xlRng = xlWorkSheet.get_Range(cellAddress, Type.Missing);
string fileLocation = #"C:\\Users\\npinto\\Desktop\\hopethisworks.txt";
foreach (Excel.Range item2 in xlRng)
{
xlRng.Copy();
File.WriteAllText(fileLocation, Clipboard.GetText());
}
string readText = System.IO.File.ReadAllText(fileLocation);
Console.WriteLine(readText);
I have updated my answer based on your original question - if I now understand correctly the cell in row B will contain the word "Good" - the cell in the same row in column D will contain a single Cell reference - e.g A4 & you want to append that data.
NOTE - if the column D cell contains "+A4" - then the text returned will be what you require to be appended - so just concatenate nextAddress rather than get xlRng2.
How about this - depending on the size of the text you may want to use a StringBuilder rather than string - but with small amounts of data there wont be any significant difference.
string RequiredOutputString = String.Empty;
foreach (Excel.Range item in range.Cells)
{
string text = (string)item.Text;
if (text == "Good")
{
//get address of all Good items
string textx = (string)item.Address;
//change address of Good items to corresponing address in D column
var cellAddress = textx.Replace("$B", "D");
// get a reference to cell in column D
Range xlRng = curWorkSheet.get_Range(cellAddress, Type.Missing);
// get the cell address in row D cell
string nextAddr = xlRng.Text;
// get a reference to the cell point to from Row D
Range xlRng2 = curWorkSheet.get_Range(nextAddr, Type.Missing);
// append that cell contents
RequiredOutputString += xlRng2.Text.Trim();
}
}
string fileLocation = #"C:\\Users\\npinto\\Desktop\\hopethisworks.txt";
File.WriteAllText(fileLocation, RequiredOutputString);

How to create a Excel Row and insert it in Excel Range?

I have an excel worksheet as below (just an example)
I have created a Microsoft.Office.Interop.Excel.Range object referring the range from Item1 to Category5 (selected cells in the above image).
Now I want to create a new Row (Market1, Market2, Market3, Market4, Market5) and add it below the range ie., below Category row.
I am using the Microsoft.Office.Interop.Excelclasses for the first time.
Can someone help me in figuring out how to create and add a new row to an existing range object.
Here is the code which I have written -
public class Class1
{
static void Main(string[] args)
{
Application appExcel = new Application();
WorkBook workBook = appExcel.Workbooks.Open(#"C:\Data.xlsx", true, false);
workSheet = (Worksheet)workBook.Sheets["Export"];
Range usedRange = workSheet.UsedRange;
Range itemCatRange = GetSection(usedRange, "Item1","Group1"); //Gets the selected range as shown in pic
//Here I want to create a new row of cells and add the newly created row at the end of the above range "itemCatRange"
}
private static Range GetSection(Range usedRange, string startHeader, string endHeader)
{
string str = string.Empty;
string end = String.Empty;
Range algAlmRange;
foreach (Range row in usedRange.Rows)
{
object firstColumnValue = row.Columns.Value2[1, 1];
if (firstColumnValue != null)
{
if (firstColumnValue.ToString() == startHeader)
{
str = row.Address;
}
else if (firstColumnValue.ToString() == endHeader)
{
end = row.Address;
}
}
}
algAlmRange = workSheet.Range[str, end];
return algAlmRange;
}
}
Something like
Range itemCatRange = GetSection(usedRange, "Item1","Group1");
Range lastRow = itemCatRange[itemCatRange.Rows, 1].EntireRow;
lastRow.Insert(XlDirection.xlDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
You may have to go down another row, or use xlUp. I haven't actually tried this.

Categories