How to write a text file with specific positions with FileStream? - c#

I am trying to write a text file with this structure
For this I am working with Seek() and Write(), but every time I want to add a new data it is overwritten, and in the end only one line is written to the file
This is the code I am using
private async void CreacionArchivoBCD(ArchivoPerfil contenidoPerfil, string nombreFinalBCD, bool checkPrevia)
{
var result = await ensayoDataProvider.ObtenerUexCapturador(checkPrevia, contenidoPerfil, codensSeleccionado.Id);
var cant = System.Text.Encoding.UTF8.GetBytes(Convert.ToString(result.Count()));
////TextWriter archivoExportar = new StreamWriter(nombreFinalBCD);
////archivoExportar.Close();
var file = new FileStream(nombreFinalBCD, FileMode.OpenOrCreate, FileAccess.Write);
file.Seek(1, SeekOrigin.Begin);
file.Write(cant, 0, cant.Length);
byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine);
file.Write(newline, 0, newline.Length);
int nroMedAux = 0;
long rutaAux = 0;
var cont = 0;
var bandera = 0;
//var posicion = 0;
foreach (var item in result)
{
var primerByte = System.Text.Encoding.UTF8.GetBytes("00");
file.Seek(0, SeekOrigin.Begin);
file.Write(primerByte, 0, primerByte.Length);
if (bandera == 0)
{
nroMedAux = item.IdMed;
rutaAux = item.Ruta;
bandera = 1;
}
var tipo = item.GetType();
if (nroMedAux != item.IdMed || rutaAux != item.Ruta)
{
nroMedAux = item.IdMed;
rutaAux = item.Ruta;
file.Write(newline, 0, newline.Length);
}
foreach (var pi in tipo.GetProperties())
{
var propName = pi.Name;
var propValue = pi.GetValue(item, null)?.ToString();
var propValueAux = propName == "Campo" ? item.GetType().GetProperty("Valor").GetValue(item, null) : propValue;
if (propName == "IdMed" || propName == "Valor")
{
continue;
}
//TODO: buscar posicion y ancho por nombre campo
var posicion = BuscarPosicion(propName, propValue, contenidoPerfil);
//var ancho = BuscarAncho(propName, contenidoPerfil);
Debug.WriteLine(propName);
Debug.WriteLine(propValueAux);
Debug.WriteLine(posicion);
if (posicion == -1)
{
continue;
}
var lacadena = propValue;
var lacadenAux = propName == "Campo" ? propValueAux : propValue;
var cadena = System.Text.Encoding.UTF8.GetBytes(lacadenAux.ToString());
file.Seek(posicion, SeekOrigin.Begin);
file.Write(cadena, 0, cadena.Length);
}
}
file.Close();
}

This is not how you should do it. Since you are writing text to a file, you should use a StreamWriter.
Then to create the column formatting, simply use string.PadLeft (or string.PadRight).
To handle streams (or IDisposable implementations in general) use the using statement. Don't close resources explicitly.
Also use the async API to improve the performance.
The following example shows the pattern you should use:
Generate the data structure (rows)
Format the data
Write all data at once to the file
The algorithm takes different cell value lengths into account to generate even columns.
If you use .NET Standard 2.1 (.NET Core 3.0, .NET 5) you can even make use of IAsyncDisposable.
private async Task WriteDataTableToFileAsync(string filePath)
{
List<List<string>> rows = GenerateData();
string fileContent = FormatData("4080", rows, 4);
await WriteToFileAsync(fileContent, filePath);
}
private List<List<string>> GenerateData()
{
// Generate the complete data first
var rows = new List<List<string>>
{
// Row 1
new List<string>
{
// Cells
"00",
"1",
"LD126",
"NN",
"1",
"0",
"0 0",
"49",
"2"
},
// Row 2
new List<string>
{
// Cells
"00",
"1",
"Rell",
"NN",
"1",
"0",
"0 0",
"49",
"2"
}
};
return rows;
}
private string FormatData(string preamble, List<List<string>> rows, int columnGapWidth)
{
preamble = preamble.PadLeft(preamble.Length + 1) + Environment.NewLine;
var fileContentBuilder = new StringBuilder(preamble);
var columnWidths = new Dictionary<int, int>();
int columnCount = rows.First().Count;
foreach (List<string> cells in rows)
{
var rowBuilder = new StringBuilder();
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
if (!columnWidths.TryGetValue(columnIndex, out int columnWidth))
{
int maxCellWidthOfColumn = rows
.Select(cells => cells[columnIndex])
.Max(cell => cell.Length);
columnWidth = maxCellWidthOfColumn + columnGapWidth;
columnWidths.Add(columnIndex, columnWidth);
}
string cell = cells[columnIndex];
rowBuilder.Append(cell.PadRight(columnWidth));
}
fileContentBuilder.Append(rowBuilder.ToString().TrimEnd());
fileContentBuilder.Append(Environment.NewLine);
}
string fileContent = fileContentBuilder.ToString().TrimEnd();
return fileContent;
}
private async Task WriteToFileAsync(string fileContent, string filePath)
{
await using var destinationFileStream = File.Open(filePath, FileMode.Create, FileAccess.Write);
await using var streamWriter = new StreamWriter(destinationFileStream);
await streamWriter.WriteAsync(fileContent);
}
You can further improve the implementation: add a Data, Row and a Cell class. The Row class has a Cell collection. The Data class has a Row collection and additional data like the preamble or formatting info like the column gap width. This way you eliminate method parameters to improve readability. Then replace the current List<List<string>> data structure with the Data class.

