スポンサーリンク

C++で.slnファイルを解析

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; } }

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: