CMake/Source/cmVisualStudioSlnParser.cxx

629 lines
19 KiB
C++
Raw Normal View History

Simplify CMake per-source license notices Per-source copyright/license notice headers that spell out copyright holder names and years are hard to maintain and often out-of-date or plain wrong. Precise contributor information is already maintained automatically by the version control tool. Ultimately it is the receiver of a file who is responsible for determining its licensing status, and per-source notices are merely a convenience. Therefore it is simpler and more accurate for each source to have a generic notice of the license name and references to more detailed information on copyright holders and full license terms. Our `Copyright.txt` file now contains a list of Contributors whose names appeared source-level copyright notices. It also references version control history for more precise information. Therefore we no longer need to spell out the list of Contributors in each source file notice. Replace CMake per-source copyright/license notice headers with a short description of the license and links to `Copyright.txt` and online information available from "https://cmake.org/licensing". The online URL also handles cases of modules being copied out of our source into other projects, so we can drop our notices about replacing links with full license text. Run the `Utilities/Scripts/filter-notices.bash` script to perform the majority of the replacements mechanically. Manually fix up shebang lines and trailing newlines in a few files. Manually update the notices in a few files that the script does not handle.
2016-09-27 19:01:08 +00:00
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmVisualStudioSlnParser.h"
#include "cmSystemTools.h"
#include "cmVisualStudioSlnData.h"
#include <cmsys/FStream.hxx>
#include <cassert>
#include <stack>
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<std::string, bool> StringData;
std::string Tag;
StringData Arg;
std::vector<StringData> 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<FileState> 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;
}
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) == 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;
}