[xbuild]: Allow expressions inside a dynamic <ItemGroup>'s metadata.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
index ebf86c4132033807b092a4b8061d52144cb5350a..fc22d1f56310b44b63bc5a3cf9858ef33f08be98 100644 (file)
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-#if NET_2_0
-
 using System;
+using System.Linq;
 using System.Collections;
+using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.IO;
 using System.Text;
@@ -40,25 +40,33 @@ using Mono.XBuild.Utilities;
 namespace Microsoft.Build.BuildEngine {
        public class BuildItem {
 
+               List<BuildItem> child_items;
                XmlElement      itemElement;
                string          finalItemSpec;
                bool            isImported;
                string          itemInclude;
                string          name;
-               BuildItemGroup  parentItemGroup;
+               BuildItemGroup  parent_item_group;
+               BuildItem       parent_item;
                //string                recursiveDir;
                IDictionary     evaluatedMetadata;
                IDictionary     unevaluatedMetadata;
+               bool            isDynamic;
+               bool            keepDuplicates = true;
+               string          removeMetadata, keepMetadata;
 
-               private BuildItem ()
+               BuildItem ()
                {
                }
                
                public BuildItem (string itemName, ITaskItem taskItem)
                {
+                       if (taskItem == null)
+                               throw new ArgumentNullException ("taskItem");
+
                        this.name = itemName;
                        this.finalItemSpec = taskItem.ItemSpec;
-                       this.itemInclude = Utilities.Escape (taskItem.ItemSpec);
+                       this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
                        this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
                        this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
                }
@@ -70,34 +78,83 @@ namespace Microsoft.Build.BuildEngine {
                        if (itemInclude == String.Empty)
                                throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
 
-                       this.name = itemName;
-                       this.finalItemSpec = itemInclude;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       name = itemName;
+                       finalItemSpec = itemInclude;
+                       this.itemInclude = itemInclude;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
                }
-               
+
                internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
                {
-                       this.isImported = false;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
-                       this.parentItemGroup = parentItemGroup;
-                       BindToXml (itemElement);
+                       child_items = new List<BuildItem> ();
+                       isImported = parentItemGroup.IsImported;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
+                       this.parent_item_group = parentItemGroup;
+                       
+                       this.itemElement = itemElement;
+                       isDynamic = parentItemGroup.IsDynamic;
+
+                       if (IsDynamic) {
+                               if (!string.IsNullOrEmpty (Remove)) {
+                                       if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
+                                               throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
+                                       if (itemElement.HasChildNodes)
+                                               throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
+                               }
+                               if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
+                       } else {
+                               if (string.IsNullOrEmpty (Include))
+                                       throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
+                               if (!string.IsNullOrEmpty (Remove))
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
+                       }
+
+                       foreach (XmlAttribute attr in itemElement.Attributes) {
+                               if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
+                                       continue;
+                               if (!IsDynamic)
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
+
+                               switch (attr.Name) {
+                               case "Remove":
+                                       Remove = attr.Value;
+                                       break;
+                               case "KeepDuplicates":
+                                       KeepDuplicates = bool.Parse (attr.Value);
+                                       break;
+                               case "RemoveMetadata":
+                                       removeMetadata = attr.Value;
+                                       break;
+                               case "KeepMetadata":
+                                       keepMetadata = attr.Value;
+                                       break;
+                               default:
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
+                               }
+                       }
                }
-               
-               private BuildItem (BuildItem parent)
+
+               BuildItem (BuildItem parent)
                {
-                       this.isImported = parent.isImported;
-                       this.name = parent.name;
-                       this.parentItemGroup = parent.parentItemGroup;
-                       this.unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
-                       this.evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
+                       isImported = parent.isImported;
+                       name = parent.Name;
+                       parent_item = parent;
+                       parent_item.child_items.Add (this);
+                       parent_item_group = parent.parent_item_group;
+                       unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
+                       evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
                }
                
                public void CopyCustomMetadataTo (BuildItem destinationItem)
                {
+                       if (destinationItem == null)
+                               throw new ArgumentNullException ("destinationItem");
+
                        foreach (DictionaryEntry de in unevaluatedMetadata)
-                               destinationItem.SetMetadata ((string) de.Key, (string) de.Value);
+                               destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
                }
                
                [MonoTODO]
@@ -108,6 +165,13 @@ namespace Microsoft.Build.BuildEngine {
 
                public string GetEvaluatedMetadata (string metadataName)
                {
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
+                               string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
+                               return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
+                                               ? MSBuildUtils.Escape (metadata)
+                                               : metadata;
+                       }
+
                        if (evaluatedMetadata.Contains (metadataName))
                                return (string) evaluatedMetadata [metadataName];
                        else
@@ -116,9 +180,12 @@ namespace Microsoft.Build.BuildEngine {
 
                public string GetMetadata (string metadataName)
                {
-                       if (ReservedNameUtils.IsReservedMetadataName (metadataName))
-                               return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
-                       else if (unevaluatedMetadata.Contains (metadataName))
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
+                               string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
+                               return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
+                                       ? MSBuildUtils.Escape (metadata)
+                                       : metadata;
+                       } else if (unevaluatedMetadata.Contains (metadataName))
                                return (string) unevaluatedMetadata [metadataName];
                        else
                                return String.Empty;
@@ -126,7 +193,10 @@ namespace Microsoft.Build.BuildEngine {
                
                public bool HasMetadata (string metadataName)
                {
-                       return evaluatedMetadata.Contains (metadataName);
+                       if (ReservedNameUtils.IsReservedMetadataName (metadataName))
+                               return true;
+                       else
+                               return evaluatedMetadata.Contains (metadataName);
                }
 
                public void RemoveMetadata (string metadataName)
@@ -137,12 +207,19 @@ namespace Microsoft.Build.BuildEngine {
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName))
                                throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
                                        metadataName));
+
+                       if (FromXml) {
+                               if (unevaluatedMetadata.Contains (metadataName)) {
+                                       XmlNode node = itemElement [metadataName];
+                                       itemElement.RemoveChild (node);
+                               }
+                       } else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.RemoveMetadata (metadataName);
+                       } 
                        
-                       if (evaluatedMetadata.Contains (metadataName))
-                               evaluatedMetadata.Remove (metadataName);
-                       
-                       if (unevaluatedMetadata.Contains (metadataName))
-                               unevaluatedMetadata.Remove (metadataName);
+                       DeleteMetadata (metadataName);
                }
 
                public void SetMetadata (string metadataName,
@@ -164,122 +241,336 @@ namespace Microsoft.Build.BuildEngine {
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName))
                                throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
                                        metadataName));