Before file.Close(), add file.Flush() or await file.FlushAsync() -> this flushes the buffer to the output.
See https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.flush?view=net-6.0.

Related

Accessing dictionary value element when there are multiple values

foreach (var file in d.GetFiles("*.csv"))
{
using (var reader = new StreamReader(file.FullName))
{
List<string> columns = new List<string>();
string colLine = File.ReadLines(file.FullName).First(); // gets the first line from file.
columns = colLine.Split(',').ToList();
reader.ReadLine(); // skip first line
dynamic x = new ExpandoObject();
var eoa = (IDictionary<string, object>)x;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
for (int idx = 0; idx < values.Length; idx++)
{
if (values[idx] == "\"\"")
{
values[idx] = "[BLANK]";
}
}
int i = 0;
foreach (var value in values)
{
List<string> val = new List<string>();
val.Add(value);
if (!eoa.ContainsKey(columns.ElementAt(i)))
{
eoa.Add(columns.ElementAt(i), val);
}
else
{
List<string> overwrite = new List<string>();
var curr = eoa[columns.ElementAt(i)];
foreach (var v in (dynamic)curr)
{
overwrite.Add(v);
}
overwrite.Add(value);
eoa[columns.ElementAt(i)] = overwrite;
}
i++;
}
}
using (StreamWriter outFile = new StreamWriter(path + #"\output.txt"))
{
foreach (var entry in eoa)
{
var test = entry.Value as List;
outFile.Write("{0}:{1} ", entry.Key, entry.Value);
}
}
}
}
I'm currently unable to get the value of an index belonging to the test variable. I try doing test[0] but get the error "Cannot apply indexing with [] to an expression of type 'object'. I am curious what other ways I can attempt so that these list elements can be accessed which belong to the Value of my dictionary.
Your value seems to be of type object, and as the error suggests you can't directly apply indexing to it. You have to cast it to a List<string> to use test[0].

C#: Write Each n items from a list to a text file

I have a list of N items in a list.
var jobIdArray = new List<string>();
jobIdArray.Add(doc.FieldValues(x => x.JobId).FirstOrDefault()); // the count varies as per the result, 16000, 26000, 33598 etc
Now I need to write each 15000 into a separate single txt file. Like
for 16000 -> 15000 + 1000 for 26000 -> 15000 + 11000 for 33598 ->
15000 + 3598
I have a program like,
jobIdArray.Add(doc.FieldValues(x => x.JobId).FirstOrDefault());
totalCount++;
// for each totalCount === 15000 {write content to excel file, totalCount == 0}
if (totalCount == 15000) // Here i need some generic condition which suits for all number of results
{
var fileLoc = #"F:\WindowsApplication" + new Guid()+".txt";
//File.Create(fileLoc);
using (var fs = File.Create(fileLoc))
{
foreach (var item in jobIdArray)
{
StreamWriter sw = new StreamWriter(fs);
sw.Write(item);
}
jobIdArray.Clear();
totalCount = 0;
fs.Close();
}
}
Any help for coding that generic condition to create text file for every 15000 would be more helpful.
Try using Linq:
void WriteGroupedItems(List<string> items,int numOfElementsPerGroup)
{
var index = 0;
var groupedItems = items.GroupBy(x => index++ / numOfElementsPerGroup);
foreach(var groupedItem in groupedItems )
{
WriteItemsToFile(groupedItem);
}
}
now your WriteItemsToFile may have similar logic you used, something like this:
void WriteItemsToFile(IEnumerable<string> list)
{
var fileLoc = #"F:\WindowsApplication" + new Guid()+".txt";
using (var fs = File.Create(fileLoc))
{
foreach (var item in list)
{
StreamWriter sw = new StreamWriter(fs);
sw.Write(item);
}
jobIdArray.Clear();
totalCount = 0;
fs.Close();
}
}
You could try to iterate through your array in blocks of 15000 and then write all elements that are not emptry to the file. I didn't test it so if it's not working then just let me know and i try to figure something out.
var jobIdArray = new List<string>();
jobIdArray.Add(doc.FieldValues(x => x.JobId).FirstOrDefault());
for (int i = 0; i < jobIdArray; i += 15000)
{
var fileLoc = #"F:\WindowsApplication" + new Guid() + ".txt";
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileLoc))
{
for (int x = i; x < 15000; i++)
{
if (!String.IsNullOrEmpty(jobIdArray[x]))
{
sw.WriteLine(jobIdArray[x]);
}
}
}
}

