aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Preprocessor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Preprocessor.cs')
-rw-r--r--src/WixToolset.Core/Preprocessor.cs1598
1 files changed, 1598 insertions, 0 deletions
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}