cmVisualStudioSlnParser.cxx 20 KB

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