Text file to two string arrays in wpf using streamreader

I'm trying to read a text file to two string arrays. Array1 is to be all the odd lines, array2 all the even lines. I then add all the items of array1 to a combobox and when that is selected, or as it gets typed, outputs array2 to a textbox.
So far, I have tried a few methods from here, but the big issue seems to be creating the arrays. I tried to get help here before, but the answers didn't actually answer my question. They must be arrays, not lists (which I tried and worked well). I am really confused by this whole thing and my attempted code is now rubbish:
private void ReadFile(string filePath, string customerPhone, string customerName)
{
string line = string.Empty;
var fileSR = new StreamReader(filePath);
bool number = true;
while((line = fileSR.ReadLine()) != null)
{
if (number)
{
customerPhone(line);
number = false;
}
else
{
customerName(line);
number = true;
}
}
fileSR.Close();
}
I'm losing confidence in this whole process, but I need to find a way to make it work, then I can learn why it does.
You are almost there, just use the List<string>.
private void ReadFile(string filePath, string customerPhone, string customerName)
{
string line = string.Empty;
using (var fileSR = new StreamReader(filePath))
{
bool number = true;
List<string> customerPhone = new List<string>();
List<string> customerName = new List<string>();
while((line = fileSR.ReadLine()) != null)
{
if (number)
{
customerPhone.Add(line);
number = false;
}
else
{
customerName.Add(line);
number = true;
}
}
fileSR.Close();
}
}
If you are interested only in Arrays, you could simply call customerName.ToArray() to convert it to an array.
Linq Solution
Alternatively you could use Linq and do this.
var bothArrays = File.ReadLines("filepath") // Read All lines
.Select((line,index) => new {line, index+1}) // index each line
.GroupBy(x=> x/2) // Convert into two groups
.SelectMany(x=> x.Select(s=>s.line).ToArray()) // Convert it to array
.ToArray();
You should use collections to return data, say IList<String>:
private static void ReadFile(String filePath,
IList<String> oddLines,
IList<String> evenLines) {
oddLines.Clear();
evenLines.Clear();
int index = 1; //TODO: start with 0 or with 1
foreach (String line in File.ReadLines(filePath)) {
if (index % 2 == 0)
evenLines.Add(line);
else
oddLines.Add(line);
index += 1;
}
}
using
List<String> names = new List<String>();
List<String> phones = new List<String>();
ReadFile(#"C:\MyDate.txt", names, phones);
// If you want array representation
String[] myNames = names.ToArray();
String[] myPhones = phones.ToArray();
// Let's print out names
Console.Write(String.Join(Envrironment.NewLine, names));
Please, notice, that using File.ReadLines usually more convenient than StreamReader which should be wrapped in using:
// foreach (String line in File.ReadLines(filePath)) equals to
using (var fileSR = new StreamReader(filePath)) {
while ((line = fileSR.ReadLine()) != null) {
...
}
}
This worked! I have these class level strings:
string cFileName = "customer.txt";
string[] cName = new string[0];
string[] cPhone = new string[0];
And then this in the Window Loaded event, but could be used in it's own method:
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
//read file on start
int counter = 0;
string line;
StreamReader custSR = new StreamReader(cFileName);
line = custSR.ReadLine();
while (custSR.Peek() != -1)
{
Array.Resize(ref cPhone, cPhone.Length + 1);
cPhone[cPhone.Length - 1] = line;
counter++;
line = custSR.ReadLine();
Array.Resize(ref cName, cName.Length + 1);
cName[cName.Length - 1] = line;
counter++;
line = custSR.ReadLine();
phoneComboBox.Items.Add(cPhone[cPhone.Length - 1]);
}
custSR.Close();
//focus when program starts
phoneComboBox.Focus();
}

