1 /*============================================================================
2 CMake - Cross Platform Makefile Generator
3 Copyright 2000-2013 Kitware, Inc., Insight Software Consortium
5 Distributed under the OSI-approved BSD License (the "License");
6 see accompanying file Copyright.txt for details.
8 This software is distributed WITHOUT ANY WARRANTY; without even the
9 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 See the License for more information.
11 ============================================================================*/
12 #include "cmVisualStudioSlnParser.h"
14 #include "cmSystemTools.h"
15 #include "cmVisualStudioSlnData.h"
20 //----------------------------------------------------------------------------
32 //----------------------------------------------------------------------------
33 class cmVisualStudioSlnParser::ParsedLine
36 bool IsComment() const;
37 bool IsKeyValuePair() const;
39 const std::string& GetTag() const { return this->Tag; }
40 const std::string& GetArg() const { return this->Arg.first; }
41 std::string GetArgVerbatim() const;
42 size_t GetValueCount() const { return this->Values.size(); }
43 const std::string& GetValue(size_t idxValue) const;
44 std::string GetValueVerbatim(size_t idxValue) const;
46 void SetTag(const std::string& tag) { this->Tag = tag; }
47 void SetArg(const std::string& arg) { this->Arg = StringData(arg, false); }
48 void SetQuotedArg(const std::string& arg)
49 { this->Arg = StringData(arg, true); }
50 void AddValue(const std::string& value)
51 { this->Values.push_back(StringData(value, false)); }
52 void AddQuotedValue(const std::string& value)
53 { this->Values.push_back(StringData(value, true)); }
55 void CopyVerbatim(const std::string& line) { this->Tag = line; }
58 typedef std::pair<std::string, bool> StringData;
61 std::vector<StringData> Values;
62 static const std::string BadString;
63 static const std::string Quote;
66 //----------------------------------------------------------------------------
67 const std::string cmVisualStudioSlnParser::ParsedLine::BadString;
68 const std::string cmVisualStudioSlnParser::ParsedLine::Quote("\"");
70 //----------------------------------------------------------------------------
71 bool cmVisualStudioSlnParser::ParsedLine::IsComment() const
73 assert(!this->Tag.empty());
74 return (this->Tag[0]== '#');
77 //----------------------------------------------------------------------------
78 bool cmVisualStudioSlnParser::ParsedLine::IsKeyValuePair() const
80 assert(!this->Tag.empty());
81 return this->Arg.first.empty() && this->Values.size() == 1;
84 //----------------------------------------------------------------------------
85 std::string cmVisualStudioSlnParser::ParsedLine::GetArgVerbatim() const
88 return Quote + this->Arg.first + Quote;
90 return this->Arg.first;
93 //----------------------------------------------------------------------------
95 cmVisualStudioSlnParser::ParsedLine::GetValue(size_t idxValue) const
97 if (idxValue < this->Values.size())
98 return this->Values[idxValue].first;
103 //----------------------------------------------------------------------------
105 cmVisualStudioSlnParser::ParsedLine::GetValueVerbatim(size_t idxValue) const
107 if (idxValue < this->Values.size())
109 const StringData& data = this->Values[idxValue];
111 return Quote + data.first + Quote;
119 //----------------------------------------------------------------------------
120 class cmVisualStudioSlnParser::State
123 explicit State(DataGroupSet requestedData);
125 size_t GetCurrentLine() const { return this->CurrentLine; }
126 bool ReadLine(std::istream& input, std::string& line);
128 LineFormat NextLineFormat() const;
130 bool Process(const cmVisualStudioSlnParser::ParsedLine& line,
132 cmVisualStudioSlnParser::ResultData& result);
134 bool Finished(cmVisualStudioSlnParser::ResultData& result);
142 FileStateProjectDependencies,
144 FileStateSolutionConfigurations,
145 FileStateProjectConfigurations,
146 FileStateSolutionFilters,
147 FileStateGlobalSection,
150 std::stack<FileState> Stack;
151 std::string EndIgnoreTag;
152 DataGroupSet RequestedData;
155 void IgnoreUntilTag(const std::string& endTag);
158 //----------------------------------------------------------------------------
159 cmVisualStudioSlnParser::State::State(DataGroupSet requestedData) :
160 RequestedData(requestedData),
163 if (this->RequestedData.test(DataGroupProjectDependenciesBit))
164 this->RequestedData.set(DataGroupProjectsBit);
165 this->Stack.push(FileStateStart);
168 //----------------------------------------------------------------------------
169 bool cmVisualStudioSlnParser::State::ReadLine(std::istream& input,
173 return !std::getline(input, line).fail();
176 //----------------------------------------------------------------------------
177 LineFormat cmVisualStudioSlnParser::State::NextLineFormat() const
179 switch (this->Stack.top())
181 case FileStateStart: return LineVerbatim;
182 case FileStateTopLevel: return LineMultiValueTag;
183 case FileStateProject: return LineSingleValueTag;
184 case FileStateProjectDependencies: return LineKeyValuePair;
185 case FileStateGlobal: return LineSingleValueTag;
186 case FileStateSolutionConfigurations: return LineKeyValuePair;
187 case FileStateProjectConfigurations: return LineKeyValuePair;
188 case FileStateSolutionFilters: return LineKeyValuePair;
189 case FileStateGlobalSection: return LineKeyValuePair;
190 case FileStateIgnore: return LineVerbatim;
197 //----------------------------------------------------------------------------
198 bool cmVisualStudioSlnParser::State::Process(
199 const cmVisualStudioSlnParser::ParsedLine& line,
200 cmSlnData& output, cmVisualStudioSlnParser::ResultData& result)
202 assert(!line.IsComment());
203 switch (this->Stack.top())
206 if (!cmSystemTools::StringStartsWith(
207 line.GetTag().c_str(), "Microsoft Visual Studio Solution File"))
209 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
213 this->Stack.push(FileStateTopLevel);
215 case FileStateTopLevel:
216 if (line.GetTag().compare("Project") == 0)
218 if (line.GetValueCount() != 3)
220 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
223 if (this->RequestedData.test(DataGroupProjectsBit))
225 if (!output.AddProject(line.GetValue(2),
229 result.SetError(ResultErrorInputData, this->GetCurrentLine());
232 this->Stack.push(FileStateProject);
235 this->IgnoreUntilTag("EndProject");
237 else if (line.GetTag().compare("Global") == 0)
238 this->Stack.push(FileStateGlobal);
241 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
245 case FileStateProject:
246 if (line.GetTag().compare("EndProject") == 0)
248 else if (line.GetTag().compare("ProjectSection") == 0)
250 if (line.GetArg().compare("ProjectDependencies") == 0 &&
251 line.GetValue(0).compare("postProject") == 0)
253 if (this->RequestedData.test(DataGroupProjectDependenciesBit))
254 this->Stack.push(FileStateProjectDependencies);
256 this->IgnoreUntilTag("EndProjectSection");
259 this->IgnoreUntilTag("EndProjectSection");
263 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
267 case FileStateProjectDependencies:
268 if (line.GetTag().compare("EndProjectSection") == 0)
270 else if (line.IsKeyValuePair())
271 // implement dependency storing here, once needed
275 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
279 case FileStateGlobal:
280 if (line.GetTag().compare("EndGlobal") == 0)
282 else if (line.GetTag().compare("GlobalSection") == 0)
284 if (line.GetArg().compare("SolutionConfigurationPlatforms") == 0 &&
285 line.GetValue(0).compare("preSolution") == 0)
287 if (this->RequestedData.test(DataGroupSolutionConfigurationsBit))
288 this->Stack.push(FileStateSolutionConfigurations);
290 this->IgnoreUntilTag("EndGlobalSection");
292 else if (line.GetArg().compare("ProjectConfigurationPlatforms") == 0 &&
293 line.GetValue(0).compare("postSolution") == 0)
295 if (this->RequestedData.test(DataGroupProjectConfigurationsBit))
296 this->Stack.push(FileStateProjectConfigurations);
298 this->IgnoreUntilTag("EndGlobalSection");
300 else if (line.GetArg().compare("NestedProjects") == 0 &&
301 line.GetValue(0).compare("preSolution") == 0)
303 if (this->RequestedData.test(DataGroupSolutionFiltersBit))
304 this->Stack.push(FileStateSolutionFilters);
306 this->IgnoreUntilTag("EndGlobalSection");
308 else if (this->RequestedData.test(DataGroupGenericGlobalSectionsBit))
309 this->Stack.push(FileStateGlobalSection);
311 this->IgnoreUntilTag("EndGlobalSection");
315 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
319 case FileStateSolutionConfigurations:
320 if (line.GetTag().compare("EndGlobalSection") == 0)
322 else if (line.IsKeyValuePair())
323 // implement configuration storing here, once needed
327 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
331 case FileStateProjectConfigurations:
332 if (line.GetTag().compare("EndGlobalSection") == 0)
334 else if (line.IsKeyValuePair())
335 // implement configuration storing here, once needed
339 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
343 case FileStateSolutionFilters:
344 if (line.GetTag().compare("EndGlobalSection") == 0)
346 else if (line.IsKeyValuePair())
347 // implement filter storing here, once needed
351 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
355 case FileStateGlobalSection:
356 if (line.GetTag().compare("EndGlobalSection") == 0)
358 else if (line.IsKeyValuePair())
359 // implement section storing here, once needed
363 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
367 case FileStateIgnore:
368 if (line.GetTag() == this->EndIgnoreTag)
371 this->EndIgnoreTag = "";
375 result.SetError(ResultErrorBadInternalState, this->GetCurrentLine());
381 //----------------------------------------------------------------------------
382 bool cmVisualStudioSlnParser::State::Finished(
383 cmVisualStudioSlnParser::ResultData& result)
385 if (this->Stack.top() != FileStateTopLevel)
387 result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
390 result.Result = ResultOK;
394 //----------------------------------------------------------------------------
395 void cmVisualStudioSlnParser::State::IgnoreUntilTag(const std::string& endTag)
397 this->Stack.push(FileStateIgnore);
398 this->EndIgnoreTag = endTag;
401 //----------------------------------------------------------------------------
402 cmVisualStudioSlnParser::ResultData::ResultData()
407 //----------------------------------------------------------------------------
408 void cmVisualStudioSlnParser::ResultData::Clear()
410 *this = ResultData();
413 //----------------------------------------------------------------------------
414 void cmVisualStudioSlnParser::ResultData::SetError(ParseResult error,
417 this->Result = error;
418 this->ResultLine = line;
421 //----------------------------------------------------------------------------
422 const cmVisualStudioSlnParser::DataGroupSet
423 cmVisualStudioSlnParser::DataGroupProjects(
424 1 << cmVisualStudioSlnParser::DataGroupProjectsBit);
426 const cmVisualStudioSlnParser::DataGroupSet
427 cmVisualStudioSlnParser::DataGroupProjectDependencies(
428 1 << cmVisualStudioSlnParser::DataGroupProjectDependenciesBit);
430 const cmVisualStudioSlnParser::DataGroupSet
431 cmVisualStudioSlnParser::DataGroupSolutionConfigurations(
432 1 << cmVisualStudioSlnParser::DataGroupSolutionConfigurationsBit);
434 const cmVisualStudioSlnParser::DataGroupSet
435 cmVisualStudioSlnParser::DataGroupProjectConfigurations(
436 1 << cmVisualStudioSlnParser::DataGroupProjectConfigurationsBit);
438 const cmVisualStudioSlnParser::DataGroupSet
439 cmVisualStudioSlnParser::DataGroupSolutionFilters(
440 1 << cmVisualStudioSlnParser::DataGroupSolutionFiltersBit);
442 const cmVisualStudioSlnParser::DataGroupSet
443 cmVisualStudioSlnParser::DataGroupGenericGlobalSections(
444 1 << cmVisualStudioSlnParser::DataGroupGenericGlobalSectionsBit);
446 const cmVisualStudioSlnParser::DataGroupSet
447 cmVisualStudioSlnParser::DataGroupAll(~0);
449 //----------------------------------------------------------------------------
450 bool cmVisualStudioSlnParser::Parse(std::istream& input,
452 DataGroupSet dataGroups)
454 this->LastResult.Clear();
455 if (!this->IsDataGroupSetSupported(dataGroups))
457 this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0);
460 State state(dataGroups);
461 return this->ParseImpl(input, output, state);
464 //----------------------------------------------------------------------------
465 bool cmVisualStudioSlnParser::ParseFile(const std::string& file,
467 DataGroupSet dataGroups)
469 this->LastResult.Clear();
470 if (!this->IsDataGroupSetSupported(dataGroups))
472 this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0);
475 std::ifstream f(file.c_str());
478 this->LastResult.SetError(ResultErrorOpeningInput, 0);
481 State state(dataGroups);
482 return this->ParseImpl(f, output, state);
485 //----------------------------------------------------------------------------
486 cmVisualStudioSlnParser::ParseResult
487 cmVisualStudioSlnParser::GetParseResult() const
489 return this->LastResult.Result;
492 //----------------------------------------------------------------------------
493 size_t cmVisualStudioSlnParser::GetParseResultLine() const
495 return this->LastResult.ResultLine;
498 //----------------------------------------------------------------------------
499 bool cmVisualStudioSlnParser::GetParseHadBOM() const
501 return this->LastResult.HadBOM;
504 //----------------------------------------------------------------------------
506 cmVisualStudioSlnParser::IsDataGroupSetSupported(DataGroupSet dataGroups) const
508 return (dataGroups & DataGroupProjects) == dataGroups;
509 //only supporting DataGroupProjects for now
512 //----------------------------------------------------------------------------
513 bool cmVisualStudioSlnParser::ParseImpl(std::istream& input,
518 // Does the .sln start with a Byte Order Mark?
519 if (!this->ParseBOM(input, line, state))
523 line = cmSystemTools::TrimWhitespace(line);
526 ParsedLine parsedLine;
527 switch (state.NextLineFormat())
529 case LineMultiValueTag:
530 if (!this->ParseMultiValueTag(line, parsedLine, state))
533 case LineSingleValueTag:
534 if (!this->ParseSingleValueTag(line, parsedLine, state))
537 case LineKeyValuePair:
538 if (!this->ParseKeyValuePair(line, parsedLine, state))
542 parsedLine.CopyVerbatim(line);
545 if (parsedLine.IsComment())
547 if (!state.Process(parsedLine, output, this->LastResult))
550 while (state.ReadLine(input, line));
551 return state.Finished(this->LastResult);
554 //----------------------------------------------------------------------------
555 bool cmVisualStudioSlnParser::ParseBOM(std::istream& input,
560 if (!input.get(bom, 4))
562 this->LastResult.SetError(ResultErrorReadingInput, 1);
565 this->LastResult.HadBOM =
566 (bom[0] == char(0xEF) && bom[1] == char(0xBB) && bom[2] == char(0xBF));
567 if (!state.ReadLine(input, line))
569 this->LastResult.SetError(ResultErrorReadingInput, 1);
572 if (!this->LastResult.HadBOM)
573 line = bom + line; // it wasn't a BOM, prepend it to first line
577 //----------------------------------------------------------------------------
578 bool cmVisualStudioSlnParser::ParseMultiValueTag(const std::string& line,
579 ParsedLine& parsedLine,
582 size_t idxEqualSign = line.find('=');
583 const std::string& fullTag = line.substr(0, idxEqualSign);
584 if (!this->ParseTag(fullTag, parsedLine, state))
586 if (idxEqualSign != line.npos)
588 size_t idxFieldStart = idxEqualSign + 1;
589 if (idxFieldStart < line.size())
591 size_t idxParsing = idxFieldStart;
592 bool inQuotes = false;
595 idxParsing = line.find_first_of(",\"", idxParsing);
596 bool fieldOver = false;
597 if (idxParsing == line.npos)
602 this->LastResult.SetError(ResultErrorInputStructure,
603 state.GetCurrentLine());
607 else if (line[idxParsing] == ',' && !inQuotes)
609 else if (line[idxParsing] == '"')
610 inQuotes = !inQuotes;
613 if (!this->ParseValue(line.substr(idxFieldStart,
614 idxParsing - idxFieldStart),
617 if (idxParsing == line.npos)
618 break; //end of last field
619 idxFieldStart = idxParsing + 1;
628 //----------------------------------------------------------------------------
629 bool cmVisualStudioSlnParser::ParseSingleValueTag(const std::string& line,
630 ParsedLine& parsedLine,
633 size_t idxEqualSign = line.find('=');
634 const std::string& fullTag = line.substr(0, idxEqualSign);
635 if (!this->ParseTag(fullTag, parsedLine, state))
637 if (idxEqualSign != line.npos)
639 if (!this->ParseValue(line.substr(idxEqualSign + 1), parsedLine))
645 //----------------------------------------------------------------------------
646 bool cmVisualStudioSlnParser::ParseKeyValuePair(const std::string& line,
647 ParsedLine& parsedLine,
650 size_t idxEqualSign = line.find('=');
651 if (idxEqualSign == line.npos)
653 parsedLine.CopyVerbatim(line);
656 const std::string& key = line.substr(0, idxEqualSign);
657 parsedLine.SetTag(cmSystemTools::TrimWhitespace(key));
658 const std::string& value = line.substr(idxEqualSign + 1);
659 parsedLine.AddValue(cmSystemTools::TrimWhitespace(value));
663 //----------------------------------------------------------------------------
664 bool cmVisualStudioSlnParser::ParseTag(const std::string& fullTag,
665 ParsedLine& parsedLine,
668 size_t idxLeftParen = fullTag.find('(');
669 if (idxLeftParen == fullTag.npos)
671 parsedLine.SetTag(cmSystemTools::TrimWhitespace(fullTag));
675 cmSystemTools::TrimWhitespace(fullTag.substr(0, idxLeftParen)));
676 size_t idxRightParen = fullTag.rfind(')');
677 if (idxRightParen == fullTag.npos)
679 this->LastResult.SetError(ResultErrorInputStructure,
680 state.GetCurrentLine());
683 const std::string& arg = cmSystemTools::TrimWhitespace(
684 fullTag.substr(idxLeftParen + 1, idxRightParen - idxLeftParen - 1));
687 if (arg[arg.size() - 1] != '"')
689 this->LastResult.SetError(ResultErrorInputStructure,
690 state.GetCurrentLine());
693 parsedLine.SetQuotedArg(arg.substr(1, arg.size() - 2));
696 parsedLine.SetArg(arg);
700 //----------------------------------------------------------------------------
701 bool cmVisualStudioSlnParser::ParseValue(const std::string& value,
702 ParsedLine& parsedLine)
704 const std::string& trimmed = cmSystemTools::TrimWhitespace(value);
706 parsedLine.AddValue(trimmed);
707 else if (trimmed[0] == '"' && trimmed[trimmed.size() - 1] == '"')
708 parsedLine.AddQuotedValue(trimmed.substr(1, trimmed.size() - 2));
710 parsedLine.AddValue(trimmed);