5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Collections.Specialized;
37 using Microsoft.Build.Framework;
38 using Microsoft.Build.Utilities;
39 using Mono.XBuild.Utilities;
41 namespace Microsoft.Build.BuildEngine {
42 public class BuildItem {
44 List<BuildItem> child_items;
45 XmlElement itemElement;
50 BuildItemGroup parent_item_group;
51 BuildItem parent_item;
52 //string recursiveDir;
53 IDictionary evaluatedMetadata;
54 IDictionary unevaluatedMetadata;
60 public BuildItem (string itemName, ITaskItem taskItem)
63 throw new ArgumentNullException ("taskItem");
66 this.finalItemSpec = taskItem.ItemSpec;
67 this.itemInclude = Utilities.Escape (taskItem.ItemSpec);
68 this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
69 this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
72 public BuildItem (string itemName, string itemInclude)
74 if (itemInclude == null)
75 throw new ArgumentNullException ("itemInclude");
76 if (itemInclude == String.Empty)
77 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
80 finalItemSpec = itemInclude;
81 this.itemInclude = itemInclude;
82 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
83 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
86 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
88 child_items = new List<BuildItem> ();
89 isImported = parentItemGroup.IsImported;
90 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
91 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
92 this.parent_item_group = parentItemGroup;
94 this.itemElement = itemElement;
96 if (Include == String.Empty)
97 throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
100 BuildItem (BuildItem parent)
102 isImported = parent.isImported;
104 parent_item = parent;
105 parent_item.child_items.Add (this);
106 parent_item_group = parent.parent_item_group;
107 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
108 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
111 public void CopyCustomMetadataTo (BuildItem destinationItem)
113 if (destinationItem == null)
114 throw new ArgumentNullException ("destinationItem");
116 foreach (DictionaryEntry de in unevaluatedMetadata)
117 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
121 public BuildItem Clone ()
123 return (BuildItem) this.MemberwiseClone ();
126 public string GetEvaluatedMetadata (string metadataName)
128 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
129 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
130 return (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : metadata;
133 if (evaluatedMetadata.Contains (metadataName))
134 return (string) evaluatedMetadata [metadataName];
139 public string GetMetadata (string metadataName)
141 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
142 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName);
143 return (metadataName.ToLower () == "fullpath") ? Utilities.Escape (metadata) : metadata;
144 } else if (unevaluatedMetadata.Contains (metadataName))
145 return (string) unevaluatedMetadata [metadataName];
150 public bool HasMetadata (string metadataName)
152 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
155 return evaluatedMetadata.Contains (metadataName);
158 public void RemoveMetadata (string metadataName)
160 if (metadataName == null)
161 throw new ArgumentNullException ("metadataName");
163 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
164 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
168 if (unevaluatedMetadata.Contains (metadataName)) {
169 XmlNode node = itemElement [metadataName];
170 itemElement.RemoveChild (node);
172 } else if (HasParentItem) {
173 if (parent_item.child_items.Count > 1)
175 parent_item.RemoveMetadata (metadataName);
178 DeleteMetadata (metadataName);
181 public void SetMetadata (string metadataName,
182 string metadataValue)
184 SetMetadata (metadataName, metadataValue, false);
187 public void SetMetadata (string metadataName,
188 string metadataValue,
189 bool treatMetadataValueAsLiteral)
191 if (metadataName == null)
192 throw new ArgumentNullException ("metadataName");
194 if (metadataValue == null)
195 throw new ArgumentNullException ("metadataValue");
197 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
198 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
201 if (treatMetadataValueAsLiteral && !HasParentItem)
202 metadataValue = Utilities.Escape (metadataValue);
205 XmlElement element = itemElement [metadataName];
206 if (element == null) {
207 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
208 element.InnerText = metadataValue;
209 itemElement.AppendChild (element);
211 element.InnerText = metadataValue;
212 } else if (HasParentItem) {
213 if (parent_item.child_items.Count > 1)
215 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
217 if (FromXml || HasParentItem) {
218 parent_item_group.ParentProject.MarkProjectAsDirty ();
219 parent_item_group.ParentProject.NeedToReevaluate ();
222 DeleteMetadata (metadataName);
223 AddMetadata (metadataName, metadataValue);
226 void AddMetadata (string name, string value)
228 if (parent_item_group != null) {
229 Expression e = new Expression ();
230 e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
231 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
232 typeof (string), ExpressionOptions.ExpandItemRefs);
234 evaluatedMetadata [name] = Utilities.Unescape (value);
236 unevaluatedMetadata [name] = value;
239 void DeleteMetadata (string name)
241 if (evaluatedMetadata.Contains (name))
242 evaluatedMetadata.Remove (name);
244 if (unevaluatedMetadata.Contains (name))
245 unevaluatedMetadata.Remove (name);
248 internal void Evaluate (Project project, bool evaluatedTo)
250 // FIXME: maybe make Expression.ConvertTo (null, ...) work as Utilities.Unescape ()?
251 if (project == null) {
252 this.finalItemSpec = Utilities.Unescape (Include);
256 foreach (XmlNode xn in itemElement.ChildNodes) {
257 XmlElement xe = xn as XmlElement;
258 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
259 AddMetadata (xe.Name, xe.InnerText);
262 DirectoryScanner directoryScanner;
263 Expression includeExpr, excludeExpr;
264 ITaskItem[] includes, excludes;
266 includeExpr = new Expression ();
267 includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
268 excludeExpr = new Expression ();
269 excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
271 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
272 ExpressionOptions.ExpandItemRefs);
273 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
274 ExpressionOptions.ExpandItemRefs);
276 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
277 ExpressionOptions.ExpandItemRefs);
279 directoryScanner = new DirectoryScanner ();
281 directoryScanner.Includes = includes;
282 directoryScanner.Excludes = excludes;
284 if (project.FullFileName != String.Empty)
285 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
287 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
289 directoryScanner.Scan ();
291 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
292 AddEvaluatedItem (project, evaluatedTo, matchedItem);
295 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
298 BuildItem bi = new BuildItem (this);
299 bi.finalItemSpec = taskitem.ItemSpec;
301 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
302 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
303 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
306 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
309 project.EvaluatedItems.AddItem (bi);
311 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
312 big = new BuildItemGroup (null, project, null, true);
313 project.EvaluatedItemsByName.Add (bi.Name, big);
315 big = project.EvaluatedItemsByName [bi.Name];
321 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
322 big = new BuildItemGroup (null, project, null, true);
323 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
325 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
331 // during item's eval phase, any item refs in this item, have either
332 // already been expanded or are non-existant, so expand can be _false_
334 // during prop's eval phase, this isn't reached, as it parses expressions
335 // with allowItems=false, so no ItemReferences are created at all
337 // at other times, item refs have already been expanded, so expand: false
338 internal string ConvertToString (Expression transform, ExpressionOptions options)
340 return GetItemSpecFromTransform (transform, options);
343 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
346 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
350 internal void Detach ()
353 itemElement.ParentNode.RemoveChild (itemElement);
354 else if (HasParentItem) {
355 if (parent_item.child_items.Count > 1)
357 parent_item.Detach ();
361 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
365 if (transform == null) {
366 if (options == ExpressionOptions.ExpandItemRefs) {
367 // With usual code paths, this will never execute,
368 // but letting this be here, incase BI.ConvertTo*
369 // is called directly
370 Expression expr = new Expression ();
371 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
373 return (string) expr.ConvertTo (parent_item_group.ParentProject,
374 typeof (string), ExpressionOptions.ExpandItemRefs);
376 return finalItemSpec;
379 // Transform, _DONT_ expand itemrefs
380 sb = new StringBuilder ();
381 foreach (object o in transform.Collection) {
383 sb.Append ((string)o);
384 } else if (o is PropertyReference) {
385 sb.Append (((PropertyReference)o).ConvertToString (
386 parent_item_group.ParentProject,
387 ExpressionOptions.DoNotExpandItemRefs));
388 } else if (o is ItemReference) {
389 sb.Append (((ItemReference)o).ConvertToString (
390 parent_item_group.ParentProject,
391 ExpressionOptions.DoNotExpandItemRefs));
392 } else if (o is MetadataReference) {
393 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
396 return sb.ToString ();
400 void SplitParentItem ()
402 BuildItem parent = parent_item;
403 List <BuildItem> list = new List <BuildItem> ();
404 XmlElement insertAfter = parent.itemElement;
405 foreach (BuildItem bi in parent.child_items) {
406 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
407 insertAfter = added.itemElement;
410 parent.parent_item_group.ReplaceWith (parent, list);
411 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
414 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
418 XmlDocument doc = parent.itemElement.OwnerDocument;
419 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
420 newElement.SetAttribute ("Include", child.FinalItemSpec);
421 if (parent.itemElement.HasAttribute ("Condition"))
422 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
423 foreach (XmlNode xn in parent.itemElement)
424 newElement.AppendChild (xn.Clone ());
425 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
427 newParent = new BuildItem (newElement, parent.parent_item_group);
428 newParent.child_items.Add (child);
429 child.parent_item = newParent;
434 public string Condition {
437 return itemElement.GetAttribute ("Condition");
443 itemElement.SetAttribute ("Condition", value);
444 else if (!HasParentItem)
445 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
449 public string Exclude {
452 return itemElement.GetAttribute ("Exclude");
458 itemElement.SetAttribute ("Exclude", value);
460 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
464 public string FinalItemSpec {
465 get { return finalItemSpec; }
468 public string Include {
471 return itemElement.GetAttribute ("Include");
472 else if (HasParentItem)
473 return parent_item.Include;
479 itemElement.SetAttribute ("Include", value);
480 else if (HasParentItem) {
481 if (parent_item.child_items.Count > 1)
483 parent_item.Include = value;
489 public bool IsImported {
490 get { return isImported; }
496 return itemElement.Name;
497 else if (HasParentItem)
498 return parent_item.Name;
504 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
505 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
506 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
507 foreach (XmlNode xn in itemElement)
508 newElement.AppendChild (xn.Clone ());
509 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
510 itemElement = newElement;
511 } else if (HasParentItem) {
512 if (parent_item.child_items.Count > 1)
514 parent_item.Name = value;
520 internal bool FromXml {
521 get { return itemElement != null; }
524 internal bool HasParentItem {
525 get { return parent_item != null; }
528 internal BuildItem ParentItem {
529 get { return parent_item; }
532 internal BuildItemGroup ParentItemGroup {
533 get { return parent_item_group; }
534 set { parent_item_group = value; }