aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Preprocess
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-09-17 15:35:20 -0700
committerRob Mensching <rob@firegiant.com>2017-09-17 16:00:11 -0700
commitd3d3649a68cb1fa589fdd987a6690dbd5d671f0d (patch)
tree44fe37ee352b7e3a355cc1e08b1d7d5988c14cc0 /src/WixToolset.Core/Preprocess
parenta62610d23d6feb98be3b1e529a4e81b19d77d9d8 (diff)
downloadwix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.gz
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.bz2
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.zip
Initial code commit
Diffstat (limited to '')
-rw-r--r--src/WixToolset.Core/Preprocess/IfContext.cs110
-rw-r--r--src/WixToolset.Core/Preprocessor.cs1598
-rw-r--r--src/WixToolset.Core/PreprocessorCore.cs560
3 files changed, 2268 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Preprocess/IfContext.cs b/src/WixToolset.Core/Preprocess/IfContext.cs
new file mode 100644
index 00000000..64b5bd91
--- /dev/null
+++ b/src/WixToolset.Core/Preprocess/IfContext.cs
@@ -0,0 +1,110 @@
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.Preprocess
4{
5 using System;
6
7 /// <summary>
8 /// Current state of the if context.
9 /// </summary>
10 internal enum IfState
11 {
12 /// <summary>Context currently in unknown state.</summary>
13 Unknown,
14
15 /// <summary>Context currently inside if statement.</summary>
16 If,
17
18 /// <summary>Context currently inside elseif statement..</summary>
19 ElseIf,
20
21 /// <summary>Conext currently inside else statement.</summary>
22 Else,
23 }
24
25 /// <summary>
26 /// Context for an if statement in the preprocessor.
27 /// </summary>
28 internal sealed class IfContext
29 {
30 private bool active;
31 private bool keep;
32 private bool everKept;
33 private IfState state;
34
35 /// <summary>
36 /// Creates a default if context object, which are used for if's within an inactive preprocessor block
37 /// </summary>
38 public IfContext()
39 {
40 this.active = false;
41 this.keep = false;
42 this.everKept = true;
43 this.state = IfState.If;
44 }
45
46 /// <summary>
47 /// Creates an if context object.
48 /// </summary>
49 /// <param name="active">Flag if context is currently active.</param>
50 /// <param name="keep">Flag if context is currently true.</param>
51 /// <param name="state">State of context to start in.</param>
52 public IfContext(bool active, bool keep, IfState state)
53 {
54 this.active = active;
55 this.keep = keep;
56 this.everKept = keep;
57 this.state = state;
58 }
59
60 /// <summary>
61 /// Gets and sets if this if context is currently active.
62 /// </summary>
63 /// <value>true if context is active.</value>
64 public bool Active
65 {
66 get { return this.active; }
67 set { this.active = value; }
68 }
69
70 /// <summary>
71 /// Gets and sets if context is current true.
72 /// </summary>
73 /// <value>true if context is currently true.</value>
74 public bool IsTrue
75 {
76 get
77 {
78 return this.keep;
79 }
80
81 set
82 {
83 this.keep = value;
84 if (this.keep)
85 {
86 this.everKept = true;
87 }
88 }
89 }
90
91 /// <summary>
92 /// Gets if the context was ever true.
93 /// </summary>
94 /// <value>True if context was ever true.</value>
95 public bool WasEverTrue
96 {
97 get { return this.everKept; }
98 }
99
100 /// <summary>
101 /// Gets the current state of the if context.
102 /// </summary>
103 /// <value>Current state of context.</value>
104 public IfState IfState
105 {
106 get { return this.state; }
107 set { this.state = value; }
108 }
109 }
110}
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
new file mode 100644
index 00000000..a9fbcbb7
--- /dev/null
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -0,0 +1,1598 @@
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.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 using System.Text;
12 using System.Text.RegularExpressions;
13 using System.Xml;
14 using System.Xml.Linq;
15 using WixToolset.Data;
16 using WixToolset.Extensibility;
17 using WixToolset.Preprocess;
18
19 /// <summary>
20 /// Preprocessor object
21 /// </summary>
22 public sealed class Preprocessor
23 {
24 private readonly Regex defineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
25 private readonly Regex pragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
26
27 private readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings()
28 {
29 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
30 XmlResolver = null,
31 };
32 private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
33 {
34 ConformanceLevel = System.Xml.ConformanceLevel.Fragment,
35 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
36 XmlResolver = null,
37 };
38
39 private List<IPreprocessorExtension> extensions;
40 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
41 private List<InspectorExtension> inspectorExtensions;
42
43 private SourceLineNumber currentLineNumber;
44 private Stack<SourceLineNumber> sourceStack;
45
46 private PreprocessorCore core;
47 private TextWriter preprocessOut;
48
49 private Stack<bool> includeNextStack;
50 private Stack<string> currentFileStack;
51
52 private Platform currentPlatform;
53
54 /// <summary>
55 /// Creates a new preprocesor.
56 /// </summary>
57 public Preprocessor()
58 {
59 this.IncludeSearchPaths = new List<string>();
60
61 this.extensions = new List<IPreprocessorExtension>();
62 this.extensionsByPrefix = new Dictionary<string, IPreprocessorExtension>();
63 this.inspectorExtensions = new List<InspectorExtension>();
64
65 this.sourceStack = new Stack<SourceLineNumber>();
66
67 this.includeNextStack = new Stack<bool>();
68 this.currentFileStack = new Stack<string>();
69
70 this.currentPlatform = Platform.X86;
71 }
72
73 /// <summary>
74 /// Event for ifdef/ifndef directives.
75 /// </summary>
76 public event IfDefEventHandler IfDef;
77
78 /// <summary>
79 /// Event for included files.
80 /// </summary>
81 public event IncludedFileEventHandler IncludedFile;
82
83 /// <summary>
84 /// Event for preprocessed stream.
85 /// </summary>
86 public event ProcessedStreamEventHandler ProcessedStream;
87
88 /// <summary>
89 /// Event for resolved variables.
90 /// </summary>
91 public event ResolvedVariableEventHandler ResolvedVariable;
92
93 /// <summary>
94 /// Enumeration for preprocessor operations in if statements.
95 /// </summary>
96 private enum PreprocessorOperation
97 {
98 /// <summary>The and operator.</summary>
99 And,
100
101 /// <summary>The or operator.</summary>
102 Or,
103
104 /// <summary>The not operator.</summary>
105 Not
106 }
107
108 /// <summary>
109 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
110 /// </summary>
111 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
112 public Platform CurrentPlatform
113 {
114 get { return this.currentPlatform; }
115 set { this.currentPlatform = value; }
116 }
117
118 /// <summary>
119 /// Ordered list of search paths that the precompiler uses to find included files.
120 /// </summary>
121 /// <value>List of ordered search paths to use during precompiling.</value>
122 public IList<string> IncludeSearchPaths { get; private set; }
123
124 /// <summary>
125 /// Specifies the text stream to display the postprocessed data to.
126 /// </summary>
127 /// <value>TextWriter to write preprocessed xml to.</value>
128 public TextWriter PreprocessOut
129 {
130 get { return this.preprocessOut; }
131 set { this.preprocessOut = value; }
132 }
133
134 /// <summary>
135 /// Get the source line information for the current element. The precompiler will insert
136 /// special source line number processing instructions before each element that it
137 /// encounters. This is where those line numbers are read and processed. This function
138 /// may return an array of source line numbers because the element may have come from
139 /// an included file, in which case the chain of imports is expressed in the array.
140 /// </summary>
141 /// <param name="node">Element to get source line information for.</param>
142 /// <returns>Returns the stack of imports used to author the element being processed.</returns>
143 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
144 public static SourceLineNumber GetSourceLineNumbers(XmlNode node)
145 {
146 return null;
147 }
148
149 /// <summary>
150 /// Get the source line information for the current element. The precompiler will insert
151 /// special source line number information for each element that it encounters.
152 /// </summary>
153 /// <param name="node">Element to get source line information for.</param>
154 /// <returns>
155 /// The source line number used to author the element being processed or
156 /// null if the preprocessor did not process the element or the node is
157 /// not an element.
158 /// </returns>
159 public static SourceLineNumber GetSourceLineNumbers(XObject node)
160 {
161 return SourceLineNumber.GetFromXAnnotation(node);
162 }
163
164 /// <summary>
165 /// Adds an extension.
166 /// </summary>
167 /// <param name="extension">The extension to add.</param>
168 public void AddExtension(IPreprocessorExtension extension)
169 {
170 this.extensions.Add(extension);
171
172 if (null != extension.Prefixes)
173 {
174 foreach (string prefix in extension.Prefixes)
175 {
176 IPreprocessorExtension collidingExtension;
177 if (!this.extensionsByPrefix.TryGetValue(prefix, out collidingExtension))
178 {
179 this.extensionsByPrefix.Add(prefix, extension);
180 }
181 else
182 {
183 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString()));
184 }
185 }
186 }
187
188 //if (null != extension.InspectorExtension)
189 //{
190 // this.inspectorExtensions.Add(extension.InspectorExtension);
191 //}
192 }
193
194 /// <summary>
195 /// Preprocesses a file.
196 /// </summary>
197 /// <param name="sourceFile">The file to preprocess.</param>
198 /// <param name="variables">The variables defined prior to preprocessing.</param>
199 /// <returns>XDocument with the postprocessed data.</returns>
200 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
201 public XDocument Process(string sourceFile, IDictionary<string, string> variables)
202 {
203 using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
204 {
205 InspectorCore inspectorCore = new InspectorCore();
206 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
207 {
208 inspectorExtension.Core = inspectorCore;
209 inspectorExtension.InspectSource(sourceStream);
210
211 // reset
212 inspectorExtension.Core = null;
213 sourceStream.Position = 0;
214 }
215
216 if (inspectorCore.EncounteredError)
217 {
218 return null;
219 }
220
221 using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings))
222 {
223 return Process(reader, variables, sourceFile);
224 }
225 }
226 }
227
228 /// <summary>
229 /// Preprocesses a file.
230 /// </summary>
231 /// <param name="sourceFile">The file to preprocess.</param>
232 /// <param name="variables">The variables defined prior to preprocessing.</param>
233 /// <returns>XDocument with the postprocessed data.</returns>
234 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
235 public XDocument Process(XmlReader reader, IDictionary<string, string> variables, string sourceFile = null)
236 {
237 if (String.IsNullOrEmpty(sourceFile) && !String.IsNullOrEmpty(reader.BaseURI))
238 {
239 Uri uri = new Uri(reader.BaseURI);
240 sourceFile = uri.AbsolutePath;
241 }
242
243 this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables);
244 this.core.ResolvedVariableHandler = this.ResolvedVariable;
245 this.core.CurrentPlatform = this.currentPlatform;
246 this.currentLineNumber = new SourceLineNumber(sourceFile);
247 this.currentFileStack.Clear();
248 this.currentFileStack.Push(this.core.GetVariableValue(this.currentLineNumber, "sys", "SOURCEFILEDIR"));
249
250 // Process the reader into the output.
251 XDocument output = new XDocument();
252 try
253 {
254 foreach (PreprocessorExtension extension in this.extensions)
255 {
256 extension.Core = this.core;
257 extension.Initialize();
258 }
259
260 this.PreprocessReader(false, reader, output, 0);
261 }
262 catch (XmlException e)
263 {
264 this.UpdateCurrentLineNumber(reader, 0);
265 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
266 }
267
268 // Fire event with post-processed document.
269 ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output);
270 this.OnProcessedStream(args);
271
272 // preprocess the generated XML Document
273 foreach (PreprocessorExtension extension in this.extensions)
274 {
275 extension.PreprocessDocument(output);
276 }
277
278 // finalize the preprocessing
279 foreach (PreprocessorExtension extension in this.extensions)
280 {
281 extension.Finish();
282 extension.Core = null;
283 }
284
285 if (this.core.EncounteredError)
286 {
287 return null;
288 }
289 else
290 {
291 if (null != this.preprocessOut)
292 {
293 output.Save(this.preprocessOut);
294 this.preprocessOut.Flush();
295 }
296
297 return output;
298 }
299 }
300
301 /// <summary>
302 /// Determins if string is an operator.
303 /// </summary>
304 /// <param name="operation">String to check.</param>
305 /// <returns>true if string is an operator.</returns>
306 private static bool IsOperator(string operation)
307 {
308 if (operation == null)
309 {
310 return false;
311 }
312
313 operation = operation.Trim();
314 if (0 == operation.Length)
315 {
316 return false;
317 }
318
319 if ("=" == operation ||
320 "!=" == operation ||
321 "<" == operation ||
322 "<=" == operation ||
323 ">" == operation ||
324 ">=" == operation ||
325 "~=" == operation)
326 {
327 return true;
328 }
329 return false;
330 }
331
332 /// <summary>
333 /// Determines if expression is currently inside quotes.
334 /// </summary>
335 /// <param name="expression">Expression to evaluate.</param>
336 /// <param name="index">Index to start searching in expression.</param>
337 /// <returns>true if expression is inside in quotes.</returns>
338 private static bool InsideQuotes(string expression, int index)
339 {
340 if (index == -1)
341 {
342 return false;
343 }
344
345 int numQuotes = 0;
346 int tmpIndex = 0;
347 while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex)))
348 {
349 numQuotes++;
350 tmpIndex++;
351 }
352
353 // found an even number of quotes before the index, so we're not inside
354 if (numQuotes % 2 == 0)
355 {
356 return false;
357 }
358
359 // found an odd number of quotes, so we are inside
360 return true;
361 }
362
363 /// <summary>
364 /// Fires an event when an ifdef/ifndef directive is processed.
365 /// </summary>
366 /// <param name="ea">ifdef/ifndef event arguments.</param>
367 private void OnIfDef(IfDefEventArgs ea)
368 {
369 if (null != this.IfDef)
370 {
371 this.IfDef(this, ea);
372 }
373 }
374
375 /// <summary>
376 /// Fires an event when an included file is processed.
377 /// </summary>
378 /// <param name="ea">Included file event arguments.</param>
379 private void OnIncludedFile(IncludedFileEventArgs ea)
380 {
381 if (null != this.IncludedFile)
382 {
383 this.IncludedFile(this, ea);
384 }
385 }
386
387 /// <summary>
388 /// Fires an event after the file is preprocessed.
389 /// </summary>
390 /// <param name="ea">Included file event arguments.</param>
391 private void OnProcessedStream(ProcessedStreamEventArgs ea)
392 {
393 if (null != this.ProcessedStream)
394 {
395 this.ProcessedStream(this, ea);
396 }
397 }
398
399 /// <summary>
400 /// Tests expression to see if it starts with a keyword.
401 /// </summary>
402 /// <param name="expression">Expression to test.</param>
403 /// <param name="operation">Operation to test for.</param>
404 /// <returns>true if expression starts with a keyword.</returns>
405 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation)
406 {
407 expression = expression.ToUpper(CultureInfo.InvariantCulture);
408 switch (operation)
409 {
410 case PreprocessorOperation.Not:
411 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal))
412 {
413 return true;
414 }
415 break;
416 case PreprocessorOperation.And:
417 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal))
418 {
419 return true;
420 }
421 break;
422 case PreprocessorOperation.Or:
423 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal))
424 {
425 return true;
426 }
427 break;
428 default:
429 break;
430 }
431 return false;
432 }
433
434 /// <summary>
435 /// Processes an xml reader into an xml writer.
436 /// </summary>
437 /// <param name="include">Specifies if reader is from an included file.</param>
438 /// <param name="reader">Reader for the source document.</param>
439 /// <param name="container">Node where content should be added.</param>
440 /// <param name="offset">Original offset for the line numbers being processed.</param>
441 private void PreprocessReader(bool include, XmlReader reader, XContainer container, int offset)
442 {
443 XContainer currentContainer = container;
444 Stack<XContainer> containerStack = new Stack<XContainer>();
445
446 IfContext ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code
447 Stack<IfContext> ifStack = new Stack<IfContext>();
448
449 // process the reader into the writer
450 while (reader.Read())
451 {
452 // update information here in case an error occurs before the next read
453 this.UpdateCurrentLineNumber(reader, offset);
454
455 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
456
457 // check for changes in conditional processing
458 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
459 {
460 bool ignore = false;
461 string name = null;
462
463 switch (reader.LocalName)
464 {
465 case "if":
466 ifStack.Push(ifContext);
467 if (ifContext.IsTrue)
468 {
469 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(reader.Value), IfState.If);
470 }
471 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
472 {
473 ifContext = new IfContext();
474 }
475 ignore = true;
476 break;
477
478 case "ifdef":
479 ifStack.Push(ifContext);
480 name = reader.Value.Trim();
481 if (ifContext.IsTrue)
482 {
483 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If);
484 }
485 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
486 {
487 ifContext = new IfContext();
488 }
489 ignore = true;
490 OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
491 break;
492
493 case "ifndef":
494 ifStack.Push(ifContext);
495 name = reader.Value.Trim();
496 if (ifContext.IsTrue)
497 {
498 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If);
499 }
500 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
501 {
502 ifContext = new IfContext();
503 }
504 ignore = true;
505 OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
506 break;
507
508 case "elseif":
509 if (0 == ifStack.Count)
510 {
511 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
512 }
513
514 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
515 {
516 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
517 }
518
519 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
520 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
521 {
522 ifContext.IsTrue = this.EvaluateExpression(reader.Value);
523 }
524 else if (ifContext.IsTrue)
525 {
526 ifContext.IsTrue = false;
527 }
528 ignore = true;
529 break;
530
531 case "else":
532 if (0 == ifStack.Count)
533 {
534 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
535 }
536
537 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
538 {
539 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
540 }
541
542 ifContext.IfState = IfState.Else; // we're now in an else
543 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now
544 ignore = true;
545 break;
546
547 case "endif":
548 if (0 == ifStack.Count)
549 {
550 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
551 }
552
553 ifContext = (IfContext)ifStack.Pop();
554 ignore = true;
555 break;
556 }
557
558 if (ignore) // ignore this node since we just handled it above
559 {
560 continue;
561 }
562 }
563
564 if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing
565 {
566 continue;
567 }
568
569 switch (reader.NodeType)
570 {
571 case XmlNodeType.XmlDeclaration:
572 XDocument document = currentContainer as XDocument;
573 if (null != document)
574 {
575 document.Declaration = new XDeclaration(null, null, null);
576 while (reader.MoveToNextAttribute())
577 {
578 switch (reader.LocalName)
579 {
580 case "version":
581 document.Declaration.Version = reader.Value;
582 break;
583
584 case "encoding":
585 document.Declaration.Encoding = reader.Value;
586 break;
587
588 case "standalone":
589 document.Declaration.Standalone = reader.Value;
590 break;
591 }
592 }
593
594 }
595 //else
596 //{
597 // display an error? Can this happen?
598 //}
599 break;
600
601 case XmlNodeType.ProcessingInstruction:
602 switch (reader.LocalName)
603 {
604 case "define":
605 this.PreprocessDefine(reader.Value);
606 break;
607
608 case "error":
609 this.PreprocessError(reader.Value);
610 break;
611
612 case "warning":
613 this.PreprocessWarning(reader.Value);
614 break;
615
616 case "undef":
617 this.PreprocessUndef(reader.Value);
618 break;
619
620 case "include":
621 this.UpdateCurrentLineNumber(reader, offset);
622 this.PreprocessInclude(reader.Value, currentContainer);
623 break;
624
625 case "foreach":
626 this.PreprocessForeach(reader, currentContainer, offset);
627 break;
628
629 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
630 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "foreach", "endforeach"));
631
632 case "pragma":
633 this.PreprocessPragma(reader.Value, currentContainer);
634 break;
635
636 default:
637 // unknown processing instructions are currently ignored
638 break;
639 }
640 break;
641
642 case XmlNodeType.Element:
643 if (0 < this.includeNextStack.Count && this.includeNextStack.Peek())
644 {
645 if ("Include" != reader.LocalName)
646 {
647 this.core.OnMessage(WixErrors.InvalidDocumentElement(this.currentLineNumber, reader.Name, "include", "Include"));
648 }
649
650 this.includeNextStack.Pop();
651 this.includeNextStack.Push(false);
652 break;
653 }
654
655 bool empty = reader.IsEmptyElement;
656 XNamespace ns = XNamespace.Get(reader.NamespaceURI);
657 XElement element = new XElement(ns + reader.LocalName);
658 currentContainer.Add(element);
659
660 this.UpdateCurrentLineNumber(reader, offset);
661 element.AddAnnotation(this.currentLineNumber);
662
663 while (reader.MoveToNextAttribute())
664 {
665 string value = this.core.PreprocessString(this.currentLineNumber, reader.Value);
666 XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI);
667 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
668 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
669 }
670
671 if (!empty)
672 {
673 containerStack.Push(currentContainer);
674 currentContainer = element;
675 }
676 break;
677
678 case XmlNodeType.EndElement:
679 if (0 < reader.Depth || !include)
680 {
681 currentContainer = containerStack.Pop();
682 }
683 break;
684
685 case XmlNodeType.Text:
686 string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value);
687 currentContainer.Add(postprocessedText);
688 break;
689
690 case XmlNodeType.CDATA:
691 string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value);
692 currentContainer.Add(new XCData(postprocessedValue));
693 break;
694
695 default:
696 break;
697 }
698 }
699
700 if (0 != ifStack.Count)
701 {
702 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
703 }
704
705 // TODO: can this actually happen?
706 if (0 != containerStack.Count)
707 {
708 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes"));
709 }
710 }
711
712 /// <summary>
713 /// Processes an error processing instruction.
714 /// </summary>
715 /// <param name="errorMessage">Text from source.</param>
716 private void PreprocessError(string errorMessage)
717 {
718 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
719
720 // resolve other variables in the error message
721 errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage);
722
723 throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage));
724 }
725
726 /// <summary>
727 /// Processes a warning processing instruction.
728 /// </summary>
729 /// <param name="warningMessage">Text from source.</param>
730 private void PreprocessWarning(string warningMessage)
731 {
732 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
733
734 // resolve other variables in the warning message
735 warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage);
736
737 this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage));
738 }
739
740 /// <summary>
741 /// Processes a define processing instruction and creates the appropriate parameter.
742 /// </summary>
743 /// <param name="originalDefine">Text from source.</param>
744 private void PreprocessDefine(string originalDefine)
745 {
746 Match match = defineRegex.Match(originalDefine);
747 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
748
749 if (!match.Success)
750 {
751 throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine));
752 }
753
754 string defineName = match.Groups["varName"].Value;
755 string defineValue = match.Groups["varValue"].Value;
756
757 // strip off the optional quotes
758 if (1 < defineValue.Length &&
759 ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal))
760 || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal))))
761 {
762 defineValue = defineValue.Substring(1, defineValue.Length - 2);
763 }
764
765 // resolve other variables in the variable value
766 defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue);
767
768 if (defineName.StartsWith("var.", StringComparison.Ordinal))
769 {
770 this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue);
771 }
772 else
773 {
774 this.core.AddVariable(sourceLineNumbers, defineName, defineValue);
775 }
776 }
777
778 /// <summary>
779 /// Processes an undef processing instruction and creates the appropriate parameter.
780 /// </summary>
781 /// <param name="originalDefine">Text from source.</param>
782 private void PreprocessUndef(string originalDefine)
783 {
784 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
785 string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim());
786
787 if (name.StartsWith("var.", StringComparison.Ordinal))
788 {
789 this.core.RemoveVariable(sourceLineNumbers, name.Substring(4));
790 }
791 else
792 {
793 this.core.RemoveVariable(sourceLineNumbers, name);
794 }
795 }
796
797 /// <summary>
798 /// Processes an included file.
799 /// </summary>
800 /// <param name="includePath">Path to included file.</param>
801 /// <param name="parent">Parent container for included content.</param>
802 private void PreprocessInclude(string includePath, XContainer parent)
803 {
804 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
805
806 // preprocess variables in the path
807 includePath = this.core.PreprocessString(sourceLineNumbers, includePath);
808
809 string includeFile = this.GetIncludeFile(includePath);
810
811 if (null == includeFile)
812 {
813 throw new WixException(WixErrors.FileNotFound(sourceLineNumbers, includePath, "include"));
814 }
815
816 using (XmlReader reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
817 {
818 this.PushInclude(includeFile);
819
820 // process the included reader into the writer
821 try
822 {
823 this.PreprocessReader(true, reader, parent, 0);
824 }
825 catch (XmlException e)
826 {
827 this.UpdateCurrentLineNumber(reader, 0);
828 throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message));
829 }
830
831 this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile));
832
833 this.PopInclude();
834 }
835 }
836
837 /// <summary>
838 /// Preprocess a foreach processing instruction.
839 /// </summary>
840 /// <param name="reader">The xml reader.</param>
841 /// <param name="container">The container where to output processed data.</param>
842 /// <param name="offset">Offset for the line numbers.</param>
843 private void PreprocessForeach(XmlReader reader, XContainer container, int offset)
844 {
845 // find the "in" token
846 int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
847 if (0 > indexOfInToken)
848 {
849 throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value));
850 }
851
852 // parse out the variable name
853 string varName = reader.Value.Substring(0, indexOfInToken).Trim();
854 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
855
856 // preprocess the variable values string because it might be a variable itself
857 varValuesString = this.core.PreprocessString(this.currentLineNumber, varValuesString);
858
859 string[] varValues = varValuesString.Split(';');
860
861 // go through all the empty strings
862 while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType)
863 {
864 }
865
866 // get the offset of this xml fragment (for some reason its always off by 1)
867 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo;
868 if (null != lineInfoReader)
869 {
870 offset += lineInfoReader.LineNumber - 1;
871 }
872
873 XmlTextReader textReader = reader as XmlTextReader;
874 // dump the xml to a string (maintaining whitespace if possible)
875 if (null != textReader)
876 {
877 textReader.WhitespaceHandling = WhitespaceHandling.All;
878 }
879
880 StringBuilder fragmentBuilder = new StringBuilder();
881 int nestedForeachCount = 1;
882 while (nestedForeachCount != 0)
883 {
884 if (reader.NodeType == XmlNodeType.ProcessingInstruction)
885 {
886 switch (reader.LocalName)
887 {
888 case "foreach":
889 ++nestedForeachCount;
890 // Output the foreach statement
891 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value);
892 break;
893
894 case "endforeach":
895 --nestedForeachCount;
896 if (0 != nestedForeachCount)
897 {
898 fragmentBuilder.Append("<?endforeach ?>");
899 }
900 break;
901
902 default:
903 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value);
904 break;
905 }
906 }
907 else if (reader.NodeType == XmlNodeType.Element)
908 {
909 fragmentBuilder.Append(reader.ReadOuterXml());
910 continue;
911 }
912 else if (reader.NodeType == XmlNodeType.Whitespace)
913 {
914 // Or output the whitespace
915 fragmentBuilder.Append(reader.Value);
916 }
917 else if (reader.NodeType == XmlNodeType.None)
918 {
919 throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber));
920 }
921
922 reader.Read();
923 }
924
925 using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
926 using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
927 {
928 // process each iteration, updating the variable's value each time
929 foreach (string varValue in varValues)
930 {
931 // Always overwrite foreach variables.
932 this.core.AddVariable(this.currentLineNumber, varName, varValue, false);
933
934 try
935 {
936 this.PreprocessReader(false, loopReader, container, offset);
937 }
938 catch (XmlException e)
939 {
940 this.UpdateCurrentLineNumber(loopReader, offset);
941 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
942 }
943
944 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
945 }
946 }
947 }
948
949 /// <summary>
950 /// Processes a pragma processing instruction
951 /// </summary>
952 /// <param name="pragmaText">Text from source.</param>
953 private void PreprocessPragma(string pragmaText, XContainer parent)
954 {
955 Match match = pragmaRegex.Match(pragmaText);
956 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
957
958 if (!match.Success)
959 {
960 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText));
961 }
962
963 // resolve other variables in the pragma argument(s)
964 string pragmaArgs = this.core.PreprocessString(sourceLineNumbers, match.Groups["pragmaValue"].Value).Trim();
965
966 try
967 {
968 this.core.PreprocessPragma(sourceLineNumbers, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent);
969 }
970 catch (Exception e)
971 {
972 throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message));
973 }
974 }
975
976 /// <summary>
977 /// Gets the next token in an expression.
978 /// </summary>
979 /// <param name="originalExpression">Expression to parse.</param>
980 /// <param name="expression">Expression with token removed.</param>
981 /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param>
982 /// <returns>Next token.</returns>
983 private string GetNextToken(string originalExpression, ref string expression, out bool stringLiteral)
984 {
985 stringLiteral = false;
986 string token = String.Empty;
987 expression = expression.Trim();
988 if (0 == expression.Length)
989 {
990 return String.Empty;
991 }
992
993 if (expression.StartsWith("\"", StringComparison.Ordinal))
994 {
995 stringLiteral = true;
996 int endingQuotes = expression.IndexOf('\"', 1);
997 if (-1 == endingQuotes)
998 {
999 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1000 }
1001
1002 // cut the quotes off the string
1003 token = this.core.PreprocessString(this.currentLineNumber, expression.Substring(1, endingQuotes - 1));
1004
1005 // advance past this string
1006 expression = expression.Substring(endingQuotes + 1).Trim();
1007 }
1008 else if (expression.StartsWith("$(", StringComparison.Ordinal))
1009 {
1010 // Find the ending paren of the expression
1011 int endingParen = -1;
1012 int openedCount = 1;
1013 for (int i = 2; i < expression.Length; i++)
1014 {
1015 if ('(' == expression[i])
1016 {
1017 openedCount++;
1018 }
1019 else if (')' == expression[i])
1020 {
1021 openedCount--;
1022 }
1023
1024 if (openedCount == 0)
1025 {
1026 endingParen = i;
1027 break;
1028 }
1029 }
1030
1031 if (-1 == endingParen)
1032 {
1033 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1034 }
1035 token = expression.Substring(0, endingParen + 1);
1036
1037 // Advance past this variable
1038 expression = expression.Substring(endingParen + 1).Trim();
1039 }
1040 else
1041 {
1042 // Cut the token off at the next equal, space, inequality operator,
1043 // or end of string, whichever comes first
1044 int space = expression.IndexOf(" ", StringComparison.Ordinal);
1045 int equals = expression.IndexOf("=", StringComparison.Ordinal);
1046 int lessThan = expression.IndexOf("<", StringComparison.Ordinal);
1047 int lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal);
1048 int greaterThan = expression.IndexOf(">", StringComparison.Ordinal);
1049 int greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal);
1050 int notEquals = expression.IndexOf("!=", StringComparison.Ordinal);
1051 int equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal);
1052 int closingIndex;
1053
1054 if (space == -1)
1055 {
1056 space = Int32.MaxValue;
1057 }
1058
1059 if (equals == -1)
1060 {
1061 equals = Int32.MaxValue;
1062 }
1063
1064 if (lessThan == -1)
1065 {
1066 lessThan = Int32.MaxValue;
1067 }
1068
1069 if (lessThanEquals == -1)
1070 {
1071 lessThanEquals = Int32.MaxValue;
1072 }
1073
1074 if (greaterThan == -1)
1075 {
1076 greaterThan = Int32.MaxValue;
1077 }
1078
1079 if (greaterThanEquals == -1)
1080 {
1081 greaterThanEquals = Int32.MaxValue;
1082 }
1083
1084 if (notEquals == -1)
1085 {
1086 notEquals = Int32.MaxValue;
1087 }
1088
1089 if (equalsNoCase == -1)
1090 {
1091 equalsNoCase = Int32.MaxValue;
1092 }
1093
1094 closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals)))))));
1095
1096 if (Int32.MaxValue == closingIndex)
1097 {
1098 closingIndex = expression.Length;
1099 }
1100
1101 // If the index is 0, we hit an operator, so return it
1102 if (0 == closingIndex)
1103 {
1104 // Length 2 operators
1105 if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase)
1106 {
1107 closingIndex = 2;
1108 }
1109 else // Length 1 operators
1110 {
1111 closingIndex = 1;
1112 }
1113 }
1114
1115 // Cut out the new token
1116 token = expression.Substring(0, closingIndex).Trim();
1117 expression = expression.Substring(closingIndex).Trim();
1118 }
1119
1120 return token;
1121 }
1122
1123 /// <summary>
1124 /// Gets the value for a variable.
1125 /// </summary>
1126 /// <param name="originalExpression">Original expression for error message.</param>
1127 /// <param name="variable">Variable to evaluate.</param>
1128 /// <returns>Value of variable.</returns>
1129 private string EvaluateVariable(string originalExpression, string variable)
1130 {
1131 // By default it's a literal and will only be evaluated if it
1132 // matches the variable format
1133 string varValue = variable;
1134
1135 if (variable.StartsWith("$(", StringComparison.Ordinal))
1136 {
1137 try
1138 {
1139 varValue = this.core.PreprocessString(this.currentLineNumber, variable);
1140 }
1141 catch (ArgumentNullException)
1142 {
1143 // non-existent variables are expected
1144 varValue = null;
1145 }
1146 }
1147 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
1148 {
1149 // make sure it doesn't contain parenthesis
1150 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1151 }
1152 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
1153 {
1154 // shouldn't contain quotes
1155 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1156 }
1157
1158 return varValue;
1159 }
1160
1161 /// <summary>
1162 /// Gets the left side value, operator, and right side value of an expression.
1163 /// </summary>
1164 /// <param name="originalExpression">Original expression to evaluate.</param>
1165 /// <param name="expression">Expression modified while processing.</param>
1166 /// <param name="leftValue">Left side value from expression.</param>
1167 /// <param name="operation">Operation in expression.</param>
1168 /// <param name="rightValue">Right side value from expression.</param>
1169 private void GetNameValuePair(string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue)
1170 {
1171 bool stringLiteral;
1172 leftValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1173
1174 // If it wasn't a string literal, evaluate it
1175 if (!stringLiteral)
1176 {
1177 leftValue = this.EvaluateVariable(originalExpression, leftValue);
1178 }
1179
1180 // Get the operation
1181 operation = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1182 if (IsOperator(operation))
1183 {
1184 if (stringLiteral)
1185 {
1186 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1187 }
1188
1189 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1190
1191 // If it wasn't a string literal, evaluate it
1192 if (!stringLiteral)
1193 {
1194 rightValue = this.EvaluateVariable(originalExpression, rightValue);
1195 }
1196 }
1197 else
1198 {
1199 // Prepend the token back on the expression since it wasn't an operator
1200 // and put the quotes back on the literal if necessary
1201
1202 if (stringLiteral)
1203 {
1204 operation = "\"" + operation + "\"";
1205 }
1206 expression = (operation + " " + expression).Trim();
1207
1208 // If no operator, just check for existence
1209 operation = "";
1210 rightValue = "";
1211 }
1212 }
1213
1214 /// <summary>
1215 /// Evaluates an expression.
1216 /// </summary>
1217 /// <param name="originalExpression">Original expression to evaluate.</param>
1218 /// <param name="expression">Expression modified while processing.</param>
1219 /// <returns>true if expression evaluates to true.</returns>
1220 private bool EvaluateAtomicExpression(string originalExpression, ref string expression)
1221 {
1222 // Quick test to see if the first token is a variable
1223 bool startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal);
1224
1225 string leftValue;
1226 string rightValue;
1227 string operation;
1228 this.GetNameValuePair(originalExpression, ref expression, out leftValue, out operation, out rightValue);
1229
1230 bool expressionValue = false;
1231
1232 // If the variables don't exist, they were evaluated to null
1233 if (null == leftValue || null == rightValue)
1234 {
1235 if (operation.Length > 0)
1236 {
1237 throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression));
1238 }
1239
1240 // false expression
1241 }
1242 else if (operation.Length == 0)
1243 {
1244 // There is no right side of the equation.
1245 // If the variable was evaluated, it exists, so the expression is true
1246 if (startsWithVariable)
1247 {
1248 expressionValue = true;
1249 }
1250 else
1251 {
1252 throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression));
1253 }
1254 }
1255 else
1256 {
1257 leftValue = leftValue.Trim();
1258 rightValue = rightValue.Trim();
1259 if ("=" == operation)
1260 {
1261 if (leftValue == rightValue)
1262 {
1263 expressionValue = true;
1264 }
1265 }
1266 else if ("!=" == operation)
1267 {
1268 if (leftValue != rightValue)
1269 {
1270 expressionValue = true;
1271 }
1272 }
1273 else if ("~=" == operation)
1274 {
1275 if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase))
1276 {
1277 expressionValue = true;
1278 }
1279 }
1280 else
1281 {
1282 // Convert the numbers from strings
1283 int rightInt;
1284 int leftInt;
1285 try
1286 {
1287 rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture);
1288 leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture);
1289 }
1290 catch (FormatException)
1291 {
1292 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1293 }
1294 catch (OverflowException)
1295 {
1296 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1297 }
1298
1299 // Compare the numbers
1300 if ("<" == operation && leftInt < rightInt ||
1301 "<=" == operation && leftInt <= rightInt ||
1302 ">" == operation && leftInt > rightInt ||
1303 ">=" == operation && leftInt >= rightInt)
1304 {
1305 expressionValue = true;
1306 }
1307 }
1308 }
1309
1310 return expressionValue;
1311 }
1312
1313 /// <summary>
1314 /// Gets a sub-expression in parenthesis.
1315 /// </summary>
1316 /// <param name="originalExpression">Original expression to evaluate.</param>
1317 /// <param name="expression">Expression modified while processing.</param>
1318 /// <param name="endSubExpression">Index of end of sub-expression.</param>
1319 /// <returns>Sub-expression in parenthesis.</returns>
1320 private string GetParenthesisExpression(string originalExpression, string expression, out int endSubExpression)
1321 {
1322 endSubExpression = 0;
1323
1324 // if the expression doesn't start with parenthesis, leave it alone
1325 if (!expression.StartsWith("(", StringComparison.Ordinal))
1326 {
1327 return expression;
1328 }
1329
1330 // search for the end of the expression with the matching paren
1331 int openParenIndex = 0;
1332 int closeParenIndex = 1;
1333 while (openParenIndex != -1 && openParenIndex < closeParenIndex)
1334 {
1335 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1336 if (closeParenIndex == -1)
1337 {
1338 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1339 }
1340
1341 if (InsideQuotes(expression, closeParenIndex))
1342 {
1343 // ignore stuff inside quotes (it's a string literal)
1344 }
1345 else
1346 {
1347 // Look to see if there is another open paren before the close paren
1348 // and skip over the open parens while they are in a string literal
1349 do
1350 {
1351 openParenIndex++;
1352 openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex);
1353 }
1354 while (InsideQuotes(expression, openParenIndex));
1355 }
1356
1357 // Advance past the closing paren
1358 closeParenIndex++;
1359 }
1360
1361 endSubExpression = closeParenIndex;
1362
1363 // Return the expression minus the parenthesis
1364 return expression.Substring(1, closeParenIndex - 2);
1365 }
1366
1367 /// <summary>
1368 /// Updates expression based on operation.
1369 /// </summary>
1370 /// <param name="currentValue">State to update.</param>
1371 /// <param name="operation">Operation to apply to current value.</param>
1372 /// <param name="prevResult">Previous result.</param>
1373 private void UpdateExpressionValue(ref bool currentValue, PreprocessorOperation operation, bool prevResult)
1374 {
1375 switch (operation)
1376 {
1377 case PreprocessorOperation.And:
1378 currentValue = currentValue && prevResult;
1379 break;
1380 case PreprocessorOperation.Or:
1381 currentValue = currentValue || prevResult;
1382 break;
1383 case PreprocessorOperation.Not:
1384 currentValue = !currentValue;
1385 break;
1386 default:
1387 throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString()));
1388 }
1389 }
1390
1391 /// <summary>
1392 /// Evaluate an expression.
1393 /// </summary>
1394 /// <param name="expression">Expression to evaluate.</param>
1395 /// <returns>Boolean result of expression.</returns>
1396 private bool EvaluateExpression(string expression)
1397 {
1398 string tmpExpression = expression;
1399 return this.EvaluateExpressionRecurse(expression, ref tmpExpression, PreprocessorOperation.And, true);
1400 }
1401
1402 /// <summary>
1403 /// Recurse through the expression to evaluate if it is true or false.
1404 /// The expression is evaluated left to right.
1405 /// The expression is case-sensitive (converted to upper case) with the
1406 /// following exceptions: variable names and keywords (and, not, or).
1407 /// Comparisons with = and != are string comparisons.
1408 /// Comparisons with inequality operators must be done on valid integers.
1409 ///
1410 /// The operator precedence is:
1411 /// ""
1412 /// ()
1413 /// &lt;, &gt;, &lt;=, &gt;=, =, !=
1414 /// Not
1415 /// And, Or
1416 ///
1417 /// Valid expressions include:
1418 /// not $(var.B) or not $(var.C)
1419 /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A))
1420 /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C)
1421 /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir)
1422 /// $(var.A) and $(var.B)>2 or $(var.B) &lt;= 2
1423 /// $(var.A) != "2"
1424 /// </summary>
1425 /// <param name="originalExpression">The original expression</param>
1426 /// <param name="expression">The expression currently being evaluated</param>
1427 /// <param name="prevResultOperation">The operation to apply to this result</param>
1428 /// <param name="prevResult">The previous result to apply to this result</param>
1429 /// <returns>Boolean to indicate if the expression is true or false</returns>
1430 private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult)
1431 {
1432 bool expressionValue = false;
1433 expression = expression.Trim();
1434 if (expression.Length == 0)
1435 {
1436 throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression));
1437 }
1438
1439 // If the expression starts with parenthesis, evaluate it
1440 if (expression.IndexOf('(') == 0)
1441 {
1442 int endSubExpressionIndex;
1443 string subExpression = this.GetParenthesisExpression(originalExpression, expression, out endSubExpressionIndex);
1444 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true);
1445
1446 // Now get the rest of the expression that hasn't been evaluated
1447 expression = expression.Substring(endSubExpressionIndex).Trim();
1448 }
1449 else
1450 {
1451 // Check for NOT
1452 if (StartsWithKeyword(expression, PreprocessorOperation.Not))
1453 {
1454 expression = expression.Substring(3).Trim();
1455 if (expression.Length == 0)
1456 {
1457 throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression));
1458 }
1459
1460 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true);
1461 }
1462 else // Expect a literal
1463 {
1464 expressionValue = this.EvaluateAtomicExpression(originalExpression, ref expression);
1465
1466 // Expect the literal that was just evaluated to already be cut off
1467 }
1468 }
1469 this.UpdateExpressionValue(ref expressionValue, prevResultOperation, prevResult);
1470
1471 // If there's still an expression left, it must start with AND or OR.
1472 if (expression.Trim().Length > 0)
1473 {
1474 if (StartsWithKeyword(expression, PreprocessorOperation.And))
1475 {
1476 expression = expression.Substring(3);
1477 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.And, expressionValue);
1478 }
1479 else if (StartsWithKeyword(expression, PreprocessorOperation.Or))
1480 {
1481 expression = expression.Substring(2);
1482 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Or, expressionValue);
1483 }
1484 else
1485 {
1486 throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression));
1487 }
1488 }
1489
1490 return expressionValue;
1491 }
1492
1493 /// <summary>
1494 /// Update the current line number with the reader's current state.
1495 /// </summary>
1496 /// <param name="reader">The xml reader for the preprocessor.</param>
1497 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param>
1498 private void UpdateCurrentLineNumber(XmlReader reader, int offset)
1499 {
1500 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo;
1501 if (null != lineInfoReader)
1502 {
1503 int newLine = lineInfoReader.LineNumber + offset;
1504
1505 if (this.currentLineNumber.LineNumber != newLine)
1506 {
1507 this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine);
1508 }
1509 }
1510 }
1511
1512 /// <summary>
1513 /// Pushes a file name on the stack of included files.
1514 /// </summary>
1515 /// <param name="fileName">Name to push on to the stack of included files.</param>
1516 private void PushInclude(string fileName)
1517 {
1518 if (1023 < this.currentFileStack.Count)
1519 {
1520 throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count));
1521 }
1522
1523 this.currentFileStack.Push(fileName);
1524 this.sourceStack.Push(this.currentLineNumber);
1525 this.currentLineNumber = new SourceLineNumber(fileName);
1526 this.includeNextStack.Push(true);
1527 }
1528
1529 /// <summary>
1530 /// Pops a file name from the stack of included files.
1531 /// </summary>
1532 private void PopInclude()
1533 {
1534 this.currentLineNumber = this.sourceStack.Pop();
1535
1536 this.currentFileStack.Pop();
1537 this.includeNextStack.Pop();
1538 }
1539
1540 /// <summary>
1541 /// Go through search paths, looking for a matching include file.
1542 /// Start the search in the directory of the source file, then go
1543 /// through the search paths in the order given on the command line
1544 /// (leftmost first, ...).
1545 /// </summary>
1546 /// <param name="includePath">User-specified path to the included file (usually just the file name).</param>
1547 /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns>
1548 private string GetIncludeFile(string includePath)
1549 {
1550 string finalIncludePath = null;
1551
1552 includePath = includePath.Trim();
1553
1554 // remove quotes (only if they match)
1555 if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) ||
1556 (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal)))
1557 {
1558 includePath = includePath.Substring(1, includePath.Length - 2);
1559 }
1560
1561 // check if the include file is a full path
1562 if (Path.IsPathRooted(includePath))
1563 {
1564 if (File.Exists(includePath))
1565 {
1566 finalIncludePath = includePath;
1567 }
1568 }
1569 else // relative path
1570 {
1571 // build a string to test the directory containing the source file first
1572 string currentFolder = this.currentFileStack.Peek();
1573 string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath);
1574
1575 // test the source file directory
1576 if (File.Exists(includeTestPath))
1577 {
1578 finalIncludePath = includeTestPath;
1579 }
1580 else // test all search paths in the order specified on the command line
1581 {
1582 foreach (string includeSearchPath in this.IncludeSearchPaths)
1583 {
1584 // if the path exists, we have found the final string
1585 includeTestPath = Path.Combine(includeSearchPath, includePath);
1586 if (File.Exists(includeTestPath))
1587 {
1588 finalIncludePath = includeTestPath;
1589 break;
1590 }
1591 }
1592 }
1593 }
1594
1595 return finalIncludePath;
1596 }
1597 }
1598}
diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs
new file mode 100644
index 00000000..b58fc80c
--- /dev/null
+++ b/src/WixToolset.Core/PreprocessorCore.cs
@@ -0,0 +1,560 @@
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.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// The preprocessor core.
15 /// </summary>
16 internal class PreprocessorCore : IPreprocessorCore
17 {
18 private static readonly char[] variableSplitter = new char[] { '.' };
19 private static readonly char[] argumentSplitter = new char[] { ',' };
20
21 private Platform currentPlatform;
22 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
23 private string sourceFile;
24 private IDictionary<string, string> variables;
25
26 /// <summary>
27 /// Instantiate a new PreprocessorCore.
28 /// </summary>
29 /// <param name="extensionsByPrefix">The extensions indexed by their prefixes.</param>
30 /// <param name="messageHandler">The message handler.</param>
31 /// <param name="sourceFile">The source file being preprocessed.</param>
32 /// <param name="variables">The variables defined prior to preprocessing.</param>
33 internal PreprocessorCore(Dictionary<string, IPreprocessorExtension> extensionsByPrefix, string sourceFile, IDictionary<string, string> variables)
34 {
35 this.extensionsByPrefix = extensionsByPrefix;
36 this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile);
37
38 this.variables = new Dictionary<string, string>();
39 foreach (var entry in variables)
40 {
41 this.AddVariable(null, entry.Key, entry.Value);
42 }
43 }
44
45 /// <summary>
46 /// Event for resolved variables.
47 /// </summary>
48 private event ResolvedVariableEventHandler ResolvedVariable;
49
50 /// <summary>
51 /// Sets event for ResolvedVariableEventHandler.
52 /// </summary>
53 public ResolvedVariableEventHandler ResolvedVariableHandler
54 {
55 set { this.ResolvedVariable = value; }
56 }
57
58 /// <summary>
59 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
60 /// </summary>
61 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
62 public Platform CurrentPlatform
63 {
64 get { return this.currentPlatform; }
65 set { this.currentPlatform = value; }
66 }
67
68 /// <summary>
69 /// Gets whether the core encountered an error while processing.
70 /// </summary>
71 /// <value>Flag if core encountered an error during processing.</value>
72 public bool EncounteredError
73 {
74 get { return Messaging.Instance.EncounteredError; }
75 }
76
77 /// <summary>
78 /// Replaces parameters in the source text.
79 /// </summary>
80 /// <param name="sourceLineNumbers">The source line information for the function.</param>
81 /// <param name="value">Text that may contain parameters to replace.</param>
82 /// <returns>Text after parameters have been replaced.</returns>
83 public string PreprocessString(SourceLineNumber sourceLineNumbers, string value)
84 {
85 StringBuilder sb = new StringBuilder();
86 int currentPosition = 0;
87 int end = 0;
88
89 while (-1 != (currentPosition = value.IndexOf('$', end)))
90 {
91 if (end < currentPosition)
92 {
93 sb.Append(value, end, currentPosition - end);
94 }
95
96 end = currentPosition + 1;
97 string remainder = value.Substring(end);
98 if (remainder.StartsWith("$", StringComparison.Ordinal))
99 {
100 sb.Append("$");
101 end++;
102 }
103 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
104 {
105 currentPosition = remainder.IndexOf(')');
106 if (-1 == currentPosition)
107 {
108 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
109 break;
110 }
111
112 sb.Append("$"); // just put the resource reference back as was
113 sb.Append(remainder, 0, currentPosition + 1);
114
115 end += currentPosition + 1;
116 }
117 else if (remainder.StartsWith("(", StringComparison.Ordinal))
118 {
119 int openParenCount = 1;
120 int closingParenCount = 0;
121 bool isFunction = false;
122 bool foundClosingParen = false;
123
124 // find the closing paren
125 int closingParenPosition;
126 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
127 {
128 switch (remainder[closingParenPosition])
129 {
130 case '(':
131 openParenCount++;
132 isFunction = true;
133 break;
134 case ')':
135 closingParenCount++;
136 break;
137 }
138 if (openParenCount == closingParenCount)
139 {
140 foundClosingParen = true;
141 break;
142 }
143 }
144
145 // move the currentPosition to the closing paren
146 currentPosition += closingParenPosition;
147
148 if (!foundClosingParen)
149 {
150 if (isFunction)
151 {
152 this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder));
153 break;
154 }
155 else
156 {
157 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
158 break;
159 }
160 }
161
162 string subString = remainder.Substring(1, closingParenPosition - 1);
163 string result = null;
164 if (isFunction)
165 {
166 result = this.EvaluateFunction(sourceLineNumbers, subString);
167 }
168 else
169 {
170 result = this.GetVariableValue(sourceLineNumbers, subString, false);
171 }
172
173 if (null == result)
174 {
175 if (isFunction)
176 {
177 this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString));
178 break;
179 }
180 else
181 {
182 this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString));
183 break;
184 }
185 }
186 else
187 {
188 if (!isFunction)
189 {
190 this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result));
191 }
192 }
193 sb.Append(result);
194 end += closingParenPosition + 1;
195 }
196 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
197 {
198 sb.Append('$');
199 }
200 }
201
202 if (end < value.Length)
203 {
204 sb.Append(value.Substring(end));
205 }
206
207 return sb.ToString();
208 }
209
210 /// <summary>
211 /// Evaluate a Pragma.
212 /// </summary>
213 /// <param name="sourceLineNumbers">The source line information for the function.</param>
214 /// <param name="pragmaName">The pragma's full name (<prefix>.<pragma>).</param>
215 /// <param name="args">The arguments to the pragma.</param>
216 /// <param name="parent">The parent element of the pragma.</param>
217 public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent)
218 {
219 string[] prefixParts = pragmaName.Split(variableSplitter, 2);
220 // Check to make sure there are 2 parts and neither is an empty string.
221 if (2 != prefixParts.Length)
222 {
223 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
224 }
225 string prefix = prefixParts[0];
226 string pragma = prefixParts[1];
227
228 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
229 {
230 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
231 }
232
233 switch (prefix)
234 {
235 case "wix":
236 switch (pragma)
237 {
238 // Add any core defined pragmas here
239 default:
240 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
241 break;
242 }
243 break;
244 default:
245 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
246 if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent))
247 {
248 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
249 }
250 break;
251 }
252 }
253
254 /// <summary>
255 /// Evaluate a function.
256 /// </summary>
257 /// <param name="sourceLineNumbers">The source line information for the function.</param>
258 /// <param name="function">The function expression including the prefix and name.</param>
259 /// <returns>The function value.</returns>
260 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function)
261 {
262 string[] prefixParts = function.Split(variableSplitter, 2);
263 // Check to make sure there are 2 parts and neither is an empty string.
264 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
265 {
266 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
267 }
268 string prefix = prefixParts[0];
269
270 string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
271 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
272 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
273 {
274 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
275 }
276 string functionName = functionParts[0];
277
278 // Remove the trailing closing paren.
279 string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
280
281 // Parse the arguments and preprocess them.
282 string[] args = allArgs.Split(argumentSplitter);
283 for (int i = 0; i < args.Length; i++)
284 {
285 args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim());
286 }
287
288 string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args);
289
290 // If the function didn't evaluate, try to evaluate the original value as a variable to support
291 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
292 if (null == result)
293 {
294 result = this.GetVariableValue(sourceLineNumbers, function, false);
295 }
296
297 return result;
298 }
299
300 /// <summary>
301 /// Evaluate a function.
302 /// </summary>
303 /// <param name="sourceLineNumbers">The source line information for the function.</param>
304 /// <param name="prefix">The function prefix.</param>
305 /// <param name="function">The function name.</param>
306 /// <param name="args">The arguments for the function.</param>
307 /// <returns>The function value or null if the function is not defined.</returns>
308 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args)
309 {
310 if (String.IsNullOrEmpty(prefix))
311 {
312 throw new ArgumentNullException("prefix");
313 }
314
315 if (String.IsNullOrEmpty(function))
316 {
317 throw new ArgumentNullException("function");
318 }
319
320 switch (prefix)
321 {
322 case "fun":
323 switch (function)
324 {
325 case "AutoVersion":
326 // Make sure the base version is specified
327 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
328 {
329 throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers));
330 }
331
332 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
333 DateTime now = DateTime.UtcNow;
334 TimeSpan build = now - new DateTime(2000, 1, 1);
335 TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day);
336
337 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
338
339 default:
340 return null;
341 }
342 default:
343 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
344 if (null != extension)
345 {
346 try
347 {
348 return extension.EvaluateFunction(prefix, function, args);
349 }
350 catch (Exception e)
351 {
352 throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message));
353 }
354 }
355 else
356 {
357 return null;
358 }
359 }
360 }
361
362 /// <summary>
363 /// Get the value of a variable expression like var.name.
364 /// </summary>
365 /// <param name="sourceLineNumbers">The source line information for the variable.</param>
366 /// <param name="variable">The variable expression including the optional prefix and name.</param>
367 /// <param name="allowMissingPrefix">true to allow the variable prefix to be missing.</param>
368 /// <returns>The variable value.</returns>
369 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix)
370 {
371 // Strip the "$(" off the front.
372 if (variable.StartsWith("$(", StringComparison.Ordinal))
373 {
374 variable = variable.Substring(2);
375 }
376
377 string[] parts = variable.Split(variableSplitter, 2);
378
379 if (1 == parts.Length) // missing prefix
380 {
381 if (allowMissingPrefix)
382 {
383 return this.GetVariableValue(sourceLineNumbers, "var", parts[0]);
384 }
385 else
386 {
387 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
388 }
389 }
390 else
391 {
392 // check for empty variable name
393 if (0 < parts[1].Length)
394 {
395 string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]);
396
397 // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix
398 if (null == result && allowMissingPrefix && variable.Contains("."))
399 {
400 result = this.GetVariableValue(sourceLineNumbers, "var", variable);
401 }
402
403 return result;
404 }
405 else
406 {
407 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
408 }
409 }
410 }
411
412 /// <summary>
413 /// Get the value of a variable.
414 /// </summary>
415 /// <param name="sourceLineNumbers">The source line information for the function.</param>
416 /// <param name="prefix">The variable prefix.</param>
417 /// <param name="name">The variable name.</param>
418 /// <returns>The variable value or null if the variable is not set.</returns>
419 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name)
420 {
421 if (String.IsNullOrEmpty(prefix))
422 {
423 throw new ArgumentNullException("prefix");
424 }
425
426 if (String.IsNullOrEmpty(name))
427 {
428 throw new ArgumentNullException("name");
429 }
430
431 switch (prefix)
432 {
433 case "env":
434 return Environment.GetEnvironmentVariable(name);
435 case "sys":
436 switch (name)
437 {
438 case "CURRENTDIR":
439 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
440 case "SOURCEFILEDIR":
441 return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar);
442 case "SOURCEFILEPATH":
443 return sourceLineNumbers.FileName;
444 case "PLATFORM":
445 this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
446
447 goto case "BUILDARCH";
448
449 case "BUILDARCH":
450 switch (this.currentPlatform)
451 {
452 case Platform.X86:
453 return "x86";
454 case Platform.X64:
455 return "x64";
456 case Platform.IA64:
457 return "ia64";
458 case Platform.ARM:
459 return "arm";
460 default:
461 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString());
462 }
463 default:
464 return null;
465 }
466 case "var":
467 string result = null;
468 return this.variables.TryGetValue(name, out result) ? result : null;
469 default:
470 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
471 if (null != extension)
472 {
473 try
474 {
475 return extension.GetVariableValue(prefix, name);
476 }
477 catch (Exception e)
478 {
479 throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message));
480 }
481 }
482 else
483 {
484 return null;
485 }
486 }
487 }
488
489 /// <summary>
490 /// Sends a message to the message delegate if there is one.
491 /// </summary>
492 /// <param name="mea">Message event arguments.</param>
493 public void OnMessage(MessageEventArgs e)
494 {
495 Messaging.Instance.OnMessage(e);
496 }
497
498 /// <summary>
499 /// Sends resolved variable to delegate if there is one.
500 /// </summary>
501 /// <param name="mea">Message event arguments.</param>
502 public void OnResolvedVariable(ResolvedVariableEventArgs mea)
503 {
504 if (null != this.ResolvedVariable)
505 {
506 this.ResolvedVariable(this, mea);
507 }
508 }
509
510 /// <summary>
511 /// Add a variable.
512 /// </summary>
513 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
514 /// <param name="name">The variable name.</param>
515 /// <param name="value">The variable value.</param>
516 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value)
517 {
518 this.AddVariable(sourceLineNumbers, name, value, true);
519 }
520
521 /// <summary>
522 /// Add a variable.
523 /// </summary>
524 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
525 /// <param name="name">The variable name.</param>
526 /// <param name="value">The variable value.</param>
527 /// <param name="overwrite">Set to true to show variable overwrite warning.</param>
528 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning)
529 {
530 string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name);
531
532 if (null == currentValue)
533 {
534 this.variables.Add(name, value);
535 }
536 else
537 {
538 if (showWarning)
539 {
540 this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue));
541 }
542
543 this.variables[name] = value;
544 }
545 }
546
547 /// <summary>
548 /// Remove a variable.
549 /// </summary>
550 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
551 /// <param name="name">The variable name.</param>
552 internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name)
553 {
554 if (!this.variables.Remove(name))
555 {
556 this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name));
557 }
558 }
559 }
560}