This provides text file conversion capabilities like the common Unix utilities "ux2dos" and "dos2ux".
I needed the capability to perform Unix text file conversions, round trip, within a VB.NET project without being dependant upon external binaries. I couldn't find anything like that on NuGet so I wrote my own.
I'm fairly new to VB.NET and I am self taught. It's for my first original GitHub project! This function works for two text conversion modes currently and I want to get some feedback on my technique here before I complete the project by adding 4 more text conversion modes. It should be able to do conversions between Unix, DOS, and Mac formats. The file should pass an MD5 check if it is converted out and back between any given conversion mode. I want it to run totally Async so it can be used in GUI applications. I hope I am doing the Async stuff correctly...
Imports System.IO
Imports System.Text
Public Class ConvertLineEndings
''' <summary>
''' These are the different conversions that this library can perform.
''' </summary>
Private Enum TextConvertMode
Dos2Ux
Ux2Dos
'Dos2Mac
'Mac2Dos
'Mac2Ux
'Ux2Mac
End Enum
''' <summary>
''' Converts a DOS text file to have Unix line endings.
''' </summary>
''' <param name="originalFile">The file to convert.</param>
''' <param name="newFile">The name of a new file to create.</param>
''' <returns>Exit code.</returns>
Public Shared Async Function Dos2Ux(originalFile As String, newFile As String) As Task(Of Integer)
Return Await ReplaceLineEndings(originalFile, newFile, TextConvertMode.Dos2Ux)
End Function
''' <summary>
''' Converts a DOS text file to have Unix line endings.
''' </summary>
''' <param name="originalFile">The file to convert.</param>
''' <param name="newFile">The name of a new file to create.</param>
''' <returns>Exit code.</returns>
Public Shared Async Function Ux2Dos(originalFile As String, newFile As String) As Task(Of Integer)
Return Await ReplaceLineEndings(originalFile, newFile, TextConvertMode.Ux2Dos)
End Function
''' <summary>
''' Loads a whole text file in to memory, Performs a find\replace, and writes a new file.
''' </summary>
''' <param name="originalFile">The file to convert.</param>
''' <param name="newFile">The name of a new file to create.</param>
''' <param name="convertMode">This is the type of conversion we are going to perform</param>
''' <returns>Exit code.</returns>
Private Shared Async Function ReplaceLineEndings(originalFile As String, newFile As String,
convertMode As TextConvertMode) As Task(Of Integer)
Const cr As Char = ChrW(13)
Const lf As Char = ChrW(10)
' Attempt to detect encoding
Dim fileEncoding As Encoding = GetEncoding(originalFile)
If fileEncoding Is Nothing Then Return 4
Debug.Print(fileEncoding.ToString())
Dim convertedText As New StringBuilder
Dim oldFileStream As FileStream = Nothing
Try
oldFileStream = New FileStream(originalFile, FileMode.Open)
Using oldFile As New StreamReader(oldFileStream, fileEncoding, True)
Do Until oldFile.EndOfStream ' Read through the whole file
Dim readBuffer(0) As Char
Dim readChars As Integer = Await oldFile.ReadAsync(readBuffer, 0, 1)
If readChars < 1 Then Exit Do ' Short circuit
Select Case convertMode
Case TextConvertMode.Dos2Ux
If readBuffer(0) = cr AndAlso oldFile.Peek() = 10 Then
' Strip out CR chars if followed by LF
Await oldFile.ReadAsync(readBuffer, 0, 1)
End If
Case TextConvertMode.Ux2Dos
If readBuffer(0) = cr AndAlso oldFile.Peek() = 10 Then
ReDim Preserve readBuffer(1)
' This is a DOS line ending, keep it.
Dim tempBuffer(1) As Char
Await oldFile.ReadAsync(tempBuffer, 0, 1)
readBuffer(1) = tempBuffer(0)
ElseIf readBuffer(0) = lf Then
ReDim readBuffer(1)
' Add preceeding CR
readBuffer(0) = cr
readBuffer(1) = lf
End If
Case Else
Debug.Print("Unimplemented text conversion mode")
Return -1
End Select
convertedText.Append(readBuffer)
Loop
End Using
oldFileStream = Nothing
Catch ex As Exception
Debug.Print("Error: " & ex.Message & Environment.NewLine & "Number: " & ex.HResult.ToString)
Return ex.HResult
Finally
If oldFileStream IsNot Nothing Then oldFileStream.Dispose()
End Try
'Write the result out to a new file
Try
File.WriteAllText(newFile, convertedText.ToString(), New UTF8Encoding(False))
Catch ex As Exception
Debug.Print("Error: " & ex.Message & Environment.NewLine & "Number: " & ex.HResult.ToString)
Return ex.HResult
End Try
Return 0 ' Exit status 0 is a good thing
End Function
''' <summary>
''' Attempt to detect the encoding of a file.
''' </summary>
''' <param name="filename">The file to get the encoding pattern from.</param>
''' <returns>Encoding type, defaults to ASCII.</returns>
Public Shared Function GetEncoding(filename As String) As Encoding
Dim bom = New Byte(3) {}
Try ' to read BOM
Using file = New FileStream(filename, FileMode.Open, FileAccess.Read)
file.Read(bom, 0, 4)
End Using
Catch ex As Exception
Debug.Print("Error: " & ex.Message & Environment.NewLine & "Number: " & ex.HResult.ToString)
Return Nothing
End Try
' Detect BOM type
If bom(0) = &H2B AndAlso bom(1) = &H2F AndAlso bom(2) = &H76 Then Return Encoding.UTF7
If bom(0) = &HEF AndAlso bom(1) = &HBB AndAlso bom(2) = &HBF Then Return Encoding.UTF8
If bom(0) = &HFF AndAlso bom(1) = &HFE Then Return Encoding.Unicode
If bom(0) = &HFE AndAlso bom(1) = &HFF Then Return Encoding.BigEndianUnicode
If bom(0) = 0 AndAlso bom(1) = 0 AndAlso bom(2) = &HFE AndAlso bom(3) = &HFF Then Return Encoding.UTF32
' Default to
Return Encoding.ASCII
End Function
End Class