+
+                       if (treatMetadataValueAsLiteral && !HasParentItem)
+                               metadataValue = MSBuildUtils.Escape (metadataValue);
+
+                       if (FromXml) {
+                               XmlElement element = itemElement [metadataName];
+                               if (element == null) {
+                                       element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
+                                       element.InnerText = metadataValue;
+                                       itemElement.AppendChild (element);
+                               } else
+                                       element.InnerText = metadataValue;
+                       } else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
+                       }
+                       if (FromXml || HasParentItem) {
+                               parent_item_group.ParentProject.MarkProjectAsDirty ();
+                               parent_item_group.ParentProject.NeedToReevaluate ();
+                       }
                        
-                       RemoveMetadata (metadataName);
-                       
-                       evaluatedMetadata.Add (metadataName, metadataValue);
-                               
-                       if (treatMetadataValueAsLiteral) {      
-                               unevaluatedMetadata.Add (metadataName, Utilities.Escape (metadataValue));
+                       DeleteMetadata (metadataName);
+                       AddMetadata (metadataName, metadataValue);
+               }
+
+               void AddMetadata (string name, string value)
+               {
+                       var options = IsDynamic ?
+                                     ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
+
+                       if (parent_item_group != null) {
+                               Expression e = new Expression ();
+                               e.Parse (value, options);
+                               evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
+                                               typeof (string), ExpressionOptions.ExpandItemRefs);
                        } else
-                               unevaluatedMetadata.Add (metadataName, metadataValue);
+                               evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
+                               
+                       unevaluatedMetadata [name] = value;
                }
-               
-               private void BindToXml (XmlElement xmlElement)
+
+               void DeleteMetadata (string name)
                {
-                       if (xmlElement == null)
-                               throw new ArgumentNullException ("xmlElement");
+                       if (evaluatedMetadata.Contains (name))
+                               evaluatedMetadata.Remove (name);
                        
-                       this.itemElement = xmlElement;
-                       this.name = xmlElement.Name;
-                       
-                       if (Include == String.Empty)
-                               throw new InvalidProjectFileException ("Item must have Include attribute.");
-                       
-                       foreach (XmlElement xe in xmlElement.ChildNodes)
-                               this.SetMetadata (xe.Name, xe.InnerText);
+                       if (unevaluatedMetadata.Contains (name))
+                               unevaluatedMetadata.Remove (name);
                }
 