Displaying File lines in separate rows and each word on separate column using UWP C# XAML

What should I do in order to display all file's lines on separate rows in XAML?
I have such code:
public async void ReadFile()
{
var path = #"CPU.xls";
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path);
var readFile = await Windows.Storage.FileIO.ReadLinesAsync(file);
foreach (string line in readFile.OrderBy(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[4], out lineNo);
if (success) return lineNo;
return int.MaxValue;
}))
{
string[] splitLines = line.Split(';');
ObservableCollection<ItemsData> items = new ObservableCollection<ItemsData>();
for (int index = 0; index < splitLines.Length; index++)
{
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
}
itemsControl.DataContext = items;
}
}
Unfortunately, instead of getting each line on each new row, I get the same line five times.
I get something like:
John 24 IT
John 24 IT
John 24 IT
instead of getting:
John 24 IT
Mike 14 GB
Steve 22 RU
You're replacing your item at each occurrence of the loop.
First, create your collection outside of the loop:
var items = new ObservableCollection<ItemsData>();
foreach (string line in readFile...
Inside of the loop, remove the for, since you don't actually use it:
string[] splitLines = line.Split(';');
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
Then, when you exit the foreach loop, set the DataContext:
itemsControl.DataContext = items;
The final code would look like:
public async void ReadFile()
{
var path = #"CPU.xls";
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
var file = await folder.GetFileAsync(path);
var readFile = await Windows.Storage.FileIO.ReadLinesAsync(file);
var items = new ObservableCollection<ItemsData>();
foreach (string line in readFile.OrderBy(line =>
{
int lineNo;
var success = int.TryParse(line.Split(';')[4], out lineNo);
if (success) return lineNo;
return int.MaxValue;
}))
{
string[] splitLines = line.Split(';');
ItemsData dataitem = new ItemsData
{
value0 = splitLines[0],
value1 = splitLines[1],
value2 = splitLines[2],
value3 = splitLines[3],
value4 = splitLines[4],
};
items.Add(dataitem);
}
itemsControl.DataContext = items;
}

Clearing a line of a txt file by the line ID

I have looked all over for the answer to this, but I can't find it anywhere. I need to be able to clear a line from a txt file by the last integer in the line (the ID number), but I have no idea how to do that. Please help? Basically I was thinking that I need to find the last integer, and if it does not equal to the input, then it would move to the next line until it finds the right integer. Then that line is cleared. Here is some of my code that obviously doesn't work:
public static void TicID(CommandArgs args)
{
if (args.Parameters.Count == 1)
{
if (i == 1)
{
try
{
string idToDelete = args.Parameters[0];
StreamReader idreader = new StreamReader("Tickets.txt");
StreamWriter iddeleter = new StreamWriter("Tickets.txt");
string id = Convert.ToString(idreader.Read());
string line = null;
while (idreader.Peek() >= 0)
{
if (String.Compare(id, idToDelete) == 0)
{
iddeleter.WriteLine(line);
}
else
{
idreader.ReadLine();
}
}
}
The most straightforward way to delete lines is to write the lines that should not be deleted:
var idToDelete = "1";
var path = #"C:\Temp\Test.txt";
var lines = File.ReadAllLines(path);
using (var writer = new StreamWriter(path, false)) {
for (var i = 0; i < lines.Length; i++) {
var line = lines[i];
//assuming it's a CSV file
var cols = line.Split(',');
var id = cols[cols.Length - 1];
if (id != idToDelete) {
writer.WriteLine(line);
}
}
}
This is the LINQ-way:
var lines = from line in File.ReadAllLines(path)
let cols = line.Split(',')
let id = cols[cols.Length - 1]
where id != idToDelete
select line;
File.WriteAllLines(path, lines);
Create a StreamReader object and read line by line into a string array or something like that using StreamReader's instance method ReadLine() and now find your line of choice in your text array and delete it.
Important note:
do { /*see description above*/ } while (streamReader.Peak() != -1);

Categories