aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/CompilerCore.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/CompilerCore.cs')
-rw-r--r--src/WixToolset.Core/CompilerCore.cs1932
1 files changed, 1932 insertions, 0 deletions
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs
new file mode 100644
index 00000000..8640a2da
--- /dev/null
+++ b/src/WixToolset.Core/CompilerCore.cs
@@ -0,0 +1,1932 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization;
11 using System.IO;
12 using System.Reflection;
13 using System.Security.Cryptography;
14 using System.Text;
15 using System.Text.RegularExpressions;
16 using System.Xml.Linq;
17 using WixToolset.Data;
18 using WixToolset.Data.Rows;
19 using WixToolset.Extensibility;
20 using Wix = WixToolset.Data.Serialize;
21
22 internal enum ValueListKind
23 {
24 /// <summary>
25 /// A list of values with nothing before the final value.
26 /// </summary>
27 None,
28
29 /// <summary>
30 /// A list of values with 'and' before the final value.
31 /// </summary>
32 And,
33
34 /// <summary>
35 /// A list of values with 'or' before the final value.
36 /// </summary>
37 Or
38 }
39
40 /// <summary>
41 /// Core class for the compiler.
42 /// </summary>
43 internal sealed class CompilerCore : ICompilerCore
44 {
45 internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
46 internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
47
48 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
49 public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
50 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
51
52 private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled);
53
54 private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * "
55 private static readonly Regex IllegalLongFilename = new Regex(IllegalLongFilenameCharacters, RegexOptions.Compiled);
56
57 private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above.
58 private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
59
60 private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * "
61 private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
62
63 private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / "
64 private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$"));
65
66 private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline);
67
68 private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
69
70 // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
71 private static readonly List<String> BuiltinBundleVariables = new List<string>(
72 new string[] {
73 "AdminToolsFolder",
74 "AppDataFolder",
75 "CommonAppDataFolder",
76 "CommonFiles64Folder",
77 "CommonFilesFolder",
78 "CompatibilityMode",
79 "Date",
80 "DesktopFolder",
81 "FavoritesFolder",
82 "FontsFolder",
83 "InstallerName",
84 "InstallerVersion",
85 "LocalAppDataFolder",
86 "LogonUser",
87 "MyPicturesFolder",
88 "NTProductType",
89 "NTSuiteBackOffice",
90 "NTSuiteDataCenter",
91 "NTSuiteEnterprise",
92 "NTSuitePersonal",
93 "NTSuiteSmallBusiness",
94 "NTSuiteSmallBusinessRestricted",
95 "NTSuiteWebServer",
96 "PersonalFolder",
97 "Privileged",
98 "ProgramFiles64Folder",
99 "ProgramFiles6432Folder",
100 "ProgramFilesFolder",
101 "ProgramMenuFolder",
102 "RebootPending",
103 "SendToFolder",
104 "ServicePackLevel",
105 "StartMenuFolder",
106 "StartupFolder",
107 "System64Folder",
108 "SystemFolder",
109 "TempFolder",
110 "TemplateFolder",
111 "TerminalServer",
112 "UserLanguageID",
113 "UserUILanguageID",
114 "VersionMsi",
115 "VersionNT",
116 "VersionNT64",
117 "WindowsFolder",
118 "WindowsVolume",
119 "WixBundleAction",
120 "WixBundleForcedRestartPackage",
121 "WixBundleElevated",
122 "WixBundleInstalled",
123 "WixBundleProviderKey",
124 "WixBundleTag",
125 "WixBundleVersion",
126 });
127
128 private static readonly List<string> DisallowedMsiProperties = new List<string>(
129 new string[] {
130 "ACTION",
131 "ADDLOCAL",
132 "ADDSOURCE",
133 "ADDDEFAULT",
134 "ADVERTISE",
135 "ALLUSERS",
136 "REBOOT",
137 "REINSTALL",
138 "REINSTALLMODE",
139 "REMOVE"
140 });
141
142 private TableDefinitionCollection tableDefinitions;
143 private Dictionary<XNamespace, ICompilerExtension> extensions;
144 private Intermediate intermediate;
145 private bool showPedanticMessages;
146
147 private HashSet<string> activeSectionInlinedDirectoryIds;
148 private HashSet<string> activeSectionSimpleReferences;
149
150 /// <summary>
151 /// Constructor for all compiler core.
152 /// </summary>
153 /// <param name="intermediate">The Intermediate object representing compiled source document.</param>
154 /// <param name="tableDefinitions">The loaded table definition collection.</param>
155 /// <param name="extensions">The WiX extensions collection.</param>
156 internal CompilerCore(Intermediate intermediate, TableDefinitionCollection tableDefinitions, Dictionary<XNamespace, ICompilerExtension> extensions)
157 {
158 this.tableDefinitions = tableDefinitions;
159 this.extensions = extensions;
160 this.intermediate = intermediate;
161 }
162
163 /// <summary>
164 /// Gets the section the compiler is currently emitting symbols into.
165 /// </summary>
166 /// <value>The section the compiler is currently emitting symbols into.</value>
167 public Section ActiveSection { get; private set; }
168
169 /// <summary>
170 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
171 /// </summary>
172 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
173 public Platform CurrentPlatform { get; set; }
174
175 /// <summary>
176 /// Gets whether the compiler core encountered an error while processing.
177 /// </summary>
178 /// <value>Flag if core encountered an error during processing.</value>
179 public bool EncounteredError
180 {
181 get { return Messaging.Instance.EncounteredError; }
182 }
183
184 /// <summary>
185 /// Gets or sets the option to show pedantic messages.
186 /// </summary>
187 /// <value>The option to show pedantic messages.</value>
188 public bool ShowPedanticMessages
189 {
190 get { return this.showPedanticMessages; }
191 set { this.showPedanticMessages = value; }
192 }
193
194 /// <summary>
195 /// Gets the table definitions used by the compiler core.
196 /// </summary>
197 /// <value>Table definition collection.</value>
198 public TableDefinitionCollection TableDefinitions
199 {
200 get { return this.tableDefinitions; }
201 }
202
203 /// <summary>
204 /// Convert a bit array into an int value.
205 /// </summary>
206 /// <param name="bits">The bit array to convert.</param>
207 /// <returns>The converted int value.</returns>
208 public int CreateIntegerFromBitArray(BitArray bits)
209 {
210 if (32 != bits.Length)
211 {
212 throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits");
213 }
214
215 int[] intArray = new int[1];
216 bits.CopyTo(intArray, 0);
217
218 return intArray[0];
219 }
220
221 /// <summary>
222 /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array.
223 /// </summary>
224 /// <param name="attributeNames">Array of attributes that map to bits.</param>
225 /// <param name="attributeName">Name of attribute to check.</param>
226 /// <param name="attributeValue">Value of attribute to check.</param>
227 /// <param name="bits">The bit array in which the bit will be set if found.</param>
228 /// <param name="offset">The offset into the bit array.</param>
229 /// <returns>true if the bit was set; false otherwise.</returns>
230 public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset)
231 {
232 for (int i = 0; i < attributeNames.Length; i++)
233 {
234 if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal))
235 {
236 bits.Set(i + offset, YesNoType.Yes == attributeValue);
237 return true;
238 }
239 }
240
241 return false;
242 }
243
244 /// <summary>
245 /// Verifies that a filename is ambiguous.
246 /// </summary>
247 /// <param name="filename">Filename to verify.</param>
248 /// <returns>true if the filename is ambiguous; false otherwise.</returns>
249 public static bool IsAmbiguousFilename(string filename)
250 {
251 if (null == filename || 0 == filename.Length)
252 {
253 return false;
254 }
255
256 return CompilerCore.AmbiguousFilename.IsMatch(filename);
257 }
258
259 /// <summary>
260 /// Verifies that a value is a legal identifier.
261 /// </summary>
262 /// <param name="value">The value to verify.</param>
263 /// <returns>true if the value is an identifier; false otherwise.</returns>
264 public bool IsValidIdentifier(string value)
265 {
266 return Common.IsIdentifier(value);
267 }
268
269 /// <summary>
270 /// Verifies if an identifier is a valid loc identifier.
271 /// </summary>
272 /// <param name="identifier">Identifier to verify.</param>
273 /// <returns>True if the identifier is a valid loc identifier.</returns>
274 public bool IsValidLocIdentifier(string identifier)
275 {
276 if (String.IsNullOrEmpty(identifier))
277 {
278 return false;
279 }
280
281 Match match = Common.WixVariableRegex.Match(identifier);
282
283 return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length);
284 }
285
286 /// <summary>
287 /// Verifies if a filename is a valid long filename.
288 /// </summary>
289 /// <param name="filename">Filename to verify.</param>
290 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
291 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
292 /// <returns>True if the filename is a valid long filename</returns>
293 public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false)
294 {
295 if (String.IsNullOrEmpty(filename))
296 {
297 return false;
298 }
299
300 // check for a non-period character (all periods is not legal)
301 bool nonPeriodFound = false;
302 foreach (char character in filename)
303 {
304 if ('.' != character)
305 {
306 nonPeriodFound = true;
307 break;
308 }
309 }
310
311 if (allowWildcards)
312 {
313 return (nonPeriodFound && CompilerCore.LegalWildcardLongFilename.IsMatch(filename));
314 }
315 else if (allowRelative)
316 {
317 return (nonPeriodFound && CompilerCore.LegalRelativeLongFilename.IsMatch(filename));
318 }
319 else
320 {
321 return (nonPeriodFound && CompilerCore.LegalLongFilename.IsMatch(filename));
322 }
323 }
324
325 /// <summary>
326 /// Verifies if a filename is a valid short filename.
327 /// </summary>
328 /// <param name="filename">Filename to verify.</param>
329 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
330 /// <returns>True if the filename is a valid short filename</returns>
331 public bool IsValidShortFilename(string filename, bool allowWildcards)
332 {
333 return Common.IsValidShortFilename(filename, allowWildcards);
334 }
335
336 /// <summary>
337 /// Replaces the illegal filename characters to create a legal name.
338 /// </summary>
339 /// <param name="filename">Filename to make valid.</param>
340 /// <param name="replace">Replacement string for invalid characters in filename.</param>
341 /// <returns>Valid filename.</returns>
342 public static string MakeValidLongFileName(string filename, string replace)
343 {
344 return CompilerCore.IllegalLongFilename.Replace(filename, replace);
345 }
346
347 /// <summary>
348 /// Creates a short file/directory name using an identifier and long file/directory name as input.
349 /// </summary>
350 /// <param name="longName">The long file/directory name.</param>
351 /// <param name="keepExtension">The option to keep the extension on generated short names.</param>
352 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
353 /// <param name="args">Any additional information to include in the hash for the generated short name.</param>
354 /// <returns>The generated 8.3-compliant short file/directory name.</returns>
355 public string CreateShortName(string longName, bool keepExtension, bool allowWildcards, params string[] args)
356 {
357 // canonicalize the long name if its not a localization identifier (they are case-sensitive)
358 if (!this.IsValidLocIdentifier(longName))
359 {
360 longName = longName.ToLowerInvariant();
361 }
362
363 // collect all the data
364 List<string> strings = new List<string>(1 + args.Length);
365 strings.Add(longName);
366 strings.AddRange(args);
367
368 // prepare for hashing
369 string stringData = String.Join("|", strings);
370 byte[] data = Encoding.UTF8.GetBytes(stringData);
371
372 // hash the data
373 byte[] hash;
374 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
375 {
376 hash = sha1.ComputeHash(data);
377 }
378
379 // generate the short file/directory name without an extension
380 StringBuilder shortName = new StringBuilder(Convert.ToBase64String(hash));
381 shortName.Remove(8, shortName.Length - 8).Replace('+', '-').Replace('/', '_');
382
383 if (keepExtension)
384 {
385 string extension = Path.GetExtension(longName);
386
387 if (4 < extension.Length)
388 {
389 extension = extension.Substring(0, 4);
390 }
391
392 shortName.Append(extension);
393
394 // check the generated short name to ensure its still legal (the extension may not be legal)
395 if (!this.IsValidShortFilename(shortName.ToString(), allowWildcards))
396 {
397 // remove the extension (by truncating the generated file name back to the generated characters)
398 shortName.Length -= extension.Length;
399 }
400 }
401
402 return shortName.ToString().ToLowerInvariant();
403 }
404
405 /// <summary>
406 /// Verifies the given string is a valid product version.
407 /// </summary>
408 /// <param name="version">The product version to verify.</param>
409 /// <returns>True if version is a valid product version</returns>
410 public static bool IsValidProductVersion(string version)
411 {
412 if (!Common.IsValidBinderVariable(version))
413 {
414 Version ver = new Version(version);
415
416 if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build)
417 {
418 return false;
419 }
420 }
421
422 return true;
423 }
424
425 /// <summary>
426 /// Verifies the given string is a valid module or bundle version.
427 /// </summary>
428 /// <param name="version">The version to verify.</param>
429 /// <returns>True if version is a valid module or bundle version.</returns>
430 public static bool IsValidModuleOrBundleVersion(string version)
431 {
432 return Common.IsValidModuleOrBundleVersion(version);
433 }
434
435 /// <summary>
436 /// Get an element's inner text and trims any extra whitespace.
437 /// </summary>
438 /// <param name="element">The element with inner text to be trimmed.</param>
439 /// <returns>The node's inner text trimmed.</returns>
440 public string GetTrimmedInnerText(XElement element)
441 {
442 string value = Common.GetInnerText(element);
443 return (null == value) ? null : value.Trim();
444 }
445
446 /// <summary>
447 /// Gets element's inner text and ensure's it is safe for use in a condition by trimming any extra whitespace.
448 /// </summary>
449 /// <param name="element">The element to ensure inner text is a condition.</param>
450 /// <returns>The value converted into a safe condition.</returns>
451 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
452 public string GetConditionInnerText(XElement element)
453 {
454 string value = element.Value;
455 if (0 < value.Length)
456 {
457 value = value.Trim();
458 value = value.Replace('\t', ' ');
459 value = value.Replace('\r', ' ');
460 value = value.Replace('\n', ' ');
461 }
462 else // return null for a non-existant condition
463 {
464 value = null;
465 }
466
467 return value;
468 }
469
470 /// <summary>
471 /// Creates a version 3 name-based UUID.
472 /// </summary>
473 /// <param name="namespaceGuid">The namespace UUID.</param>
474 /// <param name="value">The value.</param>
475 /// <returns>The generated GUID for the given namespace and value.</returns>
476 public string CreateGuid(Guid namespaceGuid, string value)
477 {
478 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
479 }
480
481 /// <summary>
482 /// Creates a row in the active section.
483 /// </summary>
484 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
485 /// <param name="tableName">Name of table to create row in.</param>
486 /// <returns>New row.</returns>
487 public Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null)
488 {
489 return this.CreateRow(sourceLineNumbers, tableName, this.ActiveSection, identifier);
490 }
491
492 /// <summary>
493 /// Creates a row in the active given <paramref name="section"/>.
494 /// </summary>
495 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
496 /// <param name="tableName">Name of table to create row in.</param>
497 /// <param name="section">The section to which the row is added. If null, the row is added to the active section.</param>
498 /// <returns>New row.</returns>
499 internal Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Section section, Identifier identifier = null)
500 {
501 TableDefinition tableDefinition = this.tableDefinitions[tableName];
502 Table table = section.EnsureTable(tableDefinition);
503 Row row = table.CreateRow(sourceLineNumbers);
504
505 if (null != identifier)
506 {
507 row.Access = identifier.Access;
508 row[0] = identifier.Id;
509 }
510
511 return row;
512 }
513
514 /// <summary>
515 /// Creates directories using the inline directory syntax.
516 /// </summary>
517 /// <param name="sourceLineNumbers">Source line information.</param>
518 /// <param name="attribute">The attribute to parse.</param>
519 /// <param name="parentId">Optional identifier of parent directory.</param>
520 /// <returns>Identifier of the leaf directory created.</returns>
521 public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId)
522 {
523 string id = null;
524 string[] inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
525
526 if (null != inlineSyntax)
527 {
528 // Special case the single entry in the inline syntax since it is the most common case
529 // and needs no extra processing. It's just a reference to an existing directory.
530 if (1 == inlineSyntax.Length)
531 {
532 id = inlineSyntax[0];
533 this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
534 }
535 else // start creating rows for the entries in the inline syntax
536 {
537 id = parentId;
538
539 int pathStartsAt = 0;
540 if (inlineSyntax[0].EndsWith(":"))
541 {
542 // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide?
543 //if (null != parentId)
544 //{
545 // this.core.OnMessage(WixErrors.Xxx(sourceLineNumbers));
546 //}
547
548 id = inlineSyntax[0].TrimEnd(':');
549 this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
550
551 pathStartsAt = 1;
552 }
553
554 for (int i = pathStartsAt; i < inlineSyntax.Length; ++i)
555 {
556 Identifier inlineId = this.CreateDirectoryRow(sourceLineNumbers, null, id, inlineSyntax[i]);
557 id = inlineId.Id;
558 }
559 }
560 }
561
562 return id;
563 }
564
565 /// <summary>
566 /// Creates a patch resource reference to the list of resoures to be filtered when producing a patch. This method should only be used when processing children of a patch family.
567 /// </summary>
568 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
569 /// <param name="tableName">Name of table to create row in.</param>
570 /// <param name="primaryKeys">Array of keys that make up the primary key of the table.</param>
571 /// <returns>New row.</returns>
572 public void CreatePatchFamilyChildReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
573 {
574 Row patchReferenceRow = this.CreateRow(sourceLineNumbers, "WixPatchRef");
575 patchReferenceRow[0] = tableName;
576 patchReferenceRow[1] = String.Join("/", primaryKeys);
577 }
578
579 /// <summary>
580 /// Creates a Registry row in the active section.
581 /// </summary>
582 /// <param name="sourceLineNumbers">Source and line number of the current row.</param>
583 /// <param name="root">The registry entry root.</param>
584 /// <param name="key">The registry entry key.</param>
585 /// <param name="name">The registry entry name.</param>
586 /// <param name="value">The registry entry value.</param>
587 /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param>
588 /// <param name="escapeLeadingHash">If true, "escape" leading '#' characters so the value is written as a REG_SZ.</param>
589 public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId, bool escapeLeadingHash)
590 {
591 Identifier id = null;
592
593 if (!this.EncounteredError)
594 {
595 if (-1 > root || 3 < root)
596 {
597 throw new ArgumentOutOfRangeException("root");
598 }
599
600 if (null == key)
601 {
602 throw new ArgumentNullException("key");
603 }
604
605 if (null == componentId)
606 {
607 throw new ArgumentNullException("componentId");
608 }
609
610 // escape the leading '#' character for string registry values
611 if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal))
612 {
613 value = String.Concat("#", value);
614 }
615
616 id = this.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name));
617 Row row = this.CreateRow(sourceLineNumbers, "Registry", id);
618 row[1] = root;
619 row[2] = key;
620 row[3] = name;
621 row[4] = value;
622 row[5] = componentId;
623 }
624
625 return id;
626 }
627
628 /// <summary>
629 /// Creates a Registry row in the active section.
630 /// </summary>
631 /// <param name="sourceLineNumbers">Source and line number of the current row.</param>
632 /// <param name="root">The registry entry root.</param>
633 /// <param name="key">The registry entry key.</param>
634 /// <param name="name">The registry entry name.</param>
635 /// <param name="value">The registry entry value.</param>
636 /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param>
637 public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId)
638 {
639 return this.CreateRegistryRow(sourceLineNumbers, root, key, name, value, componentId, true);
640 }
641
642 /// <summary>
643 /// Create a WixSimpleReference row in the active section.
644 /// </summary>
645 /// <param name="sourceLineNumbers">Source line information for the row.</param>
646 /// <param name="tableName">The table name of the simple reference.</param>
647 /// <param name="primaryKeys">The primary keys of the simple reference.</param>
648 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
649 {
650 if (!this.EncounteredError)
651 {
652 string joinedKeys = String.Join("/", primaryKeys);
653 string id = String.Concat(tableName, ":", joinedKeys);
654
655 // If this simple reference hasn't been added to the active section already, add it.
656 if (this.activeSectionSimpleReferences.Add(id))
657 {
658 WixSimpleReferenceRow wixSimpleReferenceRow = (WixSimpleReferenceRow)this.CreateRow(sourceLineNumbers, "WixSimpleReference");
659 wixSimpleReferenceRow.TableName = tableName;
660 wixSimpleReferenceRow.PrimaryKeys = joinedKeys;
661 }
662 }
663 }
664
665 /// <summary>
666 /// A row in the WixGroup table is added for this child node and its parent node.
667 /// </summary>
668 /// <param name="sourceLineNumbers">Source line information for the row.</param>
669 /// <param name="parentType">Type of child's complex reference parent.</param>
670 /// <param name="parentId">Id of the parenet node.</param>
671 /// <param name="childType">Complex reference type of child</param>
672 /// <param name="childId">Id of the Child Node.</param>
673 public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId)
674 {
675 if (!this.EncounteredError)
676 {
677 if (null == parentId || ComplexReferenceParentType.Unknown == parentType)
678 {
679 return;
680 }
681
682 if (null == childId)
683 {
684 throw new ArgumentNullException("childId");
685 }
686
687 WixGroupRow WixGroupRow = (WixGroupRow)this.CreateRow(sourceLineNumbers, "WixGroup");
688 WixGroupRow.ParentId = parentId;
689 WixGroupRow.ParentType = parentType;
690 WixGroupRow.ChildId = childId;
691 WixGroupRow.ChildType = childType;
692 }
693 }
694
695 /// <summary>
696 /// Add the appropriate rows to make sure that the given table shows up
697 /// in the resulting output.
698 /// </summary>
699 /// <param name="sourceLineNumbers">Source line numbers.</param>
700 /// <param name="tableName">Name of the table to ensure existance of.</param>
701 public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName)
702 {
703 if (!this.EncounteredError)
704 {
705 Row row = this.CreateRow(sourceLineNumbers, "WixEnsureTable");
706 row[0] = tableName;
707
708 // We don't add custom table definitions to the tableDefinitions collection,
709 // so if it's not in there, it better be a custom table. If the Id is just wrong,
710 // instead of a custom table, we get an unresolved reference at link time.
711 if (!this.tableDefinitions.Contains(tableName))
712 {
713 this.CreateSimpleReference(sourceLineNumbers, "WixCustomTable", tableName);
714 }
715 }
716 }
717
718 /// <summary>
719 /// Get an attribute value.
720 /// </summary>
721 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
722 /// <param name="attribute">The attribute containing the value to get.</param>
723 /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param>
724 /// <returns>The attribute's value.</returns>
725 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
726 public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly)
727 {
728 return Common.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
729 }
730
731 /// <summary>
732 /// Get a valid code page by web name or number from a string attribute.
733 /// </summary>
734 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
735 /// <param name="attribute">The attribute containing the value to get.</param>
736 /// <returns>A valid code page integer value.</returns>
737 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
738 public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
739 {
740 if (null == attribute)
741 {
742 throw new ArgumentNullException("attribute");
743 }
744
745 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
746
747 try
748 {
749 int codePage = Common.GetValidCodePage(value);
750 return codePage;
751 }
752 catch (NotSupportedException)
753 {
754 this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
755 }
756
757 return CompilerConstants.IllegalInteger;
758 }
759
760 /// <summary>
761 /// Get a valid code page by web name or number from a string attribute.
762 /// </summary>
763 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
764 /// <param name="attribute">The attribute containing the value to get.</param>
765 /// <param name="onlyAscii">Whether to allow Unicode (UCS) or UTF code pages.</param>
766 /// <returns>A valid code page integer value or variable expression.</returns>
767 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
768 public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false)
769 {
770 if (null == attribute)
771 {
772 throw new ArgumentNullException("attribute");
773 }
774
775 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
776
777 // allow for localization of code page names and values
778 if (IsValidLocIdentifier(value))
779 {
780 return value;
781 }
782
783 try
784 {
785 int codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers);
786 return codePage.ToString(CultureInfo.InvariantCulture);
787 }
788 catch (NotSupportedException)
789 {
790 // not a valid windows code page
791 this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
792 }
793 catch (WixException e)
794 {
795 this.OnMessage(e.Error);
796 }
797
798 return null;
799 }
800
801 /// <summary>
802 /// Get an integer attribute value and displays an error for an illegal integer value.
803 /// </summary>
804 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
805 /// <param name="attribute">The attribute containing the value to get.</param>
806 /// <param name="minimum">The minimum legal value.</param>
807 /// <param name="maximum">The maximum legal value.</param>
808 /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns>
809 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
810 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
811 {
812 return Common.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum);
813 }
814
815 /// <summary>
816 /// Get a long integral attribute value and displays an error for an illegal long value.
817 /// </summary>
818 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
819 /// <param name="attribute">The attribute containing the value to get.</param>
820 /// <param name="minimum">The minimum legal value.</param>
821 /// <param name="maximum">The maximum legal value.</param>
822 /// <returns>The attribute's long value or a special value if an error occurred during conversion.</returns>
823 public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum)
824 {
825 Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing.");
826
827 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
828
829 if (0 < value.Length)
830 {
831 try
832 {
833 long longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat);
834
835 if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue)
836 {
837 this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, longValue));
838 }
839 else if (minimum > longValue || maximum < longValue)
840 {
841 this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum));
842 longValue = CompilerConstants.IllegalLong;
843 }
844
845 return longValue;
846 }
847 catch (FormatException)
848 {
849 this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
850 }
851 catch (OverflowException)
852 {
853 this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
854 }
855 }
856
857 return CompilerConstants.IllegalLong;
858 }
859
860 /// <summary>
861 /// Get a date time attribute value and display errors for illegal values.
862 /// </summary>
863 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
864 /// <param name="attribute">The attribute containing the value to get.</param>
865 /// <returns>Int representation of the date time.</returns>
866 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
867 public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
868 {
869 if (null == attribute)
870 {
871 throw new ArgumentNullException("attribute");
872 }
873
874 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
875
876 if (0 < value.Length)
877 {
878 try
879 {
880 DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat);
881
882 return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) +
883 (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2);
884 }
885 catch (ArgumentOutOfRangeException)
886 {
887 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
888 }
889 catch (FormatException)
890 {
891 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
892 }
893 catch (OverflowException)
894 {
895 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
896 }
897 }
898
899 return CompilerConstants.IllegalInteger;
900 }
901
902 /// <summary>
903 /// Get an integer attribute value or localize variable and displays an error for
904 /// an illegal value.
905 /// </summary>
906 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
907 /// <param name="attribute">The attribute containing the value to get.</param>
908 /// <param name="minimum">The minimum legal value.</param>
909 /// <param name="maximum">The maximum legal value.</param>
910 /// <returns>The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion.</returns>
911 public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
912 {
913 if (null == attribute)
914 {
915 throw new ArgumentNullException("attribute");
916 }
917
918 Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
919
920 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
921
922 if (0 < value.Length)
923 {
924 if (IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value))
925 {
926 return value;
927 }
928 else
929 {
930 try
931 {
932 int integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat);
933
934 if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
935 {
936 this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, integer));
937 }
938 else if (minimum > integer || maximum < integer)
939 {
940 this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
941 integer = CompilerConstants.IllegalInteger;
942 }
943
944 return value;
945 }
946 catch (FormatException)
947 {
948 this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
949 }
950 catch (OverflowException)
951 {
952 this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
953 }
954 }
955 }
956
957 return null;
958 }
959
960 /// <summary>
961 /// Get a guid attribute value and displays an error for an illegal guid value.
962 /// </summary>
963 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
964 /// <param name="attribute">The attribute containing the value to get.</param>
965 /// <param name="generatable">Determines whether the guid can be automatically generated.</param>
966 /// <param name="canBeEmpty">If true, no error is raised on empty value. If false, an error is raised.</param>
967 /// <returns>The attribute's guid value or a special value if an error occurred.</returns>
968 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
969 [SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation")]
970 public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false)
971 {
972 if (null == attribute)
973 {
974 throw new ArgumentNullException("attribute");
975 }
976
977 EmptyRule emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly;
978 string value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
979
980 if (String.IsNullOrEmpty(value) && canBeEmpty)
981 {
982 return String.Empty;
983 }
984 else if (!String.IsNullOrEmpty(value))
985 {
986 // If the value starts and ends with braces or parenthesis, accept that and strip them off.
987 if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal))
988 || (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal)))
989 {
990 value = value.Substring(1, value.Length - 2);
991 }
992
993 try
994 {
995 Guid guid;
996
997 if (generatable && "*".Equals(value, StringComparison.Ordinal))
998 {
999 return value;
1000 }
1001
1002 if (CompilerCore.PutGuidHere.IsMatch(value))
1003 {
1004 this.OnMessage(WixErrors.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1005 return CompilerConstants.IllegalGuid;
1006 }
1007 else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
1008 {
1009 return value;
1010 }
1011 else
1012 {
1013 guid = new Guid(value);
1014 }
1015
1016 string uppercaseGuid = guid.ToString().ToUpper(CultureInfo.InvariantCulture);
1017
1018 if (this.showPedanticMessages)
1019 {
1020 if (uppercaseGuid != value)
1021 {
1022 this.OnMessage(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1023 }
1024 }
1025
1026 return String.Concat("{", uppercaseGuid, "}");
1027 }
1028 catch (FormatException)
1029 {
1030 this.OnMessage(WixErrors.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1031 }
1032 }
1033
1034 return CompilerConstants.IllegalGuid;
1035 }
1036
1037 /// <summary>
1038 /// Get an identifier attribute value and displays an error for an illegal identifier value.
1039 /// </summary>
1040 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1041 /// <param name="attribute">The attribute containing the value to get.</param>
1042 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
1043 public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1044 {
1045 string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeEmpty);
1046 AccessModifier access = AccessModifier.Public;
1047
1048 Match match = CompilerCore.LegalIdentifierWithAccess.Match(value);
1049 if (!match.Success)
1050 {
1051 return null;
1052 }
1053 else if (match.Groups["access"].Success)
1054 {
1055 access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true);
1056 }
1057
1058 value = match.Groups["id"].Value;
1059
1060 if (Common.IsIdentifier(value) && 72 < value.Length)
1061 {
1062 this.OnMessage(WixWarnings.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1063 }
1064
1065 return new Identifier(value, access);
1066 }
1067
1068 /// <summary>
1069 /// Get an identifier attribute value and displays an error for an illegal identifier value.
1070 /// </summary>
1071 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1072 /// <param name="attribute">The attribute containing the value to get.</param>
1073 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
1074 public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1075 {
1076 return Common.GetAttributeIdentifierValue(sourceLineNumbers, attribute);
1077 }
1078
1079 /// <summary>
1080 /// Gets a yes/no value and displays an error for an illegal yes/no value.
1081 /// </summary>
1082 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1083 /// <param name="attribute">The attribute containing the value to get.</param>
1084 /// <returns>The attribute's YesNoType value.</returns>
1085 public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1086 {
1087 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1088
1089 YesNoType result = YesNoType.IllegalValue;
1090 if (value.Equals("yes") || value.Equals("true"))
1091 {
1092 result = YesNoType.Yes;
1093 }
1094 else if (value.Equals("no") || value.Equals("false"))
1095 {
1096 result = YesNoType.No;
1097 }
1098 else
1099 {
1100 this.OnMessage(WixErrors.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1101 }
1102
1103 return result;
1104 }
1105
1106 /// <summary>
1107 /// Gets a yes/no/default value and displays an error for an illegal yes/no value.
1108 /// </summary>
1109 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1110 /// <param name="attribute">The attribute containing the value to get.</param>
1111 /// <returns>The attribute's YesNoDefaultType value.</returns>
1112 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1113 public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1114 {
1115 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1116
1117 if (0 < value.Length)
1118 {
1119 switch (Wix.Enums.ParseYesNoDefaultType(value))
1120 {
1121 case Wix.YesNoDefaultType.@default:
1122 return YesNoDefaultType.Default;
1123 case Wix.YesNoDefaultType.no:
1124 return YesNoDefaultType.No;
1125 case Wix.YesNoDefaultType.yes:
1126 return YesNoDefaultType.Yes;
1127 case Wix.YesNoDefaultType.NotSet:
1128 // Previous code never returned 'NotSet'!
1129 break;
1130 default:
1131 this.OnMessage(WixErrors.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1132 break;
1133 }
1134 }
1135
1136 return YesNoDefaultType.IllegalValue;
1137 }
1138
1139 /// <summary>
1140 /// Gets a yes/no/always value and displays an error for an illegal value.
1141 /// </summary>
1142 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1143 /// <param name="attribute">The attribute containing the value to get.</param>
1144 /// <returns>The attribute's YesNoAlwaysType value.</returns>
1145 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1146 public YesNoAlwaysType GetAttributeYesNoAlwaysValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1147 {
1148 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1149
1150 if (0 < value.Length)
1151 {
1152 switch (Wix.Enums.ParseYesNoAlwaysType(value))
1153 {
1154 case Wix.YesNoAlwaysType.@always:
1155 return YesNoAlwaysType.Always;
1156 case Wix.YesNoAlwaysType.no:
1157 return YesNoAlwaysType.No;
1158 case Wix.YesNoAlwaysType.yes:
1159 return YesNoAlwaysType.Yes;
1160 case Wix.YesNoAlwaysType.NotSet:
1161 // Previous code never returned 'NotSet'!
1162 break;
1163 default:
1164 this.OnMessage(WixErrors.IllegalYesNoAlwaysValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1165 break;
1166 }
1167 }
1168
1169 return YesNoAlwaysType.IllegalValue;
1170 }
1171
1172 /// <summary>
1173 /// Gets a short filename value and displays an error for an illegal short filename value.
1174 /// </summary>
1175 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1176 /// <param name="attribute">The attribute containing the value to get.</param>
1177 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
1178 /// <returns>The attribute's short filename value.</returns>
1179 public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards)
1180 {
1181 if (null == attribute)
1182 {
1183 throw new ArgumentNullException("attribute");
1184 }
1185
1186 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1187
1188 if (0 < value.Length)
1189 {
1190 if (!this.IsValidShortFilename(value, allowWildcards) && !this.IsValidLocIdentifier(value))
1191 {
1192 this.OnMessage(WixErrors.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1193 }
1194 else if (CompilerCore.IsAmbiguousFilename(value))
1195 {
1196 this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1197 }
1198 }
1199
1200 return value;
1201 }
1202
1203 /// <summary>
1204 /// Gets a long filename value and displays an error for an illegal long filename value.
1205 /// </summary>
1206 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1207 /// <param name="attribute">The attribute containing the value to get.</param>
1208 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
1209 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
1210 /// <returns>The attribute's long filename value.</returns>
1211 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1212 public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false)
1213 {
1214 if (null == attribute)
1215 {
1216 throw new ArgumentNullException("attribute");
1217 }
1218
1219 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1220
1221 if (0 < value.Length)
1222 {
1223 if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value))
1224 {
1225 if (allowRelative)
1226 {
1227 this.OnMessage(WixErrors.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1228 }
1229 else
1230 {
1231 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1232 }
1233 }
1234 else if (allowRelative)
1235 {
1236 string normalizedPath = value.Replace('\\', '/');
1237 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
1238 {
1239 this.OnMessage(WixErrors.PayloadMustBeRelativeToCache(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1240 }
1241 }
1242 else if (CompilerCore.IsAmbiguousFilename(value))
1243 {
1244 this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1245 }
1246 }
1247
1248 return value;
1249 }
1250
1251 /// <summary>
1252 /// Gets a version value or possibly a binder variable and displays an error for an illegal version value.
1253 /// </summary>
1254 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1255 /// <param name="attribute">The attribute containing the value to get.</param>
1256 /// <returns>The attribute's version value.</returns>
1257 public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1258 {
1259 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1260
1261 if (!String.IsNullOrEmpty(value))
1262 {
1263 try
1264 {
1265 return new Version(value).ToString();
1266 }
1267 catch (FormatException) // illegal integer in version
1268 {
1269 // Allow versions to contain binder variables.
1270 if (Common.ContainsValidBinderVariable(value))
1271 {
1272 return value;
1273 }
1274
1275 this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1276 }
1277 catch (ArgumentException)
1278 {
1279 this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1280 }
1281 }
1282
1283 return null;
1284 }
1285
1286 /// <summary>
1287 /// Gets a RegistryRoot value and displays an error for an illegal value.
1288 /// </summary>
1289 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1290 /// <param name="attribute">The attribute containing the value to get.</param>
1291 /// <param name="allowHkmu">Whether HKMU is considered a valid value.</param>
1292 /// <returns>The attribute's RegisitryRootType value.</returns>
1293 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1294 public Wix.RegistryRootType GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
1295 {
1296 Wix.RegistryRootType registryRoot = Wix.RegistryRootType.NotSet;
1297 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1298
1299 if (0 < value.Length)
1300 {
1301 registryRoot = Wix.Enums.ParseRegistryRootType(value);
1302
1303 if (Wix.RegistryRootType.IllegalValue == registryRoot || (!allowHkmu && Wix.RegistryRootType.HKMU == registryRoot))
1304 {
1305 // TODO: Find a way to expose the valid values programatically!
1306 if (allowHkmu)
1307 {
1308 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1309 "HKMU", "HKCR", "HKCU", "HKLM", "HKU"));
1310 }
1311 else
1312 {
1313 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1314 "HKCR", "HKCU", "HKLM", "HKU"));
1315 }
1316 }
1317 }
1318
1319 return registryRoot;
1320 }
1321
1322 /// <summary>
1323 /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value.
1324 /// </summary>
1325 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1326 /// <param name="attribute">The attribute containing the value to get.</param>
1327 /// <param name="allowHkmu">Whether HKMU is returned as -1 (true), or treated as an error (false).</param>
1328 /// <returns>The attribute's RegisitryRootType value.</returns>
1329 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1330 public int GetAttributeMsidbRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
1331 {
1332 Wix.RegistryRootType registryRoot = this.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu);
1333
1334 switch (registryRoot)
1335 {
1336 case Wix.RegistryRootType.NotSet:
1337 return CompilerConstants.IntegerNotSet;
1338 case Wix.RegistryRootType.HKCR:
1339 return Core.Native.MsiInterop.MsidbRegistryRootClassesRoot;
1340 case Wix.RegistryRootType.HKCU:
1341 return Core.Native.MsiInterop.MsidbRegistryRootCurrentUser;
1342 case Wix.RegistryRootType.HKLM:
1343 return Core.Native.MsiInterop.MsidbRegistryRootLocalMachine;
1344 case Wix.RegistryRootType.HKU:
1345 return Core.Native.MsiInterop.MsidbRegistryRootUsers;
1346 case Wix.RegistryRootType.HKMU:
1347 // This is gross, but there was *one* registry root parsing instance
1348 // (in Compiler.ParseRegistrySearchElement()) that did not explicitly
1349 // handle HKMU and it fell through to the default error case. The
1350 // others treated it as -1, which is what we do here.
1351 if (allowHkmu)
1352 {
1353 return -1;
1354 }
1355 break;
1356 }
1357
1358 return CompilerConstants.IntegerNotSet;
1359 }
1360
1361 /// <summary>
1362 /// Gets an InstallUninstallType value and displays an error for an illegal value.
1363 /// </summary>
1364 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1365 /// <param name="attribute">The attribute containing the value to get.</param>
1366 /// <returns>The attribute's InstallUninstallType value.</returns>
1367 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1368 public Wix.InstallUninstallType GetAttributeInstallUninstallValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1369 {
1370 Wix.InstallUninstallType installUninstall = Wix.InstallUninstallType.NotSet;
1371 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1372
1373 if (0 < value.Length)
1374 {
1375 installUninstall = Wix.Enums.ParseInstallUninstallType(value);
1376
1377 if (Wix.InstallUninstallType.IllegalValue == installUninstall)
1378 {
1379 // TODO: Find a way to expose the valid values programatically!
1380 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1381 "install", "uninstall", "both"));
1382 }
1383 }
1384
1385 return installUninstall;
1386 }
1387
1388 /// <summary>
1389 /// Gets an ExitType value and displays an error for an illegal value.
1390 /// </summary>
1391 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1392 /// <param name="attribute">The attribute containing the value to get.</param>
1393 /// <returns>The attribute's ExitType value.</returns>
1394 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1395 public Wix.ExitType GetAttributeExitValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1396 {
1397 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1398
1399 Wix.ExitType result = Wix.ExitType.NotSet;
1400 if (!Enum.TryParse<Wix.ExitType>(value, out result))
1401 {
1402 result = Wix.ExitType.IllegalValue;
1403
1404 // TODO: Find a way to expose the valid values programatically!
1405 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1406 "success", "cancel", "error", "suspend"));
1407 }
1408
1409 return result;
1410 }
1411
1412 /// <summary>
1413 /// Gets a Bundle variable value and displays an error for an illegal value.
1414 /// </summary>
1415 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1416 /// <param name="attribute">The attribute containing the value to get.</param>
1417 /// <returns>The attribute's value.</returns>
1418 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1419 public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1420 {
1421 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1422
1423 if (!String.IsNullOrEmpty(value))
1424 {
1425 if (CompilerCore.BuiltinBundleVariables.Contains(value))
1426 {
1427 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables);
1428 this.OnMessage(WixErrors.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues));
1429 }
1430 }
1431
1432 return value;
1433 }
1434
1435 /// <summary>
1436 /// Gets an MsiProperty name value and displays an error for an illegal value.
1437 /// </summary>
1438 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1439 /// <param name="attribute">The attribute containing the value to get.</param>
1440 /// <returns>The attribute's value.</returns>
1441 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1442 public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1443 {
1444 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1445
1446 if (0 < value.Length)
1447 {
1448 if (CompilerCore.DisallowedMsiProperties.Contains(value))
1449 {
1450 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties);
1451 this.OnMessage(WixErrors.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues));
1452 }
1453 }
1454
1455 return value;
1456 }
1457
1458 /// <summary>
1459 /// Checks if the string contains a property (i.e. "foo[Property]bar")
1460 /// </summary>
1461 /// <param name="possibleProperty">String to evaluate for properties.</param>
1462 /// <returns>True if a property is found in the string.</returns>
1463 public bool ContainsProperty(string possibleProperty)
1464 {
1465 return Common.ContainsProperty(possibleProperty);
1466 }
1467
1468 /// <summary>
1469 /// Generate an identifier by hashing data from the row.
1470 /// </summary>
1471 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
1472 /// <param name="args">Information to hash.</param>
1473 /// <returns>The generated identifier.</returns>
1474 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
1475 public Identifier CreateIdentifier(string prefix, params string[] args)
1476 {
1477 string id = Common.GenerateIdentifier(prefix, args);
1478 return new Identifier(id, AccessModifier.Private);
1479 }
1480
1481 /// <summary>
1482 /// Create an identifier based on passed file name
1483 /// </summary>
1484 /// <param name="name">File name to generate identifer from</param>
1485 /// <returns></returns>
1486 public Identifier CreateIdentifierFromFilename(string filename)
1487 {
1488 string id = Common.GetIdentifierFromName(filename);
1489 return new Identifier(id, AccessModifier.Private);
1490 }
1491
1492 /// <summary>
1493 /// Attempts to use an extension to parse the attribute.
1494 /// </summary>
1495 /// <param name="element">Element containing attribute to be parsed.</param>
1496 /// <param name="attribute">Attribute to be parsed.</param>
1497 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
1498 public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary<string, string> context = null)
1499 {
1500 // Ignore attributes defined by the W3C because we'll assume they are always right.
1501 if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
1502 attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))
1503 {
1504 return;
1505 }
1506
1507 ICompilerExtension extension;
1508 if (this.TryFindExtension(attribute.Name.NamespaceName, out extension))
1509 {
1510 extension.ParseAttribute(element, attribute, context);
1511 }
1512 else
1513 {
1514 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1515 this.OnMessage(WixErrors.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName));
1516 }
1517 }
1518
1519 /// <summary>
1520 /// Attempts to use an extension to parse the element.
1521 /// </summary>
1522 /// <param name="parentElement">Element containing element to be parsed.</param>
1523 /// <param name="element">Element to be parsed.</param>
1524 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
1525 public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context = null)
1526 {
1527 ICompilerExtension extension;
1528 if (this.TryFindExtension(element.Name.Namespace, out extension))
1529 {
1530 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement);
1531 extension.ParseElement(parentElement, element, context);
1532 }
1533 else
1534 {
1535 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1536 this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
1537 }
1538 }
1539
1540 /// <summary>
1541 /// Process all children of the element looking for extensions and erroring on the unexpected.
1542 /// </summary>
1543 /// <param name="element">Element to parse children.</param>
1544 public void ParseForExtensionElements(XElement element)
1545 {
1546 foreach (XElement child in element.Elements())
1547 {
1548 if (element.Name.Namespace == child.Name.Namespace)
1549 {
1550 this.UnexpectedElement(element, child);
1551 }
1552 else
1553 {
1554 this.ParseExtensionElement(element, child);
1555 }
1556 }
1557 }
1558
1559 /// <summary>
1560 /// Attempts to use an extension to parse the element, with support for setting component keypath.
1561 /// </summary>
1562 /// <param name="parentElement">Element containing element to be parsed.</param>
1563 /// <param name="element">Element to be parsed.</param>
1564 /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param>
1565 public ComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context)
1566 {
1567 ComponentKeyPath keyPath = null;
1568
1569 ICompilerExtension extension;
1570 if (this.TryFindExtension(element.Name.Namespace, out extension))
1571 {
1572 keyPath = extension.ParsePossibleKeyPathElement(parentElement, element, context);
1573 }
1574 else
1575 {
1576 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1577 this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
1578 }
1579
1580 return keyPath;
1581 }
1582
1583 /// <summary>
1584 /// Displays an unexpected attribute error if the attribute is not the namespace attribute.
1585 /// </summary>
1586 /// <param name="element">Element containing unexpected attribute.</param>
1587 /// <param name="attribute">The unexpected attribute.</param>
1588 public void UnexpectedAttribute(XElement element, XAttribute attribute)
1589 {
1590 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1591 Common.UnexpectedAttribute(sourceLineNumbers, attribute);
1592 }
1593
1594 /// <summary>
1595 /// Display an unexepected element error.
1596 /// </summary>
1597 /// <param name="parentElement">The parent element.</param>
1598 /// <param name="childElement">The unexpected child element.</param>
1599 public void UnexpectedElement(XElement parentElement, XElement childElement)
1600 {
1601 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement);
1602
1603 this.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName));
1604 }
1605
1606 /// <summary>
1607 /// Sends a message to the message delegate if there is one.
1608 /// </summary>
1609 /// <param name="mea">Message event arguments.</param>
1610 public void OnMessage(MessageEventArgs e)
1611 {
1612 Messaging.Instance.OnMessage(e);
1613 }
1614
1615 /// <summary>
1616 /// Verifies that the calling assembly version is equal to or newer than the given <paramref name="requiredVersion"/>.
1617 /// </summary>
1618 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1619 /// <param name="requiredVersion">The version required of the calling assembly.</param>
1620 internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion)
1621 {
1622 // an null or empty string means any version will work
1623 if (!string.IsNullOrEmpty(requiredVersion))
1624 {
1625 Assembly caller = Assembly.GetCallingAssembly();
1626 AssemblyName name = caller.GetName();
1627 FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location);
1628
1629 Version versionRequired = new Version(requiredVersion);
1630 Version versionCurrent = new Version(fv.FileVersion);
1631
1632 if (versionRequired > versionCurrent)
1633 {
1634 if (this.GetType().Assembly.Equals(caller))
1635 {
1636 this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired));
1637 }
1638 else
1639 {
1640 this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name));
1641 }
1642 }
1643 }
1644 }
1645
1646 /// <summary>
1647 /// Creates a new section and makes it the active section in the core.
1648 /// </summary>
1649 /// <param name="id">Unique identifier for the section.</param>
1650 /// <param name="type">Type of section to create.</param>
1651 /// <param name="codepage">Codepage for the resulting database for this ection.</param>
1652 /// <returns>New section.</returns>
1653 internal Section CreateActiveSection(string id, SectionType type, int codepage)
1654 {
1655 this.ActiveSection = this.CreateSection(id, type, codepage);
1656
1657 this.activeSectionInlinedDirectoryIds = new HashSet<string>();
1658 this.activeSectionSimpleReferences = new HashSet<string>();
1659
1660 return this.ActiveSection;
1661 }
1662
1663 /// <summary>
1664 /// Creates a new section.
1665 /// </summary>
1666 /// <param name="id">Unique identifier for the section.</param>
1667 /// <param name="type">Type of section to create.</param>
1668 /// <param name="codepage">Codepage for the resulting database for this ection.</param>
1669 /// <returns>New section.</returns>
1670 internal Section CreateSection(string id, SectionType type, int codepage)
1671 {
1672 Section newSection = new Section(id, type, codepage);
1673 this.intermediate.AddSection(newSection);
1674
1675 return newSection;
1676 }
1677
1678 /// <summary>
1679 /// Creates a WiX complex reference in the active section.
1680 /// </summary>
1681 /// <param name="sourceLineNumbers">Source line information.</param>
1682 /// <param name="parentType">The parent type.</param>
1683 /// <param name="parentId">The parent id.</param>
1684 /// <param name="parentLanguage">The parent language.</param>
1685 /// <param name="childType">The child type.</param>
1686 /// <param name="childId">The child id.</param>
1687 /// <param name="isPrimary">Whether the child is primary.</param>
1688 internal void CreateWixComplexReferenceRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
1689 {
1690 if (!this.EncounteredError)
1691 {
1692 WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)this.CreateRow(sourceLineNumbers, "WixComplexReference");
1693 wixComplexReferenceRow.ParentId = parentId;
1694 wixComplexReferenceRow.ParentType = parentType;
1695 wixComplexReferenceRow.ParentLanguage = parentLanguage;
1696 wixComplexReferenceRow.ChildId = childId;
1697 wixComplexReferenceRow.ChildType = childType;
1698 wixComplexReferenceRow.IsPrimary = isPrimary;
1699 }
1700 }
1701
1702 /// <summary>
1703 /// Creates WixComplexReference and WixGroup rows in the active section.
1704 /// </summary>
1705 /// <param name="sourceLineNumbers">Source line information.</param>
1706 /// <param name="parentType">The parent type.</param>
1707 /// <param name="parentId">The parent id.</param>
1708 /// <param name="parentLanguage">The parent language.</param>
1709 /// <param name="childType">The child type.</param>
1710 /// <param name="childId">The child id.</param>
1711 /// <param name="isPrimary">Whether the child is primary.</param>
1712 public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
1713 {
1714 this.CreateWixComplexReferenceRow(sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary);
1715 this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, childType, childId);
1716 }
1717
1718 /// <summary>
1719 /// Creates a directory row from a name.
1720 /// </summary>
1721 /// <param name="sourceLineNumbers">Source line information.</param>
1722 /// <param name="id">Optional identifier for the new row.</param>
1723 /// <param name="parentId">Optional identifier for the parent row.</param>
1724 /// <param name="name">Long name of the directory.</param>
1725 /// <param name="shortName">Optional short name of the directory.</param>
1726 /// <param name="sourceName">Optional source name for the directory.</param>
1727 /// <param name="shortSourceName">Optional short source name for the directory.</param>
1728 /// <returns>Identifier for the newly created row.</returns>
1729 internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1730 {
1731 string defaultDir = null;
1732
1733 if (name.Equals("SourceDir") || this.IsValidShortFilename(name, false))
1734 {
1735 defaultDir = name;
1736 }
1737 else
1738 {
1739 if (String.IsNullOrEmpty(shortName))
1740 {
1741 shortName = this.CreateShortName(name, false, false, "Directory", parentId);
1742 }
1743
1744 defaultDir = String.Concat(shortName, "|", name);
1745 }
1746
1747 if (!String.IsNullOrEmpty(sourceName))
1748 {
1749 if (this.IsValidShortFilename(sourceName, false))
1750 {
1751 defaultDir = String.Concat(defaultDir, ":", sourceName);
1752 }
1753 else
1754 {
1755 if (String.IsNullOrEmpty(shortSourceName))
1756 {
1757 shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId);
1758 }
1759
1760 defaultDir = String.Concat(defaultDir, ":", shortSourceName, "|", sourceName);
1761 }
1762 }
1763
1764 // For anonymous directories, create the identifier. If this identifier already exists in the
1765 // active section, bail so we don't add duplicate anonymous directory rows (which are legal
1766 // but bloat the intermediate and ultimately make the linker do "busy work").
1767 if (null == id)
1768 {
1769 id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
1770
1771 if (!this.activeSectionInlinedDirectoryIds.Add(id.Id))
1772 {
1773 return id;
1774 }
1775 }
1776
1777 Row row = this.CreateRow(sourceLineNumbers, "Directory", id);
1778 row[1] = parentId;
1779 row[2] = defaultDir;
1780 return id;
1781 }
1782
1783 /// <summary>
1784 /// Gets the attribute value as inline directory syntax.
1785 /// </summary>
1786 /// <param name="sourceLineNumbers">Source line information.</param>
1787 /// <param name="attribute">Attribute containing the value to get.</param>
1788 /// <param name="resultUsedToCreateReference">Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference.</param>
1789 /// <returns>Inline directory syntax split into array of strings or null if the syntax did not parse.</returns>
1790 internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
1791 {
1792 string[] result = null;
1793 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1794
1795 if (!String.IsNullOrEmpty(value))
1796 {
1797 int pathStartsAt = 0;
1798 result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
1799 if (result[0].EndsWith(":", StringComparison.Ordinal))
1800 {
1801 string id = result[0].TrimEnd(':');
1802 if (1 == result.Length)
1803 {
1804 this.OnMessage(WixErrors.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
1805 return null;
1806 }
1807 else if (!this.IsValidIdentifier(id))
1808 {
1809 this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
1810 return null;
1811 }
1812
1813 pathStartsAt = 1;
1814 }
1815 else if (resultUsedToCreateReference && 1 == result.Length)
1816 {
1817 if (value.EndsWith("\\"))
1818 {
1819 if (!this.IsValidLongFilename(result[0]))
1820 {
1821 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
1822 return null;
1823 }
1824 }
1825 else if (!this.IsValidIdentifier(result[0]))
1826 {
1827 this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
1828 return null;
1829 }
1830
1831 return result; // return early to avoid additional checks below.
1832 }
1833
1834 // Check each part of the relative path to ensure that it is a valid directory name.
1835 for (int i = pathStartsAt; i < result.Length; ++i)
1836 {
1837 if (!this.IsValidLongFilename(result[i]))
1838 {
1839 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i]));
1840 return null;
1841 }
1842 }
1843
1844 if (1 < result.Length && !value.EndsWith("\\"))
1845 {
1846 this.OnMessage(WixWarnings.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1847 }
1848 }
1849
1850 return result;
1851 }
1852
1853 /// <summary>
1854 /// Finds a compiler extension by namespace URI.
1855 /// </summary>
1856 /// <param name="ns">Namespace the extension supports.</param>
1857 /// <returns>True if found compiler extension or false if nothing matches namespace URI.</returns>
1858 private bool TryFindExtension(XNamespace ns, out ICompilerExtension extension)
1859 {
1860 return this.extensions.TryGetValue(ns, out extension);
1861 }
1862
1863 private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
1864 {
1865 // Ideally, we could denote the list kind (and the list itself) directly in the
1866 // message XML, and detect and expand in the MessageHandler.GenerateMessageString()
1867 // method. Doing so would make vararg-style messages much easier, but impacts
1868 // every single message we format. For now, callers just have to know when a
1869 // message takes a list of values in a single string argument, the caller will
1870 // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
1871 // that the list is an 'and' or 'or' list.)
1872
1873 // For a localizable solution, we need to be able to get the list format string
1874 // from resources. We aren't currently localized right now, so the values are
1875 // just hard-coded.
1876 const string valueFormat = "'{0}'";
1877 const string valueSeparator = ", ";
1878 string terminalTerm = String.Empty;
1879
1880 switch (kind)
1881 {
1882 case ValueListKind.None:
1883 terminalTerm = "";
1884 break;
1885 case ValueListKind.And:
1886 terminalTerm = "and ";
1887 break;
1888 case ValueListKind.Or:
1889 terminalTerm = "or ";
1890 break;
1891 }
1892
1893 StringBuilder list = new StringBuilder();
1894
1895 // This weird construction helps us determine when we're adding the last value
1896 // to the list. Instead of adding them as we encounter them, we cache the current
1897 // value and append the *previous* one.
1898 string previousValue = null;
1899 bool haveValues = false;
1900 foreach (string value in values)
1901 {
1902 if (null != previousValue)
1903 {
1904 if (haveValues)
1905 {
1906 list.Append(valueSeparator);
1907 }
1908 list.AppendFormat(valueFormat, previousValue);
1909 haveValues = true;
1910 }
1911
1912 previousValue = value;
1913 }
1914
1915 // If we have no previous value, that means that the list contained no values, and
1916 // something has gone very wrong.
1917 Debug.Assert(null != previousValue);
1918 if (null != previousValue)
1919 {
1920 if (haveValues)
1921 {
1922 list.Append(valueSeparator);
1923 list.Append(terminalTerm);
1924 }
1925 list.AppendFormat(valueFormat, previousValue);
1926 haveValues = true;
1927 }
1928
1929 return list.ToString();
1930 }
1931 }
1932}