Я покажу вам полное решение и объясню вам. Но давайте сначала посмотрим на это:
#include <iostream>
#include <vector>
#include <fstream>
#include <regex>
#include <string>
#include <algorithm>
// I omit in the example here the manual input of the filenames. This exercise can be done by somebody else
// Use fixed filenames in this example.
const std::string inputFileName("r:\\input.txt");
const std::string outputFileName("r:\\output.txt");
// The delimiter for the source csv file
std::regex re{ R"(\|)" };
std::string addQuotes(const std::string& s) {
// if there are single quotes in the string, then replace them with double quotes
std::string result = std::regex_replace(s, std::regex(R"(")"), R"("")");
// If there is any quote (") or comma in the file, then quote the complete string
if (std::any_of(result.begin(), result.end(), [](const char c) { return ((c == '\"') || (c == ',')); })) {
result = "\"" + result + "\"";
}
return result;
}
// Some output function
void printData(std::vector<std::vector<std::string>>& v, std::ostream& os) {
// Go throug all rows
std::for_each(v.begin(), v.end(), [&os](const std::vector<std::string>& vs) {
// Define delimiter
std::string delimiter{ "" };
// Show the delimited strings
for (const std::string& s : vs) {
os << delimiter << s;
delimiter = ",";
}
os << "\n";
});
}
int main() {
// We first open the ouput file, becuse, if this cannot be opened, then no meaning to do the rest of the exercise
// Open output file and check, if it could be opened
if (std::ofstream outputFileStream(outputFileName); outputFileStream) {
// Open the input file and check, if it could be opened
if (std::ifstream inputFileStream(inputFileName); inputFileStream) {
// In this variable we will store all lines from the CSV file including the splitted up columns
std::vector<std::vector<std::string>> data{};
// Now read all lines of the CSV file and split it into tokens
for (std::string line{}; std::getline(inputFileStream, line); ) {
// Split line into tokens and add to our resulting data vector
data.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {}));
}
std::for_each(data.begin(), data.end(), [](std::vector<std::string>& vs) {
std::transform(vs.begin(), vs.end(), vs.begin(), addQuotes);
});
// Output, to file
printData(data, outputFileStream);
// And to the screen
printData(data, std::cout);
}
else {
std::cerr << "\n*** Error: could not open input file '" << inputFileName << "'\n";
}
}
else {
std::cerr << "\n*** Error: could not open output file '" << outputFileName << "'\n";
}
return 0;
}
Итак, давайте посмотрим. У нас есть функция
main
, чтение файлов CSV, разбиение их на токены, преобразование и запись addQuotes
. При необходимости добавьте кавычку printData
печать преобразованных данных в выходной поток
Начнем с main
. main
сначала откроет входной файл и выходной файл.
Входной файл содержит вид структурированных данных и также называется csv (значения, разделенные запятыми). Но здесь у нас не запятая, а символ канала в качестве разделителя.
И результат обычно сохраняется в 2d-векторе. В измерении 1 строки и другое измерение для столбцов.
Итак, что нам нужно делать дальше? Как мы видим, нам нужно сначала прочитать все полные текстовые строки из исходного потока. Это легко сделать с помощью одной строки:
for (std::string line{}; std::getline(inputFileStream, line); ) {
Как вы можете видеть здесь , оператор for имеет часть объявления / инициализации, затем условие, а затем оператор , проведенный в конце л oop. Это хорошо известно.
Сначала мы определяем переменную "line" типа std::string
и используем инициализатор по умолчанию для создания пустой строки. Затем мы используем std::getline
, чтобы прочитать из потока полную строку и поместить ее в нашу переменную. std::getline
возвращает ссылку на поток, и поток имеет перегруженный оператор bool, куда он возвращается, если произошел сбой (или конец файла). Таким образом, для l oop не требуется дополнительная проверка конца файла. И мы не используем последний оператор для l oop, потому что при чтении строки указатель файла продвигается автоматически.
Это дает нам очень простое значение для l oop, для чтения полный файл строка за строкой.
Обратите внимание: при определении переменной "строка" в for для l oop, будет применена область для l oop. Смысл, это видно только в течение l oop. Как правило, это хорошее решение для предотвращения загрязнения внешнего пространства имен.
ОК, теперь следующая строка:
data.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), digit), {}));
Э-э, что это?
ОК, давайте go пошагово. Во-первых, мы явно хотим добавить что-то к нашему 2-мерному вектору данных. Мы будем использовать функцию std::vector
s emplace_back . Мы могли бы также использовать push_back , но это означало бы, что нам нужно сделать ненужное копирование данных. Следовательно, мы выбрали emplace_back
для создания на месте того, что мы хотим добавить к нашему двухмерному вектору данных.
И что мы хотим добавить? Мы хотим добавить полную строку, поэтому вектор столбцов. В нашем случае это std::vector<std::string>
. И, поскольку мы хотим сделать на месте конструирование этого вектора, мы называем его конструктором диапазона векторов. Пожалуйста, смотрите здесь: Конструктор № 5 . Конструктор диапазона принимает в качестве параметра 2 итератора, начальный и конечный итератор, и копирует все значения, на которые указывают итераторы, в вектор.
Итак, мы ожидаем начальный и конечный итератор. И что мы видим здесь:
- Начальный итератор:
std::sregex_token_iterator(line.begin(), line.end(), digit)
- А конечный итератор просто
{}
Но что это вещь, sregex_token_iterator ?
Это итератор, который перебирает шаблоны в строке. И шаблон дается регулярное выражение . Вы можете прочитать здесь о библиотеке C ++ regex. Поскольку он очень мощный, вам, к сожалению, нужно узнать о нем немного дольше. И я не могу покрыть это здесь. Но давайте опишем его базовую функциональность c для нашей цели: вы можете описать шаблон на каком-то мета-языке, и std::sregex_token_iterator
будет искать этот шаблон и, если найдет совпадение, вернуть соответствующие данные. В нашем случае картина очень проста: цифры. Это можно описать с помощью "\ d +" и, значит, попытаться сопоставить одну или несколько цифр.
Теперь с {}
в качестве конечного итератора. Возможно, вы читали, что {}
будет выполнять построение / инициализацию по умолчанию. И если вы прочитаете здесь, номер 1 , то увидите, что «конструктор по умолчанию» создает итератор конца последовательности. Итак, именно то, что нам нужно.
После того, как мы прочитаем все данные, мы преобразуем отдельные строки в требуемый вывод. Это будет сделано с помощью std::transform
и функции addQuotes
. Стратегия здесь заключается в том, чтобы сначала заменить одинарные кавычки на двойные.
И затем, смотрим, если в строке есть запятая или кавычка, мы дополнительно заключаем всю строку в кавычки.
И, наконец, что не менее важно, мы имеем простая функция вывода и печать преобразованных данных в файл и на экран.