-               internal void Evaluate (bool evaluatedTo)
+               internal void Evaluate (Project project, bool evaluatedTo)
                {
+                       // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
+                       if (project == null) {
+                               this.finalItemSpec = MSBuildUtils.Unescape (Include);
+                               return;
+                       }
+
+                       foreach (XmlNode xn in itemElement.ChildNodes) {
+                               XmlElement xe = xn as XmlElement;
+                               if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
+                                       AddMetadata (xe.Name, xe.InnerText);
+                       }
+
+                       if (IsDynamic) {
+                               if (!evaluatedTo)
+                                       return;
+
+                               if (!string.IsNullOrEmpty (Remove)) {
+                                       RemoveItems (project);
+                                       return;
+                               }
+
+                               if (string.IsNullOrEmpty (Include)) {
+                                       UpdateMetadata (project);
+                                       return;
+                               }
+                       }
+                       
                        DirectoryScanner directoryScanner;
                        Expression includeExpr, excludeExpr;
-                       string includes, excludes;
+                       ITaskItem[] includes, excludes;
+
+                       var options = IsDynamic ?
+                               ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
 
                        includeExpr = new Expression ();
-                       includeExpr.Parse (Include);
+                       includeExpr.Parse (Include, options);
                        excludeExpr = new Expression ();
-                       excludeExpr.Parse (Exclude);
+                       excludeExpr.Parse (Exclude, options);
                        
-                       includes = (string) includeExpr.ConvertTo (parentItemGroup.Project, typeof (string));
-                       excludes = (string) excludeExpr.ConvertTo (parentItemGroup.Project, typeof (string));
+                       includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
+                                                               ExpressionOptions.ExpandItemRefs);
+                       excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
+                                                               ExpressionOptions.ExpandItemRefs);
+
+                       this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
+                                                       ExpressionOptions.ExpandItemRefs);
 
-                       this.finalItemSpec = includes;
-                       
                        directoryScanner = new DirectoryScanner ();
                        
                        directoryScanner.Includes = includes;
                        directoryScanner.Excludes = excludes;
-                       if (parentItemGroup.Project.FullFileName != String.Empty) {
-                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (parentItemGroup.Project.FullFileName));
-                       } else {
+
+                       if (project.FullFileName != String.Empty)
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
+                       else
                                directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
+                       
+                       directoryScanner.Scan ();
+                       
+                       foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
+                               AddEvaluatedItem (project, evaluatedTo, matchedItem);
+               }
+
+               bool CheckCondition (Project project)
+               {
+                       if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
+                               return false;
+                       if (parent_item != null && !parent_item.CheckCondition (project))
+                               return false;
+                       return ConditionParser.ParseAndEvaluate (Condition, project);
+               }
+
+               void UpdateMetadata (Project project)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return;
+
+                       foreach (BuildItem item in group) {
+                               if (!item.CheckCondition (project))
+                                       continue;
+                               
+                               foreach (string name in evaluatedMetadata.Keys) {
+                                       item.SetMetadata (name, (string)evaluatedMetadata [name]);
+                               }
+
+                               AddAndRemoveMetadata (project, item);
                        }
