Reading Text File and Appending

Pages: 123
Nov 11, 2020 at 1:30am
So I am starting to create this program to read a text file and copy the text to another file until it reaches the line that is </settings>. I thought I was doing well but everything copies. I am not sure what I am missing and was hoping someone could help. I also tried messing with my if statement but when I changed the != to == only the stuff after </settings> copied. I know the program is not complete as I plan to do this a couple times then save the new file so it's not finished yet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  //

#include <fstream>
#include <iostream>
#include <string>
#include <stdio.h>
#include <Windows.h>

int main()
{
    int results;
    char oldname[] = "c:\\synergyii\\config\\clientconfig.xml";
    char newname[] = "c:\\synergyii\\config\\clientconfig.txt";
    results = rename(oldname, newname);
    if (results == 0)
        puts("File successfully renamed");
    else
        perror("Error renaming file");

    
    std::ifstream inf{ "c:\\synergyii\\config\\clientconfig.txt" };

    std::ofstream outf{ "c:\\synergyii\\config\\clientconfig.xml" };

    if (!inf)
    {
        std::cerr << "Cannot open client config for reading" << std::endl;
        return 1;
    }

    if (!outf)
    {
        std::cerr << "Cannot open client config for writing" << std::endl;
        return 1;
    }
   
    while (inf)
    {

        std::string line;

        line.assign("</settings>");

        std::string strInput;
        std::getline(inf , strInput);

        if (strInput != line)
        {
            outf << inf.rdbuf();
        }
   
    }
    return 0;

}
Last edited on Dec 17, 2020 at 12:51pm
Nov 11, 2020 at 1:49am
before you debug this (it looks ok), look at your input file.

1
2
3
4
5
6
7
</settings>
^^ that would match, no spaces or tabs etc to left and right
 </settings>
^^ nope, leading space
</settings> 
^^ nope, trailing space
.. etc
Last edited on Nov 11, 2020 at 1:50am
Nov 11, 2020 at 2:02am
Hey jonnin,

I thought that too, but there are no spaces before or after. It is the only thing in that line.
Last edited on Nov 11, 2020 at 2:02am
Nov 11, 2020 at 2:37am
Hello Vendetto,

I thought I was doing well but everything copies.

My question would be in line 49 what does "rdbuf" contain?

Next question is what is the exact input file that you are using. Without seeing what you have to work with it makes it hard to understand your code and to test the program.

Andy
Nov 11, 2020 at 3:32am
Hello Vendetto,

Working on the program I noticed:

Renaming a ".xml" file to a ".txt" file is unnecessary and a ".xml" file is a text tile. What would make more sense would be to choose a different output file name and then, maybe, at the end of the program rename the file.

Good job on defining the file stream variables and opening the files then checking if they opened.

After that I am thinking that this might work as a start:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::string line{ "</settings>" };
std::string strInput;

while (std::getline(inf, strInput))
{
    if (strInput == line)
    {
        ;// <--- Do Something
    }
    else
    {
        outf << strInput << '\n';
    }
}

There is no need for line.assign("</settings>"); as you can do this when you define the variable. Unless you want the practice. Also line = "</settings>"; would also work.

Andy
Nov 11, 2020 at 10:51am
For the copy part, this can be done simply as:

1
2
for (std::string strInput; std::getline(inf, strInput) && strInput != "</settings>"; outf << strInput << '\n');

Last edited on Nov 11, 2020 at 10:51am
Nov 11, 2020 at 11:34pm
Thank you Handy Andy!!! I am back on track. I like your suggestion on the renaming at the end or even creating a new one and not having so change the xml to a txt so will most likely incorporate it with the final version. Here is what I have and it is all working now so I can continue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//

#include <fstream>
#include <iostream>
#include <string>
#include <stdio.h>
#include <Windows.h>

