I am using closedXML to take a datatable and place this into an Excel file.
The code I am working with works, with 99% of the files I put through the application, but I get an error with a file every now and then. (no this is not a debugging issue)
The problem must originate from the data, however I am at a loss on how to resolve this.
The code I'm using is
Public Sub WriteToExcel(dt As DataTable, filePath As String)
Dim workbook = New XLWorkbook()
Dim worksheet = workbook.Worksheets.Add(dt, "Call Validate Export")
Try
workbook.SaveAs(filePath)
Process.Start(filePath)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
When saving the file I get the error
'', hexadecimal value 0x1A, is an invalid character.
between the '' there is a little arrow pointing to the right.
When reading the file in to the datatable it reads in fine, looking at the file I see no hex characters.
The file being read in is a ^ delimited csv file.
So, my question is how to I check and repair\replace the bad characters in the output that will allow me to save 100% of the time.
From the XML specification ( https://www.w3.org/TR/xml/#charsets )
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
Which implies that character #x1A is an invalid character. You should manually remove it.
#Simon,
It looks like whatever you are trying to export to Excel, it contains some invalid characters (Arrow pointing to right side).
I was receiving the similar error and upon detailed look, I come to know that I am exporting DataTable into Excel and one of the cell value of DataTable was looking like:
Please Note -> Some comment (Here, -> is actually a single character : Arrow pointing to right side)
I have removed those characters and it is working fine now.
Hope this helps.
https://github.com/ClosedXML/ClosedXML/pull/66 is a pull request which could solve your issue. Please try it.
Related
For my current project in need a way to use ä,ö etc. in a datatable that is written to a .csv
It is the same project as in: VB Reading data from SQL Server to Array, writing into .CSV
I know that I need UTF-8 but how do I use it ?
Unlike VB6/VBScript/VBA, VB.Net strings already use full Unicode internally. You can already put accented characters in your string variables (and string members for other objects), and you don't need to do anything special.
There are three things you do need to watch for, though.
First, you must be sure to use NVARCHAR rather than VARCHAR for your Sql Server columns, as well as your ADO.Net parameters. You may also need to be careful about what collation you have (but the default is almost certainly fine here).
Second, when you open your StreamWriter, you need to use unicode-capable correct Encoding. System.Text.UTF8Encoding is one option. You could also do System.Text.UnicodeEncoding (which is UTF16) or System.Text.UTF32Encoding and get accurate output.
Finally, just because you successfully create a unicode CSV file, this does not mean your downstream consumers will handle the file correctly. A lot of text editors and other tools like to assume csv data is ASCII. But that's really outside of your scope. All you can is give them valid data. If they don't process it, that's on them :)
So assuming the database is correct, and based on the other question, you have this code:
Sub WriteCsvFiles(destPath As String, headings As String(), dt As DataTable)
Dim separator As Char = ";"c
Dim header = String.Join(separator, headings)
For Each r As DataRow In dt.Rows
Dim destFile = Path.Combine(destPath, r(0).ToString().Trim() & ".csv")
Using sw As New StreamWriter(destFile)
sw.WriteLine(header)
sw.WriteLine(CsvLine(r.ItemArray, separator))
End Using
Next
End Sub
This is close. However, take a look at the remarks in the documentation for the StreamWriter constructor:
This constructor creates a StreamWriter with UTF-8 encoding without a Byte-Order Mark (BOM), so its GetPreamble method returns an empty byte array. The default UTF-8 encoding for this constructor throws an exception on invalid bytes. This behavior is different from the behavior provided by the encoding object in the Encoding.UTF8 property.
So we kind of already have UTF-8 data, but to really have a correct UTF-8 file, including correct byte-order handling for certain wide characters, we need to change things just a little bit. Where you have this right now:
Using sw As New StreamWriter(destFile)
should become:
Using sw As New StreamWriter(destFile, False, Encoding.UTF8)
It also seems very odd to create a separate file for every row that will all have the same structure. I know it's in your original question, but I'd really push back on that, or find out why, and the maybe re-write the method as so:
Sub WriteCsvFile(destFile As String, headings As IEnumerable(Of String), dt As DataTable)
Dim separator As Char = ";"c
Dim header As String = String.Join(separator, headings)
Using sw As New StreamWriter(destFile, False, Encoding.UTF8)
sw.WriteLine(header)
For Each r As DataRow In dt.Rows
sw.WriteLine(CsvLine(r.ItemArray, separator))
Next
End Using
End Sub
I have a YAML file which I am reading using VBA Excel plugin. I am getting a String of each line. I need to count the number of empty spaces in the front of the first line so that it will be a marker to find the next key. Let me know how to do that.
Option Explicit
Function empty_spaces(str_input)
empty_spaces = Len(str_input) - Len(LTrim(str_input))
End Function
harun24hr, your answer does not work with " My String " and similar.
I am writing a program in Visual Basic that writes and reads to and from a Microsoft Access Database. When reading from the database, one of the functions that I am trying to perform is to determine the number of lines in a multi-line string that was written to the database and then subsequently read from the database. Here's what I have tried so far with no luck.
Dim stringLines() As String = databaseReader("multilineString").ToString.Split(CChar("Environment.NewLine"))
Dim stringLinesCount As Integer = stringLines.Length
For some reason, this always results in stringLinesCount being equal to one, regardless of how many lines the string has. In this example, I am using Environment.NewLine, but I have tried \n, \r, vbCr, vbLf, and vbCrLf as well, and they all result in a value of one. Since none of these seem to be working, what can I use instead to determine the number of lines?
Edit:
Dim splitCharacters() As Char = {CChar(vbCrLf), CChar(vbCr), CChar(vbLf), CChar(Environment.NewLine), CChar("\n"), CChar("\r")}
Dim stringLines() As String = databaseReader("multilineString").ToString.Split(splitCharacters)
Dim stringLinesCount As Integer = stringLines.Length
Since Chris Dunaway provided the answer that I view as helpful but posted it as a comment, here's what he said:
VB cannot use C# style escape sequences, so CChar("\n") and CChar("\r") is meaningless in VB. Also, calling CChar("Environment.NewLine") is wrong because you are trying to convert the actual string "Environment.NewLine" to a single character, which obviously won't work. You can just use Environment.Newline directly in the call to String.Split.
If Chris decides to post his comment as an answer, please let me know so that I may remove this.
Im sure someone out there can help, im totally new to coding but getting into it and really enjoying. I know this is such a simple question out there for you folks but i have the following, I load a spread sheet of strings (2 columns) into a datagridview the reason i do this because there is over 100,000 find and replaces and these will generally sit within and existing string when searching, then from there i want to simply search a txt file and find and replace a number of strings in it. So it would check each row in a datagrid take from column 1 the find and use column 2 to replace then outputs the string to another txt file once the find and replace has taken place. My current results are that it just takes what was in the first file and copies without replacing in the second find.
Any assistance is gratefully received, many thanks.
Please see below my amateur code:-
Private Sub CmdBtnTestReplace_Click(sender As System.Object, e As System.EventArgs) Handles CmdBtnTestReplace.Click
Dim fName As String = "c:\backup\logs\masterUser.txt"
Dim wrtFile As String = "c:\backup\logs\masterUserFormatted.txt"
Dim strRead As New System.IO.StreamReader(fName)
Dim strWrite As New System.IO.StreamWriter(wrtFile)
Dim s As String
Dim o As String
For Each row As DataGridViewRow In DataGridView1.Rows
If Not row.IsNewRow Then
Dim Find1 As String = row.Cells(0).Value.ToString
Dim Replace1 As String = row.Cells(1).Value.ToString
Cursor.Current = Cursors.WaitCursor
s = strRead.ReadToEnd()
o = s.Replace(Find1, Replace1)
strWrite.Write(o)
End If
Next
strRead.Close()
strWrite.Close()
Cursor.Current = Cursors.Default
MessageBox.Show("Finished Replacing")
End Sub
1. What you are doing is :
creating a StreamReader whose purpose is to read chars from a File/Stream in sequence.
creating a StreamWriter whose purpose is to add content to a File/Stream.
then looping
a) read the remaining content of file fName and put it in s
b) replace words from s and put the result in o
c) add o to the existing content of the file wrtFile
then the usual closing of the stream reader/writer...
But that doesn't work because, on the secund iteration of the loop, strRead is already at the end of your loaded file, then there is nothing to read anymore, and s is always an empty string starting from the secund iteration.
Furthermore, because s is empty, o will be empty aswell.
And last of all, even if you manage to re-read the content of the file and replace the words, strWrite will not clear the initial content of the output file, but will write the resulting replaced string (o) after the previously updated content of the file.
2. Since you loaded the content of the file in a string (s = strRead.ReadToEnd()), why don't you :
load that s string before the For-Next block
loop the datagridview rows in a For-Next block
replace using the pair Find1/Replace1 s = s.Replace(Find1, Replace1)
then, save the content of s in the targeted file outside the For-Next block
3. However, improving your understanding of how streams work, what should be considered and what are forbidden is a bit outside the scope of SO I think; such documentation could be found/gathered on the MSDN page or with the help of your friend : google. The same applies for finding out/thinking of how you should arrange your code, how to achieve your goal.Let's take an example :
' Content of your file :
One Two Three Four Five Six
' Content of your DataGridView :
One | Two
Two | Three
Three | Four
Four | Five
Five | Six
Six | Seven
The resulting replacement text at the end of a similar routine as yours will be :
Seven Seven Seven Seven Seven Seven ' :/
' while the expected result would be :
Two Three Four Five Six Seven
And that's because of the iteration : already replaced portions of your file (or loaded file content) could get replaced again and again. To avoid that, either :
split the loaded content in single words, and use a "replaced" flag for each word (to avoid replacing that word more than once)
or preload all the pair Find/Replace, and parse the file content in sequence once, replacing that instance when required.
So, before using an interesting object in the framework :
you should know what it does and how it behaves
otherwise -> read the documentation
otherwise -> create a minimalistic test solution which purpose is to brute force testings on that particular object to debunck all its powers and flaws.
So, like I said in 2., move those ReadAllText() and Write() outside the For/Next block to start from and have a look at the resulting output (Ask specific questions in comments when google can't answer) Then if you're OK with it even if issue like the One Two Three example above could occur, then voila ! Otherwise, use google to gather more examples on "splitting text in words" and reformating the whole, have some tries, then get back here if you're stuck on precise issues.
I am writing a simple program that writes input to a sequential access file and then reads them back and displays them in the list box using .readLine. The numbers need to be formatted for american currency and right aligned. Problem is i cant figure out how to properly write the .readLine to do that. i am working out of a text book, and still new to vb.net, so im looking for the simplest way to solve this problem without re-writing alot of it. The code i have tried is within the DO loop below:
Private Sub btnDisplay_Click(sender As Object, e As EventArgs) Handles btnDisplay.Click
'declare new inFile Varable as a streamreader object
Dim inFile As IO.StreamReader
'opens gross.txt for input
inFile = IO.File.OpenText("gross.txt")
'.Exists() searches /bin folder for gross.txt, returns a boolean value
If IO.File.Exists("gross.txt") Then
inFile = IO.File.OpenText("gross.txt")
'fill the list with the values
Do Until inFile.Peek = -1
lstContents.Items.Add(inFile.ReadLine.PadLeft(7, " ")).ToString("C2")
Loop
Else
MessageBox.Show("The file you have requested does not exist", "Gross Pay Project",
MessageBoxButtons.OKCancel, MessageBoxIcon.Error)
End If
End Sub
We can get this down to a single line:
lstContents.Items.AddRange(File.ReadAllLines("gross.txt").Select(Function(s) Double.Parse(s).ToString("C2").PadLeft(7)).ToArray())
Or in a more readable way, with the error-handling and method defintion back:
Private Sub btnDisplay_Click(sender As Object, e As EventArgs) Handles btnDisplay.Click
Try
lstContents.Items.AddRange( _
File.ReadAllLines("gross.txt").
Select(Function(s) Decimal.Parse(s).ToString("C2").PadLeft(7)).
ToArray() )
Catch ex As IOException
MessageBox.Show("Unable to open the file you requested", "Gross Pay Project",
MessageBoxButtons.OKCancel, MessageBoxIcon.Error)
End Try
End Sub
Now to explain this code. To begin with, I replaced your call to IO.File.Exists() with a Try/Catch block. File.Exists() only checks one reason a file might not open. It ignores other reasons like file permissions and process locking. Even when checking whether a file exists, the file system is volatile and you still have a race condition situation, where a file might be deleted in between when you check if it exists and when you go to open it. It's almost always better to just skip calling File.Exists() entirely and instead use the try/catch block to handle an exception if opening the file fails, as shown above.
Going into the Try/Catch block, I changed the code so that it uses AddRange() instead of adding one item at a time in a loop. This allows us to build an array with the items we want and update the UI in a single step. Building that array may seem like extra work, but .Net provides some features to help us. In this case, the File.ReadAllLines() method reads in the entire file in one step, so that we can start with an array right from the beginning. All we have to do is make the array have the right information.
To get the information we want in the array, I perform a projection or transformation of the existing data. To accomplish that, I used a LINQ select operator (or the .Select() extension method, in this case). The LINQ extension methods generally ask you to build an inline function. In the case of this use of the select operator, that inline function expects a string variable (s) that represents a single line in the original array. I then supply code to transform that original string into the format we need.
Each line in the file begins as a string value, but we know that the string variable holds pricing information... it will be numeric. The best way to transform numeric data is generally to convert it a number, which is what I do. I chose the Decimal type, because when working with money you almost always want to prefer Decimal over Double or Single. In this case, Double may have been okay, but Decimal will still work just fine. Once I have a numeric type like Decimal, I can use a Numeric Format String to get a string back that has the correct currency symbol and formatting. The original code did not work because you were trying to use the format string with a value that was already a string type. The format strings only work with numeric types, like the Decimal we have here. Now that I have a string with the correct text, all I need to do now is pad it so that it will be right-justified when shown in the list.
Finally, you will recall that the AddRange() method expects an array. The LINQ select operator does not produce an array. It produces something called an IEnumerable. This is a type that represents a sequence of some kind. Arrays are one kind of sequence, but they are not the only kind of sequence, and the LINQ system wants to be able to work with many different kinds of sequences. Thankfully, getting back to an array is easy: just call the ToArray() method.
You should definitely use a ListView instead of a listbox. I don't think you can right-align with listbox items.