using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Collections;
using MonoDevelop.Core;
namespace MonoDevelop.Projects.Formats.MSBuild
{
public class SlnFile
{
SlnProjectCollection projects = new SlnProjectCollection ();
SlnSectionCollection sections = new SlnSectionCollection ();
SlnPropertySet metadata = new SlnPropertySet (true);
public string FormatVersion { get; set; }
public string ProductDescription { get; set; }
public string VisualStudioVersion {
get { return metadata.GetValue ("VisualStudioVersion"); }
set { metadata.SetValue ("VisualStudioVersion", value); }
}
public string MinimumVisualStudioVersion {
get { return metadata.GetValue ("MinimumVisualStudioVersion"); }
set { metadata.SetValue ("MinimumVisualStudioVersion", value); }
}
public SlnFile ()
{
}
///
/// The directory to be used as base for converting absolute paths to relative
///
public FilePath BaseDirectory {
get;
set;
}
public SlnPropertySet SolutionConfigurationsSection {
get { return sections.GetOrCreateSection ("SolutionConfigurationPlatforms", "preSolution").Properties; }
}
public SlnPropertySetCollection ProjectConfigurationsSection {
get { return sections.GetOrCreateSection ("ProjectConfigurationPlatforms", "postSolution").NestedPropertySets; }
}
public SlnSectionCollection Sections {
get { return sections; }
}
public SlnProjectCollection Projects {
get { return projects; }
}
public void Read (string file)
{
using (var sr = new StreamReader (file))
Read (sr);
}
public void Read (TextReader reader)
{
string line;
int curLineNum = 0;
bool globalFound = false;
bool productRead = false;
while ((line = reader.ReadLine ()) != null) {
curLineNum++;
line = line.Trim ();
if (line.StartsWith ("Microsoft Visual Studio Solution File")) {
int i = line.LastIndexOf (' ');
if (i == -1)
throw new InvalidSolutionFormatException (curLineNum);
FormatVersion = line.Substring (i + 1);
}
if (line.StartsWith ("# ")) {
if (!productRead) {
productRead = true;
ProductDescription = line.Substring (2);
}
} else if (line.StartsWith ("Project")) {
SlnProject p = new SlnProject ();
p.Read (reader, line, ref curLineNum);
projects.Add (p);
} else if (line == "Global") {
if (globalFound)
throw new InvalidSolutionFormatException (curLineNum, "Global section specified more than once");
globalFound = true;
while ((line = reader.ReadLine ()) != null) {
curLineNum++;
line = line.Trim ();
if (line == "EndGlobal") {
break;
} else if (line.StartsWith ("GlobalSection")) {
var sec = new SlnSection ();
sec.Read (reader, line, ref curLineNum);
sections.Add (sec);
} else
throw new InvalidSolutionFormatException (curLineNum);
}
if (line == null)
throw new InvalidSolutionFormatException (curLineNum, "Global section not closed");
} else if (line.IndexOf ('=') != -1) {
metadata.ReadLine (line, curLineNum);
}
}
if (FormatVersion == null)
throw new InvalidSolutionFormatException (curLineNum, "File header is missing");
}
public void Write (string file)
{
using (var sw = new StreamWriter (file))
Write (sw);
}
public void Write (TextWriter writer)
{
writer.NewLine = "\r\n";
writer.WriteLine ("\r\nMicrosoft Visual Studio Solution File, Format Version " + FormatVersion);
writer.WriteLine ("# " + ProductDescription);
metadata.Write (writer);
foreach (var p in projects)
p.Write (writer);
writer.WriteLine ("Global");
foreach (SlnSection s in sections)
s.Write (writer, "GlobalSection");
writer.WriteLine ("EndGlobal");
}
}
public class SlnProject
{
SlnSectionCollection sections = new SlnSectionCollection ();
public string Id { get; set; }
public string TypeGuid { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public int Line { get; private set; }
internal bool Processed { get; set; }
public SlnSectionCollection Sections {
get { return sections; }
}
internal void Read (TextReader reader, string line, ref int curLineNum)
{
Line = curLineNum;
int n = 0;
FindNext (curLineNum, line, ref n, '(');
n++;
FindNext (curLineNum, line, ref n, '"');
int n2 = n + 1;
FindNext (curLineNum, line, ref n2, '"');
TypeGuid = line.Substring (n + 1, n2 - n - 1);
n = n2 + 1;
FindNext (curLineNum, line, ref n, ')');
FindNext (curLineNum, line, ref n, '=');
FindNext (curLineNum, line, ref n, '"');
n2 = n + 1;
FindNext (curLineNum, line, ref n2, '"');
Name = line.Substring (n + 1, n2 - n - 1);
n = n2 + 1;
FindNext (curLineNum, line, ref n, ',');
FindNext (curLineNum, line, ref n, '"');
n2 = n + 1;
FindNext (curLineNum, line, ref n2, '"');
FilePath = line.Substring (n + 1, n2 - n - 1);
n = n2 + 1;
FindNext (curLineNum, line, ref n, ',');
FindNext (curLineNum, line, ref n, '"');
n2 = n + 1;
FindNext (curLineNum, line, ref n2, '"');
Id = line.Substring (n + 1, n2 - n - 1);
while ((line = reader.ReadLine ()) != null) {
curLineNum++;
line = line.Trim ();
if (line == "EndProject") {
return;
}
if (line.StartsWith ("ProjectSection")) {
if (sections == null)
sections = new SlnSectionCollection ();
var sec = new SlnSection ();
sections.Add (sec);
sec.Read (reader, line, ref curLineNum);
}
}
throw new InvalidSolutionFormatException (curLineNum, "Project section not closed");
}
void FindNext (int ln, string line, ref int i, char c)
{
i = line.IndexOf (c, i);
if (i == -1)
throw new InvalidSolutionFormatException (ln);
}
public void Write (TextWriter writer)
{
writer.Write ("Project(\"");
writer.Write (TypeGuid);
writer.Write ("\") = \"");
writer.Write (Name);
writer.Write ("\", \"");
writer.Write (FilePath);
writer.Write ("\", \"");
writer.Write (Id);
writer.WriteLine ("\"");
if (sections != null) {
foreach (SlnSection s in sections)
s.Write (writer, "ProjectSection");
}
writer.WriteLine ("EndProject");
}
}
public class SlnSection
{
SlnPropertySetCollection nestedPropertySets;
SlnPropertySet properties;
List sectionLines;
int baseIndex;
public string Id { get; set; }
public int Line { get; private set; }
internal bool Processed { get; set; }
public SlnPropertySet Properties {
get {
if (properties == null) {
properties = new SlnPropertySet ();
if (sectionLines != null) {
foreach (var line in sectionLines)
properties.ReadLine (line, Line);
sectionLines = null;
}
}
return properties;
}
}
public SlnPropertySetCollection NestedPropertySets {
get {
if (nestedPropertySets == null) {
nestedPropertySets = new SlnPropertySetCollection ();
if (sectionLines != null)
LoadPropertySets ();
}
return nestedPropertySets;
}
}
public string SectionType { get; set; }
internal void Read (TextReader reader, string line, ref int curLineNum)
{
Line = curLineNum;
int k = line.IndexOf ('(');
if (k == -1)
throw new InvalidSolutionFormatException (curLineNum, "Section id missing");
var tag = line.Substring (0, k).Trim ();
var k2 = line.IndexOf (')', k);
if (k2 == -1)
throw new InvalidSolutionFormatException (curLineNum);
Id = line.Substring (k + 1, k2 - k - 1);
k = line.IndexOf ('=', k2);
SectionType = line.Substring (k + 1).Trim ();
var endTag = "End" + tag;
sectionLines = new List ();
baseIndex = ++curLineNum;
while ((line = reader.ReadLine()) != null) {
curLineNum++;
line = line.Trim ();
if (line == endTag)
break;
sectionLines.Add (line);
}
if (line == null)
throw new InvalidSolutionFormatException (curLineNum, "Closing section tag not found");
}
void LoadPropertySets ()
{
if (sectionLines != null) {
SlnPropertySet curSet = null;
for (int n = 0; n < sectionLines.Count; n++) {
var line = sectionLines [n];
var i = line.IndexOf ('.');
if (i == -1)
throw new InvalidSolutionFormatException (baseIndex + i);
var id = line.Substring (0, i);
if (curSet == null || id != curSet.Id) {
curSet = new SlnPropertySet (id);
nestedPropertySets.Add (curSet);
}
curSet.ReadLine (line.Substring (i + 1), baseIndex + i);
}
sectionLines = null;
}
}
internal void Write (TextWriter writer, string sectionTag)
{
writer.Write ("\t");
writer.Write (sectionTag);
writer.Write ('(');
writer.Write (Id);
writer.Write (") = ");
writer.WriteLine (SectionType);
if (sectionLines != null) {
foreach (var l in sectionLines)
writer.WriteLine ("\t\t" + l);
} else if (properties != null)
properties.Write (writer);
else if (nestedPropertySets != null) {
foreach (var ps in nestedPropertySets)
ps.Write (writer);
}
writer.WriteLine ("\tEnd" + sectionTag);
}
}
public class SlnPropertySet: IDictionary
{
OrderedDictionary values = new OrderedDictionary ();
bool isMetadata;
internal bool Processed { get; set; }
public int Line { get; private set; }
public SlnPropertySet ()
{
}
public SlnPropertySet (string id)
{
Id = id;
}
internal SlnPropertySet (bool isMetadata)
{
this.isMetadata = isMetadata;
}
internal void ReadLine (string line, int currentLine)
{
if (Line == 0)
Line = currentLine;
line = line.Trim ();
int k = line.IndexOf ('=');
if (k != -1) {
var name = line.Substring (0, k).Trim ();
var val = line.Substring (k + 1).Trim ();
values.Add (name, val);
} else {
values.Add (line, null);
}
}
internal void Write (TextWriter writer)
{
foreach (DictionaryEntry e in values) {
if (!isMetadata)
writer.Write ("\t\t");
if (Id != null)
writer.Write (Id + ".");
writer.WriteLine (e.Key + " = " + e.Value);
}
}
public string Id { get; private set; }
public string GetValue (string key)
{
return (string) values [key];
}
public void SetValue (string key, string value)
{
values [key] = value;
}
public void Add (string key, string value)
{
SetValue (key, value);
}
public bool ContainsKey (string key)
{
return values.Contains (key);
}
public bool Remove (string key)
{
var wasThere = values.Contains (key);
values.Remove (key);
return wasThere;
}
public bool TryGetValue (string key, out string value)
{
value = (string) values [key];
return value != null;
}
public string this [string index] {
get {
return (string) values [index];
}
set {
values [index] = value;
}
}
public ICollection Values {
get {
return values.Values.Cast().ToList ();
}
}
public ICollection Keys {
get { return values.Keys.Cast ().ToList (); }
}
public void Add (KeyValuePair item)
{
SetValue (item.Key, item.Value);
}
public void Clear ()
{
values.Clear ();
}
internal void ClearExcept (HashSet keys)
{
foreach (var k in values.Keys.Cast().Except (keys).ToArray ())
values.Remove (k);
}
public bool Contains (KeyValuePair item)
{
var val = GetValue (item.Key);
return val == item.Value;
}
public void CopyTo (KeyValuePair[] array, int arrayIndex)
{
foreach (DictionaryEntry de in values)
array [arrayIndex++] = new KeyValuePair ((string)de.Key, (string)de.Value);
}
public bool Remove (KeyValuePair item)
{
if (Contains (item)) {
Remove (item.Key);
return true;
} else
return false;
}
public int Count {
get {
return values.Count;
}
}
public bool IsReadOnly {
get {
return false;
}
}
public IEnumerator> GetEnumerator ()
{
foreach (DictionaryEntry de in values)
yield return new KeyValuePair ((string)de.Key, (string)de.Value);
}
IEnumerator IEnumerable.GetEnumerator ()
{
foreach (DictionaryEntry de in values)
yield return new KeyValuePair ((string)de.Key, (string)de.Value);
}
}
public class SlnProjectCollection: Collection
{
public SlnProject GetProject (string id)
{
return this.FirstOrDefault (s => s.Id == id);
}
public SlnProject GetOrCreateProject (string id)
{
var p = this.FirstOrDefault (s => s.Id.Equals (id, StringComparison.OrdinalIgnoreCase));
if (p == null) {
p = new SlnProject { Id = id };
Add (p);
}
return p;
}
}
public class SlnSectionCollection: Collection
{
public SlnSection GetSection (string id)
{
return this.FirstOrDefault (s => s.Id == id);
}
public SlnSection GetOrCreateSection (string id, string sectionType)
{
var sec = this.FirstOrDefault (s => s.Id == id);
if (sec == null) {
sec = new SlnSection { Id = id };
sec.SectionType = sectionType;
Add (sec);
}
return sec;
}
public void RemoveSection (string id)
{
var s = GetSection (id);
if (s != null)
Remove (s);
}
}
public class SlnPropertySetCollection: Collection
{
public SlnPropertySet GetPropertySet (string id, bool ignoreCase = false)
{
var sc = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return this.FirstOrDefault (s => s.Id.Equals (id, sc));
}
public SlnPropertySet GetOrCreatePropertySet (string id, bool ignoreCase = false)
{
var ps = GetPropertySet (id, ignoreCase);
if (ps == null) {
ps = new SlnPropertySet (id);
Add (ps);
}
return ps;
}
}
class InvalidSolutionFormatException: Exception
{
public InvalidSolutionFormatException (int line): base ("Invalid format in line " + line)
{
}
public InvalidSolutionFormatException (int line, string msg): base ("Invalid format in line " + line + ": " + msg)
{
}
}
}