Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLluis Sanchez Gual <lluis@xamarin.com>2015-04-17 18:25:28 +0300
committerLluis Sanchez Gual <lluis@xamarin.com>2015-04-17 18:27:01 +0300
commit42b7775af9107d63082fd9f07954459d1838252f (patch)
tree53e6a6a2a96add2574ccf438fe6f020a02ad9740
parent2c8f037e166b559d03a8540d14a0536f1c395116 (diff)
Add support for custom data serialization in solutions
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/ClassDataType.cs33
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataCollection.cs111
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataDeletedValue.cs42
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataItem.cs25
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataNode.cs6
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/SerializationContext.cs13
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj2
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/IMSBuildPropertySet.cs197
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFile.cs402
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFileFormat.cs162
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs2
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionDataSectionAttribute.cs44
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionExtension.cs26
-rw-r--r--main/tests/UnitTests/MonoDevelop.Projects/PolicyTests.cs37
-rw-r--r--main/tests/UnitTests/MonoDevelop.Projects/SolutionTests.cs129
-rw-r--r--main/tests/UnitTests/UnitTests.csproj1
16 files changed, 940 insertions, 292 deletions
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/ClassDataType.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/ClassDataType.cs
index 719e316efe..c1d0954759 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/ClassDataType.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/ClassDataType.cs
@@ -313,15 +313,25 @@ namespace MonoDevelop.Core.Serialization
foreach (ItemProperty prop in Properties) {
if (prop.ReadOnly || !prop.CanSerialize (serCtx, obj))
continue;
+
+ DataCollection col = itemCol;
+
object val = prop.GetValue (obj);
- if (val == null)
- continue;
- if (!serCtx.IsDefaultValueSerializationForced (prop) && val.Equals (prop.DefaultValue))
+ if (val == null) {
+ if (serCtx.IncludeDeletedValues) {
+ if (prop.IsNested)
+ col = GetNestedCollection (col, prop.NameList, 0, true);
+ col.Add (new DataDeletedNode (prop.SingleName));
+ }
continue;
+ }
- DataCollection col = itemCol;
+ var isDefault = val.Equals (prop.DefaultValue);
+ if (isDefault && !serCtx.IsDefaultValueSerializationForced (prop))
+ continue;
+
if (prop.IsNested)
- col = GetNestedCollection (col, prop.NameList, 0);
+ col = GetNestedCollection (col, prop.NameList, 0, isDefault);
if (prop.ExpandedCollection) {
ICollectionHandler handler = prop.ExpandedCollectionHandler;
@@ -336,8 +346,12 @@ namespace MonoDevelop.Core.Serialization
}
else {
DataNode data = prop.Serialize (serCtx, obj, val);
- if (data == null)
+ if (data == null) {
+ if (serCtx.IncludeDeletedValues)
+ col.Add (new DataDeletedNode (prop.SingleName));
continue;
+ }
+ data.IsDefaultValue = isDefault;
col.Add (data);
}
}
@@ -352,7 +366,7 @@ namespace MonoDevelop.Core.Serialization
return itemCol;
}
- DataCollection GetNestedCollection (DataCollection col, string[] nameList, int pos)
+ DataCollection GetNestedCollection (DataCollection col, string[] nameList, int pos, bool isDefault)
{
if (pos == nameList.Length - 1) return col;
@@ -361,8 +375,11 @@ namespace MonoDevelop.Core.Serialization
item = new DataItem ();
item.Name = nameList[pos];
col.Add (item);
+ item.IsDefaultValue = isDefault;
}
- return GetNestedCollection (item.ItemData, nameList, pos + 1);
+ if (item.IsDefaultValue && !isDefault)
+ item.IsDefaultValue = false;
+ return GetNestedCollection (item.ItemData, nameList, pos + 1, isDefault);
}
internal protected override object OnDeserialize (SerializationContext serCtx, object mapData, DataNode data)
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataCollection.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataCollection.cs
index f1b2f12b5d..970427f092 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataCollection.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataCollection.cs
@@ -29,57 +29,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
namespace MonoDevelop.Core.Serialization
{
[Serializable]
- public class DataCollection: IEnumerable
+ public sealed class DataCollection: Collection<DataNode>
{
- List<DataNode> list = new List<DataNode> ();
-
- public DataCollection ()
- {
- }
-
- protected List<DataNode> List {
- get {
- if (list == null)
- list = new List<DataNode> ();
- return list;
- }
- }
-
- public int Count
- {
- get { return list == null ? 0 : list.Count; }
- }
-
- public virtual DataNode this [int n]
- {
- get { return List[n]; }
- set { List[n] = value; }
- }
-
- public virtual DataNode this [string name]
+ public DataNode this [string name]
{
get {
DataCollection col;
int i = FindData (name, out col, false);
- if (i != -1) return col.List [i];
+ if (i != -1) return col [i];
else return null;
}
}
int FindData (string name, out DataCollection colec, bool buildTree)
{
- if (list == null) {
- colec = null;
- return -1;
- }
-
if (name.IndexOf ('/') == -1) {
- for (int n=0; n<list.Count; n++) {
- DataNode data = list [n];
+ for (int n=0; n<Items.Count; n++) {
+ DataNode data = Items [n];
if (data.Name == name) {
colec = this;
return n;
@@ -111,8 +82,8 @@ namespace MonoDevelop.Core.Serialization
}
pos = -1;
- for (int n=0; n<colec.List.Count; n++) {
- data = colec.List [n];
+ for (int n=0; n<colec.Count; n++) {
+ data = colec [n];
if (data.Name == names [p]) {
pos = n; break;
}
@@ -122,47 +93,14 @@ namespace MonoDevelop.Core.Serialization
}
}
- public virtual IEnumerator GetEnumerator ()
- {
- return list == null ? Type.EmptyTypes.GetEnumerator() : list.GetEnumerator ();
- }
-
- public void AddRange (DataCollection col)
- {
- foreach (DataNode node in col)
- Add (node);
- }
-
- public virtual void Add (DataNode entry)
- {
- if (entry == null)
- throw new ArgumentNullException ("entry");
-
- List.Add (entry);
- }
-
- public virtual void Insert (int index, DataNode entry)
- {
- if (entry == null)
- throw new ArgumentNullException ("entry");
-
- List.Insert (index, entry);
- }
-
- public virtual void Add (DataNode entry, string itemPath)
+ public void Add (DataNode entry, string itemPath)
{
if (entry == null)
throw new ArgumentNullException ("entry");
DataCollection col;
FindData (itemPath + "/", out col, true);
- col.List.Add (entry);
- }
-
- public virtual void Remove (DataNode entry)
- {
- if (list != null)
- list.Remove (entry);
+ Add (entry);
}
public DataNode Extract (string name)
@@ -170,25 +108,13 @@ namespace MonoDevelop.Core.Serialization
DataCollection col;
int i = FindData (name, out col, false);
if (i != -1) {
- DataNode data = col.List [i];
- col.list.RemoveAt (i);
+ DataNode data = col [i];
+ col.RemoveAt (i);
return data;
}
return null;
}
- public int IndexOf (DataNode entry)
- {
- if (list == null) return -1;
- return list.IndexOf (entry);
- }
-
- public virtual void Clear ()
- {
- if (list != null)
- list.Clear ();
- }
-
public void Merge (DataCollection col)
{
ArrayList toAdd = new ArrayList ();
@@ -204,18 +130,5 @@ namespace MonoDevelop.Core.Serialization
foreach (DataNode node in toAdd)
Add (node);
}
-
- // Sorts the list using the specified key order
- public void Sort (Dictionary<string,int> nameToPosition)
- {
- list.Sort (delegate (DataNode x, DataNode y) {
- int p1, p2;
- if (!nameToPosition.TryGetValue (x.Name, out p1))
- p1 = int.MaxValue;
- if (!nameToPosition.TryGetValue (y.Name, out p2))
- p2 = int.MaxValue;
- return p1.CompareTo (p2);
- });
- }
}
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataDeletedValue.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataDeletedValue.cs
new file mode 100644
index 0000000000..55233bc925
--- /dev/null
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataDeletedValue.cs
@@ -0,0 +1,42 @@
+//
+// DataDeletedValue.cs
+//
+// Author:
+// Lluis Sanchez Gual <lluis@xamarin.com>
+//
+// Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.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;
+
+namespace MonoDevelop.Core.Serialization
+{
+ /// <summary>
+ /// A data node that represents a value that has been deleted.
+ /// </summary>
+ [Serializable]
+ public class DataDeletedNode: DataNode
+ {
+ public DataDeletedNode (string name)
+ {
+ Name = name;
+ }
+ }
+}
+
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataItem.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataItem.cs
index 19bccb54aa..5fd5d9320d 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataItem.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataItem.cs
@@ -27,6 +27,7 @@
//
using System;
+using System.Collections.Generic;
namespace MonoDevelop.Core.Serialization
{
@@ -65,6 +66,30 @@ namespace MonoDevelop.Core.Serialization
if (data == null) return null;
return data.Extract (name);
}
+
+ internal void UpdateFromItem (DataItem item, HashSet<DataItem> removedItems)
+ {
+ foreach (var d in item.ItemData) {
+ var current = ItemData[d.Name];
+ if (current != null) {
+ if (d.IsDefaultValue || d is DataDeletedNode) {
+ if (current is DataItem)
+ removedItems.Add ((DataItem)current);
+ ItemData.Remove (current);
+ }
+ else if (current.GetType () != d.GetType () || current is DataValue) {
+ var i = ItemData.IndexOf (current);
+ ItemData [i] = d;
+ if (current is DataItem)
+ removedItems.Add ((DataItem)current);
+ } else if (current is DataItem) {
+ ((DataItem)current).UpdateFromItem ((DataItem)d, removedItems);
+ }
+ } else if (!d.IsDefaultValue && !(d is DataDeletedNode)) {
+ ItemData.Add (d);
+ }
+ }
+ }
public override string ToString ()
{
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataNode.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataNode.cs
index 1f6a847719..98b1cd9b29 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataNode.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/DataNode.cs
@@ -44,5 +44,11 @@ namespace MonoDevelop.Core.Serialization
{
return "";
}
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is default value.
+ /// </summary>
+ /// <remarks>This flag is set when an object is serialized using the IncludeDefaultValues or IncludeDeletedValues</remarks>
+ public bool IsDefaultValue { get; set; }
}
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/SerializationContext.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/SerializationContext.cs
index d3179c26b8..ffa277d0d8 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/SerializationContext.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Serialization/SerializationContext.cs
@@ -81,9 +81,18 @@ namespace MonoDevelop.Core.Serialization
monitor = value;
}
}
-
+
+ /// <summary>
+ /// When set to true, properties with default values are serialized
+ /// </summary>
public bool IncludeDefaultValues { get; set; }
+ /// <summary>
+ /// When set to true, properties with default values are serialized, and properties that have
+ /// been removed are serialized as a DataDeletedValue.
+ /// </summary>
+ public bool IncludeDeletedValues { get; set; }
+
public void ResetDefaultValueSerialization ()
{
forcedSerializationProps = null;
@@ -98,7 +107,7 @@ namespace MonoDevelop.Core.Serialization
public bool IsDefaultValueSerializationForced (ItemProperty prop)
{
- if (IncludeDefaultValues)
+ if (IncludeDefaultValues || IncludeDeletedValues)
return true;
else if (forcedSerializationProps != null)
return forcedSerializationProps.Contains (prop);
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj
index c30bf344ae..d980d4c1ce 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj
@@ -505,6 +505,8 @@
<Compile Include="MonoDevelop.Projects.Extensions\ImportRedirectTypeNode.cs" />
<Compile Include="MonoDevelop.Projects.Formats.MSBuild\FileUtil.cs" />
<Compile Include="MonoDevelop.Projects.Formats.MSBuild\DefaultMSBuildEngine.cs" />
+ <Compile Include="MonoDevelop.Projects\SolutionDataSectionAttribute.cs" />
+ <Compile Include="MonoDevelop.Core.Serialization\DataDeletedValue.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Makefile.am" />
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/IMSBuildPropertySet.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/IMSBuildPropertySet.cs
index c005edddbb..8bee78d40b 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/IMSBuildPropertySet.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/IMSBuildPropertySet.cs
@@ -139,10 +139,10 @@ namespace MonoDevelop.Projects.Formats.MSBuild
continue;
object readVal = null;
if (prop.ReturnType == typeof(FilePath)) {
- FilePath def = prop.Attribute.DefaultValue != null ? (string)prop.Attribute.DefaultValue : (string)null;
+ FilePath def = (string)prop.Attribute.DefaultValue;
readVal = pset.GetPathValue (prop.Name, def);
} else if (prop.Attribute is ProjectPathItemProperty && prop.ReturnType == typeof(string)) {
- FilePath def = prop.Attribute.DefaultValue != null ? (string)prop.Attribute.DefaultValue : (string)null;
+ FilePath def = (string)prop.Attribute.DefaultValue;
readVal = pset.GetPathValue (prop.Name, def);
readVal = readVal.ToString ();
} else if (prop.ReturnType == typeof(string)) {
@@ -154,6 +154,199 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
}
+ static DataContext solutionDataContext = new DataContext ();
+
+ public static void WriteObjectProperties (this SlnPropertySet pset, object ob)
+ {
+ DataSerializer ser = new DataSerializer (solutionDataContext);
+ ser.SerializationContext.BaseFile = pset.ParentFile.FileName;
+ ser.SerializationContext.IncludeDeletedValues = true;
+ var data = ser.Serialize (ob, ob.GetType()) as DataItem;
+ if (data != null)
+ WriteDataItem (pset, data);
+ }
+
+ public static void ReadObjectProperties (this SlnPropertySet pset, object ob)
+ {
+ DataSerializer ser = new DataSerializer (solutionDataContext);
+ ser.SerializationContext.BaseFile = pset.ParentFile.FileName;
+ var data = ReadDataItem (pset);
+ ser.Deserialize (ob, data);
+ }
+
+ static void WriteDataItem (SlnPropertySet pset, DataItem item)
+ {
+ HashSet<DataItem> removedItems = new HashSet<DataItem> ();
+ Dictionary<DataNode,int> ids = new Dictionary<DataNode, int> ();
+
+ // First of all read the existing data item, since we want to keep data that has not been modified
+ // The ids collection is filled with a map of items and their ids
+ var currentItem = ReadDataItem (pset, ids);
+
+ // UpdateFromItem will add new data to the item, it will remove the data that has been removed, and
+ // will ignore unknown data that has not been set or removed
+ currentItem.UpdateFromItem (item, removedItems);
+
+ // List of IDs that are not used anymore and can be reused when writing the item
+ var unusedIds = new Queue<int> (removedItems.Select (it => ids[it]).OrderBy (i => i));
+
+ // Calculate the next free id, to be used when adding new items
+ var usedIds = ids.Where (p => !removedItems.Contains (p.Key)).Select (p => p.Value).ToArray ();
+ int nextId = usedIds.Length > 0 ? usedIds.Max () + 1 : 0;
+
+ // Clear all properties, since we are writing them again
+ pset.Clear ();
+
+ foreach (DataNode val in currentItem.ItemData)
+ WriteDataNode (pset, "", val, ids, unusedIds, ref nextId);
+ }
+
+ static void WriteDataNode (SlnPropertySet pset, string prefix, DataNode node, Dictionary<DataNode,int> ids, Queue<int> unusedIds, ref int id)
+ {
+ string name = node.Name;
+ string newPrefix = prefix.Length > 0 ? prefix + "." + name: name;
+
+ if (node is DataValue) {
+ DataValue val = (DataValue) node;
+ string value = EncodeString (val.Value);
+ pset.SetValue (newPrefix, value);
+ }
+ else {
+ DataItem it = (DataItem) node;
+ int newId;
+ if (!ids.TryGetValue (node, out newId))
+ newId = unusedIds.Count > 0 ? unusedIds.Dequeue () : (id++);
+ pset.SetValue (newPrefix, "$" + newId);
+ newPrefix = "$" + newId;
+ foreach (DataNode cn in it.ItemData)
+ WriteDataNode (pset, newPrefix, cn, ids, unusedIds, ref id);
+ }
+ }
+
+ static string EncodeString (string val)
+ {
+ if (val.Length == 0)
+ return val;
+
+ int i = val.IndexOfAny (new char[] {'\n','\r','\t'});
+ if (i != -1 || val [0] == '@') {
+ StringBuilder sb = new StringBuilder ();
+ if (i != -1) {
+ int fi = val.IndexOf ('\\');
+ if (fi != -1 && fi < i) i = fi;
+ sb.Append (val.Substring (0,i));
+ } else
+ i = 0;
+ for (int n = i; n < val.Length; n++) {
+ char c = val [n];
+ if (c == '\r')
+ sb.Append (@"\r");
+ else if (c == '\n')
+ sb.Append (@"\n");
+ else if (c == '\t')
+ sb.Append (@"\t");
+ else if (c == '\\')
+ sb.Append (@"\\");
+ else
+ sb.Append (c);
+ }
+ val = "@" + sb.ToString ();
+ }
+ char fc = val [0];
+ char lc = val [val.Length - 1];
+ if (fc == ' ' || fc == '"' || fc == '$' || lc == ' ')
+ val = "\"" + val + "\"";
+ return val;
+ }
+
+ static string DecodeString (string val)
+ {
+ val = val.Trim (' ', '\t');
+ if (val.Length == 0)
+ return val;
+ if (val [0] == '\"')
+ val = val.Substring (1, val.Length - 2);
+ if (val [0] == '@') {
+ StringBuilder sb = new StringBuilder (val.Length);
+ for (int n = 1; n < val.Length; n++) {
+ char c = val [n];
+ if (c == '\\') {
+ c = val [++n];
+ if (c == 'r') c = '\r';
+ else if (c == 'n') c = '\n';
+ else if (c == 't') c = '\t';
+ }
+ sb.Append (c);
+ }
+ return sb.ToString ();
+ }
+ else
+ return val;
+ }
+
+ static DataItem ReadDataItem (SlnPropertySet pset)
+ {
+ return ReadDataItem (pset, null);
+ }
+
+ static DataItem ReadDataItem (SlnPropertySet pset, Dictionary<DataNode,int> ids)
+ {
+ DataItem it = new DataItem ();
+
+ var lines = pset.ToArray ();
+
+ int lineNum = 0;
+ int lastLine = lines.Length - 1;
+ while (lineNum <= lastLine) {
+ if (!ReadDataNode (it, lines, lastLine, "", ids, ref lineNum))
+ lineNum++;
+ }
+ return it;
+ }
+
+ static bool ReadDataNode (DataItem item, KeyValuePair<string,string>[] lines, int lastLine, string prefix, Dictionary<DataNode,int> ids, ref int lineNum)
+ {
+ var s = lines [lineNum];
+
+ string name = s.Key;
+ if (name.Length == 0) {
+ lineNum++;
+ return true;
+ }
+
+ // Check if the line belongs to the current item
+ if (prefix.Length > 0) {
+ if (!s.Key.StartsWith (prefix + ".", StringComparison.Ordinal))
+ return false;
+ name = s.Key.Substring (prefix.Length + 1);
+ } else {
+ if (s.Key.StartsWith ("$", StringComparison.Ordinal))
+ return false;
+ }
+
+ string value = s.Value;
+ if (value.StartsWith ("$", StringComparison.Ordinal)) {
+ // New item
+ DataItem child = new DataItem ();
+ child.Name = name;
+ int id;
+ if (ids != null && int.TryParse (value.Substring (1), out id))
+ ids [child] = id;
+ lineNum++;
+ while (lineNum <= lastLine) {
+ if (!ReadDataNode (child, lines, lastLine, value, ids, ref lineNum))
+ break;
+ }
+ item.ItemData.Add (child);
+ } else {
+ value = DecodeString (value);
+ DataValue val = new DataValue (name, value);
+ item.ItemData.Add (val);
+ lineNum++;
+ }
+ return true;
+ }
+
internal static void WriteExternalProjectProperties (this MSBuildProject project, object ob, Type typeToScan, bool includeBaseMembers = false)
{
var props = GetMembers (typeToScan, includeBaseMembers);
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFile.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFile.cs
index f1af02ae84..a76cb4f75c 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFile.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFile.cs
@@ -8,6 +8,7 @@ using System.Collections;
using MonoDevelop.Core;
using MonoDevelop.Projects.Text;
using System.Text.RegularExpressions;
+using System.Globalization;
namespace MonoDevelop.Projects.Formats.MSBuild
{
@@ -34,6 +35,8 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public SlnFile ()
{
+ projects.ParentFile = this;
+ sections.ParentFile = this;
}
/// <summary>
@@ -43,7 +46,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
/// <param name="file">File.</param>
public static string GetFileVersion (string file)
{
- string strVersion = null;
+ string strVersion;
using (var reader = new StreamReader (file)) {
var strInput = reader.ReadLine();
if (strInput == null)
@@ -70,21 +73,32 @@ namespace MonoDevelop.Projects.Formats.MSBuild
/// The directory to be used as base for converting absolute paths to relative
/// </summary>
public FilePath BaseDirectory {
- get;
- set;
+ get { return FileName.ParentDirectory; }
}
+ /// <summary>
+ /// Gets the solution configurations section.
+ /// </summary>
+ /// <value>The solution configurations section.</value>
public SlnPropertySet SolutionConfigurationsSection {
- get { return sections.GetOrCreateSection ("SolutionConfigurationPlatforms", "preSolution").Properties; }
+ get { return sections.GetOrCreateSection ("SolutionConfigurationPlatforms", SlnSectionType.PreProcess).Properties; }
}
+ /// <summary>
+ /// Gets the project configurations section.
+ /// </summary>
+ /// <value>The project configurations section.</value>
public SlnPropertySetCollection ProjectConfigurationsSection {
- get { return sections.GetOrCreateSection ("ProjectConfigurationPlatforms", "postSolution").NestedPropertySets; }
+ get { return sections.GetOrCreateSection ("ProjectConfigurationPlatforms", SlnSectionType.PostProcess).NestedPropertySets; }
}
+ /// <summary>
+ /// Gets the custom MonoDevelop properties section
+ /// </summary>
+ /// <value>The custom mono develop properties.</value>
public SlnPropertySet CustomMonoDevelopProperties {
get {
- var s = sections.GetOrCreateSection ("MonoDevelopProperties", "preSolution");
+ var s = sections.GetOrCreateSection ("MonoDevelopProperties", SlnSectionType.PreProcess);
s.SkipIfEmpty = true;
return s.Properties;
}
@@ -98,8 +112,11 @@ namespace MonoDevelop.Projects.Formats.MSBuild
get { return projects; }
}
+ public FilePath FileName { get; set; }
+
public void Read (string file)
{
+ FileName = file;
format = FileUtil.GetTextFormatInfo (file);
using (var sr = new StreamReader (file))
@@ -116,19 +133,19 @@ namespace MonoDevelop.Projects.Formats.MSBuild
while ((line = reader.ReadLine ()) != null) {
curLineNum++;
line = line.Trim ();
- if (line.StartsWith ("Microsoft Visual Studio Solution File")) {
+ if (line.StartsWith ("Microsoft Visual Studio Solution File", StringComparison.Ordinal)) {
int i = line.LastIndexOf (' ');
if (i == -1)
throw new InvalidSolutionFormatException (curLineNum);
FormatVersion = line.Substring (i + 1);
prefixBlankLines = curLineNum - 1;
}
- if (line.StartsWith ("# ")) {
+ if (line.StartsWith ("# ", StringComparison.Ordinal)) {
if (!productRead) {
productRead = true;
ProductDescription = line.Substring (2);
}
- } else if (line.StartsWith ("Project")) {
+ } else if (line.StartsWith ("Project", StringComparison.Ordinal)) {
SlnProject p = new SlnProject ();
p.Read (reader, line, ref curLineNum);
projects.Add (p);
@@ -141,7 +158,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
line = line.Trim ();
if (line == "EndGlobal") {
break;
- } else if (line.StartsWith ("GlobalSection")) {
+ } else if (line.StartsWith ("GlobalSection", StringComparison.Ordinal)) {
var sec = new SlnSection ();
sec.Read (reader, line, ref curLineNum);
sections.Add (sec);
@@ -160,6 +177,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public void Write (string file)
{
+ FileName = file;
var sw = new StringWriter ();
Write (sw);
TextFile.WriteFile (file, sw.ToString(), format.ByteOrderMark, true);
@@ -189,6 +207,18 @@ namespace MonoDevelop.Projects.Formats.MSBuild
{
SlnSectionCollection sections = new SlnSectionCollection ();
+ SlnFile parentFile;
+
+ public SlnFile ParentFile {
+ get {
+ return parentFile;
+ }
+ internal set {
+ parentFile = value;
+ sections.ParentFile = parentFile;
+ }
+ }
+
public string Id { get; set; }
public string TypeGuid { get; set; }
public string Name { get; set; }
@@ -241,7 +271,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
if (line == "EndProject") {
return;
}
- if (line.StartsWith ("ProjectSection")) {
+ if (line.StartsWith ("ProjectSection", StringComparison.Ordinal)) {
if (sections == null)
sections = new SlnSectionCollection ();
var sec = new SlnSection ();
@@ -288,8 +318,11 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public string Id { get; set; }
public int Line { get; private set; }
+
internal bool Processed { get; set; }
+ public SlnFile ParentFile { get; internal set; }
+
public bool IsEmpty {
get {
return (properties == null || properties.Count == 0) && (nestedPropertySets == null || nestedPropertySets.All (t => t.IsEmpty));
@@ -313,6 +346,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
get {
if (properties == null) {
properties = new SlnPropertySet ();
+ properties.ParentSection = this;
if (sectionLines != null) {
foreach (var line in sectionLines)
properties.ReadLine (line, Line);
@@ -326,7 +360,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public SlnPropertySetCollection NestedPropertySets {
get {
if (nestedPropertySets == null) {
- nestedPropertySets = new SlnPropertySetCollection ();
+ nestedPropertySets = new SlnPropertySetCollection (this);
if (sectionLines != null)
LoadPropertySets ();
}
@@ -334,7 +368,24 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
}
- public string SectionType { get; set; }
+ public SlnSectionType SectionType { get; set; }
+
+ SlnSectionType ToSectionType (int curLineNum, string s)
+ {
+ if (s == "preSolution" || s == "preProject")
+ return SlnSectionType.PreProcess;
+ if (s == "postSolution" || s == "postProject")
+ return SlnSectionType.PostProcess;
+ throw new InvalidSolutionFormatException (curLineNum, "Invalid section type: " + s);
+ }
+
+ string FromSectionType (bool isProjectSection, SlnSectionType type)
+ {
+ if (type == SlnSectionType.PreProcess)
+ return isProjectSection ? "preProject" : "preSolution";
+ else
+ return isProjectSection ? "postProject" : "postSolution";
+ }
internal void Read (TextReader reader, string line, ref int curLineNum)
{
@@ -349,7 +400,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
Id = line.Substring (k + 1, k2 - k - 1);
k = line.IndexOf ('=', k2);
- SectionType = line.Substring (k + 1).Trim ();
+ SectionType = ToSectionType (curLineNum, line.Substring (k + 1).Trim ());
var endTag = "End" + tag;
@@ -396,7 +447,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
writer.Write ('(');
writer.Write (Id);
writer.Write (") = ");
- writer.WriteLine (SectionType);
+ writer.WriteLine (FromSectionType (sectionTag == "ProjectSection", SectionType));
if (sectionLines != null) {
foreach (var l in sectionLines)
writer.WriteLine ("\t\t" + l);
@@ -410,18 +461,36 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
}
+ /// <summary>
+ /// A collection of properties
+ /// </summary>
public class SlnPropertySet: IDictionary<string,string>
{
OrderedDictionary values = new OrderedDictionary ();
bool isMetadata;
internal bool Processed { get; set; }
+
+ public SlnFile ParentFile {
+ get { return ParentSection != null ? ParentSection.ParentFile : null; }
+ }
+
+ public SlnSection ParentSection { get; set; }
+
+ /// <summary>
+ /// Text file line of this section in the original file
+ /// </summary>
+ /// <value>The line.</value>
public int Line { get; private set; }
- public SlnPropertySet ()
+ internal SlnPropertySet ()
{
}
+ /// <summary>
+ /// Creates a new property set with the specified ID
+ /// </summary>
+ /// <param name="id">Identifier.</param>
public SlnPropertySet (string id)
{
Id = id;
@@ -432,6 +501,10 @@ namespace MonoDevelop.Projects.Formats.MSBuild
this.isMetadata = isMetadata;
}
+ /// <summary>
+ /// Gets a value indicating whether this property set is empty.
+ /// </summary>
+ /// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty {
get {
return values.Count == 0;
@@ -464,28 +537,163 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
}
+ /// <summary>
+ /// Gets the identifier of the property set
+ /// </summary>
+ /// <value>The identifier.</value>
public string Id { get; private set; }
- public string GetValue (string key)
+ public string GetValue (string name, string defaultValue = null)
{
- return (string) values [key];
+ string res;
+ if (TryGetValue (name, out res))
+ return res;
+ else
+ return defaultValue;
}
- public void SetValue (string key, string value)
+ public FilePath GetPathValue (string name, FilePath defaultValue = default(FilePath), bool relativeToSolution = true, FilePath relativeToPath = default(FilePath))
{
- values [key] = value;
+ string val;
+ if (TryGetValue (name, out val)) {
+ string baseDir = null;
+ if (relativeToPath != null) {
+ baseDir = relativeToPath;
+ } else if (relativeToSolution && ParentFile != null && ParentFile.FileName != null) {
+ baseDir = ParentFile.FileName.ParentDirectory;
+ }
+ return MSBuildProjectService.FromMSBuildPath (baseDir, val);
+ }
+ else
+ return defaultValue;
+ }
+
+ public bool TryGetPathValue (string name, out FilePath value, FilePath defaultValue = default(FilePath), bool relativeToSolution = true, FilePath relativeToPath = default(FilePath))
+ {
+ string val;
+ if (TryGetValue (name, out val)) {
+ string baseDir = null;
+
+ if (relativeToPath != null) {
+ baseDir = relativeToPath;
+ } else if (relativeToSolution && ParentFile != null && ParentFile.FileName != null) {
+ baseDir = ParentFile.FileName.ParentDirectory;
+ }
+ string path;
+ var res = MSBuildProjectService.FromMSBuildPath (baseDir, val, out path);
+ value = path;
+ return res;
+ }
+ else {
+ value = defaultValue;
+ return value != default(FilePath);
+ }
+ }
+
+ public T GetValue<T> (string name)
+ {
+ return (T) GetValue (name, typeof(T), default(T));
+ }
+
+ public T GetValue<T> (string name, T defaultValue)
+ {
+ return (T) GetValue (name, typeof(T), defaultValue);
+ }
+
+ public object GetValue (string name, Type t, object defaultValue)
+ {
+ string val;
+ if (TryGetValue (name, out val)) {
+ if (t == typeof(bool))
+ return (object) val.Equals ("true", StringComparison.InvariantCultureIgnoreCase);
+ if (t.IsEnum)
+ return Enum.Parse (t, val, true);
+ if (t.IsGenericType && t.GetGenericTypeDefinition () == typeof(Nullable<>)) {
+ var at = t.GetGenericArguments () [0];
+ if (string.IsNullOrEmpty (val))
+ return null;
+ return Convert.ChangeType (val, at, CultureInfo.InvariantCulture);
+
+ }
+ return Convert.ChangeType (val, t, CultureInfo.InvariantCulture);
+ }
+ else
+ return defaultValue;
+ }
+
+ public void SetValue (string name, string value, string defaultValue = null, bool preserveExistingCase = false)
+ {
+ if (value == null && defaultValue == "")
+ value = "";
+ if (value == defaultValue) {
+ // if the value is default, only remove the property if it was not already the default
+ // to avoid unnecessary project file churn
+ string res;
+ if (TryGetValue (name, out res) && !string.Equals (defaultValue ?? "", res, preserveExistingCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
+ Remove (name);
+ return;
+ }
+ string currentValue;
+ if (preserveExistingCase && TryGetValue (name, out currentValue) && string.Equals (value, currentValue, StringComparison.OrdinalIgnoreCase))
+ return;
+ values [name] = value;
+ }
+
+ public void SetValue (string name, FilePath value, FilePath defaultValue = default(FilePath), bool relativeToSolution = true, FilePath relativeToPath = default(FilePath))
+ {
+ var isDefault = value.CanonicalPath == defaultValue.CanonicalPath;
+ if (isDefault) {
+ // if the value is default, only remove the property if it was not already the default
+ // to avoid unnecessary project file churn
+ if (ContainsKey (name) && (defaultValue == null || defaultValue != GetPathValue (name, relativeToSolution:relativeToSolution, relativeToPath:relativeToPath)))
+ Remove (name);
+ return;
+ }
+ string baseDir = null;
+ if (relativeToPath != null) {
+ baseDir = relativeToPath;
+ } else if (relativeToSolution && ParentFile != null && ParentFile.FileName != null) {
+ baseDir = ParentFile.FileName.ParentDirectory;
+ }
+ values [name] = MSBuildProjectService.ToMSBuildPath (baseDir, value, false);
+ }
+
+ public void SetValue (string name, object value, object defaultValue = null)
+ {
+ var isDefault = object.Equals (value, defaultValue);
+ if (isDefault) {
+ // if the value is default, only remove the property if it was not already the default
+ // to avoid unnecessary project file churn
+ if (ContainsKey (name) && (defaultValue == null || !object.Equals (defaultValue, GetValue (name, defaultValue.GetType (), null))))
+ Remove (name);
+ return;
+ }
+
+ if (value is bool)
+ values [name] = (bool)value ? "TRUE" : "FALSE";
+ else
+ values [name] = Convert.ToString (value, CultureInfo.InvariantCulture);
}
- public void Add (string key, string value)
+ void IDictionary<string,string>.Add (string key, string value)
{
SetValue (key, value);
}
+ /// <summary>
+ /// Determines whether the current instance contains an entry with the specified key
+ /// </summary>
+ /// <returns><c>true</c>, if key was containsed, <c>false</c> otherwise.</returns>
+ /// <param name="key">Key.</param>
public bool ContainsKey (string key)
{
return values.Contains (key);
}
+ /// <summary>
+ /// Removes a property
+ /// </summary>
+ /// <param name="key">Property name</param>
public bool Remove (string key)
{
var wasThere = values.Contains (key);
@@ -493,12 +701,22 @@ namespace MonoDevelop.Projects.Formats.MSBuild
return wasThere;
}
+ /// <summary>
+ /// Tries to get the value of a property
+ /// </summary>
+ /// <returns><c>true</c>, if the property exists, <c>false</c> otherwise.</returns>
+ /// <param name="key">Property name</param>
+ /// <param name="value">Value.</param>
public bool TryGetValue (string key, out string value)
{
value = (string) values [key];
return value != null;
}
+ /// <summary>
+ /// Gets or sets the value of a property
+ /// </summary>
+ /// <param name="index">Index.</param>
public string this [string index] {
get {
return (string) values [index];
@@ -518,7 +736,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
get { return values.Keys.Cast<string> ().ToList (); }
}
- public void Add (KeyValuePair<string, string> item)
+ void ICollection<KeyValuePair<string, string>>.Add (KeyValuePair<string, string> item)
{
SetValue (item.Key, item.Value);
}
@@ -534,7 +752,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
values.Remove (k);
}
- public bool Contains (KeyValuePair<string, string> item)
+ bool ICollection<KeyValuePair<string, string>>.Contains (KeyValuePair<string, string> item)
{
var val = GetValue (item.Key);
return val == item.Value;
@@ -546,9 +764,9 @@ namespace MonoDevelop.Projects.Formats.MSBuild
array [arrayIndex++] = new KeyValuePair<string, string> ((string)de.Key, (string)de.Value);
}
- public bool Remove (KeyValuePair<string, string> item)
+ bool ICollection<KeyValuePair<string, string>>.Remove (KeyValuePair<string, string> item)
{
- if (Contains (item)) {
+ if (((ICollection<KeyValuePair<string, string>>)this).Contains (item)) {
Remove (item.Key);
return true;
} else
@@ -561,7 +779,14 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
}
- public bool IsReadOnly {
+ internal void SetLines (IEnumerable<KeyValuePair<string,string>> lines)
+ {
+ values.Clear ();
+ foreach (var line in lines)
+ values [line.Key] = line.Value;
+ }
+
+ bool ICollection<KeyValuePair<string, string>>.IsReadOnly {
get {
return false;
}
@@ -582,6 +807,19 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public class SlnProjectCollection: Collection<SlnProject>
{
+ SlnFile parentFile;
+
+ internal SlnFile ParentFile {
+ get {
+ return parentFile;
+ }
+ set {
+ parentFile = value;
+ foreach (var it in this)
+ it.ParentFile = parentFile;
+ }
+ }
+
public SlnProject GetProject (string id)
{
return this.FirstOrDefault (s => s.Id == id);
@@ -596,16 +834,60 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
return p;
}
+
+ protected override void InsertItem (int index, SlnProject item)
+ {
+ base.InsertItem (index, item);
+ item.ParentFile = ParentFile;
+ }
+
+ protected override void SetItem (int index, SlnProject item)
+ {
+ base.SetItem (index, item);
+ item.ParentFile = ParentFile;
+ }
+
+ protected override void RemoveItem (int index)
+ {
+ var it = this [index];
+ it.ParentFile = null;
+ base.RemoveItem (index);
+ }
+
+ protected override void ClearItems ()
+ {
+ foreach (var it in this)
+ it.ParentFile = null;
+ base.ClearItems ();
+ }
}
public class SlnSectionCollection: Collection<SlnSection>
{
+ SlnFile parentFile;
+
+ internal SlnFile ParentFile {
+ get {
+ return parentFile;
+ }
+ set {
+ parentFile = value;
+ foreach (var it in this)
+ it.ParentFile = parentFile;
+ }
+ }
+
public SlnSection GetSection (string id)
{
return this.FirstOrDefault (s => s.Id == id);
}
- public SlnSection GetOrCreateSection (string id, string sectionType)
+ public SlnSection GetSection (string id, SlnSectionType sectionType)
+ {
+ return this.FirstOrDefault (s => s.Id == id && s.SectionType == sectionType);
+ }
+
+ public SlnSection GetOrCreateSection (string id, SlnSectionType sectionType)
{
if (id == null)
throw new ArgumentNullException ("id");
@@ -626,10 +908,43 @@ namespace MonoDevelop.Projects.Formats.MSBuild
if (s != null)
Remove (s);
}
+
+ protected override void InsertItem (int index, SlnSection item)
+ {
+ base.InsertItem (index, item);
+ item.ParentFile = ParentFile;
+ }
+
+ protected override void SetItem (int index, SlnSection item)
+ {
+ base.SetItem (index, item);
+ item.ParentFile = ParentFile;
+ }
+
+ protected override void RemoveItem (int index)
+ {
+ var it = this [index];
+ it.ParentFile = null;
+ base.RemoveItem (index);
+ }
+
+ protected override void ClearItems ()
+ {
+ foreach (var it in this)
+ it.ParentFile = null;
+ base.ClearItems ();
+ }
}
public class SlnPropertySetCollection: Collection<SlnPropertySet>
{
+ SlnSection parentSection;
+
+ internal SlnPropertySetCollection (SlnSection parentSection)
+ {
+ this.parentSection = parentSection;
+ }
+
public SlnPropertySet GetPropertySet (string id, bool ignoreCase = false)
{
var sc = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
@@ -645,6 +960,32 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
return ps;
}
+
+ protected override void InsertItem (int index, SlnPropertySet item)
+ {
+ base.InsertItem (index, item);
+ item.ParentSection = parentSection;
+ }
+
+ protected override void SetItem (int index, SlnPropertySet item)
+ {
+ base.SetItem (index, item);
+ item.ParentSection = parentSection;
+ }
+
+ protected override void RemoveItem (int index)
+ {
+ var it = this [index];
+ it.ParentSection = null;
+ base.RemoveItem (index);
+ }
+
+ protected override void ClearItems ()
+ {
+ foreach (var it in this)
+ it.ParentSection = null;
+ base.ClearItems ();
+ }
}
class InvalidSolutionFormatException: Exception
@@ -655,7 +996,14 @@ namespace MonoDevelop.Projects.Formats.MSBuild
public InvalidSolutionFormatException (int line, string msg): base ("Invalid format in line " + line + ": " + msg)
{
+
}
}
+
+ public enum SlnSectionType
+ {
+ PreProcess,
+ PostProcess
+ }
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFileFormat.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFileFormat.cs
index 6249e8c2c6..8188607c13 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFileFormat.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MSBuild/SlnFileFormat.cs
@@ -106,8 +106,6 @@ namespace MonoDevelop.Projects.Formats.MSBuild
void WriteFileInternal (string file, string sourceFile, Solution solution, bool saveProjects, ProgressMonitor monitor)
{
- string baseDir = Path.GetDirectoryName (sourceFile);
-
if (saveProjects) {
var items = solution.GetAllSolutionItems ().ToArray ();
monitor.BeginTask (items.Length + 1);
@@ -126,7 +124,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
}
SlnFile sln = new SlnFile ();
- sln.BaseDirectory = baseDir;
+ sln.FileName = file;
if (File.Exists (sourceFile)) {
try {
sln.Read (sourceFile);
@@ -196,7 +194,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
ICollection<SolutionFolder> folders = solution.RootFolder.GetAllItems<SolutionFolder> ().ToList ();
if (folders.Count > 1) {
// If folders ==1, that's the root folder
- var sec = sln.Sections.GetOrCreateSection ("NestedProjects", "preSolution");
+ var sec = sln.Sections.GetOrCreateSection ("NestedProjects", SlnSectionType.PreProcess);
foreach (SolutionFolder folder in folders) {
if (folder.IsRoot)
continue;
@@ -211,7 +209,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
// Write custom properties for configurations
foreach (SolutionConfiguration conf in solution.Configurations) {
string secId = "MonoDevelopProperties." + conf.Id;
- var sec = sln.Sections.GetOrCreateSection (secId, "preSolution");
+ var sec = sln.Sections.GetOrCreateSection (secId, SlnSectionType.PreProcess);
solution.WriteConfigurationData (monitor, sec.Properties, conf);
if (sec.IsEmpty)
sln.Sections.Remove (sec);
@@ -233,12 +231,12 @@ namespace MonoDevelop.Projects.Formats.MSBuild
proj.Name = item.Name;
proj.FilePath = FileService.NormalizeRelativePath (FileService.AbsoluteToRelativePath (sln.BaseDirectory, item.FileName)).Replace ('/', '\\');
- var sec = proj.Sections.GetOrCreateSection ("MonoDevelopProperties", "preProject");
+ var sec = proj.Sections.GetOrCreateSection ("MonoDevelopProperties", SlnSectionType.PreProcess);
sec.SkipIfEmpty = true;
folder.ParentSolution.WriteSolutionFolderItemData (monitor, sec.Properties, ce);
if (item.ItemDependencies.Count > 0) {
- sec = proj.Sections.GetOrCreateSection ("ProjectDependencies", "postProject");
+ sec = proj.Sections.GetOrCreateSection ("ProjectDependencies", SlnSectionType.PostProcess);
sec.Properties.ClearExcept (unknownProjects);
foreach (var dep in item.ItemDependencies)
sec.Properties.SetValue (dep.ItemId, dep.ItemId);
@@ -254,7 +252,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
WriteFolderFiles (proj, (SolutionFolder) ce);
//Write custom properties
- var sec = proj.Sections.GetOrCreateSection ("MonoDevelopProperties", "preProject");
+ var sec = proj.Sections.GetOrCreateSection ("MonoDevelopProperties", SlnSectionType.PreProcess);
sec.SkipIfEmpty = true;
folder.ParentSolution.WriteSolutionFolderItemData (monitor, sec.Properties, ce);
}
@@ -267,7 +265,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
void WriteFolderFiles (SlnProject proj, SolutionFolder folder)
{
if (folder.Files.Count > 0) {
- var sec = proj.Sections.GetOrCreateSection ("SolutionItems", "preProject");
+ var sec = proj.Sections.GetOrCreateSection ("SolutionItems", SlnSectionType.PreProcess);
sec.Properties.Clear ();
foreach (FilePath f in folder.Files) {
string relFile = MSBuildProjectService.ToMSBuildPathRelative (folder.ParentSolution.ItemDirectory, f);
@@ -349,151 +347,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
sln.ReadSolutionFolderItemData (monitor, sec.Properties, item);
}
-
- void WriteDataItem (SlnPropertySet pset, DataItem item)
- {
- pset.Clear ();
- int id = 0;
- foreach (DataNode val in item.ItemData)
- WriteDataNode (pset, "", val, ref id);
- }
-
- void WriteDataNode (SlnPropertySet pset, string prefix, DataNode node, ref int id)
- {
- string name = node.Name;
- string newPrefix = prefix.Length > 0 ? prefix + "." + name: name;
-
- if (node is DataValue) {
- DataValue val = (DataValue) node;
- string value = EncodeString (val.Value);
- pset.SetValue (newPrefix, value);
- }
- else {
- DataItem it = (DataItem) node;
- pset.SetValue (newPrefix, "$" + id);
- newPrefix = "$" + id;
- id ++;
- foreach (DataNode cn in it.ItemData)
- WriteDataNode (pset, newPrefix, cn, ref id);
- }
- }
-
- string EncodeString (string val)
- {
- if (val.Length == 0)
- return val;
-
- int i = val.IndexOfAny (new char[] {'\n','\r','\t'});
- if (i != -1 || val [0] == '@') {
- StringBuilder sb = new StringBuilder ();
- if (i != -1) {
- int fi = val.IndexOf ('\\');
- if (fi != -1 && fi < i) i = fi;
- sb.Append (val.Substring (0,i));
- } else
- i = 0;
- for (int n = i; n < val.Length; n++) {
- char c = val [n];
- if (c == '\r')
- sb.Append (@"\r");
- else if (c == '\n')
- sb.Append (@"\n");
- else if (c == '\t')
- sb.Append (@"\t");
- else if (c == '\\')
- sb.Append (@"\\");
- else
- sb.Append (c);
- }
- val = "@" + sb.ToString ();
- }
- char fc = val [0];
- char lc = val [val.Length - 1];
- if (fc == ' ' || fc == '"' || fc == '$' || lc == ' ')
- val = "\"" + val + "\"";
- return val;
- }
-
- string DecodeString (string val)
- {
- val = val.Trim (' ', '\t');
- if (val.Length == 0)
- return val;
- if (val [0] == '\"')
- val = val.Substring (1, val.Length - 2);
- if (val [0] == '@') {
- StringBuilder sb = new StringBuilder (val.Length);
- for (int n = 1; n < val.Length; n++) {
- char c = val [n];
- if (c == '\\') {
- c = val [++n];
- if (c == 'r') c = '\r';
- else if (c == 'n') c = '\n';
- else if (c == 't') c = '\t';
- }
- sb.Append (c);
- }
- return sb.ToString ();
- }
- else
- return val;
- }
-
- DataItem ReadDataItem (SlnSection sec)
- {
- DataItem it = new DataItem ();
-
- var lines = sec.Properties.ToArray ();
- int lineNum = 0;
- int lastLine = lines.Length - 1;
- while (lineNum <= lastLine) {
- if (!ReadDataNode (it, lines, lastLine, "", ref lineNum))
- lineNum++;
- }
- return it;
- }
-
- bool ReadDataNode (DataItem item, KeyValuePair<string,string>[] lines, int lastLine, string prefix, ref int lineNum)
- {
- var s = lines [lineNum];
-
- // Check if the line belongs to the current item
- if (prefix.Length > 0) {
- if (!s.Key.StartsWith (prefix + "."))
- return false;
- } else {
- if (s.Key.StartsWith ("$"))
- return false;
- }
-
- string name = s.Key;
- if (name.Length == 0) {
- lineNum++;
- return true;
- }
-
- string value = s.Value;
- if (value.StartsWith ("$")) {
- // New item
- DataItem child = new DataItem ();
- child.Name = name;
- lineNum++;
- while (lineNum <= lastLine) {
- if (!ReadDataNode (child, lines, lastLine, value, ref lineNum))
- break;
- }
- item.ItemData.Add (child);
- }
- else {
- value = DecodeString (value);
- DataValue val = new DataValue (name, value);
- item.ItemData.Add (val);
- lineNum++;
- }
- return true;
- }
-
string ToSlnConfigurationId (ItemConfiguration configuration)
{
if (configuration.Platform.Length == 0)
@@ -897,7 +751,7 @@ namespace MonoDevelop.Projects.Formats.MSBuild
void LoadNestedProjects (SlnSection sec, IDictionary<string, SolutionFolderItem> entries, ProgressMonitor monitor)
{
- if (sec == null || String.Compare (sec.SectionType, "preSolution", StringComparison.OrdinalIgnoreCase) != 0)
+ if (sec == null || sec.SectionType != SlnSectionType.PreProcess)
return;
foreach (var kvp in sec.Properties) {
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
index 6f5d3f6484..32cbc108c0 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
@@ -923,6 +923,7 @@ namespace MonoDevelop.Projects
internal void ReadSolution (ProgressMonitor monitor, SlnFile file)
{
SolutionExtension.OnReadSolution (monitor, file);
+ file.CustomMonoDevelopProperties.ReadObjectProperties (this);
}
/*protected virtual*/ void OnReadSolution (ProgressMonitor monitor, SlnFile file)
@@ -960,6 +961,7 @@ namespace MonoDevelop.Projects
/*protected virtual*/ void OnWriteSolution (ProgressMonitor monitor, SlnFile file)
{
FileFormat.SlnFileFormat.WriteFileInternal (file, this, monitor);
+ file.CustomMonoDevelopProperties.WriteObjectProperties (this);
}
internal void WriteConfigurationData (ProgressMonitor monitor, SlnPropertySet properties, SolutionConfiguration configuration)
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionDataSectionAttribute.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionDataSectionAttribute.cs
new file mode 100644
index 0000000000..a772ae4f2c
--- /dev/null
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionDataSectionAttribute.cs
@@ -0,0 +1,44 @@
+//
+// SolutionDataSectionAttribute.cs
+//
+// Author:
+// Lluis Sanchez Gual <lluis@xamarin.com>
+//
+// Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.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 MonoDevelop.Projects.Formats.MSBuild;
+
+namespace MonoDevelop.Projects
+{
+ public class SolutionDataSectionAttribute: Attribute
+ {
+ public SolutionDataSectionAttribute (string sectionName, SlnSectionType processOrder = SlnSectionType.PostProcess)
+ {
+ SectionName = sectionName;
+ ProcessOrder = processOrder;
+ }
+
+ public string SectionName { get; set; }
+
+ public SlnSectionType ProcessOrder { get; set; }
+ }
+}
+
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionExtension.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionExtension.cs
index 5126eb308d..545f2f8651 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionExtension.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionExtension.cs
@@ -88,7 +88,20 @@ namespace MonoDevelop.Projects
internal protected virtual void OnReadSolution (ProgressMonitor monitor, SlnFile file)
{
+ var secAttribute = (SolutionDataSectionAttribute) Attribute.GetCustomAttribute (GetType(), typeof(SolutionDataSectionAttribute));
+ if (secAttribute != null && secAttribute.ProcessOrder == SlnSectionType.PreProcess) {
+ var sec = file.Sections.GetSection (secAttribute.SectionName, SlnSectionType.PreProcess);
+ if (sec != null)
+ sec.Properties.ReadObjectProperties (this);
+ }
+
next.OnReadSolution (monitor, file);
+
+ if (secAttribute != null && secAttribute.ProcessOrder == SlnSectionType.PostProcess) {
+ var sec = file.Sections.GetSection (secAttribute.SectionName, SlnSectionType.PostProcess);
+ if (sec != null)
+ sec.Properties.ReadObjectProperties (this);
+ }
}
internal protected virtual void OnReadSolutionFolderItemData (ProgressMonitor monitor, SlnPropertySet properties, SolutionFolderItem item)
@@ -103,7 +116,20 @@ namespace MonoDevelop.Projects
internal protected virtual void OnWriteSolution (ProgressMonitor monitor, SlnFile file)
{
+ var secAttribute = (SolutionDataSectionAttribute) Attribute.GetCustomAttribute (GetType(), typeof(SolutionDataSectionAttribute));
+ if (secAttribute != null && secAttribute.ProcessOrder == SlnSectionType.PreProcess) {
+ var sec = file.Sections.GetOrCreateSection (secAttribute.SectionName, SlnSectionType.PreProcess);
+ sec.SkipIfEmpty = true;
+ sec.Properties.WriteObjectProperties (this);
+ }
+
next.OnWriteSolution (monitor, file);
+
+ if (secAttribute != null && secAttribute.ProcessOrder == SlnSectionType.PostProcess) {
+ var sec = file.Sections.GetOrCreateSection (secAttribute.SectionName, SlnSectionType.PostProcess);
+ sec.SkipIfEmpty = true;
+ sec.Properties.WriteObjectProperties (this);
+ }
}
internal protected virtual void OnWriteSolutionFolderItemData (ProgressMonitor monitor, SlnPropertySet properties, SolutionFolderItem item)
diff --git a/main/tests/UnitTests/MonoDevelop.Projects/PolicyTests.cs b/main/tests/UnitTests/MonoDevelop.Projects/PolicyTests.cs
new file mode 100644
index 0000000000..3a5e23abf4
--- /dev/null
+++ b/main/tests/UnitTests/MonoDevelop.Projects/PolicyTests.cs
@@ -0,0 +1,37 @@
+//
+// PolicyTests.cs
+//
+// Author:
+// Lluis Sanchez Gual <lluis@xamarin.com>
+//
+// Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.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 NUnit.Framework;
+using UnitTests;
+
+namespace MonoDevelop.Projects
+{
+ [TestFixture]
+ public class PolicyTests: TestBase
+ {
+ }
+}
+
diff --git a/main/tests/UnitTests/MonoDevelop.Projects/SolutionTests.cs b/main/tests/UnitTests/MonoDevelop.Projects/SolutionTests.cs
index 3f50885211..4b288fe6f4 100644
--- a/main/tests/UnitTests/MonoDevelop.Projects/SolutionTests.cs
+++ b/main/tests/UnitTests/MonoDevelop.Projects/SolutionTests.cs
@@ -35,6 +35,8 @@ using MonoDevelop.Core;
using MonoDevelop.Projects.Formats.MSBuild;
using MonoDevelop.Core.ProgressMonitoring;
using System.Threading.Tasks;
+using MonoDevelop.Core.Serialization;
+using MonoDevelop.Projects.Extensions;
namespace MonoDevelop.Projects
{
@@ -894,6 +896,103 @@ namespace MonoDevelop.Projects
Assert.IsTrue (p.ItemDependencies.Contains (lib2Reloaded));
Assert.AreEqual (1, p.ItemDependencies.Count);
}
+
+ [Test]
+ public async Task WriteCustomData ()
+ {
+ var en = new CustomSolutionItemNode<TestSolutionExtension> ();
+ WorkspaceObject.RegisterCustomExtension (en);
+ try {
+ string solFile = Util.GetSampleProject ("solution-custom-data", "custom-data.sln");
+
+ var sol = new Solution ();
+ var ext = sol.GetService<TestSolutionExtension> ();
+ Assert.NotNull (ext);
+ ext.Prop1 = "one";
+ ext.Prop2 = "two";
+ ext.Extra = new ComplexSolutionData {
+ Prop3 = "three",
+ Prop4 = "four"
+ };
+ var savedFile = solFile + ".saved.sln";
+ await sol.SaveAsync (savedFile, Util.GetMonitor ());
+ Assert.AreEqual (File.ReadAllText (solFile), File.ReadAllText (savedFile));
+ } finally {
+ WorkspaceObject.UnregisterCustomExtension (en);
+ }
+ }
+
+ [Test]
+ public async Task ReadCustomData ()
+ {
+ var en = new CustomSolutionItemNode<TestSolutionExtension> ();
+ WorkspaceObject.RegisterCustomExtension (en);
+ try {
+ string solFile = Util.GetSampleProject ("solution-custom-data", "custom-data.sln");
+ var sol = (Solution) await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solFile);
+
+ var ext = sol.GetService<TestSolutionExtension> ();
+ Assert.NotNull (ext);
+ Assert.AreEqual ("one", ext.Prop1);
+ Assert.AreEqual ("two", ext.Prop2);
+ Assert.NotNull (ext.Extra);
+ Assert.AreEqual ("three", ext.Extra.Prop3);
+ Assert.AreEqual ("four", ext.Extra.Prop4);
+ } finally {
+ WorkspaceObject.UnregisterCustomExtension (en);
+ }
+ }
+
+ [Test]
+ public async Task KeepUnknownCustomData ()
+ {
+ var en = new CustomSolutionItemNode<TestSolutionExtension> ();
+ WorkspaceObject.RegisterCustomExtension (en);
+ try {
+ FilePath solFile = Util.GetSampleProject ("solution-custom-data", "custom-data-keep-unknown.sln");
+ var sol = (Solution) await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solFile);
+
+ var ext = sol.GetService<TestSolutionExtension> ();
+ ext.Prop1 = "one-mod";
+ ext.Prop2 = "";
+ ext.Extra.Prop3 = "three-mod";
+ ext.Extra.Prop4 = "";
+
+ var refFile = solFile.ParentDirectory.Combine ("custom-data-keep-unknown.sln.saved");
+
+ await sol.SaveAsync (Util.GetMonitor ());
+
+ Assert.AreEqual (File.ReadAllText (refFile), File.ReadAllText (sol.FileName));
+
+ } finally {
+ WorkspaceObject.UnregisterCustomExtension (en);
+ }
+ }
+
+ [Test]
+ public async Task RemoveCustomData ()
+ {
+ var en = new CustomSolutionItemNode<TestSolutionExtension> ();
+ WorkspaceObject.RegisterCustomExtension (en);
+ try {
+ FilePath solFile = Util.GetSampleProject ("solution-custom-data", "custom-data.sln");
+ var sol = (Solution) await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solFile);
+
+ var ext = sol.GetService<TestSolutionExtension> ();
+ ext.Prop1 = "xx";
+ ext.Prop2 = "";
+ ext.Extra = null;
+
+ var refFile = solFile.ParentDirectory.Combine ("no-custom-data.sln");
+
+ await sol.SaveAsync (Util.GetMonitor ());
+
+ Assert.AreEqual (File.ReadAllText (refFile), File.ReadAllText (sol.FileName));
+
+ } finally {
+ WorkspaceObject.UnregisterCustomExtension (en);
+ }
+ }
}
class SomeItem: SolutionItem
@@ -931,4 +1030,34 @@ namespace MonoDevelop.Projects
UnboundEvents++;
}
}
+
+ class CustomSolutionItemNode<T>: ProjectModelExtensionNode where T:new()
+ {
+ public override object CreateInstance ()
+ {
+ return new T ();
+ }
+ }
+
+ [SolutionDataSection ("TestData")]
+ class TestSolutionExtension: SolutionExtension
+ {
+ [ItemProperty ("prop1", DefaultValue = "xx")]
+ public string Prop1 { get; set; }
+
+ [ItemProperty ("prop2", DefaultValue = "")]
+ public string Prop2 { get; set; }
+
+ [ItemProperty ("extra")]
+ public ComplexSolutionData Extra { get; set; }
+ }
+
+ class ComplexSolutionData
+ {
+ [ItemProperty ("prop3")]
+ public string Prop3 { get; set; }
+
+ [ItemProperty ("prop4", DefaultValue = "")]
+ public string Prop4 { get; set; }
+ }
}
diff --git a/main/tests/UnitTests/UnitTests.csproj b/main/tests/UnitTests/UnitTests.csproj
index 194233df60..b9b03e3275 100644
--- a/main/tests/UnitTests/UnitTests.csproj
+++ b/main/tests/UnitTests/UnitTests.csproj
@@ -270,6 +270,7 @@
<Compile Include="MonoDevelop.Projects\StringTagTests.cs" />
<Compile Include="MonoDevelop.Ide.Templates\FileTemplateParserTests.cs" />
<Compile Include="MonoDevelop.Ide.Templates\ProjectCreateInformationTests.cs" />
+ <Compile Include="MonoDevelop.Projects\PolicyTests.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\md.targets" />