/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2013 Kitware, Inc., Insight Software Consortium Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmVisualStudioSlnParser.h" #include "cmSystemTools.h" #include "cmVisualStudioSlnData.h" #include #include //---------------------------------------------------------------------------- namespace { enum LineFormat { LineMultiValueTag, LineSingleValueTag, LineKeyValuePair, LineVerbatim }; } //---------------------------------------------------------------------------- class cmVisualStudioSlnParser::ParsedLine { public: bool IsComment() const; bool IsKeyValuePair() const; const std::string& GetTag() const { return this->Tag; } const std::string& GetArg() const { return this->Arg.first; } std::string GetArgVerbatim() const; size_t GetValueCount() const { return this->Values.size(); } const std::string& GetValue(size_t idxValue) const; std::string GetValueVerbatim(size_t idxValue) const; void SetTag(const std::string& tag) { this->Tag = tag; } void SetArg(const std::string& arg) { this->Arg = StringData(arg, false); } void SetQuotedArg(const std::string& arg) { this->Arg = StringData(arg, true); } void AddValue(const std::string& value) { this->Values.push_back(StringData(value, false)); } void AddQuotedValue(const std::string& value) { this->Values.push_back(StringData(value, true)); } void CopyVerbatim(const std::string& line) { this->Tag = line; } private: typedef std::pair StringData; std::string Tag; StringData Arg; std::vector Values; static const std::string BadString; static const std::string Quote; }; //---------------------------------------------------------------------------- const std::string cmVisualStudioSlnParser::ParsedLine::BadString; const std::string 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 Quote + this->Arg.first + Quote; else return this->Arg.first; } //---------------------------------------------------------------------------- const std::string& cmVisualStudioSlnParser::ParsedLine::GetValue(size_t idxValue) const { if (idxValue < this->Values.size()) return this->Values[idxValue].first; else return BadString; } //---------------------------------------------------------------------------- std::string cmVisualStudioSlnParser::ParsedLine::GetValueVerbatim(size_t idxValue) const { if (idxValue < this->Values.size()) { const StringData& data = this->Values[idxValue]; if (data.second) return Quote + data.first + Quote; else return data.first; } else 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(const cmVisualStudioSlnParser::ParsedLine& 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 Stack; std::string EndIgnoreTag; DataGroupSet RequestedData; size_t CurrentLine; void IgnoreUntilTag(const std::string& endTag); }; //---------------------------------------------------------------------------- cmVisualStudioSlnParser::State::State(DataGroupSet requestedData) : RequestedData(requestedData), CurrentLine(0) { 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: return LineSingleValueTag; case FileStateProjectDependencies: return LineKeyValuePair; case FileStateGlobal: return LineSingleValueTag; case FileStateSolutionConfigurations: return LineKeyValuePair; case FileStateProjectConfigurations: return LineKeyValuePair; case FileStateSolutionFilters: return LineKeyValuePair; case FileStateGlobalSection: return LineKeyValuePair; case FileStateIgnore: return LineVerbatim; default: assert(false); return LineVerbatim; } } //---------------------------------------------------------------------------- bool cmVisualStudioSlnParser::State::Process( const cmVisualStudioSlnParser::ParsedLine& line, cmSlnData& output, cmVisualStudioSlnParser::ResultData& result) { assert(!line.IsComment()); switch (this->Stack.top()) { case FileStateStart: if (!cmSystemTools::StringStartsWith( line.GetTag().c_str(), "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().compare("Project") == 0) { 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().compare("Global") == 0) this->Stack.push(FileStateGlobal); else { result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); return false; } break; case FileStateProject: if (line.GetTag().compare("EndProject") == 0) this->Stack.pop(); else if (line.GetTag().compare("ProjectSection") == 0) { if (line.GetArg().compare("ProjectDependencies") == 0 && line.GetValue(0).compare("postProject") == 0) { 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().compare("EndProjectSection") == 0) 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().compare("EndGlobal") == 0) this->Stack.pop(); else if (line.GetTag().compare("GlobalSection") == 0) { if (line.GetArg().compare("SolutionConfigurationPlatforms") == 0 && line.GetValue(0).compare("preSolution") == 0) { if (this->RequestedData.test(DataGroupSolutionConfigurationsBit)) this->Stack.push(FileStateSolutionConfigurations); else this->IgnoreUntilTag("EndGlobalSection"); } else if (line.GetArg().compare("ProjectConfigurationPlatforms") == 0 && line.GetValue(0).compare("postSolution") == 0) { if (this->RequestedData.test(DataGroupProjectConfigurationsBit)) this->Stack.push(FileStateProjectConfigurations); else this->IgnoreUntilTag("EndGlobalSection"); } else if (line.GetArg().compare("NestedProjects") == 0 && line.GetValue(0).compare("preSolution") == 0) { 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().compare("EndGlobalSection") == 0) this->Stack.pop(); else if (line.IsKeyValuePair()) // implement configuration storing here, once needed ; else { result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); return false; } break; case FileStateProjectConfigurations: if (line.GetTag().compare("EndGlobalSection") == 0) this->Stack.pop(); else if (line.IsKeyValuePair()) // implement configuration storing here, once needed ; else { result.SetError(ResultErrorInputStructure, this->GetCurrentLine()); return false; } break; case FileStateSolutionFilters: if (line.GetTag().compare("EndGlobalSection") == 0) 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().compare("EndGlobalSection") == 0) 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 = ""; } 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(const std::string& endTag) { this->Stack.push(FileStateIgnore); this->EndIgnoreTag = endTag; } //---------------------------------------------------------------------------- cmVisualStudioSlnParser::ResultData::ResultData() : Result(ResultOK) , ResultLine(0) {} //---------------------------------------------------------------------------- void cmVisualStudioSlnParser::ResultData::Clear() { *this = ResultData(); } //---------------------------------------------------------------------------- void cmVisualStudioSlnParser::ResultData::SetError(ParseResult error, size_t line) { this->Result = error; this->ResultLine = line; } //---------------------------------------------------------------------------- const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupProjects( 1 << cmVisualStudioSlnParser::DataGroupProjectsBit); const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupProjectDependencies( 1 << cmVisualStudioSlnParser::DataGroupProjectDependenciesBit); const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupSolutionConfigurations( 1 << cmVisualStudioSlnParser::DataGroupSolutionConfigurationsBit); const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupProjectConfigurations( 1 << cmVisualStudioSlnParser::DataGroupProjectConfigurationsBit); const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupSolutionFilters( 1 << cmVisualStudioSlnParser::DataGroupSolutionFiltersBit); const cmVisualStudioSlnParser::DataGroupSet cmVisualStudioSlnParser::DataGroupGenericGlobalSections( 1 << cmVisualStudioSlnParser::DataGroupGenericGlobalSectionsBit); const cmVisualStudioSlnParser::DataGroupSet 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(const std::string& file, cmSlnData& output, DataGroupSet dataGroups) { this->LastResult.Clear(); if (!this->IsDataGroupSetSupported(dataGroups)) { this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0); return false; } std::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) == dataGroups; //only supporting DataGroupProjects for now } //---------------------------------------------------------------------------- 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 = cmSystemTools::TrimWhitespace(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 = bom + line; // it wasn't a BOM, prepend it to first line return true; } //---------------------------------------------------------------------------- bool cmVisualStudioSlnParser::ParseMultiValueTag(const std::string& line, ParsedLine& parsedLine, State& state) { size_t idxEqualSign = line.find('='); const std::string& fullTag = line.substr(0, idxEqualSign); if (!this->ParseTag(fullTag, parsedLine, state)) return false; if (idxEqualSign != line.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 == line.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 == line.npos) break; //end of last field idxFieldStart = idxParsing + 1; } ++idxParsing; } } } return true; } //---------------------------------------------------------------------------- bool cmVisualStudioSlnParser::ParseSingleValueTag(const std::string& line, ParsedLine& parsedLine, State& state) { size_t idxEqualSign = line.find('='); const std::string& fullTag = line.substr(0, idxEqualSign); if (!this->ParseTag(fullTag, parsedLine, state)) return false; if (idxEqualSign != line.npos) { if (!this->ParseValue(line.substr(idxEqualSign + 1), parsedLine)) return false; } return true; } //---------------------------------------------------------------------------- bool cmVisualStudioSlnParser::ParseKeyValuePair(const std::string& line, ParsedLine& parsedLine, State& /*state*/) { size_t idxEqualSign = line.find('='); if (idxEqualSign == line.npos) { parsedLine.CopyVerbatim(line); return true; } const std::string& key = line.substr(0, idxEqualSign); parsedLine.SetTag(cmSystemTools::TrimWhitespace(key)); const std::string& value = line.substr(idxEqualSign + 1); parsedLine.AddValue(cmSystemTools::TrimWhitespace(value)); return true; } //---------------------------------------------------------------------------- bool cmVisualStudioSlnParser::ParseTag(const std::string& fullTag, ParsedLine& parsedLine, State& state) { size_t idxLeftParen = fullTag.find('('); if (idxLeftParen == fullTag.npos) { parsedLine.SetTag(cmSystemTools::TrimWhitespace(fullTag)); return true; } parsedLine.SetTag( cmSystemTools::TrimWhitespace(fullTag.substr(0, idxLeftParen))); size_t idxRightParen = fullTag.rfind(')'); if (idxRightParen == fullTag.npos) { this->LastResult.SetError(ResultErrorInputStructure, state.GetCurrentLine()); return false; } const std::string& arg = cmSystemTools::TrimWhitespace( fullTag.substr(idxLeftParen + 1, idxRightParen - idxLeftParen - 1)); if (arg[0] == '"') { if (arg[arg.size() - 1] != '"') { 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(const std::string& value, ParsedLine& parsedLine) { const std::string& trimmed = cmSystemTools::TrimWhitespace(value); if (trimmed.empty()) parsedLine.AddValue(trimmed); else if (trimmed[0] == '"' && trimmed[trimmed.size() - 1] == '"') parsedLine.AddQuotedValue(trimmed.substr(1, trimmed.size() - 2)); else parsedLine.AddValue(trimmed); return true; }