I am trying to read data from Excel and store it into a DataTable using OpenXML. I want data in my DataTable as it is in Excel sheet but when there is a empty cell in Excel, it was not looking as expected.
Because code row.Descendants<Cell>().ElementAt(i) skips empty cells while reading data and in DataTable Rows and Columns are stored incorrectly. I resolved this issue using below code but when my excel has more than 26 columns, it is not working as expected and again data are stored in DataTable incorrectly.
(i.e., While reading data from AA, AB, AC columns)
Can someone help me to rewrite this code to handle this issue when there is more than 26 columns.
private static int CellReferenceToIndex(Cell cell)
{
int index = 0;
string reference = cell.CellReference.ToString().ToUpper();
foreach (char ch in reference)
{
if (Char.IsLetter(ch))
{
int value = (int)ch - (int)'A';
index = (index == 0) ? value : ((index + 1) * 26) + value;
}
else
{
return index;
}
}
return index;
}
You can use example below (taken from here and improved by few validations):
public static int GetColumnIndex(this Cell cell)
{
string columnName = string.Empty;
if (cell != null)
{
string cellReference = cell.CellReference?.ToString();
if (!string.IsNullOrEmpty(cellReference))
// Using `Regex` to "pull out" only letters from cell reference
// (leave only "AB" column name from "AB123" cell reference)
columnName = Regex.Match(cellReference, #"[A-Z]{1,3}").Value;
}
// Column name validations (not null, not empty and contains only UPPERCASED letters)
// *uppercasing may be done manually with columnName.ToUpper()
if (string.IsNullOrEmpty(columnName))
throw new ArgumentException("Column name was not defined.", nameof(columnName));
else if (!Regex.IsMatch(columnName, #"^[A-Z]{1,3}$"))
throw new ArgumentException("Column name is not valid.", nameof(columnName));
int index = 0;
int pow = 1;
// A - 1 iteration, AA - 2 iterations, AAA - 3 iterations.
// On each iteration pow value multiplies by 26
// Letter number (in alphabet) + 1 multiplied by pow value
for (int i = columnName.Length - 1; i >= 0; i--)
{
index += (columnName[i] - 'A' + 1) * pow;
pow *= 26;
}
// Index couldn't be greater than 16384
if (index >= 16384)
throw new IndexOutOfRangeException("Index of provided column name (" + index + ") exceeds max range (16384).");
return index;
}
All exception throws you can replace with return -1 and some kind of Log("...") if you have logging. Otherwise you may not be sure what's problem happened and why was returned -1.
Usage is obvious:
var cells = row.Descendants<Cell>();
foreach (Cell cell in cells)
{
int columnIndex = cell.GetColumnIndex();
// Do what you want with that
}
EDIT.
I'm not sure what you're trying to achieve. And what you mean here:
Because code row.Descendants<Cell>().ElementAt(i) skips empty cells...
I didn't see that. Look at example below:
Random ElementAt in range between 0 and Descendants<Cell>().Count() works too and shows both empty and non-empty cells:
Related
From my C# application, I am using WorkSheet.UsedRange to find the range of data in a WorkSheet.
I need to check if a specific cell C13 belongs to UsedRange or not; and if it belongs, what is its Row Number & Column Number within the Range.
Example, if the Range starts from Row 10 and Col B, then C13 is Row 4 and Col 2 within the Range.
I found a similar question here:
Checking if selected cell is in specific range
The solution given there is Intersect method.
But in C#, Excel.ApplicationClass's Intersect method takes a very high number of parameters, and while I can pass Missing.Value to so many parameters, I want to know if there is an alternate way other than using Intersect method.
Thanks.
Can this work?
private static string GetRelativeAddress(Range cell, Range range)
{
int startRow = range.Row;
int startColumn = range.Column;
int endRow = range.Row + range.Rows.Count - 1;
int endColumn = range.Column + range.Columns.Count - 1;
if (cell.Row >= startRow && cell.Row <= endRow &&
cell.Column >= startColumn && cell.Column <= endColumn)
{
return $"R{cell.Row - startRow + 1}C{cell.Column - startColumn + 1}";
}
return String.Empty;
}
I need to check if excel cell is empty, null or whitespace (in case there is some string in it). This code should work, but it takes every cell (empty or not) and puts it in rowArray (i guarantee there shouldnt be more than 20 values in this:
object[] rowArray = new object[20];
for (int i = 4; i < 38; i++)
{
excelRange = (Excel.Range)excelWorkSheet.Cells[i, 2];
if (!string.IsNullOrEmpty(excelRange.Text.ToString()))
{
int j = i - 4;
rowArray[j]= excelRange.Text.ToString();
}
}
If you want to check with NULL and whitespace in the string then you can use String.IsNullOrWhiteSpace which indicates
whether a specified string is null, empty, or consists only of white-space characters.
if (!string.IsNullOrWhiteSpace(excelRange.Text.ToString()))
{
int j = i - 4;
rowArray[j]= excelRange.Text.ToString();
}
I am getting error "Not enough storage is available to complete this operation" during writing to cell in excel using c#.
I know that excel sheet not allows us to write more than 32,767 characters per cell.
I am writing 32,000 characters per cell.
It allows me to write first 3 cell in 1st columns but for the 4th, it throws the above error. what is the reason of that? any idea?
TOTAL LENGTH OF xml_txt_length is 2,65,000.
I have marked code by ******* in comments below where i am getting error.
my code is below:
if (xml_txt_length > 32000) // checking length of text is more than 32,000. if yes than need to split it to write in cell
{
int counter = 0; // used to multiply to 32,000
while (true)
{
if (counter == 0)
{
cell_string = xmlText.Substring(0, 32000);
xml_string += cell_string;
// writing to cell for column1
oSheet3.Cells[counter + 1, 1] = cell_string;
}
else
{
// if taken 32,000 characters exceeds the end length of string then go into this condition and take actual final position.
if ((32000 * counter) + 32000 >= xml_txt_length)
{
// below substring taking start position and up to end character of string instead of putting directly last 32,000th character position
cell_string = xmlText.Substring(32000 * counter, Convert.ToInt32(xml_txt_length - (32000 * counter)));
xml_string += cell_string;
//writing to cell for column 1
oSheet3.Cells[counter + 1, 1] = cell_string;
break;
}
else
{
// taking the start and upto 32,000 characters from string
cell_string = xmlText.Substring(32000 * counter, 32000);
xml_string += cell_string;
// writing to cell for column 1
// ********************************************************
// **** HERE I AM GETTING ERROR FOR CELL 4 IN COLUMN 1 ****
oSheet3.Cells[counter + 1, 1] = cell_string;
cell_string = string.Empty;
}
}
if (counter >= Math.Floor(xml_txt_length / 32000))
break;
counter++;
}
}
else
oSheet3.Cells[1, 1] = xmlText;
I wish to be able to instantiate my Cell class while naming the cell instance with such name as "A", "B", "C", etc. just like in an Excel spreadsheet.
I have my Cell class like so:
public class Cell {
public Cell(Range nativeCell) {
NativeCell = nativeCell;
}
public Range NativeCell { get; private set; }
}
And my Sheet class:
public class Sheet {
private IDictionary<string, Cell> _cells;
public Sheet(Worksheet nativeSheet) {
NativeSheet = nativeSheet;
_cells = new Dictionary<string, Cell>();
for (int rowIndex = 1; rowIndex <= NativeSheet.Rows.Count; ++rowIndex)
for (int colIndex = 1; colIndex <= NativeSheet.Columns.Count; ++colIndex) {
ICell newCell = new Cell(NativeSheet.Cells(rowIndex, colIndex));
newCell.Name = ?? // This name should look like "A1", "B1", "AA3", "CB20", etc.
Cells.Add(newCell.Name, newCell);
}
}
public IDictionary<string, Cell> Cells {
get {
return _cells;
}
}
public Worksheet NativeSheet { get; private set; }
}
I would need to generate a name based on the alphabetic letters and double and triple them once I encounter the last alphabet letter 'Z'. The algorithm would have to generate the letters that I would concatenate with the rowIndex value that would result to this naming strategy such as Excel.
The letters would be:
A, B, C, D...Z, AA, AB, AC...AZ, BA, BB, BC...BZ, CA...XAA, XAB, XAC...
While we clearly know that colIndex value 1 will definitely designate column "A", value 2 = "B", value 3 = "C", etc.
My problem is particularly when we double the letters.
Do you have any idea on how I could achieve this in the simplest possible form?
Thanks! =)
Here is this.
Translate a column index into an Excel Column Name
Shouldn't be to hard to make it recursive and give you exactly what you need. I hope this helps.
This function will do it for you. It is in VB.NET but I trust you'll be able to port it to C# if need be.
I have updated the answer with the C# version of the function.
VB.NET
''' <summary>Returns the Excel-style name of the column from the column index.</summary>
''' <param name="colIndex">The column index.</param>
Function GetColumnName(ByVal colIndex As Integer) As String
If colIndex < 1 Then Throw New ArgumentException("Column number must be greater or equal to 1.")
Dim result As New List(Of String)
'letter codes start at Chr(65)'
Do While colIndex > 0
'reduce the column number by 1 else the 26th column (Z) will become 0 (#) '
'add 65 to the result and find the Chr() value. '
'insert the character at position 0 of the character list '
'integer divide by 26 to remove the column from the stack and repeat till '
'there are no columns in the stack. '
result.Insert(0, Chr(65 + CInt((colIndex - 1) Mod 26)))
colIndex = (colIndex - 1) \ 26
Loop
Return String.Join("", result.ToArray)
End Function
C#
/// <summary>Returns the Excel-style name of the column from the column index.</summary>
/// <param name="colIndex">The column index.</param>
static string GetColumnName(int colIndex)
{
if (colIndex < 1)
throw new ArgumentException("Column number must be greater or equal to 1.");
var result = new List<char>();
//letter codes start at Chr(65)'
while (colIndex > 0)
{
//reduce the column number by 1 else the 26th column (Z) will become 0 (#)
//add 65 to the result and find the Chr() value.
//insert the character at position 0 of the char list
//integer divide the column index by 26 to remove the last calculated column
//from the stack and repeat till there are no columns in the stack.
result.Insert(0, Microsoft.VisualBasic.Strings.Chr(65 + Convert.ToInt32((colIndex - 1) % 26)));
colIndex = (int)((colIndex-1)/ 26);
}
return new string(result.ToArray());
}
I tested this up to column index 1000 and it worked without fail. I hope you find it useful.
Is there an Array or Data Type (eg <List>) that supports the arranging of values (specifically strings) into a triangular shape like this...
1
2 3
4 5 6
In my above example, each of these numbers holds a value of 2 characters of a string. If my string was "Hello I am a cat", it would be split up into "He ll oI am ac at".
I want my program to store these values in an array similar to the triangle above - how could this be achieved? Is there a way to have the values moved around (e.g. 1=2 2=3 3=4).
What is wrong with just storing it in an array? The offsets are all defined (i.e., row #1 at 0, row #2 at 1, row #3 at 3 etc.) in a typical arithmetic sequence.
So, the array you'll have: [1, 2, 3, 4, 5, 6, ...]
Row #1 offset: 0; // Base
Row #2 offset: (1) = 1;
Row #3 offset: (1 + 2) = 3;
Row #4 offset: (1 + 2 + 3) = 6;
Row #5 offset: (1 + 2 + 3 + 4) = 10;
And so on and so forth. The offset for row n is adding one through to n-1
The benefit is that, in building up this triangle, you can just keep "Add"-ing to the end of the array. If you split your string by some delimiter, the output of string.Split (an array) may even already be what you need!
To move/shift the elements, just add one element to the front! The offsets are all the same, but each element will be shifted to the next position!
The best physical data type to store this would be a list or an array. You can, however, quite easily write a class to abstract away calculating offsets etc.
public class Triangle<T> {
private List<T> list = new List<T>();
private int rows;
public int Rows { get { return rows; } }
private void CalculateRows() {
rows = (int)Math.Ceiling(Math.Sqrt(list.Count * 2 + 0.25) - 0.5);
}
public void Add(T item) {
list.Add(item);
CalculateRows();
}
public T this[int column, int row] {
get {
if (row < 0 || row > Rows - 1) {
throw new ArgumentOutOfRangeException("row");
}
if (column < 0 || column > row) {
throw new ArgumentOutOfRangeException("column");
}
int rowOffset = row * (row + 1) / 2;
return list[rowOffset + column];
}
}
public int ColumnsForRow(int row) {
if (row < 0 || row > Rows - 1) {
throw new ArgumentOutOfRangeException("row");
}
if (row < Rows - 1) {
return row + 1;
}
return list.Count - (row * (row + 1) / 2);
}
public void ShiftLeft() {
list.Add(list[0]);
list.RemoveAt(0);
}
public void ShiftRight() {
list.Insert(0, list[list.Count - 1]);
list.RemoveAt(list.Count - 1);
}
}