I was searching the web but failed to find the correct example.
The goal is to have a function:
private void InsertLine(string source, string position, string content)
And write to a file using the StreamWriter so you do not need to read all lines because I the file is potentially huge.
My function so far:
private void InsertLine(string source, string position, string content)
{
if (!File.Exists(source))
throw new Exception(String.Format("Source:{0} does not exsists", source));
var pos = GetPosition(position);
int line_number = 0;
string line;
using (var fs = File.Open(source, FileMode.Open, FileAccess.ReadWrite))
{
var destinationReader = new StreamReader(fs);
var writer = new StreamWriter(fs);
while (( line = destinationReader.ReadLine()) != null)
{
if (line_number == pos)
{
writer.WriteLine(content);
break;
}
line_number++;
}
}
}
The function does not work in the file as nothing happens.
You can't just insert a line into a file. A file is a sequence of bytes.
You need to:
Write all of the preceding lines
Write the line to be inserted
Write all of the following lines
Here's some untested code based upon yours:
private void InsertLine(string source, string position, string content)
{
if (!File.Exists(source))
throw new Exception(String.Format("Source:{0} does not exsists", source));
// I don't know what all of this is for....
var pos = GetPosition(position);
int line_number = 0;
string line;
using (var fs = File.Open(source, FileMode.Open, FileAccess.ReadWrite))
{
var destinationReader = new StreamReader(fs);
var writer = new StreamWriter(fs);
while (( line = destinationReader.ReadLine()) != null)
{
writer.WriteLine(line); // ADDED: You need to write every original line
if (line_number == pos)
{
writer.WriteLine(content);
// REMOVED the break; here. You need to write all following lines
}
line_number++; // MOVED this out of the if {}. Always count lines.
}
}
}
This probably won't work as expected, however. You're trying to write to the same file you're reading. You should open a new (temporary) file, perform the copy + insert, and then move/rename the temporary file to replace the original file.
Related
I have a text file containing some information that I want to edit. The file looks something like this:
id: 31
name: Anna
profession: Doctor
I can read that entry with a StreamReader, and present it in my application. I then want the user to be able to change the name and profession of an entry, so I'd like to edit these specific rows to the new values, while keeping id intact (in my real code, there's not just a few rows, but a lot of rows where only some should be changed). So, for example, I want the file, at the end of my operation, to look like this.
id: 31
name: Emma
profession: Programmer
However, I also have to take into account that sometimes the rows don't exist beforehand. For example, before editing Anna to Emma, it's not certain that she had a profession, the file could have looked like this:
id: 31
name: Anna
And in that case, I want to add the line profession: Programmerto the end there.
I tried using a FileStreamwith ReadWrite access, that I give to a StreamReader and a StreamWriter, but then I found no way of changing or replacing a line of text, only reading it and writing a new identical line while keeping the old.
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
using (StreamReader reader = new StreamReader(fileStream))
using (StreamWriter writer = new StreamWriter(fileStream))
{
bool idExists = false;
bool nameExists = false;
bool tagsExist = false;
string line;
while((line = reader.ReadLine()) != null)
{
if (line.StartsWith("id:"))
idExists = true;
else if (line.StartsWith("name:"))
{
nameExists = true;
line = $"name: {entryToSave.Name}";
writer.WriteLine(line); // Will write an additional line and not replace
}
else if (line.StartsWith("profession:"))
{
professionExists = true;
line = $"profession: {entryToSave.Profession}";
writer.WriteLine(line); // Will write an additional line and not replace
}
}
if (!idExists)
writer.WriteLine($"id: {generatedId}");
if (!nameExists)
writer.WriteLine($"name: {entryToSave.Name}");
if (!professionExists)
writer.WriteLine($"profession: {entryToSave.Profession}");
}
I also tried using File.ReadAllLines, loop through the lines, and then write back all the lines to the file, only modifying the lines that were to be modified. However, I don't have access to the file through File.WriteAllLines for some reason that I don't understand, as a StreamWriter has access. Code:
var previousData = File.ReadAllLines(filePath);
var newData = new List<string>();
bool idExists = false;
bool nameExists = false;
bool professionExists = false;
for (int i = 0; i < previousData.Length; i++)
{
var line = previousData[i];
if (line.StartsWith("id:")
idExists = true;
else if (line.StartsWith("name:")
{
nameExists = true;
line = $"name: {entryToSave.Name}";
}
else if (line.StartsWith("profession:"))
{
professionExists = true;
line = $"profession: {entryToSave.Profession}";
}
newData.Add(line);
}
if (!idExists)
newData.Add($"id: {generatedId}");
if (!nameExists)
newData.Add($"name: {entryToSave.Name}");
if (!professionExists)
newData.Add($"profession: {entryToSave.Profession}");
File.WriteAllLines(filePath, newData.ToArray()); // Access denied
How is this most easily achieved, without file streams interfering with each other?
If you've already presented the data to the user in entries, enabling the user to edit name and profession, you could just read the file, get the ID and fill the remainder of the file with the value of the entries. The following is an example console application.
static void Main(string[] args)
{
var filePath = "test.txt";
// Simulated input from user
// these should come from entries in the application?
var name = "Foo";
var profession = "Bar";
var personData = new PersonData(); // class declared below
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
using (StreamReader reader = new StreamReader(fileStream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line.StartsWith("id:"))
personData.ID = line;
}
} // Now reader and filestream is closed, file is available again.
// You don't specify what you would like to happen if personData.ID is null,
// so I make an assumption the generatedId is what you'd like to use.
if (string.IsNullOrWhiteSpace(personData.ID)
personData.ID = $"id: {generatedId}";
// Add the data from the entries
personData.Name = $"name: {name}";
personData.Profession = $"profession: {profession}";
File.Delete(filePath); // remove the file
using (FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (StreamWriter writer = new StreamWriter(fileStream))
{
writer.WriteLine(personData.ID);
writer.WriteLine(personData.Name);
writer.WriteLine(personData.Profession);
}
}
private class PersonData
{
public string ID { get; set; }
public string Name { get; set; }
public string Profession { get; set; }
}
Now you just have to find out how to get access to the file if you're having permission problems.
I have a txt file with data such as the following:
:10FF800000040B4E00040B4E00047D1400047D148D
:10FF900000040B4E0004CF6200040B4E00040B4E15
:10FFA00000040B4E00040B4E00040B4E00040B4EDD
:10FFB00000047D1400047D1400047D1400047D14ED
:10FFC00000040B4E000000000000000000000000D4
:10FFD0000000000000040B4E0000000000000000C4
:10FFE0000000000000000000000000000000000011
:10FFF0000000000000000000060000000000BFF844
:020000020000FC
:020000040014E6
:043FF0005AC8A58C7A
:00000001FF
what I want to do with my C# program is to add a line after or before a specific line, lets say add the line:
:020000098723060
before this line:
:020000020000FC
I have tried using the File.ReadLines("file.txt").Last(); but that just gives me the last one, what if i want the third or fourth? also, is there any way to identify the ":" in the file?
The simplest way - if you're happy to read the whole file into memory - would be just:
public void InsertLineBefore(string file, string lineToFind, string lineToInsert)
{
List<string> lines = File.ReadLines(file).ToList();
int index = lines.IndexOf(lineToFind);
// TODO: Validation (if index is -1, we couldn't find it)
lines.Insert(index, lineToInsert);
File.WriteAllLines(file, lines);
}
public void InsertLineAfter(string file, string lineToFind, string lineToInsert)
{
List<string> lines = File.ReadLines(file).ToList();
int index = lines.IndexOf(lineToFind);
// TODO: Validation (if index is -1, we couldn't find it)
lines.Insert(index + 1, lineToInsert);
File.WriteAllLines(file, lines);
}
There are significantly more efficient ways of doing this, but this approach is really simple.
A brute force approach
string[] lines = File.ReadAllLines("file.txt");
using(StreamWrite sw = new StreamWriter("file.txt"))
{
foreach(string line in lines)
{
if(line == ":020000020000FC")
sw.WriteLine(":020000098723060");
sw.WriteLine(line);
}
}
I would say it is better to read and write line by line, especially if the target file tend to be of large size:
using (StreamReader r = new StreamReader("Test.txt"))
{
using (StreamWriter w = new StreamWriter("TestOut.txt"))
{
while (!r.EndOfStream)
{
string line = r.ReadLine();
w.WriteLine(line);
if (line == ":020000020000FC")
w.WriteLine(":020000098723060");
}
w.Close();
r.Close();
}
}
Not sure if you're trying to avoid reading the entire file in due to size, etc...but can't you just read the file and then replace...e.g.
var text = readFile(somePath);
writeFile( text.replace(":020000020000FC\n",":020000098723060\n:020000020000FC\n") , somePath);
Here is a solution, though it may not be the best, it does work:
public void AddTextToFile(string filePath, int lineNumber, string txt) //zero based lineNumber
{
Collection<string> newLines = new Collection<string>(File.ReadAllLines(filePath).ToList());
if (lineNumber < newLines.Count)
newLines.Insert(lineNumber, txt);
else
newLines.Add(txt);
using (StreamWriter writer = new StreamWriter(filePath, false))
{
foreach (string s in newLines)
writer.WriteLine(s);
}
}
And to answer your question about determining if ":" exists in a string, the answer is yes, in the example above, you could check if the line contains it by...
if(newLines[idx].Contains(':'))
//do something
The ":" character doesn't really help the implementation, the lines are all newline-delimited already.
Here's an attempt at a method that doesn't load it all to memory or output to a different file.
Never cross the streams.
static Int32 GetCharPos(StreamReader s)
{
var ia = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
Int32 charpos = (Int32)s.GetType().InvokeMember("charPos", ia, null, s, null);
Int32 charlen = (Int32)s.GetType().InvokeMember("charLen", ia, null, s, null);
return (Int32)s.BaseStream.Position - charlen + charpos;
}
static void Appsert(string data, string precedingEntry = null)
{
if (precedingEntry == null)
{
using (var filestream = new FileStream(dataPath, FileMode.Append))
using (var tw = new StreamWriter(filestream))
{
tw.WriteLine(data);
return;
}
}
int seekPos = -1;
using (var readstream = new FileStream(dataPath,
FileMode.Open, FileAccess.Read, FileShare.Write))
using (var writestream = new FileStream(dataPath,
FileMode.Open, FileAccess.Write, FileShare.Read))
using (var tr = new StreamReader(readstream))
{
while (seekPos == -1)
{
var line = tr.ReadLine();
if (line == precedingEntry)
seekPos = GetCharPos(tr);
else if (tr.EndOfStream)
seekPos = (int)readstream.Length;
}
writestream.Seek(seekPos, SeekOrigin.Begin);
readstream.Seek(seekPos, SeekOrigin.Begin);
int readLength = 0;
var readBuffer = new byte[4096];
var writeBuffer = new byte[4096];
var writeData = tr.CurrentEncoding.GetBytes(data + Environment.NewLine);
int writeLength = writeData.Length;
writeData.CopyTo(writeBuffer, 0);
while (true & writeLength > 0)
{
readLength = readstream.Read(readBuffer, 0, readBuffer.Length);
writestream.Write(writeBuffer, 0, writeLength);
var tmp = writeBuffer;
writeBuffer = readBuffer;
writeLength = readLength;
readBuffer = tmp;
}
}
}
i have this code that compares two text files and write the difference to a log file but for some reason the log.txt file is some times blank even when is test with some lines starting with a * these are not always written either do I have to save the text file when finished writing although this does not explain why sometimes it works any help would be great
private void compare()
{
string FilePath = #"c:\snapshot\1.txt";
string Filepath2 = #"c:\snapshot\2.txt";
int counter = 0;
string line;
string line2;
var dir = "c:\\snapshot\\log.txt";
using (FileStream fs = File.Create(dir))
{
fs.Dispose();
}
StreamWriter dest = new StreamWriter(dir);
if (File.Exists(FilePath) & File.Exists(Filepath2))
{
// Read the file and display it line by line.
using (var file = File.OpenText(FilePath))
using (var file2 = File.OpenText(Filepath2))
{
while (((line = file.ReadLine()) != null & (line2 = file2.ReadLine()) != null))
{
if (line.Contains("*"))
{
dest.WriteLine(line2);
}
else if (!line.Contains(line2))
{
dest.WriteLine(line2);
}
counter++;
}
}
}
dest.Close();
}
Everything left in the buffer should be written out once you hit the close statement on your StreamReader. If you are missing stuff then it might be that you aren't reaching that line for some reason (i.e. you crash). Also, if you are trying to look at the file while it's being written (i.e. while the program is still running), you won't necessarily see everything (since it hasn't closed).
Generally, it's better to use a using statement with the StreamReader. That should ensure that it always gets closed.
private void compare()
{
string FileName1 = #"c:\snapshot\1.txt";
string FileName2 = #"c:\snapshot\2.txt";
string FileNameOutput = #"c:\snapshot\log.txt"; //dir ???
int counter = 0; // um what's this for you aren't using it.
using (FileStream fso = new FileStream(FileNameOutput, FileMode.Create, FileAccess.Write))
{
TextWriter dest = new StreamWriter(fso);
using(FileStream fs1 = new FileStream(FileName1, FileMode.Open, FileAccess.Read))
{
using (FileStream fs2 = new FileStream(FileName2, FileMode.Open, FileAccess.Read))
{
TextReader firstFile = new StreamReader(fs1);
TextReader secondFile = new StreamReader(fs2);
while (((line1 = firstFile.ReadLine()) != null & (line2 = secondFile.ReadLine()) != null))
{
if ((line1.Contains("*") || (!line1.Contains(line2)))
{
dest.Write(line2); // Writeline would give you an extra line?
}
counter++; //
}
}
}
fso.Flush();
}
I commend the overloads of FileStream to you. Do it the way I have and the code will crash at the point you instance the stream if the user running it doesn't have all the required permissions. It's a nice way of showing what you intend, and what you don't.
PS You do know contains is case and culture sensitive?
Not sure if I'm understanding your comparing logic right, but as long as I separated comparing from whole code you can adjust it to your own needs:
public static void WriteDifferences(string sourcePath, string destinationPath, string differencesPath)
{
var sourceLines = File.ReadAllLines(sourcePath).ToList();
var destinationLines = File.ReadAllLines(destinationPath).ToList();
// make lists equal size
if (sourceLines.Count > destinationLines.Count)
{
destinationLines.AddRange(Enumerable.Range(0, sourceLines.Count - destinationLines.Count).Select(x => (string)null));
}
else
{
sourceLines.AddRange(Enumerable.Range(0, destinationLines.Count - sourceLines.Count).Select(x => (string)null));
}
var differences = sourceLines.Zip(destinationLines, (source, destination) => Compare(source, destination));
File.WriteAllLines(differencesPath, differences.Where(x => x != null));
}
private static string Compare(string source, string destination)
{
return !source.Contains(destination) || source.Contains("*") ? destination : null;
}
I am trying to go line by line through text file using stream reader, and when I encounter specific text, I want to add a new line into that file. But when I do so, next line's first part(about half) gets cut off. I want to do that only once, that's why "skip" variable is there. Here is what I am doing:
var fileStream = new FileStream(fileName, FileMode.Open);
var writer = new StreamWriter(fileStream);
var reader = new StreamReader(fileName);
string line;
var skip = false;
using (writer) {
using (reader) {
while ((line = reader.ReadLine()) != null) {
if (line.Contains("some text") && !skip) {
writer.WriteLine("some text something else");
skip = true;
}
}
}
}
fileStream.Close();
Am I doing something wrong here?
EDIT: Aargh, I've just seen that you're trying to read and write to the same file.
You shouldn't do that - it's replacing the existing text, rather than inserting extra data. You can't just insert data within a file - that's not how file systems work.
Instead, read from one file, write to another file, and then move the files around afterwards if you need to.
I'd also suggest that you have using statements for the whole scope of a variable, rather than using them the way you have here. Something like this:
void CopyAndEdit(string inputFile, string outputFile)
{
// In .NET 4 you can use File.ReadLines which will
// return an IEnumerable<string>
using (TextReader reader = File.OpenText(inputFile))
{
using (TextWriter writer = File.CreateText(outputFile))
{
bool editing = true;
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line);
if (editing && line.Contains("some text"))
{
editing = false;
writer.WriteLine("some text something else");
}
}
}
}
}
I have two text files, Source.txt and Target.txt. The source will never be modified and contain N lines of text. So, I want to delete a specific line of text in Target.txt, and replace by an specific line of text from Source.txt, I know what number of line I need, actually is the line number 2, both files.
I haven something like this:
string line = string.Empty;
int line_number = 1;
int line_to_edit = 2;
using StreamReader reader = new StreamReader(#"C:\target.xml");
using StreamWriter writer = new StreamWriter(#"C:\target.xml");
while ((line = reader.ReadLine()) != null)
{
if (line_number == line_to_edit)
writer.WriteLine(line);
line_number++;
}
But when I open the Writer, the target file get erased, it writes the lines, but, when opened, the target file only contains the copied lines, the rest get lost.
What can I do?
the easiest way is :
static void lineChanger(string newText, string fileName, int line_to_edit)
{
string[] arrLine = File.ReadAllLines(fileName);
arrLine[line_to_edit - 1] = newText;
File.WriteAllLines(fileName, arrLine);
}
usage :
lineChanger("new content for this line" , "sample.text" , 34);
You can't rewrite a line without rewriting the entire file (unless the lines happen to be the same length). If your files are small then reading the entire target file into memory and then writing it out again might make sense. You can do that like this:
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
int line_to_edit = 2; // Warning: 1-based indexing!
string sourceFile = "source.txt";
string destinationFile = "target.txt";
// Read the appropriate line from the file.
string lineToWrite = null;
using (StreamReader reader = new StreamReader(sourceFile))
{
for (int i = 1; i <= line_to_edit; ++i)
lineToWrite = reader.ReadLine();
}
if (lineToWrite == null)
throw new InvalidDataException("Line does not exist in " + sourceFile);
// Read the old file.
string[] lines = File.ReadAllLines(destinationFile);
// Write the new file over the old file.
using (StreamWriter writer = new StreamWriter(destinationFile))
{
for (int currentLine = 1; currentLine <= lines.Length; ++currentLine)
{
if (currentLine == line_to_edit)
{
writer.WriteLine(lineToWrite);
}
else
{
writer.WriteLine(lines[currentLine - 1]);
}
}
}
}
}
If your files are large it would be better to create a new file so that you can read streaming from one file while you write to the other. This means that you don't need to have the whole file in memory at once. You can do that like this:
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
int line_to_edit = 2;
string sourceFile = "source.txt";
string destinationFile = "target.txt";
string tempFile = "target2.txt";
// Read the appropriate line from the file.
string lineToWrite = null;
using (StreamReader reader = new StreamReader(sourceFile))
{
for (int i = 1; i <= line_to_edit; ++i)
lineToWrite = reader.ReadLine();
}
if (lineToWrite == null)
throw new InvalidDataException("Line does not exist in " + sourceFile);
// Read from the target file and write to a new file.
int line_number = 1;
string line = null;
using (StreamReader reader = new StreamReader(destinationFile))
using (StreamWriter writer = new StreamWriter(tempFile))
{
while ((line = reader.ReadLine()) != null)
{
if (line_number == line_to_edit)
{
writer.WriteLine(lineToWrite);
}
else
{
writer.WriteLine(line);
}
line_number++;
}
}
// TODO: Delete the old file and replace it with the new file here.
}
}
You can afterwards move the file once you are sure that the write operation has succeeded (no excecption was thrown and the writer is closed).
Note that in both cases it is a bit confusing that you are using 1-based indexing for your line numbers. It might make more sense in your code to use 0-based indexing. You can have 1-based index in your user interface to your program if you wish, but convert it to a 0-indexed before sending it further.
Also, a disadvantage of directly overwriting the old file with the new file is that if it fails halfway through then you might permanently lose whatever data wasn't written. By writing to a third file first you only delete the original data after you are sure that you have another (corrected) copy of it, so you can recover the data if the computer crashes halfway through.
A final remark: I noticed that your files had an xml extension. You might want to consider if it makes more sense for you to use an XML parser to modify the contents of the files instead of replacing specific lines.
When you create a StreamWriter it always create a file from scratch, you will have to create a third file and copy from target and replace what you need, and then replace the old one.
But as I can see what you need is XML manipulation, you might want to use XmlDocument and modify your file using Xpath.
You need to Open the output file for write access rather than using a new StreamReader, which always overwrites the output file.
StreamWriter stm = null;
fi = new FileInfo(#"C:\target.xml");
if (fi.Exists)
stm = fi.OpenWrite();
Of course, you will still have to seek to the correct line in the output file, which will be hard since you can't read from it, so unless you already KNOW the byte offset to seek to, you probably really want read/write access.
FileStream stm = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
with this stream, you can read until you get to the point where you want to make changes, then write. Keep in mind that you are writing bytes, not lines, so to overwrite a line you will need to write the same number of characters as the line you want to change.
I guess the below should work (instead of the writer part from your example). I'm unfortunately with no build environment so It's from memory but I hope it helps
using (var fs = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite)))
{
var destinationReader = StreamReader(fs);
var writer = StreamWriter(fs);
while ((line = reader.ReadLine()) != null)
{
if (line_number == line_to_edit)
{
writer.WriteLine(lineToWrite);
}
else
{
destinationReader .ReadLine();
}
line_number++;
}
}
The solution works fine. But I need to change single-line text when the same text is in multiple places. For this, need to define a trackText to start finding after that text and finally change oldText with newText.
private int FindLineNumber(string fileName, string trackText, string oldText, string newText)
{
int lineNumber = 0;
string[] textLine = System.IO.File.ReadAllLines(fileName);
for (int i = 0; i< textLine.Length;i++)
{
if (textLine[i].Contains(trackText)) //start finding matching text after.
traced = true;
if (traced)
if (textLine[i].Contains(oldText)) // Match text
{
textLine[i] = newText; // replace text with new one.
traced = false;
System.IO.File.WriteAllLines(fileName, textLine);
lineNumber = i;
break; //go out from loop
}
}
return lineNumber
}