| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmVisualStudioSlnParser.h" | |
| #include <cassert> | |
| #include <memory> | |
| #include <stack> | |
| #include <utility> | |
| #include <vector> | |
| #include <cmext/string_view> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmUnreachable.h" | |
| #include "cmVisualStudioSlnData.h" | |
| namespace { | |
| enum LineFormat | |
| { | |
| LineMultiValueTag, | |
| LineSingleValueTag, | |
| LineKeyValuePair, | |
| LineVerbatim | |
| }; | |
| } | |
| class cmVisualStudioSlnParser::ParsedLine | |
| { | |
| public: | |
| bool IsComment() const; | |
| bool IsKeyValuePair() const; | |
| std::string const& GetTag() const { return this->Tag; } | |
| std::string const& GetArg() const { return this->Arg.first; } | |
| std::string GetArgVerbatim() const; | |
| size_t GetValueCount() const { return this->Values.size(); } | |
| std::string const& GetValue(size_t idxValue) const; | |
| std::string GetValueVerbatim(size_t idxValue) const; | |
| void SetTag(std::string const& tag) { this->Tag = tag; } | |
| void SetArg(std::string const& arg) { this->Arg = StringData(arg, false); } | |
| void SetQuotedArg(std::string const& arg) | |
| { | |
| this->Arg = StringData(arg, true); | |
| } | |
| void AddValue(std::string const& value) | |
| { | |
| this->Values.push_back(StringData(value, false)); | |
| } | |
| void AddQuotedValue(std::string const& value) | |
| { | |
| this->Values.push_back(StringData(value, true)); | |
| } | |
| void CopyVerbatim(std::string const& line) { this->Tag = line; } | |
| private: | |
| using StringData = std::pair<std::string, bool>; | |
| std::string Tag; | |
| StringData Arg; | |
| std::vector<StringData> Values; | |
| static std::string const BadString; | |
| static std::string const Quote; | |
| }; | |
| std::string const cmVisualStudioSlnParser::ParsedLine::BadString; | |
| std::string const cmVisualStudioSlnParser::ParsedLine::Quote("\""); | |
| bool cmVisualStudioSlnParser::ParsedLine::IsComment() const | |
| { | |
| assert(!this->Tag.empty()); | |
| return (this->Tag[0] == '#'); | |
| } | |
| bool cmVisualStudioSlnParser::ParsedLine::IsKeyValuePair() const | |
| { | |
| assert(!this->Tag.empty()); | |
| return this->Arg.first.empty() && this->Values.size() == 1; | |
| } | |
| std::string cmVisualStudioSlnParser::ParsedLine::GetArgVerbatim() const | |
| { | |
| if (this->Arg.second) { | |
| return cmStrCat(Quote, this->Arg.first, Quote); | |
| } | |
| return this->Arg.first; | |
| } | |
| std::string const& cmVisualStudioSlnParser::ParsedLine::GetValue( | |
| size_t idxValue) const | |
| { | |
| if (idxValue < this->Values.size()) { | |
| return this->Values[idxValue].first; | |
| } | |
| return BadString; | |
| } | |
| std::string cmVisualStudioSlnParser::ParsedLine::GetValueVerbatim( | |
| size_t idxValue) const | |
| { | |
| if (idxValue < this->Values.size()) { | |
| StringData const& data = this->Values[idxValue]; | |
| if (data.second) { | |
| return cmStrCat(Quote, data.first, Quote); | |
| } | |
| return data.first; | |
| } | |
| return BadString; | |
| } | |
| class cmVisualStudioSlnParser::State | |
| { | |
| public: | |
| explicit State(DataGroupSet requestedData); | |
| size_t GetCurrentLine() const { return this->CurrentLine; } | |
| bool ReadLine(std::istream& input, std::string& line); | |
| LineFormat NextLineFormat() const; | |
| bool Process(cmVisualStudioSlnParser::ParsedLine const& line, | |
| cmSlnData& output, cmVisualStudioSlnParser::ResultData& result); | |
| bool Finished(cmVisualStudioSlnParser::ResultData& result); | |
| private: | |
| enum FileState | |
| { | |
| FileStateStart, | |
| FileStateTopLevel, | |
| FileStateProject, | |
| FileStateProjectDependencies, | |
| FileStateGlobal, | |
| FileStateSolutionConfigurations, | |
| FileStateProjectConfigurations, | |
| FileStateSolutionFilters, | |
| FileStateGlobalSection, | |
| FileStateIgnore | |
| }; | |
| std::stack<FileState> Stack; | |
| std::string EndIgnoreTag; | |
| DataGroupSet RequestedData; | |
| size_t CurrentLine = 0; | |
| void IgnoreUntilTag(std::string const& endTag); | |
| }; | |
| cmVisualStudioSlnParser::State::State(DataGroupSet requestedData) | |
| : RequestedData(requestedData) | |
| { | |
| if (this->RequestedData.test(DataGroupProjectDependenciesBit)) { | |
| this->RequestedData.set(DataGroupProjectsBit); | |
| } | |
| this->Stack.push(FileStateStart); | |
| } | |
| bool cmVisualStudioSlnParser::State::ReadLine(std::istream& input, | |
| std::string& line) | |
| { | |
| ++this->CurrentLine; | |
| return !std::getline(input, line).fail(); | |
| } | |
| LineFormat cmVisualStudioSlnParser::State::NextLineFormat() const | |
| { | |
| switch (this->Stack.top()) { | |
| case FileStateStart: | |
| return LineVerbatim; | |
| case FileStateTopLevel: | |
| return LineMultiValueTag; | |
| case FileStateProject: | |
| case FileStateGlobal: | |
| return LineSingleValueTag; | |
| case FileStateProjectDependencies: | |
| case FileStateSolutionConfigurations: | |
| case FileStateProjectConfigurations: | |
| case FileStateSolutionFilters: | |
| case FileStateGlobalSection: | |
| return LineKeyValuePair; | |
| case FileStateIgnore: | |
| return LineVerbatim; | |
| default: | |
| CM_UNREACHABLE; | |
| return LineVerbatim; | |
| } | |
| } | |
| bool cmVisualStudioSlnParser::State::Process( | |
| cmVisualStudioSlnParser::ParsedLine const& line, cmSlnData& output, | |
| cmVisualStudioSlnParser::ResultData& result) | |
| { | |
| assert(!line.IsComment()); | |
| switch (this->Stack.top()) { | |
| case FileStateStart: | |
| if (!cmHasLiteralPrefix(line.GetTag(), | |
| "Microsoft Visual Studio Solution File")) { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| this->Stack.pop(); | |
| this->Stack.push(FileStateTopLevel); | |
| break; | |
| case FileStateTopLevel: | |
| if (line.GetTag() == "Project"_s) { | |
| if (line.GetValueCount() != 3) { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| if (this->RequestedData.test(DataGroupProjectsBit)) { | |
| if (!output.AddProject(line.GetValue(2), line.GetValue(0), | |
| line.GetValue(1))) { | |
| result.SetError(ResultErrorInputData, this->GetCurrentLine()); | |
| return false; | |
| } | |
| this->Stack.push(FileStateProject); | |
| } else { | |
| this->IgnoreUntilTag("EndProject"); | |
| } | |
| } else if (line.GetTag() == "Global"_s) { | |
| this->Stack.push(FileStateGlobal); | |
| } else if (line.GetTag() == "VisualStudioVersion"_s) { | |
| output.SetVisualStudioVersion(line.GetValue(0)); | |
| } else if (line.GetTag() == "MinimumVisualStudioVersion"_s) { | |
| output.SetMinimumVisualStudioVersion(line.GetValue(0)); | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateProject: | |
| if (line.GetTag() == "EndProject"_s) { | |
| this->Stack.pop(); | |
| } else if (line.GetTag() == "ProjectSection"_s) { | |
| if (line.GetArg() == "ProjectDependencies"_s && | |
| line.GetValue(0) == "postProject"_s) { | |
| if (this->RequestedData.test(DataGroupProjectDependenciesBit)) { | |
| this->Stack.push(FileStateProjectDependencies); | |
| } else { | |
| this->IgnoreUntilTag("EndProjectSection"); | |
| } | |
| } else { | |
| this->IgnoreUntilTag("EndProjectSection"); | |
| } | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateProjectDependencies: | |
| if (line.GetTag() == "EndProjectSection"_s) { | |
| this->Stack.pop(); | |
| } else if (line.IsKeyValuePair()) { | |
| // implement dependency storing here, once needed | |
| ; | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateGlobal: | |
| if (line.GetTag() == "EndGlobal"_s) { | |
| this->Stack.pop(); | |
| } else if (line.GetTag() == "GlobalSection"_s) { | |
| if (line.GetArg() == "SolutionConfigurationPlatforms"_s && | |
| line.GetValue(0) == "preSolution"_s) { | |
| if (this->RequestedData.test(DataGroupSolutionConfigurationsBit)) { | |
| this->Stack.push(FileStateSolutionConfigurations); | |
| } else { | |
| this->IgnoreUntilTag("EndGlobalSection"); | |
| } | |
| } else if (line.GetArg() == "ProjectConfigurationPlatforms"_s && | |
| line.GetValue(0) == "postSolution"_s) { | |
| if (this->RequestedData.test(DataGroupProjectConfigurationsBit)) { | |
| this->Stack.push(FileStateProjectConfigurations); | |
| } else { | |
| this->IgnoreUntilTag("EndGlobalSection"); | |
| } | |
| } else if (line.GetArg() == "NestedProjects"_s && | |
| line.GetValue(0) == "preSolution"_s) { | |
| if (this->RequestedData.test(DataGroupSolutionFiltersBit)) { | |
| this->Stack.push(FileStateSolutionFilters); | |
| } else { | |
| this->IgnoreUntilTag("EndGlobalSection"); | |
| } | |
| } else if (this->RequestedData.test( | |
| DataGroupGenericGlobalSectionsBit)) { | |
| this->Stack.push(FileStateGlobalSection); | |
| } else { | |
| this->IgnoreUntilTag("EndGlobalSection"); | |
| } | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateSolutionConfigurations: | |
| if (line.GetTag() == "EndGlobalSection"_s) { | |
| this->Stack.pop(); | |
| } else if (line.IsKeyValuePair()) { | |
| output.AddConfiguration(line.GetValue(0)); | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateProjectConfigurations: | |
| if (line.GetTag() == "EndGlobalSection"_s) { | |
| this->Stack.pop(); | |
| } else if (line.IsKeyValuePair()) { | |
| std::vector<std::string> tagElements = | |
| cmSystemTools::SplitString(line.GetTag(), '.'); | |
| if (tagElements.size() != 3 && tagElements.size() != 4) { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| std::string guid = tagElements[0]; | |
| std::string solutionConfiguration = tagElements[1]; | |
| std::string activeBuild = tagElements[2]; | |
| cmSlnProjectEntry* projectEntry = output.GetProjectByGUID(guid); | |
| if (!projectEntry) { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| if (activeBuild == "ActiveCfg"_s) { | |
| projectEntry->AddProjectConfiguration(solutionConfiguration, | |
| line.GetValue(0)); | |
| } | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateSolutionFilters: | |
| if (line.GetTag() == "EndGlobalSection"_s) { | |
| this->Stack.pop(); | |
| } else if (line.IsKeyValuePair()) { | |
| // implement filter storing here, once needed | |
| ; | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateGlobalSection: | |
| if (line.GetTag() == "EndGlobalSection"_s) { | |
| this->Stack.pop(); | |
| } else if (line.IsKeyValuePair()) { | |
| // implement section storing here, once needed | |
| ; | |
| } else { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| break; | |
| case FileStateIgnore: | |
| if (line.GetTag() == this->EndIgnoreTag) { | |
| this->Stack.pop(); | |
| this->EndIgnoreTag.clear(); | |
| } | |
| break; | |
| default: | |
| result.SetError(ResultErrorBadInternalState, this->GetCurrentLine()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::State::Finished( | |
| cmVisualStudioSlnParser::ResultData& result) | |
| { | |
| if (this->Stack.top() != FileStateTopLevel) { | |
| result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); | |
| return false; | |
| } | |
| result.Result = ResultOK; | |
| return true; | |
| } | |
| void cmVisualStudioSlnParser::State::IgnoreUntilTag(std::string const& endTag) | |
| { | |
| this->Stack.push(FileStateIgnore); | |
| this->EndIgnoreTag = endTag; | |
| } | |
| cmVisualStudioSlnParser::ResultData::ResultData() = default; | |
| void cmVisualStudioSlnParser::ResultData::Clear() | |
| { | |
| *this = ResultData(); | |
| } | |
| void cmVisualStudioSlnParser::ResultData::SetError(ParseResult error, | |
| size_t line) | |
| { | |
| this->Result = error; | |
| this->ResultLine = line; | |
| } | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupProjects( | |
| 1 << cmVisualStudioSlnParser::DataGroupProjectsBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupProjectDependencies( | |
| 1 << cmVisualStudioSlnParser::DataGroupProjectDependenciesBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupSolutionConfigurations( | |
| 1 << cmVisualStudioSlnParser::DataGroupSolutionConfigurationsBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupProjectConfigurations( | |
| 1 << cmVisualStudioSlnParser::DataGroupProjectConfigurationsBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupSolutionFilters( | |
| 1 << cmVisualStudioSlnParser::DataGroupSolutionFiltersBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupGenericGlobalSections( | |
| 1 << cmVisualStudioSlnParser::DataGroupGenericGlobalSectionsBit); | |
| cmVisualStudioSlnParser::DataGroupSet const | |
| cmVisualStudioSlnParser::DataGroupAll(~0); | |
| bool cmVisualStudioSlnParser::Parse(std::istream& input, cmSlnData& output, | |
| DataGroupSet dataGroups) | |
| { | |
| this->LastResult.Clear(); | |
| if (!this->IsDataGroupSetSupported(dataGroups)) { | |
| this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0); | |
| return false; | |
| } | |
| State state(dataGroups); | |
| return this->ParseImpl(input, output, state); | |
| } | |
| bool cmVisualStudioSlnParser::ParseFile(std::string const& file, | |
| cmSlnData& output, | |
| DataGroupSet dataGroups) | |
| { | |
| this->LastResult.Clear(); | |
| if (!this->IsDataGroupSetSupported(dataGroups)) { | |
| this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0); | |
| return false; | |
| } | |
| cmsys::ifstream f(file.c_str()); | |
| if (!f) { | |
| this->LastResult.SetError(ResultErrorOpeningInput, 0); | |
| return false; | |
| } | |
| State state(dataGroups); | |
| return this->ParseImpl(f, output, state); | |
| } | |
| cmVisualStudioSlnParser::ParseResult cmVisualStudioSlnParser::GetParseResult() | |
| const | |
| { | |
| return this->LastResult.Result; | |
| } | |
| size_t cmVisualStudioSlnParser::GetParseResultLine() const | |
| { | |
| return this->LastResult.ResultLine; | |
| } | |
| bool cmVisualStudioSlnParser::GetParseHadBOM() const | |
| { | |
| return this->LastResult.HadBOM; | |
| } | |
| bool cmVisualStudioSlnParser::IsDataGroupSetSupported( | |
| DataGroupSet dataGroups) const | |
| { | |
| return (dataGroups & DataGroupProjects) != 0; | |
| } | |
| bool cmVisualStudioSlnParser::ParseImpl(std::istream& input, cmSlnData& output, | |
| State& state) | |
| { | |
| std::string line; | |
| // Does the .sln start with a Byte Order Mark? | |
| if (!this->ParseBOM(input, line, state)) { | |
| return false; | |
| } | |
| do { | |
| line = cmTrimWhitespace(line); | |
| if (line.empty()) { | |
| continue; | |
| } | |
| ParsedLine parsedLine; | |
| switch (state.NextLineFormat()) { | |
| case LineMultiValueTag: | |
| if (!this->ParseMultiValueTag(line, parsedLine, state)) { | |
| return false; | |
| } | |
| break; | |
| case LineSingleValueTag: | |
| if (!this->ParseSingleValueTag(line, parsedLine, state)) { | |
| return false; | |
| } | |
| break; | |
| case LineKeyValuePair: | |
| if (!this->ParseKeyValuePair(line, parsedLine, state)) { | |
| return false; | |
| } | |
| break; | |
| case LineVerbatim: | |
| parsedLine.CopyVerbatim(line); | |
| break; | |
| } | |
| if (parsedLine.IsComment()) { | |
| continue; | |
| } | |
| if (!state.Process(parsedLine, output, this->LastResult)) { | |
| return false; | |
| } | |
| } while (state.ReadLine(input, line)); | |
| return state.Finished(this->LastResult); | |
| } | |
| bool cmVisualStudioSlnParser::ParseBOM(std::istream& input, std::string& line, | |
| State& state) | |
| { | |
| char bom[4]; | |
| if (!input.get(bom, 4)) { | |
| this->LastResult.SetError(ResultErrorReadingInput, 1); | |
| return false; | |
| } | |
| this->LastResult.HadBOM = | |
| (bom[0] == char(0xEF) && bom[1] == char(0xBB) && bom[2] == char(0xBF)); | |
| if (!state.ReadLine(input, line)) { | |
| this->LastResult.SetError(ResultErrorReadingInput, 1); | |
| return false; | |
| } | |
| if (!this->LastResult.HadBOM) { | |
| line = cmStrCat( | |
| bom, std::move(line)); // it wasn't a BOM, prepend it to first line | |
| } | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::ParseMultiValueTag(std::string const& line, | |
| ParsedLine& parsedLine, | |
| State& state) | |
| { | |
| size_t idxEqualSign = line.find('='); | |
| auto fullTag = cm::string_view(line).substr(0, idxEqualSign); | |
| if (!this->ParseTag(fullTag, parsedLine, state)) { | |
| return false; | |
| } | |
| if (idxEqualSign != std::string::npos) { | |
| size_t idxFieldStart = idxEqualSign + 1; | |
| if (idxFieldStart < line.size()) { | |
| size_t idxParsing = idxFieldStart; | |
| bool inQuotes = false; | |
| for (;;) { | |
| idxParsing = line.find_first_of(",\"", idxParsing); | |
| bool fieldOver = false; | |
| if (idxParsing == std::string::npos) { | |
| fieldOver = true; | |
| if (inQuotes) { | |
| this->LastResult.SetError(ResultErrorInputStructure, | |
| state.GetCurrentLine()); | |
| return false; | |
| } | |
| } else if (line[idxParsing] == ',' && !inQuotes) { | |
| fieldOver = true; | |
| } else if (line[idxParsing] == '"') { | |
| inQuotes = !inQuotes; | |
| } | |
| if (fieldOver) { | |
| if (!this->ParseValue( | |
| line.substr(idxFieldStart, idxParsing - idxFieldStart), | |
| parsedLine)) { | |
| return false; | |
| } | |
| if (idxParsing == std::string::npos) { | |
| break; // end of last field | |
| } | |
| idxFieldStart = idxParsing + 1; | |
| } | |
| ++idxParsing; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::ParseSingleValueTag(std::string const& line, | |
| ParsedLine& parsedLine, | |
| State& state) | |
| { | |
| size_t idxEqualSign = line.find('='); | |
| auto fullTag = cm::string_view(line).substr(0, idxEqualSign); | |
| if (!this->ParseTag(fullTag, parsedLine, state)) { | |
| return false; | |
| } | |
| if (idxEqualSign != std::string::npos) { | |
| if (!this->ParseValue(line.substr(idxEqualSign + 1), parsedLine)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::ParseKeyValuePair(std::string const& line, | |
| ParsedLine& parsedLine, | |
| State& /*state*/) | |
| { | |
| size_t idxEqualSign = line.find('='); | |
| if (idxEqualSign == std::string::npos) { | |
| parsedLine.CopyVerbatim(line); | |
| return true; | |
| } | |
| std::string const& key = line.substr(0, idxEqualSign); | |
| parsedLine.SetTag(cmTrimWhitespace(key)); | |
| std::string const& value = line.substr(idxEqualSign + 1); | |
| parsedLine.AddValue(cmTrimWhitespace(value)); | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::ParseTag(cm::string_view fullTag, | |
| ParsedLine& parsedLine, State& state) | |
| { | |
| size_t idxLeftParen = fullTag.find('('); | |
| if (idxLeftParen == cm::string_view::npos) { | |
| parsedLine.SetTag(cmTrimWhitespace(fullTag)); | |
| return true; | |
| } | |
| parsedLine.SetTag(cmTrimWhitespace(fullTag.substr(0, idxLeftParen))); | |
| size_t idxRightParen = fullTag.rfind(')'); | |
| if (idxRightParen == cm::string_view::npos) { | |
| this->LastResult.SetError(ResultErrorInputStructure, | |
| state.GetCurrentLine()); | |
| return false; | |
| } | |
| std::string const& arg = cmTrimWhitespace( | |
| fullTag.substr(idxLeftParen + 1, idxRightParen - idxLeftParen - 1)); | |
| if (arg.front() == '"') { | |
| if (arg.back() != '"') { | |
| this->LastResult.SetError(ResultErrorInputStructure, | |
| state.GetCurrentLine()); | |
| return false; | |
| } | |
| parsedLine.SetQuotedArg(arg.substr(1, arg.size() - 2)); | |
| } else { | |
| parsedLine.SetArg(arg); | |
| } | |
| return true; | |
| } | |
| bool cmVisualStudioSlnParser::ParseValue(std::string const& value, | |
| ParsedLine& parsedLine) | |
| { | |
| std::string const& trimmed = cmTrimWhitespace(value); | |
| if (trimmed.empty()) { | |
| parsedLine.AddValue(trimmed); | |
| } else if (trimmed.front() == '"' && trimmed.back() == '"') { | |
| parsedLine.AddQuotedValue(trimmed.substr(1, trimmed.size() - 2)); | |
| } else { | |
| parsedLine.AddValue(trimmed); | |
| } | |
| return true; | |
| } |