context: //
  // SolutionParser.cs: Generates a project file from a solution file.
  //
  // Author:
  //   Jonathan Chambers (joncham@gmail.com)
  //   Ankit Jain <jankit@novell.com>
  //   Lluis Sanchez Gual <lluis@novell.com>
  //
  // (C) 2009 Jonathan Chambers
  // Copyright 2008, 2009 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.IO;
  using Microsoft.Build.Evaluation;
  using Microsoft.Build.Execution;
  using Microsoft.Build.Construction;
  using Microsoft.Build.Exceptions;
  using System.Xml;
  
  namespace Mono.XBuild.CommandLine {
  	class ProjectInfo {
  		public string Name;
  		public string FileName;
  		public Guid Guid;
  
  		public ProjectInfo (string name, string fileName)
  		{
  			Name = name;
  			FileName = fileName;
  		}
  
  		public Dictionary<TargetInfo, TargetInfo> TargetMap = new Dictionary<TargetInfo, TargetInfo> ();
  		public Dictionary<Guid, ProjectInfo> Dependencies = new Dictionary<Guid, ProjectInfo> ();
  		public Dictionary<string, ProjectSection> ProjectSections = new Dictionary<string, ProjectSection> ();
  		public List<string> AspNetConfigurations = new List<string> ();
  	}
  
  	class ProjectSection {
  		public string Name;
  		public Dictionary<string, string> Properties = new Dictionary<string, string> ();
  
  		public ProjectSection (string name)
  		{
  			Name = name;
  		}
  	}
  
  	struct TargetInfo {
  		public string Configuration;
  		public string Platform;
  		public bool Build;
  
  		public TargetInfo (string configuration, string platform)
  			: this (configuration, platform, false)
  		{
  		}
  
  		public TargetInfo (string configuration, string platform, bool build)
  		{
  			Configuration = configuration;
  			Platform = platform;
  			Build = build;
  		}
  	}
  
  
  	internal delegate void RaiseWarningHandler (int errorNumber, string message);
  
  	class SolutionParser {
  		static string[] buildTargets = new string[] { "Build", "Clean", "Rebuild", "Publish" };
  
  		static string guidExpression = "{[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}}";
  
  		static Regex slnVersionRegex = new Regex (@"Microsoft Visual Studio Solution File, Format Version (\d?\d.\d\d)");
  		static Regex projectRegex = new Regex ("Project\\(\"(" + guidExpression + ")\"\\) = \"(.*?)\", \"(.*?)\", \"(" + guidExpression + ")\"(\\s*?)((\\s*?)ProjectSection\\((.*?)\\) = (.*?)EndProjectSection(\\s*?))*(\\s*?)(EndProject)?", RegexOptions.Singleline);
  		static Regex projectDependenciesRegex = new Regex ("ProjectSection\\((.*?)\\) = \\w*(.*?)EndProjectSection", RegexOptions.Singleline);
  		static Regex projectDependencyRegex = new Regex ("\\s*(" + guidExpression + ") = (" + guidExpression + ")");
  		static Regex projectSectionPropertiesRegex = new Regex ("\\s*(?<name>.*) = \"(?<value>.*)\"");
  
  		static Regex globalRegex = new Regex ("Global(.*)EndGlobal", RegexOptions.Singleline);
  		static Regex globalSectionRegex = new Regex ("GlobalSection\\((.*?)\\) = \\w*(.*?)EndGlobalSection", RegexOptions.Singleline);
  
  		static Regex solutionConfigurationRegex = new Regex ("\\s*(.*?)\\|(.*?) = (.*?)\\|(.+)");
  		static Regex projectConfigurationActiveCfgRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.+?)\\|(.+?)\\.ActiveCfg = (.+?)\\|(.+)");
  		static Regex projectConfigurationBuildRegex = new Regex ("\\s*(" + guidExpression + ")\\.(.*?)\\|(.*?)\\.Build\\.0 = (.*?)\\|(.+)");
  
  		static string solutionFolderGuid = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
  		static string vcprojGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
  		static string websiteProjectGuid = "{E24C65DC-7377-472B-9ABA-BC803B73C61A}";
  
  		RaiseWarningHandler RaiseWarning;
  
  		public void ParseSolution (string file, ProjectCollection projects, ProjectRootElement p, RaiseWarningHandler RaiseWarning)
  		{
  			this.RaiseWarning = RaiseWarning;
  			EmitBeforeImports (p, file);
  
  			AddGeneralSettings (file, p);
  
  			StreamReader reader = new StreamReader (file);
  			string slnVersion = GetSlnFileVersion (reader);
  			if (slnVersion == "12.00")
  #if XBUILD_12
  				projects.DefaultToolsVersion = "12.0";
  #else
  				projects.DefaultToolsVersion = "4.0";
  #endif
  			else if (slnVersion == "11.00")
  				projects.DefaultToolsVersion = "4.0";
  			else if (slnVersion == "10.00")
  				projects.DefaultToolsVersion = "3.5";
  			else
  				projects.DefaultToolsVersion = "2.0";
  
  			string line = reader.ReadToEnd ();
  			line = line.Replace ("\r\n", "\n");
  			string solutionDir = Path.GetDirectoryName (file);
  
  			List<TargetInfo> solutionTargets = new List<TargetInfo> ();
  			Dictionary<Guid, ProjectInfo> projectInfos = new Dictionary<Guid, ProjectInfo> ();
  			Dictionary<Guid, ProjectInfo> websiteProjectInfos = new Dictionary<Guid, ProjectInfo> ();
  			List<ProjectInfo>[] infosByLevel = null;
  			Dictionary<Guid, ProjectInfo> unsupportedProjectInfos = new Dictionary<Guid, ProjectInfo> ();
  
  			Match m = projectRegex.Match (line);
  			while (m.Success) {
  				ProjectInfo projectInfo = new ProjectInfo (m.Groups[2].Value,
  								Path.GetFullPath (Path.Combine (solutionDir,
  									m.Groups [3].Value.Replace ('\\', Path.DirectorySeparatorChar))));
  				if (String.Compare (m.Groups [1].Value, solutionFolderGuid,
  						StringComparison.InvariantCultureIgnoreCase) == 0) {
  					// Ignore solution folders
  					m = m.NextMatch ();
  					continue;
  				}
  
  				projectInfo.Guid = new Guid (m.Groups [4].Value);
  
  				if (String.Compare (m.Groups [1].Value, vcprojGuid,
  						StringComparison.InvariantCultureIgnoreCase) == 0) {
  					// Ignore vcproj 
  					RaiseWarning (0, string.Format("Ignoring vcproj '{0}'.", projectInfo.Name));
  
  					unsupportedProjectInfos [projectInfo.Guid] = projectInfo;
  					m = m.NextMatch ();
  					continue;
  				}
  
  				if (String.Compare (m.Groups [1].Value, websiteProjectGuid,
  						StringComparison.InvariantCultureIgnoreCase) == 0)
  					websiteProjectInfos.Add (new Guid (m.Groups[4].Value), projectInfo);
  				else
  					projectInfos.Add (projectInfo.Guid, projectInfo);
  
  				Match projectSectionMatch = projectDependenciesRegex.Match (m.Groups[6].Value);
  				while (projectSectionMatch.Success) {
  					string section_name = projectSectionMatch.Groups [1].Value;
  					if (String.Compare (section_name, "ProjectDependencies") == 0) {
  						Match projectDependencyMatch = projectDependencyRegex.Match (projectSectionMatch.Value);
  						while (projectDependencyMatch.Success) {
  							// we might not have projectInfo available right now, so
  							// set it to null, and fill it in later
  							projectInfo.Dependencies [new Guid (projectDependencyMatch.Groups[1].Value)] = null;
  							projectDependencyMatch = projectDependencyMatch.NextMatch ();
  						}
  					} else {
  						ProjectSection section = new ProjectSection (section_name);
  						Match propertiesMatch = projectSectionPropertiesRegex.Match (
  									projectSectionMatch.Groups [2].Value);
  						while (propertiesMatch.Success) {
  							section.Properties [propertiesMatch.Groups ["name"].Value] =
  								propertiesMatch.Groups ["value"].Value;
  
  							propertiesMatch = propertiesMatch.NextMatch ();
  						}
  
  						projectInfo.ProjectSections [section_name] = section;
  					}
  					projectSectionMatch = projectSectionMatch.NextMatch ();
  				}
  				m = m.NextMatch ();
  			}
  
  			foreach (ProjectInfo projectInfo in projectInfos.Values) {
  				string filename = projectInfo.FileName;
  				string projectDir = Path.GetDirectoryName (filename);
  
  				if (!File.Exists (filename)) {
  					RaiseWarning (0, String.Format ("Project file {0} referenced in the solution file, " +
  								" not found. Ignoring.", filename));
  					continue;
  				}
  
  				Project currentProject;
  				try {
  					using (var xmlReader = XmlReader.Create (filename)) {
  						var root = ProjectRootElement.Create (xmlReader, projects);
  						root.FullPath = filename;
  						currentProject = new Project (root, null, null, projects, ProjectLoadSettings.IgnoreMissingImports);
  					}
  				} catch (InvalidProjectFileException e) {
  					RaiseWarning (0, e.Message);
  					continue;
  				}
  
  				foreach (var bi in currentProject.GetItems ("ProjectReference")) {
  					ProjectInfo info = null;
  					string projectReferenceGuid = bi.GetMetadataValue ("Project");
  					bool hasGuid = !String.IsNullOrEmpty (projectReferenceGuid);
  
  					// try to resolve the ProjectReference by GUID
  					// and fallback to project filename
  
  					if (hasGuid) {
  						Guid guid = new Guid (projectReferenceGuid);
  						projectInfos.TryGetValue (guid, out info);
  						if (info == null && unsupportedProjectInfos.TryGetValue (guid, out info)) {
  							RaiseWarning (0, String.Format (
  									"{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
  								filename, bi.EvaluatedInclude));
  							continue;
  						}
  					}
  
  					if (info == null || !hasGuid) {
  						// Project not found by guid or guid not available
  						// Try to find by project file
  
  						string fullpath = Path.GetFullPath (Path.Combine (projectDir, bi.EvaluatedInclude.Replace ('\\', Path.DirectorySeparatorChar)));
  						info = projectInfos.Values.FirstOrDefault (pi => pi.FileName == fullpath);
  
  						if (info == null) {
  							if (unsupportedProjectInfos.Values.Any (pi => pi.FileName == fullpath))
  								RaiseWarning (0, String.Format (
  										"{0}: ProjectReference '{1}' is of an unsupported type. Ignoring.",
  									filename, bi.EvaluatedInclude));
  							else
  								RaiseWarning (0, String.Format (
  										"{0}: ProjectReference '{1}' not found, neither by guid '{2}' nor by project file name '{3}'.",
  									filename, bi.EvaluatedInclude, projectReferenceGuid.Replace ("{", "").Replace ("}", ""), fullpath));
  						}
  
  					}
  
  					if (info != null)
  						projectInfo.Dependencies [info.Guid] = info;
  				}
  			}
  
  			// fill in the project info for deps found in the .sln file
  			foreach (ProjectInfo projectInfo in projectInfos.Values) {
  				List<Guid> missingInfos = new List<Guid> ();
  				foreach (KeyValuePair<Guid, ProjectInfo> dependency in projectInfo.Dependencies) {
  					if (dependency.Value == null)
  						missingInfos.Add (dependency.Key);
  				}
  
  				foreach (Guid guid in missingInfos) {
  					ProjectInfo info;
  					if (projectInfos.TryGetValue (guid, out info))
  						projectInfo.Dependencies [guid] = info;
  					else
  						projectInfo.Dependencies.Remove (guid);
  				}
  			}
  
  			Match globalMatch = globalRegex.Match (line);
  			Match globalSectionMatch = globalSectionRegex.Match (globalMatch.Groups[1].Value);
  			while (globalSectionMatch.Success) {
  				string sectionType = globalSectionMatch.Groups[1].Value;
  
gt: 				switch (sectionType) {