diff options
Diffstat (limited to 'src/WixToolset.Core/CompilerCore.cs')
-rw-r--r-- | src/WixToolset.Core/CompilerCore.cs | 1932 |
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 | |||
3 | namespace 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 | } | ||