int main()
{
    int results;
    char oldname[] = "c:\\synergyii\\config\\clientconfig.xml";
    char newname[] = "c:\\synergyii\\config\\clientconfig.txt";
    results = rename(oldname, newname);
    if (results == 0)
        puts("File successfully renamed");
    else
        perror("Error renaming file");

    
    std::ifstream inf{ "c:\\synergyii\\config\\clientconfig.txt" };

    std::ofstream outf{ "c:\\synergyii\\config\\clientconfig.xml" };

    if (!inf)
    {
        std::cerr << "Cannot open client config for reading" << std::endl;
        return 1;
    }

    if (!outf)
    {
        std::cerr << "Cannot open client config for writing" << std::endl;
        return 1;
    }

    std::string line{ "</settings>" };
    std::string strInput;

    while (std::getline(inf, strInput))
    {
        if (strInput == line)
        {
            break;
        }
   
        else 
        {
            outf << strInput << '\n';
        }

    }
    return 0;
}
Nov 16, 2020 at 2:48pm
Right now the program is working great with the code below. However, I am looking for a way to possibly compare the lines in the newlines.txt and make sure they aren't duplicated in what was already printed to the output file just so there isn't duplicated settings. Any suggestions?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

//

#include <fstream>
#include <iostream>
#include <string>
#include <stdio.h>
#include <Windows.h>

int main()
{
    int results;
    char oldname[] = "c:\\synergyii\\config\\clientconfig.xml";
    char newname[] = "c:\\synergyii\\config\\clientconfig.txt";
    results = rename(oldname, newname);
    if (results == 0)
        puts("File successfully renamed");
    else
        perror("Error renaming file");

    
    std::ifstream inf{ "c:\\synergyii\\config\\clientconfig.txt" };

    std::ofstream outf{ "c:\\synergyii\\config\\clientconfig.xml" };

    if (!inf)
    {
        std::cerr << "Cannot open client config for reading" << std::endl;
        return 1;
    }

    if (!outf)
    {
        std::cerr << "Cannot open client config for writing" << std::endl;
        return 1;
    }

    std::string line{ "</settings>" };
    std::string strInput;

    while (std::getline(inf, strInput))
    {
        if (strInput == line)
        {
            break;
        }
   
        else 
        {
            outf << strInput << '\n';
        }

    }

    inf.close();

    std::ifstream inf2{ "c:\\synergyii\\config\\newlines.txt" };

    if (!inf2)
    {
        std::cerr << "Cannot open client config for reading" << std::endl;
        return 1;
    }

    while (std::getline(inf2, strInput))
    {
        outf << strInput << '\n';
    }

    inf2.close();

    outf << "</settings>" << '\n';
    outf << "</config>" << '\n';

    outf.close();

    return 0;
}


Thanks,
V
Nov 16, 2020 at 4:00pm
Try this [not tested]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <fstream>
#include <iostream>
#include <string>
#include <cstdlib>
#include <set>

int main()
{
	const char oldname[] = "c:\\synergyii\\config\\clientconfig.xml";
	const char newname[] = "c:\\synergyii\\config\\clientconfig.txt";

	if (rename(oldname, newname) == 0)
		puts("File successfully renamed");
	else
		perror("Error renaming file");

	std::ifstream inf {"c:\\synergyii\\config\\clientconfig.txt"};
	std::ofstream outf {"c:\\synergyii\\config\\clientconfig.xml"};
	std::ifstream inf2 {"c:\\synergyii\\config\\newlines.txt"};

	if (!inf2) {
		std::cerr << "Cannot open client newlines.txt for reading" << std::endl;
		return 3;
	}

	if (!inf) {
		std::cerr << "Cannot open clientconfig.txt for reading" << std::endl;
		return 1;
	}

	if (!outf) {
		std::cerr << "Cannot open clientconfig.xml for writing" << std::endl;
		return 2;
	}

	std::set<std::string> lines;

	for (std::string strInput; std::getline(inf, strInput) && strInput != "</settings>"; lines.insert(strInput));
	for (std::string strInput; std::getline(inf2, strInput); lines.insert(strInput));

	for (const auto& l : lines)
		outf << l << '\n';

	outf << "</settings>" << '\n';
	outf << "</config>" << '\n';
}

Nov 18, 2020 at 2:35am
Hey seeplus,

This does work in preventing duplicated lines however, it is totally messing up the orders. It is putting everything in order alphabetically instead of keeping the format which is required.

Thanks,
V
Nov 18, 2020 at 3:06am
Expanding on seeplus's code.
Note that you don't need to use double backslashes to separate files.
A forward slash works just fine, even for Windows (I believe).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <fstream>
#include <iostream>
#include <string>
#include <cstdlib>
#include <set>
#include <vector>
using namespace std;