+               }
+
+               void AddAndRemoveMetadata (Project project, BuildItem item)
+               {
+                       if (!string.IsNullOrEmpty (removeMetadata)) {
+                               var removeExpr = new Expression ();
+                               removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                               var removeSpec = (string[]) removeExpr.ConvertTo (
+                                       project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
+
+                               foreach (var remove in removeSpec) {
+                                       item.DeleteMetadata (remove);
+                               }
+                       }
+
+                       if (!string.IsNullOrEmpty (keepMetadata)) {
+                               var keepExpr = new Expression ();
+                               keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                               var keepSpec = (string[]) keepExpr.ConvertTo (
+                                       project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
+
+                               var metadataNames = new string [item.evaluatedMetadata.Count];
+                               item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
+
+                               foreach (string name in metadataNames) {
+                                       if (!keepSpec.Contains (name))
+                                               item.DeleteMetadata (name);
+                               }
+                       }
+               }
+
+               void RemoveItems (Project project)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return;
+
+                       var removeExpr = new Expression ();
+                       removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                       var removes = (ITaskItem[]) removeExpr.ConvertTo (
+                               project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
+
+                       var directoryScanner = new DirectoryScanner ();
+                       
+                       directoryScanner.Includes = removes;
+
+                       if (project.FullFileName != String.Empty)
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
+                       else
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
                        
                        directoryScanner.Scan ();
+
+                       foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
+                               group.RemoveItem (matchedItem);
+                       }
+               }
+
+               bool ContainsItem (Project project, ITaskItem taskItem)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return false;
+
+                       var item = group.FindItem (taskItem);
+                       if (item == null)
+                               return false;
+
+                       foreach (string metadataName in evaluatedMetadata.Keys) {
+                               string metadataValue = (string)evaluatedMetadata [metadataName];
+                               if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
+                                       return false;
+                       }
                        
-                       foreach (string matchedFile in directoryScanner.MatchedFilenames) {
-                               AddEvaluatedItem (evaluatedTo, matchedFile);
+                       foreach (string metadataName in item.evaluatedMetadata.Keys) {
+                               string metadataValue = (string)item.evaluatedMetadata [metadataName];
+                               if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
+                                       return false;
                        }
+
+                       return true;
                }
-               
-               private void AddEvaluatedItem (bool evaluatedTo, string itemSpec)
+
+               void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
                {
-                       Project project = this.parentItemGroup.Project;
+                       if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
+                               return;
+
                        BuildItemGroup big;                     
                        BuildItem bi = new BuildItem (this);
-                       bi.finalItemSpec = itemSpec;
+                       bi.finalItemSpec = taskitem.ItemSpec;
+
+                       foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
+                               bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
+                               bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
+                       }
+
+                       project.EvaluatedItemsIgnoringCondition.AddItem (bi);
 
                        if (evaluatedTo) {
                                project.EvaluatedItems.AddItem (bi);
        
-                               if (!project.EvaluatedItemsByName.ContainsKey (bi.name)) {
-                                       big = new BuildItemGroup (null, project, null);
-                                       project.EvaluatedItemsByName.Add (bi.name, big);
+                               if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
+                                       big = new BuildItemGroup (null, project, null, true);
+                                       project.EvaluatedItemsByName.Add (bi.Name, big);
                                } else {
-                                       big = project.EvaluatedItemsByName [bi.name];
+                                       big = project.EvaluatedItemsByName [bi.Name];
                                }
 
                                big.AddItem (bi);
                        }
 
-                       if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.name)) {
-                               big = new BuildItemGroup (null, project, null);
-                               project.EvaluatedItemsByNameIgnoringCondition.Add (bi.name, big);
+                       if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
+                               big = new BuildItemGroup (null, project, null, true);
+                               project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
                        } else {
-                               big = project.EvaluatedItemsByNameIgnoringCondition [bi.name];
+                               big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
                        }
 
                        big.AddItem (bi);
+
+                       if (IsDynamic)
+                               AddAndRemoveMetadata (project, bi);
                }
                
-               internal string ConvertToString (Expression transform)
+               // during item's eval phase, any item refs in this item, have either
+               // already been expanded or are non-existant, so expand can be _false_
+               //
+               // during prop's eval phase, this isn't reached, as it parses expressions
+               // with allowItems=false, so no ItemReferences are created at all
+               //
+               // at other times, item refs have already been expanded, so expand: false
+               internal string ConvertToString (Expression transform, ExpressionOptions options)
                {
-                       return GetItemSpecFromTransform (transform);
+                       return GetItemSpecFromTransform (transform, options);
                }
                
-               internal ITaskItem ConvertToITaskItem (Expression transform)
+               internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
                {
                        TaskItem taskItem;
-                       taskItem = new TaskItem (GetItemSpecFromTransform (transform), evaluatedMetadata);
+                       taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
                        return taskItem;
                }
 
