I have a template workbook with a sheet called ProdData, I need to copy this worksheet to my current workbook.
Using C# and EPPlus, how can I copy a worksheet from one workbook to another? When I look at intellisense it seems to only show that I can copy from within the same workbook.
How do I copy the worksheet to a NEW workbook?
This works for me.
public static void CopySheetValues(string sourcePath, string sheetName, string destPath)
{
using (var src = new ExcelPackage(new FileInfo(sourcePath)))
using (var dest = new ExcelPackage(new FileInfo(destPath)))
{
var wsSrc = src.Workbook.Worksheets[1];
var wsDest = dest.Workbook.Worksheets[wsSrc.Name] ?? dest.Workbook.Worksheets.Add(wsSrc.Name);
for (var r = 1; r <= wsSrc.Dimension.Rows; r++)
{
Console.WriteLine("Row: " + r.ToString());
for (var c = 1; c <= wsSrc.Dimension.Columns; c++)
{
Console.WriteLine("Column: " + c.ToString());
var cellSrc = wsSrc.Cells[r, c];
var cellDest = wsDest.Cells[r, c];
if (cellDest.ToString() == "E10")
{
}
Console.WriteLine(cellDest.ToString());
// Copy value
cellDest.Value = cellSrc.Value;
// Copy cell properties
cellDest.Style.Numberformat = cellSrc.Style.Numberformat;
cellDest.Style.Font.Bold = cellSrc.Style.Font.Bold;
if (cellSrc.Style.Fill.BackgroundColor.Rgb != null)
{
if (cellSrc.Style.Fill.BackgroundColor.Rgb != "")
{
var color = cellSrc.Style.Fill.BackgroundColor.Rgb;
cellDest.Style.Fill.PatternType = ExcelFillStyle.Solid;
cellDest.Style.Fill.BackgroundColor.SetColor(ColorTranslator.FromHtml("#" + color));//.SetColor(color);
}
else
{
cellDest.Style.Fill.PatternType = ExcelFillStyle.Solid;
cellDest.Style.Fill.BackgroundColor.SetColor(ColorTranslator.FromHtml("#808080"));//.SetColor(color);
}
}// TODO... Add any additional properties that you may want to copy over
cellDest.Style.HorizontalAlignment =
cellSrc.Style.HorizontalAlignment;
cellDest.Style.VerticalAlignment =
cellSrc.Style.VerticalAlignment;
cellDest.Style.Border.Right.Style =
cellSrc.Style.Border.Right.Style;
cellDest.Style.Border.Left.Style =
cellSrc.Style.Border.Left.Style;
cellDest.Style.Border.Top.Style = cellSrc.Style.Border.Top.Style;
cellDest.Style.Border.Bottom.Style =
cellSrc.Style.Border.Bottom.Style;
cellDest.Style.WrapText = cellSrc.Style.WrapText;
}
}
dest.Save();
}
}
Related
I have a method which create an excel file (.xlsx) from a list of strings using DocumentFormat.OpenXml. The created file needs to be repaired when I try to open it with Excel 2016. When I click "Yes" Excel shows my file correctly.
Does anyone have any suggestions? Thanks in advance.
Here's my code:
private byte[] ExportDataXlsx(System.Data.Common.DbDataReader reader, string[] fields, string[] headers, string Culture) {
System.IO.MemoryStream sw = new System.IO.MemoryStream();
using (var workbook = Packaging.SpreadsheetDocument.Create(sw, SpreadsheetDocumentType.Workbook)) {
var sheetData = CreateSheet(workbook);
while (reader.Read()) {
Spreadsheet.Row newRow = new Spreadsheet.Row();
foreach (string column in fields) {
Spreadsheet.Cell cell = new Spreadsheet.Cell();
cell.DataType = Spreadsheet.CellValues.String;
object value = null;
try {
int index = reader.GetOrdinal(column);
cell.DataType = DbKymosDomainService.ToXlsType(reader.GetFieldType(index));
value = DbKymosDomainService.ToStringFromCulture(reader.GetValue(index), reader.GetFieldType(index), Culture);
if (cell.DataType == Spreadsheet.CellValues.Number){
value = value == null ? "" : value.ToString().Replace(",", ".");
}
}
catch { }
cell.CellValue = new Spreadsheet.CellValue(value == null ? null : value.ToString()); //
newRow.AppendChild(cell);
try { var x = newRow.InnerXml; } catch { newRow.RemoveChild(cell); }
}
sheetData.AppendChild(newRow);
}
workbook.Close();
}
byte[] data = sw.ToArray();
sw.Close();
sw.Dispose();
return data;
}
Function which create sheet:
private Spreadsheet.SheetData CreateSheet(Packaging.SpreadsheetDocument workbook)
{
var workbookPart = workbook.AddWorkbookPart();
workbook.WorkbookPart.Workbook = new Spreadsheet.Workbook();
workbook.WorkbookPart.Workbook.Sheets = new Spreadsheet.Sheets();
var sheetPart = workbook.WorkbookPart.AddNewPart<Packaging.WorksheetPart>();
var sheetData = new Spreadsheet.SheetData();
sheetPart.Worksheet = new Spreadsheet.Worksheet(sheetData);
Spreadsheet.Sheets sheets = workbook.WorkbookPart.Workbook.GetFirstChild<Spreadsheet.Sheets>();
string relationshipId = workbook.WorkbookPart.GetIdOfPart(sheetPart);
uint sheetId = 1;
if (sheets.Elements<Spreadsheet.Sheet>().Count() > 0) {
sheetId =
sheets.Elements<Spreadsheet.Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
Spreadsheet.Sheet sheet = new Spreadsheet.Sheet() { Id = relationshipId, SheetId = sheetId, Name = "Export" };
sheets.Append(sheet);
return sheetData;
}
In my experience when a file needs to be repaired after creating it using OpenXML it means that it is missing a crucial element or the crucial element is in the wrong place. I'm having difficulty following your code so that in itself points to something being in the wrong place. Code should be sequential and self-explanatory. A few pointers however to help with getting to the root cause of your issue.
I would suggest first using ClosedXML as it takes so much strain out of the coding.https://github.com/closedxml/closedxml
Debug your code and step through each step to see what's going on.
Open the created file in OpenXML Productivity Tool https://github.com/OfficeDev/Open-XML-SDK/releases/tag/v2.5 and have a look around.
Another tool that I couldn't be without is OpenXML FileViewer: https://github.com/davecra/OpenXmlFileViewer
Lastly I always run this sub routine to validate documents I create using OpenXML:
public static List<string> ValidateWordDocument(FileInfo filepath, ref Int32 maxerrors = 100)
{
try
{
using (WordprocessingDocument wDoc = WordprocessingDocument.Open(filepath.FullName, false))
{
OpenXmlValidator validator = new OpenXmlValidator();
int count = 0;
List<string> er = new List<string>()
{
string.Format($"Assessment of {filepath.Name} on {DateTime.Now} yielded the following result: {Constants.vbCrLf}")
};
// set at zero so that we can determine the total quantity of errors
validator.MaxNumberOfErrors = 0;
// String.Format("<strong> Warning : </strong>")
foreach (ValidationErrorInfo error in validator.Validate(wDoc))
{
count += 1;
if (count > maxerrors)
break;
er.Add($"Error {count}{Constants.vbCrLf}" + $"Description {error.Description}{Constants.vbCrLf}" + $"ErrorType: {error.ErrorType}{Constants.vbCrLf}" + $"Node {error.Node}{Constants.vbCrLf}" + $"Name {error.Node.LocalName}{Constants.vbCrLf}" + $"Path {error.Path.XPath}{Constants.vbCrLf}" + $"Part: {error.Part.Uri}{Constants.vbCrLf}" + $"-------------------------------------------{Constants.vbCrLf}" + $"Outer XML: {error.Node.OuterXml}" + $"-------------------------------------------{Constants.vbCrLf}");
}
int validatorcount = validator.Validate(wDoc).Count;
switch (validatorcount)
{
case object _ when validatorcount > maxerrors:
{
er.Add($"Returned {count - 1} as this is the Maximum Number set by the system. The actual number of errors in {filepath.Name} is {validatorcount}");
er.Add("A summary list of all error types encountered is given below");
List<string> expectedErrors = validator.Validate(wDoc).Select(_e => _e.Description).Distinct().ToList();
er.AddRange(expectedErrors);
break;
}
case object _ when 1 <= validatorcount && validatorcount <= maxerrors:
{
er.Add($"Returned all {validator} errors in {filepath.Name}");
break;
}
case object _ when validatorcount == 0:
{
er.Add($"No Errors found in document {filepath.Name}");
break;
}
}
return er;
wDoc.Close();
}
}
catch (Exception ex)
{
Information.Err.MessageElevate();
return null;
}
}
It helps greatly with problem solving any potential issues.
I am trying to insert data into csv file. I tried using XLWorkbook reference to access and insert data but I know that XLWorkbook can only support extension which are xlsx,xslm,xltx and xltm.
I am trying to find something similar to what I am trying to achieve through which I can insert data into specified column in csv file. I have used XLWorkbook for some other purpose but I am not aware to what I can use when I have to use csv.
//Accessing the csv file where I am trying to insert data.
string rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
string filelocation = #"\csv\TestData.csv";
string location = rootPath + filelocation;
XLWorkbook workbook = new XLWorkbook(pathfile);
IXLWorksheet worksheet = workbook.Worksheet("Sheet1");
//Insert data after first row as first row contains column header
int lastrow = worksheet.LastRowUsed().RowNumber() + 1;
//through previous function I am trying get data from database and insert those data into csv cells
worksheet.Cell(String.Format("B{0}", lastrow)).Value = dummydata.FirstName;
worksheet.Cell(String.Format("C{0}", lastrow)).Value = dummydata.LastName;
worksheet.Cell(String.Format("D{0}", lastrow)).Value = dummydata.Address1;
worksheet.Cell(String.Format("E{0}", lastrow)).Value = dummydata.Address2;
worksheet.Cell(String.Format("F{0}", lastrow)).Value = dummydata.City;
worksheet.Cell(String.Format("G{0}", lastrow)).Value = dummydata.StateProvinceCode;
worksheet.Cell(String.Format("H{0}", lastrow)).Value = dummydata.ZipCode;
worksheet.Cell(String.Format("I{0}", lastrow)).Value = dummydata.Country;
worksheet.Cell(String.Format("J{0}", lastrow)).Value = dummydata.HomePhone;
worksheet.Cell(String.Format("L{0}", lastrow)).Value = dummydata.HomePhone;
worksheet.Cell(String.Format("M{0}", lastrow)).Value = dummydata.CellPhone;
worksheet.Cell(String.Format("T{0}", lastrow)).Value = dummydata.Email;
worksheet.Cell(String.Format("U{0}", lastrow)).Value = dummydata.Country;
//After inserting save the file
workbook.Save();
You can simply copy and use this code as is. It should resolve your issues.
Here's the class I developed to replace and/or add csv cells:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace CSVManager
{
public class CSVWorker
{
private string m_FileName = string.Empty;
public CSVWorker(string fileName)
{
m_FileName = fileName;
}
public void AddCells(int row, int column, string newValue)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var csvLines = File.ReadAllLines(m_FileName, encoding);
if (row < csvLines.Length)
{
ReplaceCells(row, column, newValue);
}
else
{
using (FileStream stream = new FileStream(m_FileName, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(stream, encoding))
{
foreach (var line in csvLines)
{
writer.WriteLine(line);
}
int blankLines = row - csvLines.Length - 1;
for (int i = 0; i < blankLines; i++)
{
writer.WriteLine("");
}
string blankCols = string.Empty;
for (int i = 0; i < column-1; i++)
{
blankCols += ',';
}
writer.WriteLine(blankCols + newValue);
}
}
}
}
public void ReplaceCells(int row, int column, string newValue)
{
var encoding = Encoding.GetEncoding("iso-8859-1");
var csvLines = File.ReadAllLines(m_FileName, encoding);
for (int i = 0; i < csvLines.Length; i++)
{
//var values = csvLines[i].Split(',');
List <string> values = csvLines[i].Split(',').ToList();
if (i == row)
{
if (column < values.Count)
{
values[column] = newValue;
}
else
{
while (values.Count < column - 1)
{
values.Append(",");
}
values.Append(newValue);
}
using (FileStream stream = new FileStream(m_FileName, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(stream, encoding))
{
for (int currentLine = 0; currentLine < csvLines.Length; ++currentLine)
{
if (currentLine == i)
{
writer.WriteLine(string.Join(",", values));
}
else
{
writer.WriteLine(csvLines[currentLine]);
}
}
writer.Close();
}
stream.Close();
break;
}
}
}
}
}
}
Here's how I used it:
namespace CSVManager
{
class Program
{
static void Main(string[] args)
{
string fileName = #"C:\Users\mklig\Documents\TestCsv.csv";
CSVWorker csvWorker = new CSVWorker(fileName);
int row = 4;
int col = 4;
string newVal = "success";
//csvWorker.ReplaceCells(row, col, newVal);
csvWorker.AddCells(row, col, newVal);
}
}
}
As input, I have a set of excel files with several worksheets inside. I need to export a single csv file for each worksheet. Below is my code which works but it is very slow. It builds upon the solutions proposed in this previous post. Please consider that I have to run this on rather big .xlsx files (approx. 300Mb).
QUESTION: Is there any way to improve this?
void Main()
{
string folder = #"\\PATH_TO_FOLDER\";
var files = Directory.GetFiles(folder, "*.xlsx", SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
ConvertToCsv(file, Directory.GetParent(file) + #"\\output\");
}
}
public static void ConvertToCsv(string file, string targetFolder)
{
FileInfo finfo = new FileInfo(file);
ExcelPackage package = new ExcelPackage(finfo);
// if targetFolder doesn't exist, create it
if (!Directory.Exists(targetFolder)) {
Directory.CreateDirectory(targetFolder);
}
var worksheets = package.Workbook.Worksheets;
int sheetcount = 0;
foreach (ExcelWorksheet worksheet in worksheets)
{
sheetcount++;
var maxColumnNumber = worksheet.Dimension.End.Column;
var currentRow = new List<string>(maxColumnNumber);
var totalRowCount = worksheet.Dimension.End.Row+1;
var currentRowNum = 1;
//No need for a memory buffer, writing directly to a file
//var memory = new MemoryStream();
string file_name = targetFolder + Path.GetFileNameWithoutExtension(file) + "_" + sheetcount + ".csv";
using (var writer = new StreamWriter(file_name, false, Encoding.UTF8))
{
//the rest of the code remains the same
for (int i = 1; i < totalRowCount; i++)
{
i.Dump();
// populate line with semi columns separators
string line = "";
for (int j = 1; j < worksheet.Dimension.End.Column+1; j++)
{
if (worksheet.Cells[i, j].Value != null)
{
string cell = worksheet.Cells[i, j].Value.ToString() + ";";
line += cell;
}
}
// write line
writer.WriteLine(line);
}
}
}
}
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(
newFilePath, true );
var sheet = spreadsheetDocument.WorkbookPart.Workbook
.Sheets.Elements<Sheet>()
.FirstOrDefault();
var sheetReferenceId = sheet.Id;
WorksheetPart worksheetPart = ( WorksheetPart )
spreadsheetDocument.WorkbookPart.GetPartById( sheetReferenceId );
IEnumerable<Row> rows =
worksheetPart.Worksheet.GetFirstChild<SheetData>().Elements<Row>();
foreach ( Row row in rows )
{
int index = ( int ) row.RowIndex.Value + 1;
IEnumerable<Cell> cells = row.Elements<Cell>();
IEnumerable<Cell> cellToDelete = cells.Where( c => string.Compare( c.CellReference.Value, "DD" + index, true ) == 0 );
if ( cellToDelete.Count() > 0 )
{
cellToDelete.First().Remove();
}
}
This code only remove the text from column.But i want to delete the entire column "DD". I am using Open XML SDK for it.
We can achieve by using Microsoft.Office.Interop.Excel;
public string removerowsfromexcel(string errorexceldocasbase64text, list<int> rownums)
{
string data = string.empty;
memorystream ms = new memorystream(0);
byte[] a = convert.frombase64string(errorexceldocasbase64text);//encoding.ascii.getbytes(base64);
ms.write(a, 0, a.length);
string processfilename = "\\temp" + datetime.now.ticks + ".xlsx";
var tempfiledirectory = environment.currentdirectory + "\\tempfiles";
string processfilepath = tempfiledirectory + processfilename;
if (directory.exists(tempfiledirectory))
{
if (file.exists(processfilepath))
{
file.delete(processfilepath);
file.writeallbytes(processfilepath, a);
}
else
{
file.writeallbytes(processfilepath, a);
}
}
else
{
directory.createdirectory(tempfiledirectory);
if (directory.exists(tempfiledirectory))
{
if (file.exists(processfilepath))
{
file.delete(processfilepath);
file.writeallbytes(processfilepath, a);
}
else
{
file.writeallbytes(processfilepath, a);
}
}
}
_application docexcel = new microsoft.office.interop.excel.application { visible = false };
dynamic workbooksexcel = docexcel.workbooks.open(processfilepath);
var worksheetexcel = (_worksheet)workbooksexcel.activesheet;
foreach (var rowidx in rownums)
{
if (rowidx != 1)
{
((range)worksheetexcel.rows[rowidx, missing.value]).delete(xldeleteshiftdirection.xlshiftup);
}
}
workbooksexcel.save();
workbooksexcel.close(false);
byte[] bytes = file.readallbytes(processfilepath);
string base64 = convert.tobase64string(bytes);
docexcel.application.quit();
file.delete(processfilepath);
return base64;
}
I have a cell that contains the placeholder "$$value" in the Excel sheet, the thing is that I need to replace the placeholder's actual value using Open XML and save it as separate workbook.
Here is the code that I tried...it is not replacing the actual value and also I'm unable to save the workbook. I need to sort out this issue.
WorksheetPart worksheetPart = (WorksheetPart)myWorkbook.WorkbookPart.GetPartById(sheet.Id);
DocumentFormat.OpenXml.Spreadsheet.Worksheet worksheet = worksheetPart.Worksheet;
string _txt1 = "$$value";
if (_txt1.Contains("$$"))
{
worksheet.InnerText.Replace(_txt1, "test");
}
by default Excel stores strings in the global (1 per workbook) SharedStringTablePart. So, this is the one you need to target. However, the OpenXML format also allows inline text inside the WorksheetParts. hence, a complete solution needs to look there as well.
Here's a sample app (with some inline comments):
using DocumentFormat.OpenXml.Packaging;
using x = DocumentFormat.OpenXml.Spreadsheet;
class Program
{
private static readonly string placeHolder = "$$value";
static void Main()
{
var templatePath = #"C:\Temp\template.xlsx";
var resultPath = #"C:\Temp\result.xlsx";
string replacementText = "test";
using (Stream xlsxStream = new MemoryStream())
{
// Read template from disk
using (var fileStream = File.OpenRead(templatePath))
fileStream.CopyTo(xlsxStream);
// Do replacements
ProcessTemplate(xlsxStream, replacementText);
// Reset stream to beginning
xlsxStream.Seek(0L, SeekOrigin.Begin);
// Write results back to disk
using (var resultFile = File.Create(resultPath))
xlsxStream.CopyTo(resultFile);
}
}
private static void ProcessTemplate(Stream template, string replacementText)
{
using (var workbook = SpreadsheetDocument.Open(template, true, new OpenSettings { AutoSave = true }))
{
// Replace shared strings
SharedStringTablePart sharedStringsPart = workbook.WorkbookPart.SharedStringTablePart;
IEnumerable<x.Text> sharedStringTextElements = sharedStringsPart.SharedStringTable.Descendants<x.Text>();
DoReplace(sharedStringTextElements, replacementText);
// Replace inline strings
IEnumerable<WorksheetPart> worksheetParts = workbook.GetPartsOfType<WorksheetPart>();
foreach (var worksheet in worksheetParts)
{
var allTextElements = worksheet.Worksheet.Descendants<x.Text>();
DoReplace(allTextElements, replacementText);
}
} // AutoSave enabled
}
private static void DoReplace(IEnumerable<x.Text> textElements, string replacementText)
{
foreach (var text in textElements)
{
if (text.Text.Contains(placeHolder))
text.Text = text.Text.Replace(placeHolder, replacementText);
}
}
Solution:
private static void ProcessTemplate(Stream template, Dictionary<string,string> toReplace)
{
using (var workbook = SpreadsheetDocument.Open(template, true, new OpenSettings { AutoSave = true }))
{
workbook.WorkbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
workbook.WorkbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;
//Replace SheetNames
foreach (Sheet sheet in workbook.WorkbookPart.Workbook.Sheets)
foreach (var key in toReplace.Keys)
sheet.Name.Value = sheet.Name.Value.Replace(key, toReplace[key]);
foreach (WorksheetPart wsheetpart in workbook.WorkbookPart.WorksheetParts)
foreach (SheetData sheetd in wsheetpart.Worksheet.Descendants<x.SheetData>())
foreach (Row r in wsheetpart.Worksheet.Descendants<x.Row>())
foreach (Cell c in r.Descendants<x.Cell>())
if (c.CellFormula != null)
{
foreach (var key in toReplace.Keys)
c.CellFormula.Text = c.CellFormula.Text.Replace(key, toReplace[key]);
}
// Replace shared strings
SharedStringTablePart sharedStringsPart = workbook.WorkbookPart.SharedStringTablePart;
IEnumerable<x.Text> sharedStringTextElements = sharedStringsPart.SharedStringTable.Descendants<x.Text>();
for(int i =0;i<toReplace.Keys.Count; i++)
DoReplace(sharedStringTextElements, toReplace);
IEnumerable<x.Formula> sharedStringTextElementsF = sharedStringsPart.SharedStringTable.Descendants<x.Formula>();
for (int i = 0; i < toReplace.Keys.Count; i++)
DoReplaceFormula(sharedStringTextElementsF, toReplace);
// Replace inline strings
IEnumerable<WorksheetPart> worksheetParts = workbook.GetPartsOfType<WorksheetPart>();
foreach (var worksheet in worksheetParts)
{
var allTextElements = worksheet.Worksheet.Descendants<x.Text>();
DoReplace(allTextElements, toReplace);
var allTextElements2 = worksheet.Worksheet.Descendants<x.Formula>();
DoReplaceFormula(allTextElements2, toReplace);
}
} // AutoSave enabled
}