I am trying to create Excel file by reading data from the database. One of the columns contains Japanese text. While writing that column to excel cell and saving workbook gives following error (which makes sense as the characters are not valid xml chars ) :'', hexadecimal value 0x0B, is an invalid character.
I am writing the string as following to the excel cell using DocumentFormat.OpenXml package.
var excelCell = new Cell();
var cellValue = dtRow[col.Name].ToString();
var inlineStr = new InlineString(new Text(cellValue));
excelCell.DataType = CellValues.InlineString;
excelCell.InlineString = inlineStr;
What needs to be done to write Japanese characters to the excel using OpenXml in C#
Ok. Found the right way. Putting it as answer so that it can be helpful.
To add text to excel which is not allowed as valid xml, add the text as SharedString to the SharedStringTable
var index = InsertSharedStringItem(text, shareStringPart);
excelCell.CellValue = new CellValue(index.ToString());
excelCell.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;
}
Full documentation for adding text as shared string to excel using OpenXml
https://msdn.microsoft.com/en-us/library/office/cc861607.aspx
Related
found several matches to my request, but not for word, so I open a new thread.
I got a word document. After a specified text, I want to insert some lines.
My code looks like that:
public static class Program
{
static void Main(string[] args)
{
string Filename = #"D:\...\MasterDoc.docx";
string SearchFor = "Search this text and insert after it";
string DocText = string.Empty;
int InsertIndex = 0;
//Run or attach MS Word
Microsoft.Office.Interop.Word.Application wrdApp = RunOrAttachWordApplication();
//Open masterfile
Microsoft.Office.Interop.Word.Document doc = wrdApp.Documents.Open(Filename);
//Get complete range
Microsoft.Office.Interop.Word.Range rng = doc.Range();
//Get document text
DocText = rng.Text;
//Search indes to insert text
InsertIndex = DocText.IndexOf(SearchFor) + SearchFor.Length;
//Define range at location for text pasting
rng = doc.Range(InsertIndex, InsertIndex + 1);
//Write 'Test...' on specified location
rng.InsertAfter("Test...");
//Close document
doc.Close(); /*Right here I got a breakpoint and watch the result, so I do not need to save the document here*/
//Close application
wrdApp.Application.Quit();
wrdApp.Quit();
wrdApp = null;
System.GC.Collect();
System.Console.WriteLine("Ende...");
System.Console.ReadKey();
}
public static Microsoft.Office.Interop.Word.Application RunOrAttachWordApplication()
{
if(System.Diagnostics.Process.GetProcessesByName("Word").Length > 0)
{
return System.Runtime.InteropServices.Marshal.GetActiveObject("Word.Application") as Microsoft.Office.Interop.Word.Application;
}
else
{
return new Microsoft.Office.Interop.Word.Application();
}
}
}
Well, it works - but not correctly. The text is inserted about 50 digits before the location, I want.
Does anybody know, how to fix that or I could imagine, that there is a much better methode to do that. I cannot modify the Masterdocument as it is recreated everytime by another, external program.
Thank you very much and regards,
Jan
I find the Find.Execute method much cleaner and shorter for adding/replacing text in the Word VSTO.
doc.Range().Find.Execute(FindText: SearchFor, Replace: WdReplace.wdReplaceOne, ReplaceWith: SearchFor + " Test...");
It has many options, but for replacing the text I used:
FindText - the text to find.
Replace - how many replacements to make. [WdReplace][2]
ReplaceWith - the text.
I suspect that there is some metadata at the beginning of the file which is being ignored when you lookup the required index here:
DocText.IndexOf(SearchFor) + SearchFor.Length;
You can add the discrepancy to the insert, given that the amount is constant for all files:
//Search indes to insert text
InsertIndex = DocText.IndexOf(SearchFor) + SearchFor.Length + 50;
However, the most eloquent solution would be to stop using rng.InsertAfter and instead set rng.Text like this:
//Get document text
DocText = rng.Text;
//Search indes to insert text
InsertIndex = DocText.IndexOf(SearchFor) + SearchFor.Length;
//Update the text
DocText = DocText.Substring(0, InsertIndex) + "Test..." + DocText.Substring(InsertIndex);
// Set the new text
rng.Text = DocText;
Will unfortunatelly, the solution of EyIM does not work, as the textlength increased and its limited to 255 characters. Any other solutions? I really want to insert text, as there is some format in the document and I need to keep that.
I need export ListView to csv and i found solution from this answer.Here
The problem is that I am getting full line into one column.
I got this :
I need this:
Code below:
public static void ListViewToCSV(ListView listView, string filePath, bool includeHidden)
{
//make header string
StringBuilder result = new StringBuilder();
WriteCSVRow(result, listView.Columns.Count, i => includeHidden || listView.Columns[i].Width > 0, i => listView.Columns[i].Text);
//export data rows
foreach (ListViewItem listItem in listView.Items)
WriteCSVRow(result, listView.Columns.Count, i => includeHidden || listView.Columns[i].Width > 0, i => listItem.SubItems[i].Text);
File.WriteAllText(filePath, result.ToString());
}
private static void WriteCSVRow(StringBuilder result, int itemsCount, Func<int, bool> isColumnNeeded, Func<int, string> columnValue)
{
bool isFirstTime = true;
for (int i = 0; i < itemsCount; i++)
{
if (!isColumnNeeded(i))
continue;
if (!isFirstTime)
result.Append(",");
isFirstTime = false;
result.Append(String.Format("\"{0}\"", columnValue(i)));
}
result.AppendLine();
}
Any ideas what I have to do ?
This has nothing to do with CSVs. The first screenshot shows a perfectly good CSV. It also shows an attempt to open that file in Excel.
Excel will use the end-user's locale to decide what column, decimal separators and date formats to use. When you double-click on a text file Excel will import that data using the end-user's locale settings as the default.
In most European countries , is the decimal separator so it can't be used as a list separator too. Otherwise you wouldn't know what 100,00,234 mean. Is that 2 or 3 columns?
In such cases, the typical list separator is ;. This is specified in the end user's locale settings. In most European countries you'd see 100,00;234
If you want your end users to be able to open the generated files in Excel with double-clicking you'll have to use the list and decimal separators that match their locales.
A better option would be to generate real Excel files with, eg Epplus. It doesn't require installing Excel on the client or server, and generates real Excel (xlsx) files.
Exporting your data can be as easy as a call to :
using (var pck = new ExcelPackage())
{
var sheet = pck.Workbook.Worksheets.Add("sheet");
sheet.Cells["C1"].LoadFromCollection(items);
pck.SaveAs(new FileInfo(#"c:\workbooks\myworkbook.xlsx"));
}
I´m using EPPlus LoadFromText to parse a csv into an excel file.
var format = new ExcelTextFormat();
format.Delimiter = ';';
format.Encoding = System.Text.Encoding.UTF8;
format.Culture = System.Globalization.CultureInfo.GetCultureInfo("pt-pt");
using (ExcelPackage package = new ExcelPackage(new FileInfo(excelFilePath)))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(worksheetsName);
worksheet.Cells["A1"].LoadFromText(new FileInfo(fileName), format, OfficeOpenXml.Table.TableStyles.None, false);
package.Save();
}
When a row has more than one column with an ampersand("&"):
001;David & Goliath;10;20;David & Goliath
The call throws an exception:
"An item with the same key has already been added".
Is there a way to avoid this problem without changing the input csv data?
The two parameter call works for me, so you may be able to use it as a workaround:
worksheet.Cells["A1"].LoadFromText(
new FileInfo(path), format
);
Note that the four parameter call in you example above is doing exactly the same thing.
So this looks like a bug. Taken directly from source code, the four parameter overload:
public ExcelRangeBase LoadFromText(string Text, ExcelTextFormat Format, TableStyles TableStyle, bool FirstRowIsHeader)
{
ExcelRangeBase excelRangeBase = this.LoadFromText(Text, Format);
ExcelTable excelTable = this._worksheet.Tables.Add(excelRangeBase, "");
excelTable.ShowHeader = FirstRowIsHeader;
excelTable.TableStyle = TableStyle;
return excelRangeBase;
}
Is blowing up when trying to add the ExcelTable when there are ampersand(s) in the source data.
The ampersand will be escaped as & in XML and you are using ';' as a delimiter.
What I'm Trying to Do:
I am reading in cell values in an Excel worksheet, and the text often has special formatting such as superscript and subscript. I would like to preserve this formatting when bringing it into my C# application, using the strings for various labels and such. However, with my current implementation, all superscript and subscript characters lose that formatting. I believe it has to do with the fact that I cast things to different types throughout the process:
const string fileName = "C:\\Users\\J.Smith\\Desktop\\FeatureInfoPropertyAttributesEXCEL.xlsx";
// Prepare the required items
Workbook wb = null;
// Start Excel
Application excel = new Application {Visible = false};
try
{
// Open file
Workbook wb = excel.Workbooks.Open(fileName);
// Read sheets
Sheets sheets = wb.Worksheets;
// Select sheet
Worksheet ws = (Worksheet) sheets.Item["FeatureInfoPropertyAttributes"];
string firstCellForPropertiesRange = "A2";
string secondCellForPropertiesRange = ws.Range[firstCellForPropertiesRange].End[XlDirection.xlDown].Address;
string firstCellForHeadersRange = firstCellForPropertiesRange.Replace("A", "B");
string secondCellForHeadersRange = secondCellForPropertiesRange.Replace("A", "B");
string firstCellForProposedHeadersRange1 = firstCellForHeadersRange.Replace("B", "C");
string secondCellForProposedHeadersRange1 = secondCellForHeadersRange.Replace("B", "C");
string firstCellForProposedHeadersRange2 = firstCellForProposedHeadersRange1.Replace("C", "D");
string secondCellForProposedHeadersRange2 = secondCellForProposedHeadersRange1.Replace("C", "D");
Range propertiesRange = ws.Range[firstCellForPropertiesRange, secondCellForPropertiesRange];
Range headersRange = ws.Range[firstCellForHeadersRange, secondCellForHeadersRange];
Range proposedHeadersRange1 = ws.Range[firstCellForProposedHeadersRange1, secondCellForProposedHeadersRange1];
Range proposedHeadersRange2 =
ws.Range[firstCellForProposedHeadersRange2, secondCellForProposedHeadersRange2];
List<string> properties =
propertiesRange.Cells.Cast<object>()
.Select((t, i) => ((Range) propertiesRange.Cells[i + 1]).Value2 ?? string.Empty)
.Cast<string>()
.ToList();
List<string> existingHeaders =
headersRange.Cells.Cast<object>()
.Select((t, i) => ((Range) headersRange.Cells[i + 1]).Value2 ?? string.Empty)
.Cast<string>()
.ToList();
List<string> proposedHeaders1 =
proposedHeadersRange1.Cells.Cast<object>()
.Select(
(t, i) =>
((Range) proposedHeadersRange1.Cells[i + 1]).Value2 ?? string.Empty)
.Cast<string>()
.ToList();
List<string> proposedHeaders2 =
proposedHeadersRange2.Cells.Cast<object>()
.Select(
(t, i) =>
((Range)proposedHeadersRange2.Cells[i + 1]).Value2 ?? string.Empty)
.Cast<string>()
.ToList();
foreach (string s in proposedHeaders1.Where(s => !s.Equals(string.Empty)))
{
Console.WriteLine(s);
}
foreach (string s in proposedHeaders2.Where(s => !s.Equals(string.Empty)))
{
Console.WriteLine(s);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
excel.Visible = true;
wb.Close(false, null, null);
excel.Quit();
}
First Question:
Does C# strings support such formatting characteristics as superscript and subscript in strings?
Second Question:
If the answer to the first question is 'yes', how would I go about accomplishing this?
Update: Example of a hard coded string with superscript formatting
You can get the superscript/subscript information from Excel via the logical flags in the Font Property of the given Range:
Range curRange = ws.get_Range("A1");
if(curRange.Font.Superscript)
{
//It is a superscript
}
if(curRange.Font.Subscript)
{
//It is a subscript
}
The easiest way to represent this in C# winforms is relying on a RichTextBox (you can even make it "look like a label", as suggested in the following link) and on its SelectionCharOffset. With Labels, it is not so straightforward but there are some workarounds.
---------- UPDATE
ANSWER TO THE FIRST QUESTION:
C# supports formatting through its Objects, not its string type. Excel strings do not support formatting either; the Ranges are the ones in charge of dealing with this.
ANSWER TO THE SECOND QUESTION:
You can replicate in C# what Excel Cells/Ranges do by relying on the corresponding C# equivalent, that is: Controls/Objects. The Control meant for text-decoration is the RichTextBox and thus it is the best equivalent in this case. Nonetheless, there are different ways to deliver the result you want by using other Controls, as explained above.
I want to read an unformatted contents of the numeric cells (e.g. 0.05 instead of 5% and 123456 instead of 123,456.000).
I thought the easiest way to do it would be to change the format of the cell:
ICell cell = ...;
string s = cell.SetCellType(<ICell.CELL_TYPE_STRING-doesn't compile>).ToString();
but I do not know how to set string/numeric format.
All examples I have googled are either from POI or HSSF universes, they won't do for me (I am reading Excel 2007 spreadsheet using NPOI)
This worked for me:
string formatProofCellReading(ICell cell)
{
if (cell == null)
{
return "";
}
if (cell.CellType == CellType.NUMERIC)
{
double d = cell.NumericCellValue;
return (d.ToString());
}
return cell.ToString();
}