Parsing Visual Studio Solutions (.sln) files
This is something that intrigues me, but until now I not have found a definitive answer.
What I have use most cases is the following code:
public class Solution
{
//internal class SolutionParser
//Name: Microsoft.Build.Construction.SolutionParser
//Assembly: Microsoft.Build, Version=4.0.0.0
static readonly Type s_SolutionParser;
static readonly PropertyInfo s_SolutionParser_solutionReader;
static readonly MethodInfo s_SolutionParser_parseSolution;
static readonly PropertyInfo s_SolutionParser_projects;
static Solution()
{
s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
if (s_SolutionParser != null)
{
s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance);
s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance);
s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance);
}
}
public List<SolutionProject> Projects { get; private set; }
public Solution(string solutionFileName)
{
if (s_SolutionParser == null)
{
throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?");
}
var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null);
using (var streamReader = new StreamReader(solutionFileName))
{
s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null);
s_SolutionParser_parseSolution.Invoke(solutionParser, null);
}
var projects = new List<SolutionProject>();
var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null);
for (int i = 0; i < array.Length; i++)
{
projects.Add(new SolutionProject(array.GetValue(i)));
}
this.Projects = projects;
}
}
[DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
public class SolutionProject
{
static readonly Type s_ProjectInSolution;
static readonly PropertyInfo s_ProjectInSolution_ProjectName;
static readonly PropertyInfo s_ProjectInSolution_RelativePath;
static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;
static SolutionProject()
{
s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
if (s_ProjectInSolution != null)
{
s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);
s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance);
s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance);
}
}
public string ProjectName { get; private set; }
public string RelativePath { get; private set; }
public string ProjectGuid { get; private set; }
public SolutionProject(object solutionProject)
{
this.ProjectName = s_ProjectInSolution_ProjectName.GetValue(solutionProject, null) as string;
this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(solutionProject, null) as string;
this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(solutionProject, null) as string;
}
}
However I do not love this solution.
Other options that I have not yet explored are related to create a parser for VS Solutions.
One interesting post uses the Sprache
Which provides a monadic parser.The terms comes from the functional programming world.
In functional programming, a popular approach to building recursive descent parsers is
to model parsers as functions, and to define higher-order functions (or combinators ) that
implement grammar constructions such as sequencing, choice, and repetition. Such parsers
form an instance of a monad , an algebraic structure from mathematics that has proved
useful for addressing a number of computational problems.
There is no grammar definition for solutions. The closests thing I could find was this:
sln = ver-header entries
entries = (project | global)+
ver-header = visual-studio ", " format-version "\n" comment-version "\n"
visual-studio = "Microsoft Visual Studio Solution File"
format-version = "Format Version numeric-version
numeric-version = nv_2002 | nv_2003 | nv_2005 | nv_2008 | nv_2010 | nv_2012
nv_2002 = "7.00"
nv_2003 = "8.00"
nv_2005 = "9.00"
nv_2008 = "10.00"
nv_2010 = "11.00"
nv_2012 = "12.00"
comment-version = "# Visual Studio " year-version
year_version = "2005" | "2008" | "2010" | "2012"
project = project-id "\n" project-parms* "EndProject" "\n"
project-id = "Project(" '"{' pt-guid '}"' )" = " proj_parms
proj_parms = proj-name ", " proj-path ", " proj_guid
pt-guid = vcppguid | vbguid | vcsharpguid | webguid | siguid
vcppguid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"
vbguid = "F184B08F-C81C-45F6-A57F-5ABD9991F28F"
vcsharpguid = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"
webguid = "E24C65DC-7377-472b-9ABA-BC803B73C61A"
siguid = "2150E333-8FDC-42A3-9474-1A3956D46DE8"
proj_name = '"' STRING '"'
proj_path = '"' STRING '"'
proj_guid = HEX{8} "-" HEX{4} "-" HEX{4} "-" HEX{4} "-" HEX{12}
global = "Global" "\n" global-section* "EndGlobal" "\n"
global_section = gs_start "\n" gs_body "EndGlobalSection" "\n"
gs_start = "GlobalSection" "(" gs_kind ")" " = " gs_when
gs_kind = "SolutionConfigurationPlatforms"
| "ProjectConfigurationPlatform"
| "SolutionProperties"
| "ExtensibilityGlobals"
| "ExtensibilityAddIns"
gs_kind_05 = "SolutionConfiguration"
| "ProjectConfiguration"
| "ProjectDependencies"
gs_when = "preSolution" | "postSolution"
gs_body = gs_configs | gs_plats | gs_props
gs_configs = (gs_config "\n")*
gs_config = sol_cfg_plat " = " sol_cfg_plat
sol_cfg_plat = sol_cfg "|" sol_plat
sol_cfg = "Debug" | "Release" | STRING
sol_plat = "Win32" | "x64" | STRING
gs_plats = (gs_plat "\n")*
gs_plat = "{" proj_guid "}" "." sol_cfg_plat "." gs_action " = " sol_cfg_plat
gs_action = "ActiveCfg | "Build.0"
gs_props = "HideSolutionNode = " ("TRUE" | "FALSE") "\n"
Taken from this post
And finally I just found a parser. I haven't tried yet but it even has a nuget :) Onion.SolutionParser.Parser and Github Code