Using Data tab in MS Excel, I am able to perform "Text to Columns". how to do that using c# code?
Based on this example:
var names = new[]
{
"Brady, Tom",
"Manning, Peyton",
"Peterson, Adrian",
"Lewis, Ray",
"Reed, Ed",
"Polamalu, Troy",
"Johnson, Andre",
"Revis, Darrelle",
"Brees, Drew",
"Peppers, Julius"
};
// Write names to a file
using (var excelPackage = new ExcelPackage(new FileInfo(#"d:\tmp\TextToColumns.xlsx")))
{
var worksheet = excelPackage.Workbook.Worksheets.Add("TextToColumns");
for (int i = 1; i < names.Length; i++)
{
worksheet.Cells[String.Format("A{0}", i)].Value = names[i - 1];
}
excelPackage.Save();
}
// Split names
using (var excelPackage = new ExcelPackage(new FileInfo(#"d:\tmp\TextToColumns.xlsx")))
{
var worksheet = excelPackage.Workbook.Worksheets.First();
foreach (var cell in worksheet.Cells)
{
var splittedValues = ((String)cell.Value).Split(',');
// Write last name to the first column
cell.Value = splittedValues[0];
// Write first name to the next one column
worksheet.Cells[cell.Start.Row, cell.Start.Column + 1].Value = splittedValues[1].TrimStart();
}
excelPackage.Save();
}
Related
Using the below link, I am able to loop through the rows and able to get exact cell, but unable to set its color. Please see the code below.
How do I dynamically set the forecolour of my column data based on the value when exporting excel using EPPLUS?
var costStructureReport = new
{
CurrentQuotation = rptCostStructure.GetCostStructureReport()
};
var reportEngine = new ReportEngine();
string fileName = reportEngine.ProcessReport(ReportNames.ProjectDownload_Template, reportname + ".xlsx", costStructureReport);
var ep = new ExcelPackage(new FileInfo(fileName));
var sheet1 = ep.Workbook.Worksheets["SPR_ProjectDownload"];
var row = sheet1.Dimension.End.Row;
for(int i=0;i< costStructureReport.CurrentQuotation.Count;i++)
{
if (costStructureReport.CurrentQuotation[i].MaterialCost_ByUser)
{
sheet1.Cells[i+2, 2].Style.Font.Color.SetColor(System.Drawing.Color.Red);
sheet1.Cells[i + 2, 12].Style.Font.Color.SetColor(System.Drawing.Color.Red);
sheet1.Cells[i + 2, 12].Style.Font.Bold = true;
}
}
You can Change the Text Color using
[RangeObject].Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
And Font Colour using
[RangeObject].Interior.Color =System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
I want to search string in excel file, I used to use Microsoft.Office.Interop.Excel in my program and It works perfectly. I use this code below:
findRange1 = range1.Find("apple", LookAt: Excel.XlLookAt.xlWhole);
However, I faced a problem using interop excel in the server side. And I change my program using OpenXML. I want to do the searching method as well using OpenXML. how to do it in OpenXML?
1) Import your excel document .xlsx or xls to SpreadsheetDocument of OpenXMl.
public WorkbookPart ImportExcel()
{
try
{
string path = #"your path to excel document";
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
MemoryStream m_ms = new MemoryStream();
fs.CopyTo(m_ms);
SpreadsheetDocument m_Doc = SpreadsheetDocument.Open(m_ms, false);
return m_Doc.WorkbookPart;
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message + ex.StackTrace);
}
return null;
}
2) Create method to get index in the form of [row, column] by using specific search criteria.
public string GetIndexBySearch(string search)
{
WorkbookPart workbookPart = ImportExcel();
var sheets = workbookPart.Workbook.Descendants<Sheet>();
Sheet sheet = sheets.Where(x => x.Name.Value == "you sheet name in excel document").FirstOrDefault();
string index = string.Empty;
if (sheet != null)
{
var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
var rows = worksheetPart.Worksheet.Descendants<Row>().ToList();
// Remove the header row
rows.RemoveAt(0);
foreach (var row in rows)
{
var cellss = row.Elements<Cell>().ToList();
foreach (var cell in cellss)
{
var value = cell.InnerText;
var stringTable = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
value = stringTable.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
bool isFound = value.Trim().ToLower().Contains(search.Trim().ToLower());
if (isFound)
{
index = $"[{row.RowIndex}, {GetColumnIndex(cell.CellReference)}]";
return index;
}
}
}
}
return index;
}
3) Finally this method gives you column index by passing column name.
private static int? GetColumnIndex(string cellReference)
{
if (string.IsNullOrEmpty(cellReference))
{
return null;
}
string columnReference = Regex.Replace(cellReference.ToUpper(), #"[\d]", string.Empty);
int columnNumber = -1;
int mulitplier = 1;
foreach (char c in columnReference.ToCharArray().Reverse())
{
columnNumber += mulitplier * ((int)c - 64);
mulitplier = mulitplier * 26;
}
return columnNumber + 1;
}
4) Considering all above 3 methods are in same class called MyClass. Then you will use GetIndexBySearch like
MyClass c = new MyClass();
string index = c.GetIndexBySearch("AFL");
Output:
I am using the following code to read Excel data from the clipboard into a C# data table. The code is relatively unchanged as found from this answer to this question. I then add the data table as a data source to a DataGridView control for manipulation.
However, in my Excel data, I have blank/empty cells that I need to preserve, which this code does not do (blank cells are skipped over, effectively compressing each row leaving no empty space; the empty cells are missing from the Excel XML). How could I preserve empty cells when transferring to the data table?
Method:
private DataTable ParseClipboardData(bool blnFirstRowHasHeader)
{
var clipboard = Clipboard.GetDataObject();
if (!clipboard.GetDataPresent("XML Spreadsheet")) return null;
StreamReader streamReader = new StreamReader((MemoryStream)clipboard.GetData("XML Spreadsheet"));
streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(streamReader.ReadToEnd());
XNamespace ssNs = "urn:schemas-microsoft-com:office:spreadsheet";
DataTable dt = new DataTable();
var linqRows = xmlDocument.fwToXDocument().Descendants(ssNs + "Row").ToList<XElement>();
for (int x = 0; x < linqRows.Max(a => a.Descendants(ssNs + "Cell").Count()); x++)
dt.Columns.Add("Column " + x.ToString());
int intCol = 0;
DataRow currentRow;
linqRows.ForEach(rowElement =>
{
intCol = 0;
currentRow = dt.Rows.Add();
rowElement.Descendants(ssNs + "Cell")
.ToList<XElement>()
.ForEach(cell => currentRow[intCol++] = cell.Value);
});
if (blnFirstRowHasHeader)
{
int x = 0;
foreach (DataColumn dcCurrent in dt.Columns)
dcCurrent.ColumnName = dt.Rows[0][x++].ToString();
dt.Rows.RemoveAt(0);
}
return dt;
}
Extension method:
public static XDocument fwToXDocument(this XmlDocument xmlDocument)
{
using (XmlNodeReader xmlNodeReader = new XmlNodeReader(xmlDocument))
{
xmlNodeReader.MoveToContent();
var doc = XDocument.Load(xmlNodeReader);
return doc;
}
}
Contrived example to illustrate: (Excel 2015)
Range in Excel, copied to clipboard
DataGridView on Winform, with data table as data source
The cell's xml will have an Index attribute if the previous cell was missing (had an empty value). You can update your code to check if the column index has changed before copying it to your data table row.
linqRows.ForEach(rowElement =>
{
intCol = 0;
currentRow = dt.Rows.Add();
rowElement.Descendants(ssNs + "Cell")
.ToList<XElement>()
.ForEach(cell =>
{
int cellIndex = 0;
XAttribute indexAttribute = cell.Attribute(ssNs + "Index");
if (indexAttribute != null)
{
Int32.TryParse(indexAttribute.Value, out cellIndex);
intCol = cellIndex - 1;
}
currentRow[intCol] = cell.Value;
intCol++;
});
});
I am creating a mail merge document using OpenXML dll. I have a requirement to add a dynamic table to the word document. Currently I have been able to add the table # the end of the document but I need to add it some where in the middle of the page.
I have 4 pages in the word document and this table has to be added to the start of the 3rd page. I have been able to get the table. The only issue that I have is to add the table here.
The following is the code:
void createTemplate(string newFileName,string folderName,ArrayList mailMergeList,DataTable observations)
{
FileInfo newFile = new FileInfo(newFileName);
if (!IsFileLocked(newFile))
{
//declare and open a Word document object
WordprocessingDocument objWordDocx = WordprocessingDocument.Open(newFileName, true);
//get the main document section of the document
OpenXmlElement objMainDoc = objWordDocx.MainDocumentPart.Document;
//var wordDoc = new Microsoft.Office.Interop.Word.Document();
//Loop through merge fields
string FieldDelimiter = " MERGEFIELD ";
foreach (FieldCode field in objWordDocx.MainDocumentPart.RootElement.Descendants<FieldCode>())
{
var fieldNameStart = field.Text.LastIndexOf(FieldDelimiter, System.StringComparison.Ordinal);
String fieldname = field.Text.Substring(fieldNameStart + FieldDelimiter.Length).Trim();
fieldname = fieldname.Substring(0, fieldname.IndexOf(' '));
// fieldname
var fieldValue = "";
fieldValue = GetMergeValue(fieldname, mailMergeList);
// Go through all of the Run elements and replace the Text Elements Text Property
foreach (Run run in objWordDocx.MainDocumentPart.Document.Descendants<Run>())
{
foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldname + "»"))
{
if (fieldname.Equals("ObservationsTable"))
{
//observations
if (observations.Rows.Count > 0) //only if there is data in the Resi Obs NOI sheet we need to create a table
{
txtFromRun.Text = CreateTable(objWordDocx, newFileName, observations).ToString();
}
}
else
{
txtFromRun.Text = GetMergeValue(fieldname, mailMergeList);
}
}
}
}
//save this part
objWordDocx.MainDocumentPart.Document.Save();
//save and close the document
objWordDocx.Close();
}
}
I have been given a solution below but it is not feasible for me as I am not using Word.Interop dll.
Please guide.
Here's an open xml example. I created a dummy table:
var tab = new Table();
for (var z = 0; z < 2; z++)
{
var tr = new TableRow();
for (var j = 0; j < 2; j++)
{
var tc = new TableCell();
tc.Append(new Paragraph(new Run(new Text("i: " + z + " j:" + j))));
tr.Append(tc);
}
tab.Append(tr);
}
In my word.docx I have:
Some text
«Table»
some other text
And to loop over the merge fields:
WordprocessingDocument objWordDocx = WordprocessingDocument.Open(newFileName, true);
OpenXmlElement objMainDoc = objWordDocx.MainDocumentPart.Document;
foreach (var field in objMainDoc.Descendants<SimpleField>())
{
if (field.Instruction.Value.Trim().EndsWith("Table"))
{
var tabRun = new Run(tab);
field.Parent.ReplaceChild<SimpleField>(tabRun, field);
}
}
objWordDocx.MainDocumentPart.Document.Save();
objWordDocx.Close();
EDIT:
Version with FieldCode:
foreach (var field in objMainDoc.Descendants<FieldCode>())
{
if (field.InnerText.Trim().EndsWith("Table"))
{
var tabRun = new Run(tab);
var anc = field.Ancestors<Paragraph>().FirstOrDefault();
anc.RemoveAllChildren();
anc.Append(tabRun);
}
}
Note: this works for me as the only thing in my paragrah is the field code. If you have stuff in your paragraph which shouldn't be removed, modify the code.
In your document (wordDoc below) add a mergefield, "CustomTable" for example.
Object oMissing = System.Reflection.Missing.Value;
Object oTemplatePath = templatePath; // Path
var wordApp = new Microsoft.Office.Interop.Word.Application();
var wordDoc = new Microsoft.Office.Interop.Word.Document();
wordDoc = wordApp.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
foreach (Field field in wordDoc.Fields)
{
var fieldText = field.Code.Text;
var fieldName = fieldText.Substring(11).Split(new string[] { "\\" }, StringSplitOptions.None)[0].Trim();
field.Select();
if (fieldText.StartsWith(" MERGEFIELD"))
{
if (fieldName == "CustomTable")
{
var tab = wordDoc.Tables.Add(wordApp.Selection.Range, noOfColumns, noOfRows);
tab.Cell(1, 1).Range.Text = "Some text";
// ETC
}
}
}
I am currently using EPPlus project in order to manipulate some .xlsx files. The basic idea is that I have to create a new file from a given template.
But when I create the new file from a template, all calculated columns in the tables are messed up.
The code I am using is the following:
static void Main(string[] args)
{
const string templatePath = "template_worksheet.xlsx"; // the path of the template
const string resultPath = "result.xlsx"; // the path of our result
using (var pck = new ExcelPackage(new FileInfo(resultPath), new FileInfo(templatePath))) // creating a package with the given template, and our result as the new stream
{
// note that I am not doing any work ...
pck.Save(); // savin our work
}
}
For example for a .xlsx file (that have a table with 3 columns, the last one is just the sum of the others) the program creates a .xlsx file where the last column have the same value (which is correct only for the first row) in all rows.
The following images shows the result:
Now the questions are:
What is going on here ? Is my code wrong ?
How can I accomplish this task without that unexpected behavior ?
That definitely on to something there. I was able to reproduce it myself. It has to do with the Table you created. if you open your file and remove it using the "Convert To Range" option in the Table Tools tab the problem goes away.
I looked at the source code and it extracts the xml files at the zip level and didnt see any indication that it was actually messing with them - seemed to be a straight copy.
Very strange because if we create and save the xlsx file including a table from EPPlus the problem is not there. This works just fine:
[TestMethod]
public void Template_Copy_Test()
{
//http://stackoverflow.com/questions/28722945/epplus-with-a-template-is-not-working-as-expected
const string templatePath = "c:\\temp\\testtemplate.xlsx"; // the path of the template
const string resultPath = "c:\\temp\\result.xlsx"; // the path of our result
//Throw in some data
var dtdata = new DataTable("tblData");
dtdata.Columns.Add(new DataColumn("Col1", typeof(string)));
dtdata.Columns.Add(new DataColumn("Col2", typeof(int)));
dtdata.Columns.Add(new DataColumn("Col3", typeof(int)));
for (var i = 0; i < 20; i++)
{
var row = dtdata.NewRow();
row["Col1"] = "String Data " + i;
row["Col2"] = i * 10;
row["Col3"] = i * 100;
dtdata.Rows.Add(row);
}
var templateFile = new FileInfo(templatePath);
if (templateFile.Exists)
templateFile.Delete();
using (var pck = new ExcelPackage(templateFile))
{
var ws = pck.Workbook.Worksheets.Add("Data");
ws.Cells["A1"].LoadFromDataTable(dtdata, true);
for (var i = 2; i <= dtdata.Rows.Count + 1; i++)
ws.Cells[i, 4].Formula = String.Format("{0}*{1}", ExcelCellBase.GetAddress(i, 2), ExcelCellBase.GetAddress(i, 3));
ws.Tables.Add(ws.Cells[1, 1, dtdata.Rows.Count + 1, 4], "TestTable");
pck.Save();
}
using (var pck = new ExcelPackage(new FileInfo(resultPath), templateFile)) // creating a package with the given template, and our result as the new stream
{
// note that I am not doing any work ...
pck.Save(); // savin our work
}
}
BUT.....
If we open testtemplate.xlsx, remove the table, save/close the file, reopen, and reinsert the exact same table the problem shows up when you run this:
[TestMethod]
public void Template_Copy_Test2()
{
//http://stackoverflow.com/questions/28722945/epplus-with-a-template-is-not-working-as-expected
const string templatePath = "c:\\temp\\testtemplate.xlsx"; // the path of the template
const string resultPath = "c:\\temp\\result.xlsx"; // the path of our result
var templateFile = new FileInfo(templatePath);
using (var pck = new ExcelPackage(new FileInfo(resultPath), templateFile)) // creating a package with the given template, and our result as the new stream
{
// note that I am not doing any work ...
pck.Save(); // savin our work
}
}
It has to be something burried in their zip copy methods but I nothing jumped out at me.
But at least you can see about working around it.
Ernie
Try to use the following code. This code takes the formatting and other rules and add them as xml node to another file. Ernie described it really well here Importing excel file with all the conditional formatting rules to epplus The best part of the solution is that you can also import formatting along with your other rules. It should take you close to what you need.
//File with your rules, can be your template
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
//Other file where you want the rules
var existingFile2 = new FileInfo(#"c:\temp\temp2.xlsx");
using (var package = new ExcelPackage(existingFile))
using (var package2 = new ExcelPackage(existingFile2))
{
//Make sure there are document element for the source
var worksheet = package.Workbook.Worksheets.First();
var xdoc = worksheet.WorksheetXml;
if (xdoc.DocumentElement == null)
return;
//Make sure there are document element for the destination
var worksheet2 = package2.Workbook.Worksheets.First();
var xdoc2 = worksheet2.WorksheetXml;
if (xdoc2.DocumentElement == null)
return;
//get the extension list node 'extLst' from the ws with the formatting
var extensionlistnode = xdoc
.DocumentElement
.GetElementsByTagName("extLst")[0];
//Create the import node and append it to the end of the xml document
var newnode = xdoc2.ImportNode(extensionlistnode, true);
xdoc2.LastChild.AppendChild(newnode);
package2.Save();
}
}
Try this
var package = new ExcelPackage(excelFile)
var excelSheet = package.Workbook.Worksheets[1];
for (var i = 1; i < 5; i++){
excelWorkSheet.InsertRow(i, 1, 1); // Use value of i or whatever is suitable for you
}
package.Workbook.Calculate();
Inserting new row copies previous row format and its formula if last prm is set to 1