-               private string GetItemSpecFromTransform (Expression transform)
+               internal void Detach ()
+               {
+                       if (FromXml)
+                               itemElement.ParentNode.RemoveChild (itemElement);
+                       else if (HasParentItem) {
+                               if (parent_item.child_items.Count > 1)
+                                       SplitParentItem ();
+                               parent_item.Detach ();
+                       }
+               }
+
+               string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
                {
                        StringBuilder sb;
                
-                       if (transform == null)
-                               return finalItemSpec;
-                       else {
+                       if (transform == null) {
+                               if (options == ExpressionOptions.ExpandItemRefs) {
+                                       // With usual code paths, this will never execute,
+                                       // but letting this be here, incase BI.ConvertTo*
+                                       // is called directly
+                                       Expression expr = new Expression ();
+                                       expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                                       return (string) expr.ConvertTo (parent_item_group.ParentProject,
+                                                       typeof (string), ExpressionOptions.ExpandItemRefs);
+                               } else {
+                                       return finalItemSpec;
+                               }
+                       } else {
+                               // Transform, _DONT_ expand itemrefs
                                sb = new StringBuilder ();
                                foreach (object o in transform.Collection) {
                                        if (o is string) {
                                                sb.Append ((string)o);
                                        } else if (o is PropertyReference) {
-                                               sb.Append (((PropertyReference)o).ConvertToString (parentItemGroup.Project));
+                                               sb.Append (((PropertyReference)o).ConvertToString (
+                                                                       parent_item_group.ParentProject,
+                                                                       ExpressionOptions.DoNotExpandItemRefs));
                                        } else if (o is ItemReference) {
-                                               sb.Append (((ItemReference)o).ConvertToString (parentItemGroup.Project));
+                                               sb.Append (((ItemReference)o).ConvertToString (
+                                                                       parent_item_group.ParentProject,
+                                                                       ExpressionOptions.DoNotExpandItemRefs));
                                        } else if (o is MetadataReference) {
                                                sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
                                        }
@@ -288,6 +579,40 @@ namespace Microsoft.Build.BuildEngine {
                        }
                }
 
+               void SplitParentItem ()
+               {
+                       BuildItem parent = parent_item;
+                       List <BuildItem> list = new List <BuildItem> ();
+                       XmlElement insertAfter = parent.itemElement;
+                       foreach (BuildItem bi in parent.child_items) {
+                               BuildItem added = InsertElementAfter (parent, bi, insertAfter);
+                               insertAfter = added.itemElement;
+                               list.Add (added);
+                       }
+                       parent.parent_item_group.ReplaceWith (parent, list);
+                       parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
+               }
+
+               static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
+               {
+                       BuildItem newParent;
+
+                       XmlDocument doc = parent.itemElement.OwnerDocument;
+                       XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
+                       newElement.SetAttribute ("Include", child.FinalItemSpec);
+                       if (parent.itemElement.HasAttribute ("Condition"))
+                               newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
+                       foreach (XmlNode xn in parent.itemElement)
+                               newElement.AppendChild (xn.Clone ());
+                       parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
+
+                       newParent = new BuildItem (newElement, parent.parent_item_group);
+                       newParent.child_items.Add (child);
+                       child.parent_item = newParent;
+
+                       return newParent;
+               }
+
                public string Condition {
                        get {
                                if (FromXml)
@@ -298,7 +623,7 @@ namespace Microsoft.Build.BuildEngine {
                        set {
                                if (FromXml)
                                        itemElement.SetAttribute ("Condition", value);
-                               else
+                               else if (!HasParentItem)
                                        throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
                        }
                }
@@ -326,32 +651,87 @@ namespace Microsoft.Build.BuildEngine {
                        get {
                                if (FromXml)
                                        return itemElement.GetAttribute ("Include");
-                               else if (itemInclude != null)
-                                       return itemInclude;
+                               else if (HasParentItem)
+                                       return parent_item.Include;
                                else
-                                       return finalItemSpec;
+                                       return itemInclude;
                        }
                        set {
                                if (FromXml)
                                        itemElement.SetAttribute ("Include", value);
+                               else if (HasParentItem) {
+                                       if (parent_item.child_items.Count > 1)
+                                               SplitParentItem ();
+                                       parent_item.Include = value;
+                               } else
+                                       itemInclude = value;
                        }
                }
 
+               internal bool IsDynamic {
+                       get { return isDynamic; }
+               }
+
+               internal string Remove {
+                       get;
+                       private set;
+               }
+
+               internal bool KeepDuplicates {
+                       get { return keepDuplicates; }
+                       private set { keepDuplicates = value; }
+               }
+
                public bool IsImported {
                        get { return isImported; }
                }
 
                public string Name {
-                       get { return name; }
-                       set { name = value; }
+                       get {
+                               if (FromXml)
+                                       return itemElement.Name;
+                               else if (HasParentItem)
+                                       return parent_item.Name;
+                               else
+                                       return name;
+                       }
+                       set {
+                               if (FromXml) {
+                                       XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
+                                       newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
+                                       newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
+                                       foreach (XmlNode xn in itemElement)
+                                               newElement.AppendChild (xn.Clone ());
+                                       itemElement.ParentNode.ReplaceChild (newElement, itemElement);
+                                       itemElement = newElement;
+                               } else if (HasParentItem) {
+                                       if (parent_item.child_items.Count > 1)
+                                               SplitParentItem ();
+                                       parent_item.Name = value;
+                               } else
+                                       name = value;
+                       }
                }
                
                internal bool FromXml {
-                       get {
-                               return itemElement != null;
-                       }
+                       get { return itemElement != null; }
+               }
+
+               internal XmlElement XmlElement {
+                       get { return itemElement; }
+               }
+               
+               internal bool HasParentItem {
+                       get { return parent_item != null; }
+               }
+
+               internal BuildItem ParentItem {
+                       get { return parent_item; }
+               }
+
+               internal BuildItemGroup ParentItemGroup {
+                       get { return parent_item_group; }
+                       set { parent_item_group = value; }
                }
        }
 }
-
-#endif