I want to open file , search for string , replace the string and then in the end print from that replaced string until the end.
So far I have opened the file using
fstream file ("demo.cpp");
Used
while (getline (file,line))
but using
string.find("something")
always give me line number 0, no matter which string I put in the arguments for find()
My question is, is there any other built in function which I can use in this situation or do I have to search through all lines, manually ?
To get a line number where the match occurs, you have to count the lines:
if (ifstream file("demo.cpp")) {
int line_counter = 0;
string line;
while (getline (file,line) {
line_counter++;
if (line.find("something") != string::npos) {
cout << 'Match at line ' << line_counter << endl;
}
}
} else {
cerr << "couldn't open input file\n";
}
You can use this replaceAll function on every line , and have your "replace in file function" built upon it.
I modified it to return a boolean if a replacement occurred :
bool replaceAll(std::string& str, const std::string& from, const std::string& to) {
if(from.empty())
return false;
size_t start_pos = 0;
bool res = false;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
res = true;
}
return res;
}
Then, what is left to you is :
Iterate over all the lines of the input file
Find and replace every occurrence of your string on the file
If a replacement occurred, write the line count
Write the replaced string on another file
The ReplaceInFile method :
void replaceInFile(const std::string& srcFile, const std::string& destFile,
const std::string& search, const std::string& replace)
{
std::ofstream resultFile(destFile);
std::ifstream file(srcFile);
std::string line;
std::size_t lineCount = 0;
// You might want to chech that the files are properly opened
while(getline(file,line))
{
++lineCount;
if(replaceAll(line, search, replace))
std::cout << "Match at line " << lineCount << " " << replace << std::endl;
resultFile << line << std::endl;
}
}
Example:
int main()
{
replaceInFile("sourceFile.txt", "replaced.txt", "match", "replace");
return 0;
}
Related
I wrote a program that takes a file and reads it into a stringstream field in a class, and now I'm trying to interact with it. The problem is that when reading sequentially from several methods, one of the methods gives an error, or simply does not work. I guess the problem is how I read the file, how should I improve it?
There is my class:
class MatReader
{
protected:
...
stringstream text;
...
string PhysicsMaterial;
string Diffuse;
string NMap;
string Specular;
public:
/// <summary>
/// Read all lines in .mat document by string
/// </summary>
void ReadAllLines(string file_path);
/// <summary>
/// Getting PhysicsMaterial property
/// </summary>
string getPhysMaterial();
/// <summary>
/// Getting diffuse file path
/// </summary>
string getDiffuseLocation();
};
And there is my implementation file:
#include "MaterialHandler.h"
void MatReader::ReadAllLines(string mat_file)
{
ifstream infile(mat_file);
string str;
if (infile.is_open())
{
ofile = true;
while (!infile.eof())
{
getline(infile, str);
text << str+"\n";
}
}
else
throw exception("[ERROR] file does not exist or corrupted");
}
string MatReader::getPhysMaterial()
{
string line;
vector<string> seglist;
try
{
if (ofile == false)
throw exception("file not open");
while (getline(text, line, '"'))
{
if (!line.find("/>"))
break;
seglist.push_back(line);
}
for (uint16_t i{}; i < seglist.size(); i++)
{
if (seglist[i-1] == " PhysicsMaterial=")
{
PhysicsMaterial = seglist[i];
return seglist[i];
}
}
line.clear();
seglist.clear();
}
catch (const std::exception& ex)
{
cout << "[ERROR]: " << ex.what() << endl;
return "[ERROR]";
}
}
string MatReader::getDiffuseLocation()
{
string line;
vector<string> seglist;
try
{
if (ofile == false)
throw exception("file not open");
while (getline(text, line, '"'))
{
seglist.push_back(line);
}
for (uint16_t i{}; i < seglist.size(); i++)
{
if (seglist[i - 1] == " File=")
{
PhysicsMaterial = seglist[i];
return seglist[i];
}
}
}
catch (const std::exception& ex)
{
cout << "[ERROR]: " << ex.what() << endl;
return "[ERROR]";
}
}
The methods "getPhysMaterial()" and "getDiffuseLocation()" works separately without any problems, but if they are executed sequentially, they give an error or are not executed at all.
Thank you.
So first you need to correct the issue with your out-of-range array access.
The next issue you have is you need to reset the position of the stream in order to re-read it from the beginning.
Here is an example of how you can do that.
std::stringstream ss{ };
ss << "This is line one\n"
<< "This is line two\n"
<< "This is line three\n"
<< "This is line four\n";
// Points to the start of the stringstream.
// You can store this as a member of your class.
const std::stringstream::pos_type start{ ss.tellg( ) };
std::string line{ };
while ( std::getline( ss, line ) )
std::cout << line << '\n';
// Reset to the start of the stringstream.
ss.clear( );
ss.seekg( start );
while ( std::getline( ss, line ) )
std::cout << line << '\n';
Another issue I noticed is that you're checking for eof in the loop condition. Don't do that.. Why is iostream::eof inside a loop condition (i.e. while (!stream.eof())) considered wrong?
Do something like this instead:
std::stringstream ss{ };
if ( std::ifstream file{ "/Path/To/MyFile.txt" } )
{
std::string input{ };
while ( std::getline( file, input ) )
ss << input << '\n';
}
else
{
std::cerr << "Failed to open file\n";
return 1;
}
std::string line{ };
while ( std::getline( ss, line ) )
std::cout << line << '\n';
I have a function which reads each line in a file (using ifstream), modifies that line and then writes to a file with many "{key,value}," (by using fstream), I need to remove the last comma in that file, but after searching, I still have no idea to do this.
Does anyone have any suggestion please ?
current output file:
//bein file
std::map <std::string, std::string> my_map =
...
{key,value},
{key,value},
{last_key,last_value},
};
//some comment
expected output(remove the last comma):
//bein file
std::map <std::string, std::string> my_map =
...
{key,value},
{key,value},
{last_key,last_value}
};
//some comment
my code is here:
#include <windows.h>
#include <queue>
#include <string>
#include <iostream>
#include <regex>
#include <fstream>
#include <iterator>
#include <stdio.h>
// I think MS's names for some things are obnoxious.
const HANDLE HNULL = INVALID_HANDLE_VALUE;
const int A_DIR = FILE_ATTRIBUTE_DIRECTORY;
std::string write_cpp_file(std::string path, std::string line, std::string file_name)
{
std::string result = "output\\values.cpp";
if (path.find("values-en-rGB") != std::string::npos) {
std::fstream file("./output/result.cpp", std::ios::out | std::ios::app);
if (file)
{
file << line << std::endl;
file.close();
}
}
return result;
}
void analyze_file(std::string const &path, WIN32_FIND_DATA const &file) {
//std::cout << "path: " << path << "\n";
//std::cout << "file name: " << file.cFileName << "\n";
std::string file_name = path+file.cFileName;
std::cout << "processing file: " << file_name << "\n";
std::ifstream empSalariesOld(file_name);
//ofstream empSalariesNew("EmpSalariesNew.txt");
if (empSalariesOld)
{
std::string line;
std::regex open_comment("(.*)(<!--)(.*)");
std::regex close_comment("(.*)(-->)(.*)");
std::regex string_tag("(.*)(<string name=)(.*)");
std::regex find1("<string name=");
std::string replace1 = "{";
std::regex find2("\">");
std::string replace2 = "\",\"";
std::regex find3("</string>");
std::string replace3 = "\"},";
std::string result;
while (getline(empSalariesOld, line))
{
if (!std::regex_match(line, open_comment) &&
!std::regex_match(line, close_comment) &&
std::regex_match(line, string_tag) )
{
result = std::regex_replace(line, find1, replace1);
result = std::regex_replace(result, find2, replace2);
result = std::regex_replace(result, find3, replace3);
std::string cpp_file = write_cpp_file(path, result, file_name);
}
}
}
empSalariesOld.close();
//empSalariesNew.close();
}
//process each file in folder/subfolder
void convert(std::string const &folder_name) {
HANDLE finder; // for FindFirstFile
WIN32_FIND_DATA file; // data about current file.
std::priority_queue<std::string, std::vector<std::string>,
std::greater<std::string> > dirs;
dirs.push(folder_name); // start with passed directory
do {
std::string path = dirs.top();// retrieve directory to search
dirs.pop();
if (path[path.size()-1] != '\\') // normalize the name.
path += "\\";
std::string mask = path + "*"; // create mask for searching
// traverse a directory. Search for sub-dirs separately, because we
// don't want a mask to apply to directory names. "*.cpp" should find
// "a\b.cpp", even though "a" doesn't match "*.cpp".
//
// First search for files:
if (HNULL==(finder=FindFirstFile(mask.c_str(), &file)))
continue;
do {
if (!(file.dwFileAttributes & A_DIR))
analyze_file(path, file);
} while (FindNextFile(finder, &file));
FindClose(finder);
// Then search for subdirectories:
if (HNULL==(finder=FindFirstFile((path + "*").c_str(), &file)))
continue;
do {
if ((file.dwFileAttributes & A_DIR) && (file.cFileName[0] != '.'))
dirs.push(path + file.cFileName);
} while (FindNextFile(finder, &file));
FindClose(finder);
} while (!dirs.empty());
}
void create_output_folder()
{
std::string command = "del /Q ";
std::string path = "output\\*.cpp";
system(command.append(path).c_str());
CreateDirectory("output", NULL);
}
int main()
{
create_output_folder();
convert("./Strings");
std::cout << "finish convert" << "\n";
return 0;
}
You can do the following do a first write to file without any , outside the loop. Then for the others write inside the loop you print the , at the beginning of the line:
if (getline(empSalariesOld, line))
{
if (!std::regex_match(line, open_comment) &&
!std::regex_match(line, close_comment) &&
std::regex_match(line, string_tag) )
{
result = std::regex_replace(line, find1, replace1);
result = std::regex_replace(result, find2, replace2);
result = std::regex_replace(result, find3, replace3);
//remove the first char which is the comma
result = result.substr(1, result.size()-1)
std::string cpp_file = write_cpp_file(path, result, file_name);
}
}
while (getline(empSalariesOld, line))
{
if (!std::regex_match(line, open_comment) &&
!std::regex_match(line, close_comment) &&
std::regex_match(line, string_tag) )
{
result = std::regex_replace(line, find1, replace1);
result = std::regex_replace(result, find2, replace2);
result = std::regex_replace(result, find3, replace3);
std::string cpp_file = write_cpp_file(path, result, file_name);
}
}
I am guessing you are putting the comma in replace3. You can:
std::string replace1 = ",\n{"; \\put the comma at beginning of line then got to the next line
...
std::string replace3 = "\"}"
And since you are putting the new line in replace1 you should remove it from new file:
file << line;
Last week I got an homework to write a function: the function gets a string and a char value and should divide the string in two parts, before and after the first occurrence of the existing char.
The code worked but my teacher told me to do it again, because it is not well written code. But I don't understand how to make it better. I understand so far that defining two strings with white spaces is not good, but i get out of bounds exceptions otherwise. Since the string input changes, the string size changes everytime.
#include <iostream>
#include <string>
using namespace std;
void divide(char search, string text, string& first_part, string& sec_part)
{
bool firstc = true;
int counter = 0;
for (int i = 0; i < text.size(); i++) {
if (text.at(i) != search && firstc) {
first_part.at(i) = text.at(i);
}
else if (text.at(i) == search&& firstc == true) {
firstc = false;
sec_part.at(counter) = text.at(i);
}
else {
sec_part.at(counter) = text.at(i);
counter++;
}
}
}
int main() {
string text;
string part1=" ";
string part2=" ";
char search_char;
cout << "Please enter text? ";
getline(cin, text);
cout << "Please enter a char: ? ";
cin >> search_char;
divide(search_char,text,aprt1,part2);
cout << "First string: " << part1 <<endl;
cout << "Second string: " << part2 << endl;
system("PAUSE");
return 0;
}
I would suggest you, learn to use c++ standard functions. there are plenty utility function that can help you in programming.
void divide(const std::string& text, char search, std::string& first_part, std::string& sec_part)
{
std::string::const_iterator pos = std::find(text.begin(), text.end(), search);
first_part.append(text, 0, pos - text.begin());
sec_part.append(text, pos - text.begin());
}
int main()
{
std::string text = "thisisfirst";
char search = 'f';
std::string first;
std::string second;
divide(text, search, first, second);
}
Here I used std::find that you can read about it from here and also Iterators.
You have some other mistakes. you are passing your text by value that will do a copy every time you call your function. pass it by reference but qualify it with const that will indicate it is an input parameter not an output.
Why is your teacher right ?
The fact that you need to initialize your destination strings with empty space is terrible:
If the input string is longer, you'll get out of bound errors.
If it's shorter, you got wrong answer, because in IT and programming, "It works " is not the same as "It works".
In addition, your code does not fit the specifications. It should work all the time, independently of the current value which is stored in your output strings.
Alternative 1: your code but working
Just clear the destination strings at the beginning. Then iterate as you did, but use += or push_back() to add chars at the end of the string.
void divide(char search, string text, string& first_part, string& sec_part)
{
bool firstc = true;
first_part.clear(); // make destinations strings empty
sec_part.clear();
for (int i = 0; i < text.size(); i++) {
char c = text.at(i);
if (firstc && c != search) {
first_part += c;
}
else if (firstc && c == search) {
firstc = false;
sec_part += c;
}
else {
sec_part += c;
}
}
}
I used a temporary c instead of text.at(i) or text\[i\], in order to avoid multiple indexing But this is not really required: nowadays, optimizing compilers should produce equivalent code, whatever variant you use here.
Alternative 2: use string member functions
This alternative uses the find() function, and then constructs a string from the start until that position, and another from that position. There is a special case when the character was not found.
void divide(char search, string text, string& first_part, string& sec_part)
{
auto pos = text.find(search);
first_part = string(text, 0, pos);
if (pos== string::npos)
sec_part.clear();
else sec_part = string(text, pos, string::npos);
}
As you understand yourself these declarations
string part1=" ";
string part2=" ";
do not make sense because the entered string in the object text can essentially exceed the both initialized strings. In this case using the string method at can result in throwing an exception or the strings will have trailing spaces.
From the description of the assignment it is not clear whether the searched character should be included in one of the strings. You suppose that the character should be included in the second string.
Take into account that the parameter text should be declared as a constant reference.
Also instead of using loops it is better to use methods of the class std::string such as for example find.
The function can look the following way
#include <iostream>
#include <string>
void divide(const std::string &text, char search, std::string &first_part, std::string &sec_part)
{
std::string::size_type pos = text.find(search);
first_part = text.substr(0, pos);
if (pos == std::string::npos)
{
sec_part.clear();
}
else
{
sec_part = text.substr(pos);
}
}
int main()
{
std::string text("Hello World");
std::string first_part;
std::string sec_part;
divide(text, ' ', first_part, sec_part);
std::cout << "\"" << text << "\"\n";
std::cout << "\"" << first_part << "\"\n";
std::cout << "\"" << sec_part << "\"\n";
}
The program output is
"Hello World"
"Hello"
" World"
As you can see the separating character is included in the second string though I think that maybe it would be better to exclude it from the both strings.
An alternative and in my opinion more clear approach can look the following way
#include <iostream>
#include <string>
#include <utility>
std::pair<std::string, std::string> divide(const std::string &s, char c)
{
std::string::size_type pos = s.find(c);
return { s.substr(0, pos), pos == std::string::npos ? "" : s.substr(pos) };
}
int main()
{
std::string text("Hello World");
auto p = divide(text, ' ');
std::cout << "\"" << text << "\"\n";
std::cout << "\"" << p.first << "\"\n";
std::cout << "\"" << p.second << "\"\n";
}
Your code will only work as long the character is found within part1.length(). You need something similar to this:
void string_split_once(const char s, const string & text, string & first, string & second) {
first.clear();
second.clear();
std::size_t pos = str.find(s);
if (pos != string::npos) {
first = text.substr(0, pos);
second = text.substr(pos);
}
}
The biggest problem I see is that you are using at where you should be using push_back. See std::basic_string::push_back. at is designed to access an existing character to read or modify it. push_back appends a new character to the string.
divide could look like this :
void divide(char search, string text, string& first_part,
string& sec_part)
{
bool firstc = true;
for (int i = 0; i < text.size(); i++) {
if (text.at(i) != search && firstc) {
first_part.push_back(text.at(i));
}
else if (text.at(i) == search&& firstc == true) {
firstc = false;
sec_part.push_back(text.at(i));
}
else {
sec_part.push_back(text.at(i));
}
}
}
Since you aren't handling exceptions, consider using text[i] rather than text.at(i).
I have a function which looks like this:
bool ExpandWildCard(vector<string>& names, vector<string>& result, string& wildcard)
{
}
Here, I want to match the wildcard with each element in the vector names and if it does match, then add the element in names to the result vector.
Of course, if the wildcard is *, I could just add everything from names to results. Also I'm just trying to implement the * wildcard for now.
How can I do this in C++?
One way I thought of doing this was using find() algorithm but I am not sure I would match wildcards using that?
It looks like you are looking for some combination of std::copy_if and maybe std::regex_match:
bool ExpandWildCard(vector<string>& names, vector<string>& result, string& wildcard) {
auto oldsize = result.size();
std::copy_if(std::begin(names), std::end(names),
std::back_inserter(result),
[&](string const& name) {
return std::regex_match(name, make_regex(wildcard));
}
);
return (result.size() > oldsize);
}
Where make_regex would be the function you need to implement to convert your string into a std::regex.
The approach you are probably after in using regex_match as suggested in another answer. Elsewhere you can find code to convert a glob pattern into a regular expression.
If performance is not a concern, and you just need the functionality, you can use the shell to match the pattern for you. You can create a suitable command, pass the command through popen() and read the results and store them in your vector.
bool ExpandWildCard (const std::vector<std::string>& names,
std::vector<std::string>& result,
const std::string& wildcard)
{
std::ostringstream oss;
oss << "bash -c 'for word in ";
for (int i = 0; i < names.size(); ++i) {
if (names[i].size() > 0) oss << '"' << names[i] << '"' << ' ';
}
oss << "; do case \"$word\" in "
<< wildcard << ')' << " echo \"$word\" ;; *) ;; "
<< "esac ; done '";
FILE *fp = ::popen(oss.str().c_str(), "r");
if (fp == NULL) return false;
char *line = 0;
ssize_t len = 0;
size_t n = 0;
while ((len = ::getline(&line, &n, fp)) > 0) {
if (line[len-1] == '\n') line[len-1] = '\0';
result.push_back(line);
}
::free(line);
::pclose(fp);
return true;
}
I found this on another stack question:
//http://stackoverflow.com/questions/3418231/c-replace-part-of-a-string-with-another-string
//
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
size_t end_pos = start_pos + from.length();
str.replace(start_pos, end_pos, to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
and my method:
string convert_FANN_array_to_binary(string fann_array)
{
string result = fann_array;
cout << result << "\n";
replaceAll(result, "-1 ", "0");
cout << result << "\n";
replaceAll(result, "1 ", "1");
return result;
}
which, for this input:
cout << convert_FANN_array_to_binary("1 1 -1 -1 1 1 ");
now, the output should be "110011"
here is the output of the method:
1 1 -1 -1 1 1 // original
1 1 0 1 // replacing -1's with 0's
11 1 // result, as it was returned from convert_FANN_array_to_binary()
I've been looking at the replaceAll code, and, I'm really not sure why it is replacing consecutive -1's with one 0, and then not returning any 0's (and some 1's) in the final result. =\
A complete code:
std::string ReplaceString(std::string subject, const std::string& search,
const std::string& replace) {
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}
If you need performance, here is a more optimized function that modifies the input string, it does not create a copy of the string:
void ReplaceStringInPlace(std::string& subject, const std::string& search,
const std::string& replace) {
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
}
Tests:
std::string input = "abc abc def";
std::cout << "Input string: " << input << std::endl;
std::cout << "ReplaceString() return value: "
<< ReplaceString(input, "bc", "!!") << std::endl;
std::cout << "ReplaceString() input string not changed: "
<< input << std::endl;
ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: "
<< input << std::endl;
Output:
Input string: abc abc def
ReplaceString() return value: a!! a!! def
ReplaceString() input string not changed: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def
The bug is in str.replace(start_pos, end_pos, to);
From the std::string doc at http://www.cplusplus.com/reference/string/string/replace/
string& replace ( size_t pos1, size_t n1, const string& str );
You are using an end-position, while the function expects a length.
So change to:
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // ...
}
Note: untested.
This is going to go in my list of 'just use a Boost library' answers, but here it goes anyway:
Have you considered Boost.String? It has more features than the standard library, and where features overlap, Boost.String has a more much more natural syntax, in my opinion.
I found the replace functions given in previous answers, all using in-place str.replace() call internally, very slow when working with a string of about 2 MB length. Specifically, I called something like ReplaceAll(str, "\r", ""), and on my particular device, with the text file containing a lot of newlines, it took about 27 seconds. I then replaced it with function just concatenating sub-strings in a new copy, and it took only about 1 seconds. Here is my version of ReplaceAll():
void replaceAll(string& str, const string& from, const string& to) {
if(from.empty())
return;
string wsRet;
wsRet.reserve(str.length());
size_t start_pos = 0, pos;
while((pos = str.find(from, start_pos)) != string::npos) {
wsRet += str.substr(start_pos, pos - start_pos);
wsRet += to;
pos += from.length();
start_pos = pos;
}
wsRet += str.substr(start_pos);
str.swap(wsRet); // faster than str = wsRet;
}
Greg
C++11 now includes the header <regex> which has regular expression functionality. From the docs:
// regex_replace example
#include <iostream>
#include <string>
#include <regex>
#include <iterator>
int main ()
{
std::string s ("there is a subsequence in the string\n");
std::regex e ("\\b(sub)([^ ]*)"); // matches words beginning by "sub"
// using string/c-string (3) version:
std::cout << std::regex_replace (s,e,"sub-$2");
std::cout << std::endl;
return 0;
}
Of course, now you have two problems.
Try this:
#include <string>
string replace_str(string & str, const string & from, const string & to)
{
while(str.find(from) != string::npos)
str.replace(str.find(from), from.length(), to);
return str;
}