Fix to UriTemplate.Match to properly handle query parameters without a value. No...
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
1 //
2 // BuildItem.cs:
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 using System;
29 using System.Linq;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.IO;
34 using System.Text;
35 using System.Xml;
36 using Microsoft.Build.Framework;
37 using Microsoft.Build.Utilities;
38 using Mono.XBuild.Utilities;
39
40 namespace Microsoft.Build.BuildEngine {
41         public class BuildItem {
42
43                 List<BuildItem> child_items;
44                 XmlElement      itemElement;
45                 string          finalItemSpec;
46                 bool            isImported;
47                 string          itemInclude;
48                 string          name;
49                 BuildItemGroup  parent_item_group;
50                 BuildItem       parent_item;
51                 //string                recursiveDir;
52                 IDictionary     evaluatedMetadata;
53                 IDictionary     unevaluatedMetadata;
54                 bool            isDynamic;
55                 bool            keepDuplicates = true;
56                 string          removeMetadata, keepMetadata;
57
58                 BuildItem ()
59                 {
60                 }
61                 
62                 public BuildItem (string itemName, ITaskItem taskItem)
63                 {
64                         if (taskItem == null)
65                                 throw new ArgumentNullException ("taskItem");
66
67                         this.name = itemName;
68                         this.finalItemSpec = taskItem.ItemSpec;
69                         this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
70                         this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
71                         this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
72                 }
73
74                 public BuildItem (string itemName, string itemInclude)
75                 {
76                         if (itemInclude == null)
77                                 throw new ArgumentNullException ("itemInclude");
78                         if (itemInclude == String.Empty)
79                                 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
80
81                         name = itemName;
82                         finalItemSpec = itemInclude;
83                         this.itemInclude = itemInclude;
84                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
85                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
86                 }
87
88                 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
89                 {
90                         child_items = new List<BuildItem> ();
91                         isImported = parentItemGroup.IsImported;
92                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
93                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
94                         this.parent_item_group = parentItemGroup;
95                         
96                         this.itemElement = itemElement;
97                         isDynamic = parentItemGroup.IsDynamic;
98
99                         if (IsDynamic) {
100                                 if (!string.IsNullOrEmpty (Remove)) {
101                                         if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
102                                                 throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
103                                         if (itemElement.HasChildNodes)
104                                                 throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
105                                 }
106                                 if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
107                                         throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
108                         } else {
109                                 if (string.IsNullOrEmpty (Include))
110                                         throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
111                                 if (!string.IsNullOrEmpty (Remove))
112                                         throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
113                         }
114
115                         foreach (XmlAttribute attr in itemElement.Attributes) {
116                                 if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
117                                         continue;
118                                 if (!IsDynamic)
119                                         throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
120
121                                 switch (attr.Name) {
122                                 case "Remove":
123                                         Remove = attr.Value;
124                                         break;
125                                 case "KeepDuplicates":
126                                         KeepDuplicates = bool.Parse (attr.Value);
127                                         break;
128                                 case "RemoveMetadata":
129                                         removeMetadata = attr.Value;
130                                         break;
131                                 case "KeepMetadata":
132                                         keepMetadata = attr.Value;
133                                         break;
134                                 default:
135                                         throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
136                                 }
137                         }
138                 }
139
140                 BuildItem (BuildItem parent)
141                 {
142                         isImported = parent.isImported;
143                         name = parent.Name;
144                         parent_item = parent;
145                         parent_item.child_items.Add (this);
146                         parent_item_group = parent.parent_item_group;
147                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
148                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
149                 }
150                 
151                 public void CopyCustomMetadataTo (BuildItem destinationItem)
152                 {
153                         if (destinationItem == null)
154                                 throw new ArgumentNullException ("destinationItem");
155
156                         foreach (DictionaryEntry de in unevaluatedMetadata)
157                                 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
158                 }
159                 
160                 [MonoTODO]
161                 public BuildItem Clone ()
162                 {
163                         return (BuildItem) this.MemberwiseClone ();
164                 }
165
166                 public string GetEvaluatedMetadata (string metadataName)
167                 {
168                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
169                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
170                                 return MSBuildUtils.Unescape (metadata);
171                         }
172
173                         if (evaluatedMetadata.Contains (metadataName))
174                                 return (string) evaluatedMetadata [metadataName];
175                         else
176                                 return String.Empty;
177                 }
178
179                 public string GetMetadata (string metadataName)
180                 {
181                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
182                                 return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
183                         } else if (unevaluatedMetadata.Contains (metadataName))
184                                 return (string) unevaluatedMetadata [metadataName];
185                         else
186                                 return String.Empty;
187                 }
188                 
189                 public bool HasMetadata (string metadataName)
190                 {
191                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
192                                 return true;
193                         else
194                                 return evaluatedMetadata.Contains (metadataName);
195                 }
196
197                 public void RemoveMetadata (string metadataName)
198                 {
199                         if (metadataName == null)
200                                 throw new ArgumentNullException ("metadataName");
201                         
202                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
203                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
204                                         metadataName));
205
206                         if (FromXml) {
207                                 if (unevaluatedMetadata.Contains (metadataName)) {
208                                         XmlNode node = itemElement [metadataName];
209                                         itemElement.RemoveChild (node);
210                                 }
211                         } else if (HasParentItem) {
212                                 if (parent_item.child_items.Count > 1)
213                                         SplitParentItem ();
214                                 parent_item.RemoveMetadata (metadataName);
215                         } 
216                         
217                         DeleteMetadata (metadataName);
218                 }
219
220                 public void SetMetadata (string metadataName,
221                                          string metadataValue)
222                 {
223                         SetMetadata (metadataName, metadataValue, false);
224                 }
225                 
226                 public void SetMetadata (string metadataName,
227                                          string metadataValue,
228                                          bool treatMetadataValueAsLiteral)
229                 {
230                         if (metadataName == null)
231                                 throw new ArgumentNullException ("metadataName");
232                         
233                         if (metadataValue == null)
234                                 throw new ArgumentNullException ("metadataValue");
235                         
236                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
237                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
238                                         metadataName));
239
240                         if (treatMetadataValueAsLiteral && !HasParentItem)
241                                 metadataValue = MSBuildUtils.Escape (metadataValue);
242
243                         if (FromXml) {
244                                 XmlElement element = itemElement [metadataName];
245                                 if (element == null) {
246                                         element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
247                                         element.InnerText = metadataValue;
248                                         itemElement.AppendChild (element);
249                                 } else
250                                         element.InnerText = metadataValue;
251                         } else if (HasParentItem) {
252                                 if (parent_item.child_items.Count > 1)
253                                         SplitParentItem ();
254                                 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
255                         }
256                         if (FromXml || HasParentItem) {
257                                 parent_item_group.ParentProject.MarkProjectAsDirty ();
258                                 parent_item_group.ParentProject.NeedToReevaluate ();
259                         }
260                         
261                         DeleteMetadata (metadataName);
262                         AddMetadata (metadataName, metadataValue);
263                 }
264
265                 void AddMetadata (string name, string value)
266                 {
267                         var options = IsDynamic ?
268                                       ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
269
270                         if (parent_item_group != null) {
271                                 Expression e = new Expression ();
272                                 e.Parse (value, options);
273                                 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
274                                                 typeof (string), ExpressionOptions.ExpandItemRefs);
275                         } else
276                                 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
277                                 
278                         unevaluatedMetadata [name] = value;
279                 }
280
281                 void DeleteMetadata (string name)
282                 {
283                         if (evaluatedMetadata.Contains (name))
284                                 evaluatedMetadata.Remove (name);
285                         
286                         if (unevaluatedMetadata.Contains (name))
287                                 unevaluatedMetadata.Remove (name);
288                 }
289
290                 internal void Evaluate (Project project, bool evaluatedTo)
291                 {
292                         // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
293                         if (project == null) {
294                                 this.finalItemSpec = MSBuildUtils.Unescape (Include);
295                                 return;
296                         }
297
298                         foreach (XmlNode xn in itemElement.ChildNodes) {
299                                 XmlElement xe = xn as XmlElement;
300                                 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
301                                         AddMetadata (xe.Name, xe.InnerText);
302                         }
303
304                         if (IsDynamic) {
305                                 if (!evaluatedTo)
306                                         return;
307
308                                 if (!string.IsNullOrEmpty (Remove)) {
309                                         RemoveItems (project);
310                                         return;
311                                 }
312
313                                 if (string.IsNullOrEmpty (Include)) {
314                                         UpdateMetadata (project);
315                                         return;
316                                 }
317                         }
318                         
319                         DirectoryScanner directoryScanner;
320                         Expression includeExpr, excludeExpr;
321                         ITaskItem[] includes, excludes;
322
323                         var options = IsDynamic ?
324                                 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
325
326                         includeExpr = new Expression ();
327                         includeExpr.Parse (Include, options);
328                         excludeExpr = new Expression ();
329                         excludeExpr.Parse (Exclude, options);
330                         
331                         includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
332                                                                 ExpressionOptions.ExpandItemRefs);
333                         excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
334                                                                 ExpressionOptions.ExpandItemRefs);
335
336                         this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
337                                                         ExpressionOptions.ExpandItemRefs);
338
339                         directoryScanner = new DirectoryScanner ();
340                         
341                         directoryScanner.Includes = includes;
342                         directoryScanner.Excludes = excludes;
343
344                         if (project.FullFileName != String.Empty)
345                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
346                         else
347                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
348                         
349                         directoryScanner.Scan ();
350                         
351                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
352                                 AddEvaluatedItem (project, evaluatedTo, matchedItem);
353                 }
354
355                 bool CheckCondition (Project project)
356                 {
357                         if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
358                                 return false;
359                         if (parent_item != null && !parent_item.CheckCondition (project))
360                                 return false;
361                         return ConditionParser.ParseAndEvaluate (Condition, project);
362                 }
363
364                 void UpdateMetadata (Project project)
365                 {
366                         BuildItemGroup group;
367                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
368                                 return;
369
370                         foreach (BuildItem item in group) {
371                                 if (!item.CheckCondition (project))
372                                         continue;
373                                 
374                                 foreach (string name in evaluatedMetadata.Keys) {
375                                         item.SetMetadata (name, (string)evaluatedMetadata [name]);
376                                 }
377
378                                 AddAndRemoveMetadata (project, item);
379                         }
380                 }
381
382                 void AddAndRemoveMetadata (Project project, BuildItem item)
383                 {
384                         if (!string.IsNullOrEmpty (removeMetadata)) {
385                                 var removeExpr = new Expression ();
386                                 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
387
388                                 var removeSpec = (string[]) removeExpr.ConvertTo (
389                                         project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
390
391                                 foreach (var remove in removeSpec) {
392                                         item.DeleteMetadata (remove);
393                                 }
394                         }
395
396                         if (!string.IsNullOrEmpty (keepMetadata)) {
397                                 var keepExpr = new Expression ();
398                                 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
399
400                                 var keepSpec = (string[]) keepExpr.ConvertTo (
401                                         project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
402
403                                 var metadataNames = new string [item.evaluatedMetadata.Count];
404                                 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
405
406                                 foreach (string name in metadataNames) {
407                                         if (!keepSpec.Contains (name))
408                                                 item.DeleteMetadata (name);
409                                 }
410                         }
411                 }
412
413                 void RemoveItems (Project project)
414                 {
415                         BuildItemGroup group;
416                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
417                                 return;
418
419                         var removeExpr = new Expression ();
420                         removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
421
422                         var removes = (ITaskItem[]) removeExpr.ConvertTo (
423                                 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
424
425                         var directoryScanner = new DirectoryScanner ();
426                         
427                         directoryScanner.Includes = removes;
428
429                         if (project.FullFileName != String.Empty)
430                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
431                         else
432                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
433                         
434                         directoryScanner.Scan ();
435
436                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
437                                 group.RemoveItem (matchedItem);
438                         }
439                 }
440
441                 bool ContainsItem (Project project, ITaskItem taskItem)
442                 {
443                         BuildItemGroup group;
444                         if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
445                                 return false;
446
447                         var item = group.FindItem (taskItem);
448                         if (item == null)
449                                 return false;
450
451                         foreach (string metadataName in evaluatedMetadata.Keys) {
452                                 string metadataValue = (string)evaluatedMetadata [metadataName];
453                                 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
454                                         return false;
455                         }
456                         
457                         foreach (string metadataName in item.evaluatedMetadata.Keys) {
458                                 string metadataValue = (string)item.evaluatedMetadata [metadataName];
459                                 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
460                                         return false;
461                         }
462
463                         return true;
464                 }
465
466                 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
467                 {
468                         if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
469                                 return;
470
471                         BuildItemGroup big;                     
472                         BuildItem bi = new BuildItem (this);
473                         bi.finalItemSpec = taskitem.ItemSpec;
474
475                         foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
476                                 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
477                                 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
478                         }
479
480                         project.EvaluatedItemsIgnoringCondition.AddItem (bi);
481
482                         if (evaluatedTo) {
483                                 project.EvaluatedItems.AddItem (bi);
484         
485                                 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
486                                         big = new BuildItemGroup (null, project, null, true);
487                                         project.EvaluatedItemsByName.Add (bi.Name, big);
488                                 } else {
489                                         big = project.EvaluatedItemsByName [bi.Name];
490                                 }
491
492                                 big.AddItem (bi);
493                         }
494
495                         if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
496                                 big = new BuildItemGroup (null, project, null, true);
497                                 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
498                         } else {
499                                 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
500                         }
501
502                         big.AddItem (bi);
503
504                         if (IsDynamic)
505                                 AddAndRemoveMetadata (project, bi);
506                 }
507                 
508                 // during item's eval phase, any item refs in this item, have either
509                 // already been expanded or are non-existant, so expand can be _false_
510                 //
511                 // during prop's eval phase, this isn't reached, as it parses expressions
512                 // with allowItems=false, so no ItemReferences are created at all
513                 //
514                 // at other times, item refs have already been expanded, so expand: false
515                 internal string ConvertToString (Expression transform, ExpressionOptions options)
516                 {
517                         return GetItemSpecFromTransform (transform, options);
518                 }
519                 
520                 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
521                 {
522                         TaskItem taskItem;
523                         taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
524                         return taskItem;
525                 }
526
527                 internal void Detach ()
528                 {
529                         if (FromXml)
530                                 itemElement.ParentNode.RemoveChild (itemElement);
531                         else if (HasParentItem) {
532                                 if (parent_item.child_items.Count > 1)
533                                         SplitParentItem ();
534                                 parent_item.Detach ();
535                         }
536                 }
537
538                 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
539                 {
540                         StringBuilder sb;
541                 
542                         if (transform == null) {
543                                 if (options == ExpressionOptions.ExpandItemRefs) {
544                                         // With usual code paths, this will never execute,
545                                         // but letting this be here, incase BI.ConvertTo*
546                                         // is called directly
547                                         Expression expr = new Expression ();
548                                         expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
549
550                                         return (string) expr.ConvertTo (parent_item_group.ParentProject,
551                                                         typeof (string), ExpressionOptions.ExpandItemRefs);
552                                 } else {
553                                         return finalItemSpec;
554                                 }
555                         } else {
556                                 // Transform, _DONT_ expand itemrefs
557                                 sb = new StringBuilder ();
558                                 foreach (object o in transform.Collection) {
559                                         if (o is string) {
560                                                 sb.Append ((string)o);
561                                         } else if (o is PropertyReference) {
562                                                 sb.Append (((PropertyReference)o).ConvertToString (
563                                                                         parent_item_group.ParentProject,
564                                                                         ExpressionOptions.DoNotExpandItemRefs));
565                                         } else if (o is ItemReference) {
566                                                 sb.Append (((ItemReference)o).ConvertToString (
567                                                                         parent_item_group.ParentProject,
568                                                                         ExpressionOptions.DoNotExpandItemRefs));
569                                         } else if (o is MetadataReference) {
570                                                 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
571                                         }
572                                 }
573                                 return sb.ToString ();
574                         }
575                 }
576
577                 void SplitParentItem ()
578                 {
579                         BuildItem parent = parent_item;
580                         List <BuildItem> list = new List <BuildItem> ();
581                         XmlElement insertAfter = parent.itemElement;
582                         foreach (BuildItem bi in parent.child_items) {
583                                 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
584                                 insertAfter = added.itemElement;
585                                 list.Add (added);
586                         }
587                         parent.parent_item_group.ReplaceWith (parent, list);
588                         parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
589                 }
590
591                 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
592                 {
593                         BuildItem newParent;
594
595                         XmlDocument doc = parent.itemElement.OwnerDocument;
596                         XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
597                         newElement.SetAttribute ("Include", child.FinalItemSpec);
598                         if (parent.itemElement.HasAttribute ("Condition"))
599                                 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
600                         foreach (XmlNode xn in parent.itemElement)
601                                 newElement.AppendChild (xn.Clone ());
602                         parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
603
604                         newParent = new BuildItem (newElement, parent.parent_item_group);
605                         newParent.child_items.Add (child);
606                         child.parent_item = newParent;
607
608                         return newParent;
609                 }
610
611                 public string Condition {
612                         get {
613                                 if (FromXml)
614                                         return itemElement.GetAttribute ("Condition");
615                                 else
616                                         return String.Empty;
617                         }
618                         set {
619                                 if (FromXml)
620                                         itemElement.SetAttribute ("Condition", value);
621                                 else if (!HasParentItem)
622                                         throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
623                         }
624                 }
625
626                 public string Exclude {
627                         get {
628                                 if (FromXml)
629                                         return itemElement.GetAttribute ("Exclude");
630                                 else
631                                         return String.Empty;
632                         }
633                         set {
634                                 if (FromXml)
635                                         itemElement.SetAttribute ("Exclude", value);
636                                 else
637                                         throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
638                         }
639                 }
640
641                 public string FinalItemSpec {
642                         get { return finalItemSpec; }
643                 }
644
645                 public string Include {
646                         get {
647                                 if (FromXml)
648                                         return itemElement.GetAttribute ("Include");
649                                 else if (HasParentItem)
650                                         return parent_item.Include;
651                                 else
652                                         return itemInclude;
653                         }
654                         set {
655                                 if (FromXml)
656                                         itemElement.SetAttribute ("Include", value);
657                                 else if (HasParentItem) {
658                                         if (parent_item.child_items.Count > 1)
659                                                 SplitParentItem ();
660                                         parent_item.Include = value;
661                                 } else
662                                         itemInclude = value;
663                         }
664                 }
665
666                 internal bool IsDynamic {
667                         get { return isDynamic; }
668                 }
669
670                 internal string Remove {
671                         get;
672                         private set;
673                 }
674
675                 internal bool KeepDuplicates {
676                         get { return keepDuplicates; }
677                         private set { keepDuplicates = value; }
678                 }
679
680                 public bool IsImported {
681                         get { return isImported; }
682                 }
683
684                 public string Name {
685                         get {
686                                 if (FromXml)
687                                         return itemElement.Name;
688                                 else if (HasParentItem)
689                                         return parent_item.Name;
690                                 else
691                                         return name;
692                         }
693                         set {
694                                 if (FromXml) {
695                                         XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
696                                         newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
697                                         newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
698                                         foreach (XmlNode xn in itemElement)
699                                                 newElement.AppendChild (xn.Clone ());
700                                         itemElement.ParentNode.ReplaceChild (newElement, itemElement);
701                                         itemElement = newElement;
702                                 } else if (HasParentItem) {
703                                         if (parent_item.child_items.Count > 1)
704                                                 SplitParentItem ();
705                                         parent_item.Name = value;
706                                 } else
707                                         name = value;
708                         }
709                 }
710                 
711                 internal bool FromXml {
712                         get { return itemElement != null; }
713                 }
714
715                 internal XmlElement XmlElement {
716                         get { return itemElement; }
717                 }
718                 
719                 internal bool HasParentItem {
720                         get { return parent_item != null; }
721                 }
722
723                 internal BuildItem ParentItem {
724                         get { return parent_item; }
725                 }
726
727                 internal BuildItemGroup ParentItemGroup {
728                         get { return parent_item_group; }
729                         set { parent_item_group = value; }
730                 }
731         }
732 }