Imported Upstream version 2.8.12.2
[platform/upstream/cmake.git] / Source / cmVisualStudioSlnParser.cxx
1 /*============================================================================
2   CMake - Cross Platform Makefile Generator
3   Copyright 2000-2013 Kitware, Inc., Insight Software Consortium
4
5   Distributed under the OSI-approved BSD License (the "License");
6   see accompanying file Copyright.txt for details.
7
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"
13
14 #include "cmSystemTools.h"
15 #include "cmVisualStudioSlnData.h"
16
17 #include <cassert>
18 #include <stack>
19
20 //----------------------------------------------------------------------------
21 namespace
22 {
23   enum LineFormat
24   {
25     LineMultiValueTag,
26     LineSingleValueTag,
27     LineKeyValuePair,
28     LineVerbatim
29   };
30 }
31
32 //----------------------------------------------------------------------------
33 class cmVisualStudioSlnParser::ParsedLine
34 {
35 public:
36   bool IsComment() const;
37   bool IsKeyValuePair() const;
38
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;
45
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)); }
54
55   void CopyVerbatim(const std::string& line) { this->Tag = line; }
56
57 private:
58   typedef std::pair<std::string, bool> StringData;
59   std::string Tag;
60   StringData Arg;
61   std::vector<StringData> Values;
62   static const std::string BadString;
63   static const std::string Quote;
64 };
65
66 //----------------------------------------------------------------------------
67 const std::string cmVisualStudioSlnParser::ParsedLine::BadString;
68 const std::string cmVisualStudioSlnParser::ParsedLine::Quote("\"");
69
70 //----------------------------------------------------------------------------
71 bool cmVisualStudioSlnParser::ParsedLine::IsComment() const
72 {
73   assert(!this->Tag.empty());
74   return (this->Tag[0]== '#');
75 }
76
77 //----------------------------------------------------------------------------
78 bool cmVisualStudioSlnParser::ParsedLine::IsKeyValuePair() const
79 {
80   assert(!this->Tag.empty());
81   return this->Arg.first.empty() && this->Values.size() == 1;
82 }
83
84 //----------------------------------------------------------------------------
85 std::string cmVisualStudioSlnParser::ParsedLine::GetArgVerbatim() const
86 {
87   if (this->Arg.second)
88     return Quote + this->Arg.first + Quote;
89   else
90     return this->Arg.first;
91 }
92
93 //----------------------------------------------------------------------------
94 const std::string&
95 cmVisualStudioSlnParser::ParsedLine::GetValue(size_t idxValue) const
96 {
97   if (idxValue < this->Values.size())
98     return this->Values[idxValue].first;
99   else
100     return BadString;
101 }
102
103 //----------------------------------------------------------------------------
104 std::string
105 cmVisualStudioSlnParser::ParsedLine::GetValueVerbatim(size_t idxValue) const
106 {
107   if (idxValue < this->Values.size())
108     {
109     const StringData& data = this->Values[idxValue];
110     if (data.second)
111       return Quote + data.first + Quote;
112     else
113       return data.first;
114     }
115   else
116     return BadString;
117 }
118
119 //----------------------------------------------------------------------------
120 class cmVisualStudioSlnParser::State
121 {
122 public:
123   explicit State(DataGroupSet requestedData);
124
125   size_t GetCurrentLine() const { return this->CurrentLine; }
126   bool ReadLine(std::istream& input, std::string& line);
127
128   LineFormat NextLineFormat() const;
129
130   bool Process(const cmVisualStudioSlnParser::ParsedLine& line,
131                cmSlnData& output,
132                cmVisualStudioSlnParser::ResultData& result);
133
134   bool Finished(cmVisualStudioSlnParser::ResultData& result);
135
136 private:
137   enum FileState
138   {
139     FileStateStart,
140     FileStateTopLevel,
141     FileStateProject,
142     FileStateProjectDependencies,
143     FileStateGlobal,
144     FileStateSolutionConfigurations,
145     FileStateProjectConfigurations,
146     FileStateSolutionFilters,
147     FileStateGlobalSection,
148     FileStateIgnore
149   };
150   std::stack<FileState> Stack;
151   std::string EndIgnoreTag;
152   DataGroupSet RequestedData;
153   size_t CurrentLine;
154
155   void IgnoreUntilTag(const std::string& endTag);
156 };
157
158 //----------------------------------------------------------------------------
159 cmVisualStudioSlnParser::State::State(DataGroupSet requestedData) :
160   RequestedData(requestedData),
161   CurrentLine(0)
162 {
163   if (this->RequestedData.test(DataGroupProjectDependenciesBit))
164     this->RequestedData.set(DataGroupProjectsBit);
165   this->Stack.push(FileStateStart);
166 }
167
168 //----------------------------------------------------------------------------
169 bool cmVisualStudioSlnParser::State::ReadLine(std::istream& input,
170                                               std::string& line)
171 {
172   ++this->CurrentLine;
173   return !std::getline(input, line).fail();
174 }
175
176 //----------------------------------------------------------------------------
177 LineFormat cmVisualStudioSlnParser::State::NextLineFormat() const
178 {
179   switch (this->Stack.top())
180     {
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;
191     default:
192       assert(false);
193       return LineVerbatim;
194     }
195 }
196
197 //----------------------------------------------------------------------------
198 bool cmVisualStudioSlnParser::State::Process(
199   const cmVisualStudioSlnParser::ParsedLine& line,
200   cmSlnData& output, cmVisualStudioSlnParser::ResultData& result)
201 {
202   assert(!line.IsComment());
203   switch (this->Stack.top())
204     {
205     case FileStateStart:
206       if (!cmSystemTools::StringStartsWith(
207         line.GetTag().c_str(), "Microsoft Visual Studio Solution File"))
208         {
209         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
210         return false;
211         }
212       this->Stack.pop();
213       this->Stack.push(FileStateTopLevel);
214       break;
215     case FileStateTopLevel:
216       if (line.GetTag().compare("Project") == 0)
217         {
218         if (line.GetValueCount() != 3)
219           {
220           result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
221           return false;
222           }
223         if (this->RequestedData.test(DataGroupProjectsBit))
224           {
225           if (!output.AddProject(line.GetValue(2),
226                                  line.GetValue(0),
227                                  line.GetValue(1)))
228             {
229             result.SetError(ResultErrorInputData, this->GetCurrentLine());
230             return false;
231             }
232           this->Stack.push(FileStateProject);
233           }
234         else
235           this->IgnoreUntilTag("EndProject");
236         }
237       else if (line.GetTag().compare("Global") == 0)
238         this->Stack.push(FileStateGlobal);
239       else
240         {
241         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
242         return false;
243         }
244       break;
245     case FileStateProject:
246       if (line.GetTag().compare("EndProject") == 0)
247         this->Stack.pop();
248       else if (line.GetTag().compare("ProjectSection") == 0)
249         {
250         if (line.GetArg().compare("ProjectDependencies") == 0 &&
251             line.GetValue(0).compare("postProject") == 0)
252           {
253           if (this->RequestedData.test(DataGroupProjectDependenciesBit))
254             this->Stack.push(FileStateProjectDependencies);
255           else
256             this->IgnoreUntilTag("EndProjectSection");
257           }
258         else
259           this->IgnoreUntilTag("EndProjectSection");
260         }
261       else
262         {
263         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
264         return false;
265         }
266       break;
267     case FileStateProjectDependencies:
268       if (line.GetTag().compare("EndProjectSection") == 0)
269         this->Stack.pop();
270       else if (line.IsKeyValuePair())
271         // implement dependency storing here, once needed
272         ;
273       else
274         {
275         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
276         return false;
277         }
278       break;
279     case FileStateGlobal:
280       if (line.GetTag().compare("EndGlobal") == 0)
281         this->Stack.pop();
282       else if (line.GetTag().compare("GlobalSection") == 0)
283         {
284         if (line.GetArg().compare("SolutionConfigurationPlatforms") == 0 &&
285             line.GetValue(0).compare("preSolution") == 0)
286           {
287           if (this->RequestedData.test(DataGroupSolutionConfigurationsBit))
288             this->Stack.push(FileStateSolutionConfigurations);
289           else
290             this->IgnoreUntilTag("EndGlobalSection");
291           }
292         else if (line.GetArg().compare("ProjectConfigurationPlatforms") == 0 &&
293                  line.GetValue(0).compare("postSolution") == 0)
294           {
295           if (this->RequestedData.test(DataGroupProjectConfigurationsBit))
296             this->Stack.push(FileStateProjectConfigurations);
297           else
298             this->IgnoreUntilTag("EndGlobalSection");
299           }
300         else if (line.GetArg().compare("NestedProjects") == 0 &&
301                  line.GetValue(0).compare("preSolution") == 0)
302           {
303           if (this->RequestedData.test(DataGroupSolutionFiltersBit))
304             this->Stack.push(FileStateSolutionFilters);
305           else
306             this->IgnoreUntilTag("EndGlobalSection");
307           }
308         else if (this->RequestedData.test(DataGroupGenericGlobalSectionsBit))
309           this->Stack.push(FileStateGlobalSection);
310         else
311           this->IgnoreUntilTag("EndGlobalSection");
312         }
313       else
314         {
315         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
316         return false;
317         }
318       break;
319     case FileStateSolutionConfigurations:
320       if (line.GetTag().compare("EndGlobalSection") == 0)
321         this->Stack.pop();
322       else if (line.IsKeyValuePair())
323         // implement configuration storing here, once needed
324         ;
325       else
326         {
327         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
328         return false;
329         }
330       break;
331     case FileStateProjectConfigurations:
332       if (line.GetTag().compare("EndGlobalSection") == 0)
333         this->Stack.pop();
334       else if (line.IsKeyValuePair())
335         // implement configuration storing here, once needed
336         ;
337       else
338         {
339         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
340         return false;
341         }
342       break;
343     case FileStateSolutionFilters:
344       if (line.GetTag().compare("EndGlobalSection") == 0)
345         this->Stack.pop();
346       else if (line.IsKeyValuePair())
347         // implement filter storing here, once needed
348         ;
349       else
350         {
351         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
352         return false;
353         }
354       break;
355     case FileStateGlobalSection:
356       if (line.GetTag().compare("EndGlobalSection") == 0)
357         this->Stack.pop();
358       else if (line.IsKeyValuePair())
359         // implement section storing here, once needed
360         ;
361       else
362         {
363         result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
364         return false;
365         }
366       break;
367     case FileStateIgnore:
368       if (line.GetTag() == this->EndIgnoreTag)
369         {
370         this->Stack.pop();
371         this->EndIgnoreTag = "";
372         }
373       break;
374     default:
375       result.SetError(ResultErrorBadInternalState, this->GetCurrentLine());
376       return false;
377     }
378   return true;
379 }
380
381 //----------------------------------------------------------------------------
382 bool cmVisualStudioSlnParser::State::Finished(
383   cmVisualStudioSlnParser::ResultData& result)
384 {
385   if (this->Stack.top() != FileStateTopLevel)
386     {
387     result.SetError(ResultErrorInputStructure, this->GetCurrentLine());
388     return false;
389     }
390   result.Result = ResultOK;
391   return true;
392 }
393
394 //----------------------------------------------------------------------------
395 void cmVisualStudioSlnParser::State::IgnoreUntilTag(const std::string& endTag)
396 {
397   this->Stack.push(FileStateIgnore);
398   this->EndIgnoreTag = endTag;
399 }
400
401 //----------------------------------------------------------------------------
402 cmVisualStudioSlnParser::ResultData::ResultData()
403   : Result(ResultOK)
404   , ResultLine(0)
405 {}
406
407 //----------------------------------------------------------------------------
408 void cmVisualStudioSlnParser::ResultData::Clear()
409 {
410   *this = ResultData();
411 }
412
413 //----------------------------------------------------------------------------
414 void cmVisualStudioSlnParser::ResultData::SetError(ParseResult error,
415                                                    size_t line)
416 {
417   this->Result = error;
418   this->ResultLine = line;
419 }
420
421 //----------------------------------------------------------------------------
422 const cmVisualStudioSlnParser::DataGroupSet
423 cmVisualStudioSlnParser::DataGroupProjects(
424   1 << cmVisualStudioSlnParser::DataGroupProjectsBit);
425
426 const cmVisualStudioSlnParser::DataGroupSet
427 cmVisualStudioSlnParser::DataGroupProjectDependencies(
428   1 << cmVisualStudioSlnParser::DataGroupProjectDependenciesBit);
429
430 const cmVisualStudioSlnParser::DataGroupSet
431 cmVisualStudioSlnParser::DataGroupSolutionConfigurations(
432   1 << cmVisualStudioSlnParser::DataGroupSolutionConfigurationsBit);
433
434 const cmVisualStudioSlnParser::DataGroupSet
435 cmVisualStudioSlnParser::DataGroupProjectConfigurations(
436   1 << cmVisualStudioSlnParser::DataGroupProjectConfigurationsBit);
437
438 const cmVisualStudioSlnParser::DataGroupSet
439 cmVisualStudioSlnParser::DataGroupSolutionFilters(
440   1 << cmVisualStudioSlnParser::DataGroupSolutionFiltersBit);
441
442 const cmVisualStudioSlnParser::DataGroupSet
443 cmVisualStudioSlnParser::DataGroupGenericGlobalSections(
444   1 << cmVisualStudioSlnParser::DataGroupGenericGlobalSectionsBit);
445
446 const cmVisualStudioSlnParser::DataGroupSet
447 cmVisualStudioSlnParser::DataGroupAll(~0);
448
449 //----------------------------------------------------------------------------
450 bool cmVisualStudioSlnParser::Parse(std::istream& input,
451                                     cmSlnData& output,
452                                     DataGroupSet dataGroups)
453 {
454   this->LastResult.Clear();
455   if (!this->IsDataGroupSetSupported(dataGroups))
456     {
457     this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0);
458     return false;
459     }
460   State state(dataGroups);
461   return this->ParseImpl(input, output, state);
462 }
463
464 //----------------------------------------------------------------------------
465 bool cmVisualStudioSlnParser::ParseFile(const std::string& file,
466                                         cmSlnData& output,
467                                         DataGroupSet dataGroups)
468 {
469   this->LastResult.Clear();
470   if (!this->IsDataGroupSetSupported(dataGroups))
471     {
472     this->LastResult.SetError(ResultErrorUnsupportedDataGroup, 0);
473     return false;
474     }
475   std::ifstream f(file.c_str());
476   if (!f)
477     {
478     this->LastResult.SetError(ResultErrorOpeningInput, 0);
479     return false;
480     }
481   State state(dataGroups);
482   return this->ParseImpl(f, output, state);
483 }
484
485 //----------------------------------------------------------------------------
486 cmVisualStudioSlnParser::ParseResult
487 cmVisualStudioSlnParser::GetParseResult() const
488 {
489   return this->LastResult.Result;
490 }
491
492 //----------------------------------------------------------------------------
493 size_t cmVisualStudioSlnParser::GetParseResultLine() const
494 {
495   return this->LastResult.ResultLine;
496 }
497
498 //----------------------------------------------------------------------------
499 bool cmVisualStudioSlnParser::GetParseHadBOM() const
500 {
501   return this->LastResult.HadBOM;
502 }
503
504 //----------------------------------------------------------------------------
505 bool
506 cmVisualStudioSlnParser::IsDataGroupSetSupported(DataGroupSet dataGroups) const
507 {
508   return (dataGroups & DataGroupProjects) == dataGroups;
509     //only supporting DataGroupProjects for now
510 }
511
512 //----------------------------------------------------------------------------
513 bool cmVisualStudioSlnParser::ParseImpl(std::istream& input,
514                                         cmSlnData& output,
515                                         State& state)
516 {
517   std::string line;
518   // Does the .sln start with a Byte Order Mark?
519   if (!this->ParseBOM(input, line, state))
520     return false;
521   do
522     {
523     line = cmSystemTools::TrimWhitespace(line);
524     if (line.empty())
525       continue;
526     ParsedLine parsedLine;
527     switch (state.NextLineFormat())
528       {
529       case LineMultiValueTag:
530         if (!this->ParseMultiValueTag(line, parsedLine, state))
531           return false;
532         break;
533       case LineSingleValueTag:
534         if (!this->ParseSingleValueTag(line, parsedLine, state))
535           return false;
536         break;
537       case LineKeyValuePair:
538         if (!this->ParseKeyValuePair(line, parsedLine, state))
539           return false;
540         break;
541       case LineVerbatim:
542         parsedLine.CopyVerbatim(line);
543         break;
544       }
545     if (parsedLine.IsComment())
546       continue;
547     if (!state.Process(parsedLine, output, this->LastResult))
548       return false;
549     }
550     while (state.ReadLine(input, line));
551   return state.Finished(this->LastResult);
552 }
553
554 //----------------------------------------------------------------------------
555 bool cmVisualStudioSlnParser::ParseBOM(std::istream& input,
556                                        std::string& line,
557                                        State& state)
558 {
559   char bom[4];
560   if (!input.get(bom, 4))
561     {
562     this->LastResult.SetError(ResultErrorReadingInput, 1);
563     return false;
564     }
565   this->LastResult.HadBOM =
566     (bom[0] == char(0xEF) && bom[1] == char(0xBB) && bom[2] == char(0xBF));
567   if (!state.ReadLine(input, line))
568     {
569     this->LastResult.SetError(ResultErrorReadingInput, 1);
570     return false;
571     }
572   if (!this->LastResult.HadBOM)
573     line = bom + line;  // it wasn't a BOM, prepend it to first line
574   return true;
575 }
576
577 //----------------------------------------------------------------------------
578 bool cmVisualStudioSlnParser::ParseMultiValueTag(const std::string& line,
579                                                  ParsedLine& parsedLine,
580                                                  State& state)
581 {
582   size_t idxEqualSign = line.find('=');
583   const std::string& fullTag = line.substr(0, idxEqualSign);
584   if (!this->ParseTag(fullTag, parsedLine, state))
585     return false;
586   if (idxEqualSign != line.npos)
587     {
588     size_t idxFieldStart = idxEqualSign + 1;
589     if (idxFieldStart < line.size())
590       {
591       size_t idxParsing = idxFieldStart;
592       bool inQuotes = false;
593       for (;;)
594         {
595         idxParsing = line.find_first_of(",\"", idxParsing);
596         bool fieldOver = false;
597         if (idxParsing == line.npos)
598           {
599           fieldOver = true;
600           if (inQuotes)
601             {
602             this->LastResult.SetError(ResultErrorInputStructure,
603                                       state.GetCurrentLine());
604             return false;
605             }
606           }
607         else if (line[idxParsing] == ',' && !inQuotes)
608           fieldOver = true;
609         else if (line[idxParsing] == '"')
610           inQuotes = !inQuotes;
611         if (fieldOver)
612           {
613           if (!this->ParseValue(line.substr(idxFieldStart,
614                                             idxParsing - idxFieldStart),
615                                 parsedLine))
616             return false;
617           if (idxParsing == line.npos)
618             break;  //end of last field
619           idxFieldStart = idxParsing + 1;
620           }
621         ++idxParsing;
622         }
623       }
624     }
625   return true;
626 }
627
628 //----------------------------------------------------------------------------
629 bool cmVisualStudioSlnParser::ParseSingleValueTag(const std::string& line,
630                                                   ParsedLine& parsedLine,
631                                                   State& state)
632 {
633   size_t idxEqualSign = line.find('=');
634   const std::string& fullTag = line.substr(0, idxEqualSign);
635   if (!this->ParseTag(fullTag, parsedLine, state))
636     return false;
637   if (idxEqualSign != line.npos)
638     {
639     if (!this->ParseValue(line.substr(idxEqualSign + 1), parsedLine))
640       return false;
641     }
642   return true;
643 }
644
645 //----------------------------------------------------------------------------
646 bool cmVisualStudioSlnParser::ParseKeyValuePair(const std::string& line,
647                                                 ParsedLine& parsedLine,
648                                                 State& /*state*/)
649 {
650   size_t idxEqualSign = line.find('=');
651   if (idxEqualSign == line.npos)
652     {
653     parsedLine.CopyVerbatim(line);
654     return true;
655     }
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));
660   return true;
661 }
662
663 //----------------------------------------------------------------------------
664 bool cmVisualStudioSlnParser::ParseTag(const std::string& fullTag,
665                                        ParsedLine& parsedLine,
666                                        State& state)
667 {
668   size_t idxLeftParen = fullTag.find('(');
669   if (idxLeftParen == fullTag.npos)
670     {
671     parsedLine.SetTag(cmSystemTools::TrimWhitespace(fullTag));
672     return true;
673     }
674   parsedLine.SetTag(
675     cmSystemTools::TrimWhitespace(fullTag.substr(0, idxLeftParen)));
676   size_t idxRightParen = fullTag.rfind(')');
677   if (idxRightParen == fullTag.npos)
678     {
679     this->LastResult.SetError(ResultErrorInputStructure,
680                               state.GetCurrentLine());
681     return false;
682     }
683   const std::string& arg = cmSystemTools::TrimWhitespace(
684     fullTag.substr(idxLeftParen + 1, idxRightParen - idxLeftParen - 1));
685   if (arg[0] == '"')
686     {
687     if (arg[arg.size() - 1] != '"')
688       {
689       this->LastResult.SetError(ResultErrorInputStructure,
690                                 state.GetCurrentLine());
691       return false;
692       }
693       parsedLine.SetQuotedArg(arg.substr(1, arg.size() - 2));
694     }
695   else
696     parsedLine.SetArg(arg);
697   return true;
698 }
699
700 //----------------------------------------------------------------------------
701 bool cmVisualStudioSlnParser::ParseValue(const std::string& value,
702                                          ParsedLine& parsedLine)
703 {
704   const std::string& trimmed = cmSystemTools::TrimWhitespace(value);
705   if (trimmed.empty())
706     parsedLine.AddValue(trimmed);
707   else if (trimmed[0] == '"' && trimmed[trimmed.size() - 1] == '"')
708     parsedLine.AddQuotedValue(trimmed.substr(1, trimmed.size() - 2));
709   else
710     parsedLine.AddValue(trimmed);
711   return true;
712 }