1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmVisualStudioSlnParser.h"
11 #include <cmext/string_view>
13 #include "cmsys/FStream.hxx"
15 #include "cmStringAlgorithms.h"
16 #include "cmSystemTools.h"
17 #include "cmVisualStudioSlnData.h"
29 class cmVisualStudioSlnParser::ParsedLine
32 bool IsComment() const;
33 bool IsKeyValuePair() const;
35 const std::string
& GetTag() const { return this->Tag
; }
36 const std::string
& GetArg() const { return this->Arg
.first
; }
37 std::string
GetArgVerbatim() const;
38 size_t GetValueCount() const { return this->Values
.size(); }
39 const std::string
& GetValue(size_t idxValue
) const;
40 std::string
GetValueVerbatim(size_t idxValue
) const;
42 void SetTag(const std::string
& tag
) { this->Tag
= tag
; }
43 void SetArg(const std::string
& arg
) { this->Arg
= StringData(arg
, false); }
44 void SetQuotedArg(const std::string
& arg
)
46 this->Arg
= StringData(arg
, true);
48 void AddValue(const std::string
& value
)
50 this->Values
.push_back(StringData(value
, false));
52 void AddQuotedValue(const std::string
& value
)
54 this->Values
.push_back(StringData(value
, true));
57 void CopyVerbatim(const std::string
& line
) { this->Tag
= line
; }
60 using StringData
= std::pair
<std::string
, bool>;
63 std::vector
<StringData
> Values
;
64 static const std::string BadString
;
65 static const std::string Quote
;
68 const std::string
cmVisualStudioSlnParser::ParsedLine::BadString
;
69 const std::string
cmVisualStudioSlnParser::ParsedLine::Quote("\"");
71 bool cmVisualStudioSlnParser::ParsedLine::IsComment() const
73 assert(!this->Tag
.empty());
74 return (this->Tag
[0] == '#');
77 bool cmVisualStudioSlnParser::ParsedLine::IsKeyValuePair() const
79 assert(!this->Tag
.empty());
80 return this->Arg
.first
.empty() && this->Values
.size() == 1;
83 std::string
cmVisualStudioSlnParser::ParsedLine::GetArgVerbatim() const
85 if (this->Arg
.second
) {
86 return cmStrCat(Quote
, this->Arg
.first
, Quote
);
88 return this->Arg
.first
;
91 const std::string
& cmVisualStudioSlnParser::ParsedLine::GetValue(
92 size_t idxValue
) const
94 if (idxValue
< this->Values
.size()) {
95 return this->Values
[idxValue
].first
;
100 std::string
cmVisualStudioSlnParser::ParsedLine::GetValueVerbatim(
101 size_t idxValue
) const
103 if (idxValue
< this->Values
.size()) {
104 const StringData
& data
= this->Values
[idxValue
];
106 return cmStrCat(Quote
, data
.first
, Quote
);
113 class cmVisualStudioSlnParser::State
116 explicit State(DataGroupSet requestedData
);
118 size_t GetCurrentLine() const { return this->CurrentLine
; }
119 bool ReadLine(std::istream
& input
, std::string
& line
);
121 LineFormat
NextLineFormat() const;
123 bool Process(const cmVisualStudioSlnParser::ParsedLine
& line
,
124 cmSlnData
& output
, cmVisualStudioSlnParser::ResultData
& result
);
126 bool Finished(cmVisualStudioSlnParser::ResultData
& result
);
134 FileStateProjectDependencies
,
136 FileStateSolutionConfigurations
,
137 FileStateProjectConfigurations
,
138 FileStateSolutionFilters
,
139 FileStateGlobalSection
,
142 std::stack
<FileState
> Stack
;
143 std::string EndIgnoreTag
;
144 DataGroupSet RequestedData
;
145 size_t CurrentLine
= 0;
147 void IgnoreUntilTag(const std::string
& endTag
);
150 cmVisualStudioSlnParser::State::State(DataGroupSet requestedData
)
151 : RequestedData(requestedData
)
153 if (this->RequestedData
.test(DataGroupProjectDependenciesBit
)) {
154 this->RequestedData
.set(DataGroupProjectsBit
);
156 this->Stack
.push(FileStateStart
);
159 bool cmVisualStudioSlnParser::State::ReadLine(std::istream
& input
,
163 return !std::getline(input
, line
).fail();
166 LineFormat
cmVisualStudioSlnParser::State::NextLineFormat() const
168 switch (this->Stack
.top()) {
171 case FileStateTopLevel
:
172 return LineMultiValueTag
;
173 case FileStateProject
:
174 case FileStateGlobal
:
175 return LineSingleValueTag
;
176 case FileStateProjectDependencies
:
177 case FileStateSolutionConfigurations
:
178 case FileStateProjectConfigurations
:
179 case FileStateSolutionFilters
:
180 case FileStateGlobalSection
:
181 return LineKeyValuePair
;
182 case FileStateIgnore
:
190 bool cmVisualStudioSlnParser::State::Process(
191 const cmVisualStudioSlnParser::ParsedLine
& line
, cmSlnData
& output
,
192 cmVisualStudioSlnParser::ResultData
& result
)
194 assert(!line
.IsComment());
195 switch (this->Stack
.top()) {
197 if (!cmHasLiteralPrefix(line
.GetTag(),
198 "Microsoft Visual Studio Solution File")) {
199 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
203 this->Stack
.push(FileStateTopLevel
);
205 case FileStateTopLevel
:
206 if (line
.GetTag() == "Project"_s
) {
207 if (line
.GetValueCount() != 3) {
208 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
211 if (this->RequestedData
.test(DataGroupProjectsBit
)) {
212 if (!output
.AddProject(line
.GetValue(2), line
.GetValue(0),
214 result
.SetError(ResultErrorInputData
, this->GetCurrentLine());
217 this->Stack
.push(FileStateProject
);
219 this->IgnoreUntilTag("EndProject");
221 } else if (line
.GetTag() == "Global"_s
) {
223 this->Stack
.push(FileStateGlobal
);
224 } else if (line
.GetTag() == "VisualStudioVersion"_s
) {
225 output
.SetVisualStudioVersion(line
.GetValue(0));
226 } else if (line
.GetTag() == "MinimumVisualStudioVersion"_s
) {
227 output
.SetMinimumVisualStudioVersion(line
.GetValue(0));
229 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
233 case FileStateProject
:
234 if (line
.GetTag() == "EndProject"_s
) {
236 } else if (line
.GetTag() == "ProjectSection"_s
) {
237 if (line
.GetArg() == "ProjectDependencies"_s
&&
238 line
.GetValue(0) == "postProject"_s
) {
239 if (this->RequestedData
.test(DataGroupProjectDependenciesBit
)) {
240 this->Stack
.push(FileStateProjectDependencies
);
242 this->IgnoreUntilTag("EndProjectSection");
245 this->IgnoreUntilTag("EndProjectSection");
248 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
252 case FileStateProjectDependencies
:
253 if (line
.GetTag() == "EndProjectSection"_s
) {
255 } else if (line
.IsKeyValuePair()) {
256 // implement dependency storing here, once needed
259 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
263 case FileStateGlobal
:
264 if (line
.GetTag() == "EndGlobal"_s
) {
266 } else if (line
.GetTag() == "GlobalSection"_s
) {
267 if (line
.GetArg() == "SolutionConfigurationPlatforms"_s
&&
268 line
.GetValue(0) == "preSolution"_s
) {
269 if (this->RequestedData
.test(DataGroupSolutionConfigurationsBit
)) {
270 this->Stack
.push(FileStateSolutionConfigurations
);
272 this->IgnoreUntilTag("EndGlobalSection");
274 } else if (line
.GetArg() == "ProjectConfigurationPlatforms"_s
&&
275 line
.GetValue(0) == "postSolution"_s
) {
276 if (this->RequestedData
.test(DataGroupProjectConfigurationsBit
)) {
277 this->Stack
.push(FileStateProjectConfigurations
);
279 this->IgnoreUntilTag("EndGlobalSection");
281 } else if (line
.GetArg() == "NestedProjects"_s
&&
282 line
.GetValue(0) == "preSolution"_s
) {
283 if (this->RequestedData
.test(DataGroupSolutionFiltersBit
)) {
284 this->Stack
.push(FileStateSolutionFilters
);
286 this->IgnoreUntilTag("EndGlobalSection");
288 } else if (this->RequestedData
.test(
289 DataGroupGenericGlobalSectionsBit
)) {
290 this->Stack
.push(FileStateGlobalSection
);
292 this->IgnoreUntilTag("EndGlobalSection");
295 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
299 case FileStateSolutionConfigurations
:
300 if (line
.GetTag() == "EndGlobalSection"_s
) {
302 } else if (line
.IsKeyValuePair()) {
303 output
.AddConfiguration(line
.GetValue(0));
305 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
309 case FileStateProjectConfigurations
:
310 if (line
.GetTag() == "EndGlobalSection"_s
) {
312 } else if (line
.IsKeyValuePair()) {
313 std::vector
<std::string
> tagElements
=
314 cmSystemTools::SplitString(line
.GetTag(), '.');
315 if (tagElements
.size() != 3 && tagElements
.size() != 4) {
316 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
320 std::string guid
= tagElements
[0];
321 std::string solutionConfiguration
= tagElements
[1];
322 std::string activeBuild
= tagElements
[2];
323 cm::optional
<cmSlnProjectEntry
> projectEntry
=
324 output
.GetProjectByGUID(guid
);
327 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
331 if (activeBuild
== "ActiveCfg"_s
) {
332 projectEntry
->AddProjectConfiguration(solutionConfiguration
,
336 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
340 case FileStateSolutionFilters
:
341 if (line
.GetTag() == "EndGlobalSection"_s
) {
343 } else if (line
.IsKeyValuePair()) {
344 // implement filter storing here, once needed
347 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
351 case FileStateGlobalSection
:
352 if (line
.GetTag() == "EndGlobalSection"_s
) {
354 } else if (line
.IsKeyValuePair()) {
355 // implement section storing here, once needed
358 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
362 case FileStateIgnore
:
363 if (line
.GetTag() == this->EndIgnoreTag
) {
365 this->EndIgnoreTag
.clear();
369 result
.SetError(ResultErrorBadInternalState
, this->GetCurrentLine());
375 bool cmVisualStudioSlnParser::State::Finished(
376 cmVisualStudioSlnParser::ResultData
& result
)
378 if (this->Stack
.top() != FileStateTopLevel
) {
379 result
.SetError(ResultErrorInputStructure
, this->GetCurrentLine());
382 result
.Result
= ResultOK
;
386 void cmVisualStudioSlnParser::State::IgnoreUntilTag(const std::string
& endTag
)
388 this->Stack
.push(FileStateIgnore
);
389 this->EndIgnoreTag
= endTag
;
392 cmVisualStudioSlnParser::ResultData::ResultData() = default;
394 void cmVisualStudioSlnParser::ResultData::Clear()
396 *this = ResultData();
399 void cmVisualStudioSlnParser::ResultData::SetError(ParseResult error
,
402 this->Result
= error
;
403 this->ResultLine
= line
;
406 const cmVisualStudioSlnParser::DataGroupSet
407 cmVisualStudioSlnParser::DataGroupProjects(
408 1 << cmVisualStudioSlnParser::DataGroupProjectsBit
);
410 const cmVisualStudioSlnParser::DataGroupSet
411 cmVisualStudioSlnParser::DataGroupProjectDependencies(
412 1 << cmVisualStudioSlnParser::DataGroupProjectDependenciesBit
);
414 const cmVisualStudioSlnParser::DataGroupSet
415 cmVisualStudioSlnParser::DataGroupSolutionConfigurations(
416 1 << cmVisualStudioSlnParser::DataGroupSolutionConfigurationsBit
);
418 const cmVisualStudioSlnParser::DataGroupSet
419 cmVisualStudioSlnParser::DataGroupProjectConfigurations(
420 1 << cmVisualStudioSlnParser::DataGroupProjectConfigurationsBit
);
422 const cmVisualStudioSlnParser::DataGroupSet
423 cmVisualStudioSlnParser::DataGroupSolutionFilters(
424 1 << cmVisualStudioSlnParser::DataGroupSolutionFiltersBit
);
426 const cmVisualStudioSlnParser::DataGroupSet
427 cmVisualStudioSlnParser::DataGroupGenericGlobalSections(
428 1 << cmVisualStudioSlnParser::DataGroupGenericGlobalSectionsBit
);
430 const cmVisualStudioSlnParser::DataGroupSet
431 cmVisualStudioSlnParser::DataGroupAll(~0);
433 bool cmVisualStudioSlnParser::Parse(std::istream
& input
, cmSlnData
& output
,
434 DataGroupSet dataGroups
)
436 this->LastResult
.Clear();
437 if (!this->IsDataGroupSetSupported(dataGroups
)) {
438 this->LastResult
.SetError(ResultErrorUnsupportedDataGroup
, 0);
441 State
state(dataGroups
);
442 return this->ParseImpl(input
, output
, state
);
445 bool cmVisualStudioSlnParser::ParseFile(const std::string
& file
,
447 DataGroupSet dataGroups
)
449 this->LastResult
.Clear();
450 if (!this->IsDataGroupSetSupported(dataGroups
)) {
451 this->LastResult
.SetError(ResultErrorUnsupportedDataGroup
, 0);
454 cmsys::ifstream
f(file
.c_str());
456 this->LastResult
.SetError(ResultErrorOpeningInput
, 0);
459 State
state(dataGroups
);
460 return this->ParseImpl(f
, output
, state
);
463 cmVisualStudioSlnParser::ParseResult
cmVisualStudioSlnParser::GetParseResult()
466 return this->LastResult
.Result
;
469 size_t cmVisualStudioSlnParser::GetParseResultLine() const
471 return this->LastResult
.ResultLine
;
474 bool cmVisualStudioSlnParser::GetParseHadBOM() const
476 return this->LastResult
.HadBOM
;
479 bool cmVisualStudioSlnParser::IsDataGroupSetSupported(
480 DataGroupSet dataGroups
) const
482 return (dataGroups
& DataGroupProjects
) != 0;
485 bool cmVisualStudioSlnParser::ParseImpl(std::istream
& input
, cmSlnData
& output
,
489 // Does the .sln start with a Byte Order Mark?
490 if (!this->ParseBOM(input
, line
, state
)) {
494 line
= cmTrimWhitespace(line
);
498 ParsedLine parsedLine
;
499 switch (state
.NextLineFormat()) {
500 case LineMultiValueTag
:
501 if (!this->ParseMultiValueTag(line
, parsedLine
, state
)) {
505 case LineSingleValueTag
:
506 if (!this->ParseSingleValueTag(line
, parsedLine
, state
)) {
510 case LineKeyValuePair
:
511 if (!this->ParseKeyValuePair(line
, parsedLine
, state
)) {
516 parsedLine
.CopyVerbatim(line
);
519 if (parsedLine
.IsComment()) {
522 if (!state
.Process(parsedLine
, output
, this->LastResult
)) {
525 } while (state
.ReadLine(input
, line
));
526 return state
.Finished(this->LastResult
);
529 bool cmVisualStudioSlnParser::ParseBOM(std::istream
& input
, std::string
& line
,
533 if (!input
.get(bom
, 4)) {
534 this->LastResult
.SetError(ResultErrorReadingInput
, 1);
537 this->LastResult
.HadBOM
=
538 (bom
[0] == char(0xEF) && bom
[1] == char(0xBB) && bom
[2] == char(0xBF));
539 if (!state
.ReadLine(input
, line
)) {
540 this->LastResult
.SetError(ResultErrorReadingInput
, 1);
543 if (!this->LastResult
.HadBOM
) {
544 line
= cmStrCat(bom
, line
); // it wasn't a BOM, prepend it to first line
549 bool cmVisualStudioSlnParser::ParseMultiValueTag(const std::string
& line
,
550 ParsedLine
& parsedLine
,
553 size_t idxEqualSign
= line
.find('=');
554 auto fullTag
= cm::string_view(line
).substr(0, idxEqualSign
);
555 if (!this->ParseTag(fullTag
, parsedLine
, state
)) {
558 if (idxEqualSign
!= std::string::npos
) {
559 size_t idxFieldStart
= idxEqualSign
+ 1;
560 if (idxFieldStart
< line
.size()) {
561 size_t idxParsing
= idxFieldStart
;
562 bool inQuotes
= false;
564 idxParsing
= line
.find_first_of(",\"", idxParsing
);
565 bool fieldOver
= false;
566 if (idxParsing
== std::string::npos
) {
569 this->LastResult
.SetError(ResultErrorInputStructure
,
570 state
.GetCurrentLine());
573 } else if (line
[idxParsing
] == ',' && !inQuotes
) {
575 } else if (line
[idxParsing
] == '"') {
576 inQuotes
= !inQuotes
;
579 if (!this->ParseValue(
580 line
.substr(idxFieldStart
, idxParsing
- idxFieldStart
),
584 if (idxParsing
== std::string::npos
) {
585 break; // end of last field
587 idxFieldStart
= idxParsing
+ 1;
596 bool cmVisualStudioSlnParser::ParseSingleValueTag(const std::string
& line
,
597 ParsedLine
& parsedLine
,
600 size_t idxEqualSign
= line
.find('=');
601 auto fullTag
= cm::string_view(line
).substr(0, idxEqualSign
);
602 if (!this->ParseTag(fullTag
, parsedLine
, state
)) {
605 if (idxEqualSign
!= std::string::npos
) {
606 if (!this->ParseValue(line
.substr(idxEqualSign
+ 1), parsedLine
)) {
613 bool cmVisualStudioSlnParser::ParseKeyValuePair(const std::string
& line
,
614 ParsedLine
& parsedLine
,
617 size_t idxEqualSign
= line
.find('=');
618 if (idxEqualSign
== std::string::npos
) {
619 parsedLine
.CopyVerbatim(line
);
622 const std::string
& key
= line
.substr(0, idxEqualSign
);
623 parsedLine
.SetTag(cmTrimWhitespace(key
));
624 const std::string
& value
= line
.substr(idxEqualSign
+ 1);
625 parsedLine
.AddValue(cmTrimWhitespace(value
));
629 bool cmVisualStudioSlnParser::ParseTag(cm::string_view fullTag
,
630 ParsedLine
& parsedLine
, State
& state
)
632 size_t idxLeftParen
= fullTag
.find('(');
633 if (idxLeftParen
== cm::string_view::npos
) {
634 parsedLine
.SetTag(cmTrimWhitespace(fullTag
));
637 parsedLine
.SetTag(cmTrimWhitespace(fullTag
.substr(0, idxLeftParen
)));
638 size_t idxRightParen
= fullTag
.rfind(')');
639 if (idxRightParen
== cm::string_view::npos
) {
640 this->LastResult
.SetError(ResultErrorInputStructure
,
641 state
.GetCurrentLine());
644 const std::string
& arg
= cmTrimWhitespace(
645 fullTag
.substr(idxLeftParen
+ 1, idxRightParen
- idxLeftParen
- 1));
646 if (arg
.front() == '"') {
647 if (arg
.back() != '"') {
648 this->LastResult
.SetError(ResultErrorInputStructure
,
649 state
.GetCurrentLine());
652 parsedLine
.SetQuotedArg(arg
.substr(1, arg
.size() - 2));
654 parsedLine
.SetArg(arg
);
659 bool cmVisualStudioSlnParser::ParseValue(const std::string
& value
,
660 ParsedLine
& parsedLine
)
662 const std::string
& trimmed
= cmTrimWhitespace(value
);
663 if (trimmed
.empty()) {
664 parsedLine
.AddValue(trimmed
);
665 } else if (trimmed
.front() == '"' && trimmed
.back() == '"') {
666 parsedLine
.AddQuotedValue(trimmed
.substr(1, trimmed
.size() - 2));
668 parsedLine
.AddValue(trimmed
);