int main() {
    const string Dir { "C:/synergyii/config/" },
                 oldname = Dir + "clientconfig.xml",
                 newname = Dir + "clientconfig.txt";

    if (rename(oldname.c_str(), newname.c_str()) != 0) {
        perror("Error renaming file");
        return 1;
    }

    ifstream in_config   { newname },
             in_newlines { Dir + "newlines.txt" };
    ofstream out         { oldname };

    if (!in_config) {
        cerr << "Cannot open clientconfig.txt for reading\n";
        return 1;
    }
    if (!in_newlines) {
        cerr << "Cannot open client newlines.txt for reading\n";
        return 1;
    }
    if (!out) {
        cerr << "Cannot open clientconfig.xml for writing\n";
        return 1;
    }

    vector<string> vlines;
    set<string>    slines;

    for (string line; getline(in_config, line) && line != "</settings>"; ) {
        const auto& result = slines.insert(line);
        if (result.second)
            vlines.push_back(line);
    }

    for (string line; getline(in_newlines, line); ) {
        const auto& result = slines.insert(line);
        if (result.second)
            vlines.push_back(line);
    }

    for (const auto& line : vlines)
        out << line << '\n';
    out << "</settings>\n</config>\n";
}

Nov 18, 2020 at 3:34am
By golly I think we have something here! Now to read up and understand why it is working so well. It's not enough for me that it works but I need to understand it LOL :)

This will actually be added to a larger project that I will be developing which will include a user interface to allow for options and changes so more to come.

You guys are the best!!
Nov 18, 2020 at 10:36am
The basis behind this is that a set can't contain duplicates. If you try to insert the same item to a set it will be rejected. The return value indicates whether the insertion was successful (not duplicate) or failed (duplicate). There is also multiset which can contain duplicate values. set maintains its data as a sorted tree for quick insert/find. That's why using my original code you got the new file in sorted order. As that wasn't acceptable, dutch's solution uses a vector to store the items in order if a set insertion succeeded (ie not duplicate).

In any scenario in which non-duplicates are required, think set (or map if an associated value is also needed).
Nov 21, 2020 at 2:28am
I think I am following it now seeplus.

One more question, I want to include 2 not equal </settings>. One being "</settings>" and the other being " </settings>" (with a TAB space in front of it). I was thinking:

1
2
3
4
5
6

    for (string line; getline(in_config, line) && line != "</settings>" , "     </settings>"; ) {
        const auto& result = slines.insert(line);
        if (result.second)
            vlines.push_back(line);


but it doesn't seem to work. The reason is there might be a TAB space in front and want to account for it.

Thanks,
V
Nov 21, 2020 at 3:26am
I even tried:

1
2
3
4
    for (string line; getline(in_config, line) && line != "</settings>" && line != "'\t'</settings>"; ) {
        const auto& result = slines.insert(line);
        if (result.second)
            vlines.push_back(line);


and:

1
2
3
4
    for (string line; getline(in_config, line) && line != "</settings>" || "'\t</settings>"; ) {
        const auto& result = slines.insert(line);
        if (result.second)
            vlines.push_back(line);


and those did not work either.

Nov 21, 2020 at 10:03am
Try (not tested):

 
for (string line; getline(in_config, line) && line.find("</settings>") != string::npos); ) {

Nov 23, 2020 at 9:59am
So this was weird. It only copied all the stuff from the newlines.txt and discarded all the stuff from the original.
Nov 23, 2020 at 11:29am
I said it wasn't tested. Didn't actually insert into the vector. Ahhh.....

 
for (string line; getline(in_config, line) && line.find("</settings>") != string::npos); vlines.push_back(line)) {



Nov 23, 2020 at 3:22pm
Oh I know you said it wasn't tested. I have no issues testing things as they are suggested. The best part about this project is I have no deadline as it is helping me learn.

Now I understand what you are saying as it wasn't inserted into the vector, but now I am being told line is undefined and VS is expecting a ; in there but when I get the code to check out it still drops the stuff from clientconfig and only does the newlines. I am trying to see if there is something I am not seeing in the line that needs to be formatted differently.
Nov 23, 2020 at 4:59pm
 
for (string line; getline(in_config, line) && line.find("</settings>") == string::npos; vlines.push_back(line)) {


It's not my day for having typing trouble. Extra ).......



Last edited on Nov 24, 2020 at 10:21am
Pages: 123