VC++の.slnファイルを解析するよい方法が見つからないので書いてみた。
.slnファイルは独自形式だが、同時に単純かつ厳密なフォーマットらしいので割と簡単に書ける。
https://suzulang.com/wp-content/uploads/2025/06/winslnrw.zip
#include <iostream> #include <list> #include <memory> #include <string> #include <vector> #include <fstream> #include <unordered_map> #include <stack> #include <regex> /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // std::stringから先頭の空白を削除する関数 std::string trimLeadingWhitespace(const std::string& str) { size_t start = str.find_first_not_of(" \t\n\r"); return (start == std::string::npos) ? "" : str.substr(start); } // std::stringから、冒頭のアルファベットのみの文字列を取得。 std::string getLeadingAlphabet(const std::string& str) { size_t end = str.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); return (end == std::string::npos) ? str : str.substr(0, end); } bool isSectionMarks(const std::string& str) { // セクションの開始を示す文字列かどうかを判定 // ここでは例として "Project" と "Global" をセクションの開始とする return str == "Project" || str == "ProjectSection" || str == "EndProjectSection" || str == "EndProject" || str == "Global" || str == "GlobalSection" || str == "EndGlobalSection" || str == "EndGlobal" ; }
// slnファイルの各行を表すクラス class LineData { protected: std::string line; public: void setLine(const std::string& text) { line = text; } const std::string& getLine() const { return line; } std::string& getLine() { return line; } };
// ヘッダはセクション扱いとはしないで各行を独自の情報とみなす class HeaderLine :public LineData { protected: std::regex pattern; public: HeaderLine(std::string pat) :pattern(pat) { } virtual std::string getVersion() const{ std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); return text.substr(match.position(1), match.length(1)); } virtual void setVersion(const std::string& newvalue) { std::string text = getLine(); std::smatch match; std::regex_search(text, match, pattern); text.replace(match.position(1), match.length(1), newvalue); setLine(text); } };
// Visual Studioのバージョン情報を保持するクラス class VisualStudioVersion : public HeaderLine { public: VisualStudioVersion(): HeaderLine(R"(^VisualStudioVersion\s*=\s*([0-9.]+))") { } };
// 最小Visual Studioのバージョン情報を保持するクラス class MinimumVisualStudioVersion : public HeaderLine { public: MinimumVisualStudioVersion(): HeaderLine(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))") { } };
// ソリューションファイルのフォーマットバージョンを保持するクラス class FormatVersion : public HeaderLine { public: FormatVersion(): HeaderLine(R"(Format Version ([0-9.]+))") { } };
enum class HeaderLineTypeT { HL_VisualStudioVersion, HL_MinimumVisualStudioVersion, HL_FormatVersion, HL_ERROR }; HeaderLineTypeT HeaderLineType(const std::string& line) { std::regex format_version_regex(R"(Format Version ([0-9.]+))"); std::regex visual_studio_version_regex(R"(^VisualStudioVersion\s*=\s*([0-9.]+))"); std::regex minimum_visual_studio_version_regex(R"(^MinimumVisualStudioVersion\s*=\s*([0-9.]+))"); std::smatch match; if (std::regex_search(line, match, format_version_regex)) { return HeaderLineTypeT::HL_FormatVersion; } else if (std::regex_search(line, match, visual_studio_version_regex)) { return HeaderLineTypeT::HL_VisualStudioVersion; } else if (std::regex_search(line, match, minimum_visual_studio_version_regex)) { return HeaderLineTypeT::HL_MinimumVisualStudioVersion; } return HeaderLineTypeT::HL_ERROR; }
using SlnFileLineSPtr = std::shared_ptr<LineData>;
class Section : public std::enable_shared_from_this<Section> { protected: std::list< SlnFileLineSPtr > ::iterator head; std::list< SlnFileLineSPtr > ::iterator tail; std::string _sectionType; public: std::list < std::shared_ptr<Section> > subSections; // サブセクションのリスト std::shared_ptr<Section> This() { return shared_from_this(); } std::list < std::shared_ptr<Section> >& getSubSections(){ // グローバルセクションのサブセクションを取得するメソッド return subSections; } virtual void update() {} Section(std::string sectionname, std::list< SlnFileLineSPtr >::iterator shead) { head = shead; _sectionType = sectionname; } std::string getSectionType()const { return _sectionType; } void setSectionTail(std::list< SlnFileLineSPtr >::iterator stail) { tail = stail; } std::list< SlnFileLineSPtr > ::iterator getSectionBegin() const { return head; } std::list< SlnFileLineSPtr > ::iterator getSectionTail() const { return tail; } std::list< SlnFileLineSPtr > ::iterator getSectionEnd() const { auto itr = tail; itr++; return itr; } };
class GlobalSection : public Section { protected: std::string* sectionHeadLine() { return & (*getSectionBegin())->getLine(); } const std::string* sectionHeadLine() const{ return & (*getSectionBegin())->getLine(); } std::regex Pattern; public: GlobalSection(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { Pattern = std::regex(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); } std::string getSectionName()const{ std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { // セクション名をスライスで保存 slice = sectionHeadLine()->substr(match.position(1), match.length(1)); } return slice; } void setSectionName(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(1), match.length(1), newvalue); } } void setSectionTiming(const std::string& newvalue) { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { sectionHeadLine()->replace(match.position(2), match.length(2), newvalue); } } std::string getSectionTiming() const { std::string slice; std::smatch match; if (std::regex_match(*sectionHeadLine(), match, Pattern)) { slice = sectionHeadLine()->substr(match.position(2), match.length(2)); } return slice; } };
class SolutionConfigurationPlatforms : public GlobalSection { public: SolutionConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ProjectConfigurationPlatforms : public GlobalSection { public: ProjectConfigurationPlatforms(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class SolutionProperties: public GlobalSection { public: SolutionProperties(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} }; class ExtensibilityGlobals : public GlobalSection { public: ExtensibilityGlobals(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) : GlobalSection(sectionname,line) {} };
class Header { std::list< SlnFileLineSPtr > lines; std::weak_ptr<VisualStudioVersion> vs_ver; std::weak_ptr<MinimumVisualStudioVersion> minivs_ver; std::weak_ptr<FormatVersion> f_ver; public: std::weak_ptr<VisualStudioVersion> getVisualStudioVersion(){ return vs_ver; } std::weak_ptr<MinimumVisualStudioVersion> getMinimumVisualStudioVersion(){ return minivs_ver; } std::weak_ptr<FormatVersion> getFormatVersion(){ return f_ver; } SlnFileLineSPtr push_back(SlnFileLineSPtr line) { SlnFileLineSPtr ptr; switch (HeaderLineType(line->getLine())) { case HeaderLineTypeT::HL_FormatVersion: { auto ptmp = std::make_shared<FormatVersion>(); ptmp->setLine(line->getLine()); f_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_VisualStudioVersion: { auto ptmp = std::make_shared<VisualStudioVersion>(); ptmp->setLine(line->getLine()); vs_ver = ptmp; ptr = ptmp; break; } case HeaderLineTypeT::HL_MinimumVisualStudioVersion: { auto ptmp = std::make_shared<MinimumVisualStudioVersion>(); ptmp->setLine(line->getLine()); minivs_ver = ptmp; ptr = ptmp; break; } default: ptr = line; } lines.push_back(ptr); return ptr; } std::list< SlnFileLineSPtr > getLines() { return lines; } };
class Project :public Section{ std::string* sectionHeadLine() { return &getSectionBegin()->get()->getLine(); } const std::string* sectionHeadLine() const { return &getSectionBegin()->get()->getLine(); } std::regex projectPattern; std::string getSubstr(const int pos)const { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); return sectionHeadLine()->substr(match.position(pos), match.length(pos)); } void setReplace(const int pos,const std::string& newvalue) { std::smatch match; std::regex_search(*sectionHeadLine(), match, projectPattern); sectionHeadLine()->replace(match.position(pos), match.length(pos), newvalue); } public: Project(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line):Section(sectionname,line) { projectPattern = std::regex(R"xxx(Project\("\{([A-F0-9\-]+)\}"\)\s*=\s*"([^"]+)",\s*"([^"]+)",\s*"\{([A-F0-9\-]+)\}")xxx", std::regex::ECMAScript | std::regex::icase); } // プロジェクトの種類をGUID形式で取得するメソッド std::string getProjectTypeGUID() const {return getSubstr(1);} // プロジェクト名を取得するメソッド std::string getProjectName() const {return getSubstr(2);} // プロジェクトのパスを取得するメソッド std::string getProjectPath() const {return getSubstr(3);} // プロジェクトのGUIDを取得するメソッド std::string getProjectGUID() const {return getSubstr(4);} //////////////////////////////////////////////////////////////////////// void setProjectTypeGUID(const std::string& newvalue) {setReplace(1, newvalue);} void setProjectName(const std::string& newvalue) {setReplace(2, newvalue);} void setProjectPath(const std::string& newvalue) {setReplace(3, newvalue);} void setProjectGUID(const std::string& newvalue) {setReplace(4, newvalue);} //////////////////////////////////////////////////////////////////////// };
class Global :public Section { std::unordered_map<std::string , std::weak_ptr<GlobalSection> > subSectionsMap; // サブセクションのマップ public: Global(std::string sectionname, std::list< SlnFileLineSPtr >::iterator line) :Section(sectionname,line) { } virtual void update()override { auto& subsections = getSubSections(); for (auto s : subsections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(s); subSectionsMap[gs->getSectionName()] = gs; } } auto getGlobalSections() { return getSubSections(); } std::weak_ptr<SolutionConfigurationPlatforms> getSolutionConfigurationPlatforms(){ auto it = subSectionsMap.find("SolutionConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionConfigurationPlatforms>(); return std::dynamic_pointer_cast<SolutionConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<ProjectConfigurationPlatforms> getProjectConfigurationPlatforms() { auto it = subSectionsMap.find("ProjectConfigurationPlatforms"); if (it == subSectionsMap.end()) return std::weak_ptr<ProjectConfigurationPlatforms>(); return std::dynamic_pointer_cast<ProjectConfigurationPlatforms>((*it).second.lock()); } std::weak_ptr<SolutionProperties> getSolutionProperties() { auto it = subSectionsMap.find("SolutionProperties"); if (it == subSectionsMap.end()) return std::weak_ptr<SolutionProperties>(); return std::dynamic_pointer_cast<SolutionProperties>((*it).second.lock()); } std::weak_ptr<ExtensibilityGlobals> getExtensibilityGlobals() { auto it = subSectionsMap.find("ExtensibilityGlobals"); if (it == subSectionsMap.end()) return std::weak_ptr<ExtensibilityGlobals>(); return std::dynamic_pointer_cast<ExtensibilityGlobals>((*it).second.lock()); } };
// slnファイルを管理するクラス。 // slnファイルフォーマットは原則余計な場所で改行されないので、1行単位で保存 class SlnFileLines { std::list< SlnFileLineSPtr > _lines; std::unordered_map<std::string,std::string> _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; std::string _slnFilename; public: std::shared_ptr<Header> header; // ヘッダー部分 std::vector< std::shared_ptr<Project> > projects; // プロジェクトセクションのポインタ std::shared_ptr<Global> global; // グローバルセクションのポインタ std::list< SlnFileLineSPtr > getLines() const { return _lines; } std::vector< std::shared_ptr<Project> > getProjects() const { // プロジェクトセクションのポインタを返す return projects; } SlnFileLines() { // コンストラクタでセクションマークを初期化 _sectionMarks = { {"Project", "EndProject"}, {"ProjectSection", "EndProjectSection"}, {"Global", "EndGlobal"}, {"GlobalSection", "EndGlobalSection"}, }; header = std::make_shared<Header>(); // ヘッダー部分を初期化 } std::shared_ptr<Section> createNewSection(std::list< SlnFileLineSPtr >::iterator line_itr,std::string linehead) { std::shared_ptr<Section> newsection; // 各セクションへのショートカット if (linehead == "Project") { std::shared_ptr<Project> newproject= std::make_shared<Project>(linehead,line_itr); projects.push_back(newproject); newsection = newproject; } else if (linehead == "Global") { std::shared_ptr<Global> newglobal = std::make_shared<Global>(linehead,line_itr); global = newglobal; newsection = newglobal; } else if (linehead == "GlobalSection") { std::regex pattern(R"(^\s*GlobalSection\((\w+)\)\s*=\s*(preSolution|postSolution))"); std::smatch match; std::string name; if (std::regex_match((*line_itr)->getLine(), match, pattern)) { name = match.str(1); } if (name == "SolutionConfigurationPlatforms") { newsection = std::make_shared<SolutionConfigurationPlatforms>(linehead,line_itr); } else if (name == "ProjectConfigurationPlatforms") { newsection = std::make_shared<ProjectConfigurationPlatforms>(linehead, line_itr); } else if (name == "SolutionProperties") { newsection = std::make_shared<SolutionProperties>(linehead, line_itr); } else if (name == "ExtensibilityGlobals") { newsection = std::make_shared<ExtensibilityGlobals>(linehead, line_itr); } else { newsection = std::make_shared<GlobalSection>(linehead, line_itr); } } else { newsection = std::make_shared<Section>(linehead, line_itr); } return newsection; } void Load(const std::string& filePath) { _slnFilename = filePath; // ファイルオープン std::ifstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } ///////////////////////////////////////////////////////////////////////////////// // ファイルから行を読み込み、リストに追加 std::string line; while (std::getline(file, line)) { if (!line.empty()) { // 空行は無視 SlnFileLineSPtr lineData = std::make_shared<LineData>(); lineData->setLine(line); _lines.push_back(lineData); } } ///////////////////////////////////////////////////////////////////////////////// // ヘッダー部分の抽出 auto line_itr = _lines.begin(); for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if( isSectionMarks(linehead) == false) { // ヘッダー部分に追加 *line_itr = header->push_back(*line_itr); } else { break; } } ///////////////////////////////////////////////////////////////////////////////// // セクションの開始 std::stack< std::shared_ptr<Section> > currentSection; std::shared_ptr<Section> root = std::make_shared<Section>("root",_lines.end()); // ルートセクションを初期化 currentSection.push(root); // ルートセクションをスタックに追加 for (; line_itr != _lines.end(); ++line_itr) { std::string linehead = getLeadingAlphabet(trimLeadingWhitespace((*line_itr)->getLine())); if (_sectionMarks.find(linehead) != _sectionMarks.end()) { // セクションの開始 std::shared_ptr<Section> newSection = createNewSection(line_itr,linehead); currentSection.top()->subSections.push_back(newSection); currentSection.push(newSection); } else { std::string endSectionMark = _sectionMarks[currentSection.top()->getSectionType()]; if (linehead == endSectionMark) { // セクションの終了 if (!currentSection.empty()) { currentSection.top()->setSectionTail(line_itr); currentSection.top()->update(); currentSection.pop(); } } } } global->update(); } void Save() { // ファイルオープン std::ofstream file(_slnFilename); if (!file.is_open()) { std::cerr << "Error opening file: " << _slnFilename << std::endl; return; } for (auto itr = _lines.begin(); itr != _lines.end(); ++itr) { file << (*itr)->getLine() << std::endl; } } };
/////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// void disp(SlnFileLines& sln);
void ChangeProjectTest(SlnFileLines& sln) { std::string BeforeProjName = "ConsoleApp1"; std::string AfterProjName = "MyNewProjectName"; // 編集 auto projects = sln.getProjects(); for (auto p : projects) { std::string name = p->getProjectName(); if (name == BeforeProjName) { p->setProjectName(AfterProjName); std::string pathstr = p->getProjectPath(); std::regex pattern( std::string("^") + BeforeProjName + "\\\\"); std::smatch match; std::regex_search(pathstr, match, pattern); pathstr = std::regex_replace(pathstr, pattern, AfterProjName + "\\"); p->setProjectPath(pathstr); } } }
int main() { SlnFileLines sln; sln.Load(R"(C:\test\ConsoleApp1.sln)"); // 編集前 disp(sln); // 編集 std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; ChangeProjectTest(sln); std::cout << "@@@@@@@@@@@@@@@@@@@@@" << std::endl; // 編集後 disp(sln); sln.Save(); }
void disp(SlnFileLines& sln) { std::cout << "FormatVersion: " << sln.header->getFormatVersion().lock()->getVersion() << std::endl; std::cout << "VisualStudioVersion: " << sln.header->getVisualStudioVersion().lock()->getVersion() << std::endl; std::cout << "MinimumVisualStudioVersion: " << sln.header->getMinimumVisualStudioVersion().lock()->getVersion() << std::endl; auto projects = sln.getProjects(); for (auto p : projects) { std::cout << p->getSectionType() << std::endl; std::cout << p->getProjectGUID() << std::endl; std::cout << p->getProjectName() << std::endl; std::cout << p->getProjectPath() << std::endl; std::cout << p->getProjectTypeGUID() << std::endl; std::cout << "-------------------" << std::endl; } auto sections = sln.global->getGlobalSections(); for (auto ss : sections) { std::shared_ptr<GlobalSection> gs = std::static_pointer_cast<GlobalSection>(ss); ; std::cout << "-------------------" << std::endl; std::cout << "1 " << gs->getSectionType() << std::endl; std::cout << "2 " << gs->getSectionName() << std::endl; std::cout << "3 " << gs->getSectionTiming() << std::endl; std::cout << "4 " << (*gs->getSectionBegin())->getLine() << std::endl; std::cout << "5 " << (*gs->getSectionTail())->getLine() << std::endl; std::cout << "=================" << std::endl; } }