aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2019-03-01 16:45:55 -0800
committerRob Mensching <rob@robmensching.com>2019-03-01 16:51:02 -0800
commit615bc202834ac45a9a107e5fccd900081a4abf74 (patch)
treeceebc5b3652f0274bee745701a3538439b0275d1 /src/WixToolset.Core
parent009f11ca9cf8674b40b74888aae90bcd4817828b (diff)
downloadwix-615bc202834ac45a9a107e5fccd900081a4abf74.tar.gz
wix-615bc202834ac45a9a107e5fccd900081a4abf74.tar.bz2
wix-615bc202834ac45a9a107e5fccd900081a4abf74.zip
Include the preprocessed include files with the processed document
This change also cleans up the internal state handling of the preprocesor to pass the processing state around rather than depend on "global state" in member variables. This removes the need to "reset" the member variables before preprocessing which is much cleaner.
Diffstat (limited to 'src/WixToolset.Core')
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs6
-rw-r--r--src/WixToolset.Core/CommandLine/CompileCommand.cs6
-rw-r--r--src/WixToolset.Core/IPreprocessor.cs5
-rw-r--r--src/WixToolset.Core/IncludedFile.cs14
-rw-r--r--src/WixToolset.Core/PreprocessResult.cs15
-rw-r--r--src/WixToolset.Core/Preprocessor.cs362
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs2
7 files changed, 237 insertions, 173 deletions
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index b83aaec4..7e6ddd64 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -377,18 +377,18 @@ namespace WixToolset.Core.CommandLine
377 context.SourcePath = sourcePath; 377 context.SourcePath = sourcePath;
378 context.Variables = preprocessorVariables; 378 context.Variables = preprocessorVariables;
379 379
380 XDocument document = null; 380 IPreprocessResult result = null;
381 try 381 try
382 { 382 {
383 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>(); 383 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>();
384 document = preprocessor.Preprocess(context); 384 result = preprocessor.Preprocess(context);
385 } 385 }
386 catch (WixException e) 386 catch (WixException e)
387 { 387 {
388 this.Messaging.Write(e.Error); 388 this.Messaging.Write(e.Error);
389 } 389 }
390 390
391 return document; 391 return result?.Document;
392 } 392 }
393 393
394 private class CommandLine 394 private class CommandLine
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs
index 69e35cab..bc37ee8c 100644
--- a/src/WixToolset.Core/CommandLine/CompileCommand.cs
+++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs
@@ -63,11 +63,11 @@ namespace WixToolset.Core.CommandLine
63 context.SourcePath = sourceFile.SourcePath; 63 context.SourcePath = sourceFile.SourcePath;
64 context.Variables = this.PreprocessorVariables; 64 context.Variables = this.PreprocessorVariables;
65 65
66 XDocument document = null; 66 IPreprocessResult result = null;
67 try 67 try
68 { 68 {
69 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>(); 69 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>();
70 document = preprocessor.Preprocess(context); 70 result = preprocessor.Preprocess(context);
71 } 71 }
72 catch (WixException e) 72 catch (WixException e)
73 { 73 {
@@ -83,7 +83,7 @@ namespace WixToolset.Core.CommandLine
83 compileContext.Extensions = this.ExtensionManager.Create<ICompilerExtension>(); 83 compileContext.Extensions = this.ExtensionManager.Create<ICompilerExtension>();
84 compileContext.OutputPath = sourceFile.OutputPath; 84 compileContext.OutputPath = sourceFile.OutputPath;
85 compileContext.Platform = this.Platform; 85 compileContext.Platform = this.Platform;
86 compileContext.Source = document; 86 compileContext.Source = result?.Document;
87 87
88 var compiler = this.ServiceProvider.GetService<ICompiler>(); 88 var compiler = this.ServiceProvider.GetService<ICompiler>();
89 var intermediate = compiler.Compile(compileContext); 89 var intermediate = compiler.Compile(compileContext);
diff --git a/src/WixToolset.Core/IPreprocessor.cs b/src/WixToolset.Core/IPreprocessor.cs
index d892399c..151f8111 100644
--- a/src/WixToolset.Core/IPreprocessor.cs
+++ b/src/WixToolset.Core/IPreprocessor.cs
@@ -3,13 +3,12 @@
3namespace WixToolset.Core 3namespace WixToolset.Core
4{ 4{
5 using System.Xml; 5 using System.Xml;
6 using System.Xml.Linq;
7 using WixToolset.Extensibility.Data; 6 using WixToolset.Extensibility.Data;
8 7
9 public interface IPreprocessor 8 public interface IPreprocessor
10 { 9 {
11 XDocument Preprocess(IPreprocessContext context); 10 IPreprocessResult Preprocess(IPreprocessContext context);
12 11
13 XDocument Preprocess(IPreprocessContext context, XmlReader reader); 12 IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader);
14 } 13 }
15} 14}
diff --git a/src/WixToolset.Core/IncludedFile.cs b/src/WixToolset.Core/IncludedFile.cs
new file mode 100644
index 00000000..25d51191
--- /dev/null
+++ b/src/WixToolset.Core/IncludedFile.cs
@@ -0,0 +1,14 @@
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.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class IncludedFile : IIncludedFile
9 {
10 public string Path { get; set; }
11
12 public SourceLineNumber SourceLineNumbers { get; set; }
13 }
14}
diff --git a/src/WixToolset.Core/PreprocessResult.cs b/src/WixToolset.Core/PreprocessResult.cs
new file mode 100644
index 00000000..8595d21d
--- /dev/null
+++ b/src/WixToolset.Core/PreprocessResult.cs
@@ -0,0 +1,15 @@
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.Core
4{
5 using System.Collections.Generic;
6 using System.Xml.Linq;
7 using WixToolset.Extensibility.Data;
8
9 public class PreprocessResult : IPreprocessResult
10 {
11 public XDocument Document { get; set; }
12
13 public IEnumerable<IIncludedFile> IncludedFiles { get; set; }
14 }
15}
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
index a829f7c4..5a255234 100644
--- a/src/WixToolset.Core/Preprocessor.cs
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -48,18 +48,6 @@ namespace WixToolset.Core
48 48
49 private IMessaging Messaging { get; } 49 private IMessaging Messaging { get; }
50 50
51 private IPreprocessContext Context { get; set; }
52
53 private Stack<string> CurrentFileStack { get; } = new Stack<string>();
54
55 private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>();
56
57 private Stack<bool> IncludeNextStack { get; } = new Stack<bool>();
58
59 private Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>();
60
61 private IPreprocessHelper Helper { get; set; }
62
63 /// <summary> 51 /// <summary>
64 /// Event for ifdef/ifndef directives. 52 /// Event for ifdef/ifndef directives.
65 /// </summary> 53 /// </summary>
@@ -100,21 +88,21 @@ namespace WixToolset.Core
100 /// </summary> 88 /// </summary>
101 /// <param name="context">The preprocessing context.</param> 89 /// <param name="context">The preprocessing context.</param>
102 /// <returns>XDocument with the postprocessed data.</returns> 90 /// <returns>XDocument with the postprocessed data.</returns>
103 public XDocument Preprocess(IPreprocessContext context) 91 public IPreprocessResult Preprocess(IPreprocessContext context)
104 { 92 {
105 this.Context = context; 93 var state = new ProcessingState(this.ServiceProvider, context);
106 this.Context.CurrentSourceLineNumber = new SourceLineNumber(context.SourcePath);
107 this.Context.Variables = this.Context.Variables == null ? new Dictionary<string, string>() : new Dictionary<string, string>(this.Context.Variables);
108 94
109 this.PreProcess(); 95 this.PreProcess(state);
110 96
111 XDocument document; 97 IPreprocessResult result;
112 using (var reader = XmlReader.Create(this.Context.SourcePath, DocumentXmlReaderSettings)) 98 using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings))
113 { 99 {
114 document = this.Process(reader); 100 result = this.Process(state, reader);
115 } 101 }
116 102
117 return this.PostProcess(document); 103 this.PostProcess(state, result);
104
105 return result;
118 } 106 }
119 107
120 /// <summary> 108 /// <summary>
@@ -123,7 +111,7 @@ namespace WixToolset.Core
123 /// <param name="context">The preprocessing context.</param> 111 /// <param name="context">The preprocessing context.</param>
124 /// <param name="reader">XmlReader to processing the context.</param> 112 /// <param name="reader">XmlReader to processing the context.</param>
125 /// <returns>XDocument with the postprocessed data.</returns> 113 /// <returns>XDocument with the postprocessed data.</returns>
126 public XDocument Preprocess(IPreprocessContext context, XmlReader reader) 114 public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader)
127 { 115 {
128 if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) 116 if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI))
129 { 117 {
@@ -131,15 +119,15 @@ namespace WixToolset.Core
131 context.SourcePath = uri.AbsolutePath; 119 context.SourcePath = uri.AbsolutePath;
132 } 120 }
133 121
134 this.Context = context; 122 var state = new ProcessingState(this.ServiceProvider, context);
135 this.Context.CurrentSourceLineNumber = new SourceLineNumber(context.SourcePath);
136 this.Context.Variables = (this.Context.Variables == null) ? new Dictionary<string, string>() : new Dictionary<string, string>(this.Context.Variables);
137 123
138 this.PreProcess(); 124 this.PreProcess(state);
139 125
140 var document = this.Process(reader); 126 var result = this.Process(state, reader);
141 127
142 return this.PostProcess(document); 128 this.PostProcess(state, result);
129
130 return result;
143 } 131 }
144 132
145 /// <summary> 133 /// <summary>
@@ -148,29 +136,33 @@ namespace WixToolset.Core
148 /// <param name="context">The preprocessing context.</param> 136 /// <param name="context">The preprocessing context.</param>
149 /// <param name="reader">XmlReader to processing the context.</param> 137 /// <param name="reader">XmlReader to processing the context.</param>
150 /// <returns>XDocument with the postprocessed data.</returns> 138 /// <returns>XDocument with the postprocessed data.</returns>
151 private XDocument Process(XmlReader reader) 139 private IPreprocessResult Process(ProcessingState state, XmlReader reader)
152 { 140 {
153 this.Helper = this.ServiceProvider.GetService<IPreprocessHelper>(); 141 state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR"));
154
155 this.CurrentFileStack.Clear();
156 this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR"));
157 142
158 // Process the reader into the output. 143 // Process the reader into the output.
159 var output = new XDocument(); 144 IPreprocessResult result = null;
160 try 145 try
161 { 146 {
162 this.PreprocessReader(false, reader, output, 0); 147 this.PreprocessReader(state, false, reader, state.Output, 0);
163 148
164 // Fire event with post-processed document. 149 // Fire event with post-processed document.
165 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourcePath, output)); 150 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output));
151
152 if (!this.Messaging.EncounteredError)
153 {
154 result = this.ServiceProvider.GetService<IPreprocessResult>();
155 result.Document = state.Output;
156 result.IncludedFiles = state.IncludedFiles;
157 }
166 } 158 }
167 catch (XmlException e) 159 catch (XmlException e)
168 { 160 {
169 this.UpdateCurrentLineNumber(reader, 0); 161 this.UpdateCurrentLineNumber(state, reader, 0);
170 throw new WixException(ErrorMessages.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); 162 throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message));
171 } 163 }
172 164
173 return this.Messaging.EncounteredError ? null : output; 165 return result;
174 } 166 }
175 167
176 /// <summary> 168 /// <summary>
@@ -277,7 +269,7 @@ namespace WixToolset.Core
277 /// <param name="reader">Reader for the source document.</param> 269 /// <param name="reader">Reader for the source document.</param>
278 /// <param name="container">Node where content should be added.</param> 270 /// <param name="container">Node where content should be added.</param>
279 /// <param name="offset">Original offset for the line numbers being processed.</param> 271 /// <param name="offset">Original offset for the line numbers being processed.</param>
280 private void PreprocessReader(bool include, XmlReader reader, XContainer container, int offset) 272 private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset)
281 { 273 {
282 var currentContainer = container; 274 var currentContainer = container;
283 var containerStack = new Stack<XContainer>(); 275 var containerStack = new Stack<XContainer>();
@@ -289,9 +281,9 @@ namespace WixToolset.Core
289 while (reader.Read()) 281 while (reader.Read())
290 { 282 {
291 // update information here in case an error occurs before the next read 283 // update information here in case an error occurs before the next read
292 this.UpdateCurrentLineNumber(reader, offset); 284 this.UpdateCurrentLineNumber(state, reader, offset);
293 285
294 var sourceLineNumbers = this.Context.CurrentSourceLineNumber; 286 var sourceLineNumbers = state.Context.CurrentSourceLineNumber;
295 287
296 // check for changes in conditional processing 288 // check for changes in conditional processing
297 if (XmlNodeType.ProcessingInstruction == reader.NodeType) 289 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
@@ -305,7 +297,7 @@ namespace WixToolset.Core
305 ifStack.Push(ifContext); 297 ifStack.Push(ifContext);
306 if (ifContext.IsTrue) 298 if (ifContext.IsTrue)
307 { 299 {
308 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(reader.Value), IfState.If); 300 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If);
309 } 301 }
310 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 302 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
311 { 303 {
@@ -319,7 +311,7 @@ namespace WixToolset.Core
319 name = reader.Value.Trim(); 311 name = reader.Value.Trim();
320 if (ifContext.IsTrue) 312 if (ifContext.IsTrue)
321 { 313 {
322 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); 314 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
323 } 315 }
324 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 316 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
325 { 317 {
@@ -334,7 +326,7 @@ namespace WixToolset.Core
334 name = reader.Value.Trim(); 326 name = reader.Value.Trim();
335 if (ifContext.IsTrue) 327 if (ifContext.IsTrue)
336 { 328 {
337 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); 329 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
338 } 330 }
339 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 331 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
340 { 332 {
@@ -358,7 +350,7 @@ namespace WixToolset.Core
358 ifContext.IfState = IfState.ElseIf; // we're now in an elseif 350 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
359 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test 351 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
360 { 352 {
361 ifContext.IsTrue = this.EvaluateExpression(reader.Value); 353 ifContext.IsTrue = this.EvaluateExpression(state, reader.Value);
362 } 354 }
363 else if (ifContext.IsTrue) 355 else if (ifContext.IsTrue)
364 { 356 {
@@ -441,35 +433,35 @@ namespace WixToolset.Core
441 switch (reader.LocalName) 433 switch (reader.LocalName)
442 { 434 {
443 case "define": 435 case "define":
444 this.PreprocessDefine(reader.Value); 436 this.PreprocessDefine(state, reader.Value);
445 break; 437 break;
446 438
447 case "error": 439 case "error":
448 this.PreprocessError(reader.Value); 440 this.PreprocessError(state, reader.Value);
449 break; 441 break;
450 442
451 case "warning": 443 case "warning":
452 this.PreprocessWarning(reader.Value); 444 this.PreprocessWarning(state, reader.Value);
453 break; 445 break;
454 446
455 case "undef": 447 case "undef":
456 this.PreprocessUndef(reader.Value); 448 this.PreprocessUndef(state, reader.Value);
457 break; 449 break;
458 450
459 case "include": 451 case "include":
460 this.UpdateCurrentLineNumber(reader, offset); 452 this.UpdateCurrentLineNumber(state, reader, offset);
461 this.PreprocessInclude(reader.Value, currentContainer); 453 this.PreprocessInclude(state, reader.Value, currentContainer);
462 break; 454 break;
463 455
464 case "foreach": 456 case "foreach":
465 this.PreprocessForeach(reader, currentContainer, offset); 457 this.PreprocessForeach(state, reader, currentContainer, offset);
466 break; 458 break;
467 459
468 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error 460 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
469 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); 461 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach"));
470 462
471 case "pragma": 463 case "pragma":
472 this.PreprocessPragma(reader.Value, currentContainer); 464 this.PreprocessPragma(state, reader.Value, currentContainer);
473 break; 465 break;
474 466
475 default: 467 default:
@@ -479,15 +471,15 @@ namespace WixToolset.Core
479 break; 471 break;
480 472
481 case XmlNodeType.Element: 473 case XmlNodeType.Element:
482 if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek()) 474 if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek())
483 { 475 {
484 if ("Include" != reader.LocalName) 476 if ("Include" != reader.LocalName)
485 { 477 {
486 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); 478 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
487 } 479 }
488 480
489 this.IncludeNextStack.Pop(); 481 state.IncludeNextStack.Pop();
490 this.IncludeNextStack.Push(false); 482 state.IncludeNextStack.Push(false);
491 break; 483 break;
492 } 484 }
493 485
@@ -496,12 +488,12 @@ namespace WixToolset.Core
496 var element = new XElement(ns + reader.LocalName); 488 var element = new XElement(ns + reader.LocalName);
497 currentContainer.Add(element); 489 currentContainer.Add(element);
498 490
499 this.UpdateCurrentLineNumber(reader, offset); 491 this.UpdateCurrentLineNumber(state, reader, offset);
500 element.AddAnnotation(sourceLineNumbers); 492 element.AddAnnotation(sourceLineNumbers);
501 493
502 while (reader.MoveToNextAttribute()) 494 while (reader.MoveToNextAttribute())
503 { 495 {
504 var value = this.Helper.PreprocessString(this.Context, reader.Value); 496 var value = state.Helper.PreprocessString(state.Context, reader.Value);
505 497
506 var attribNamespace = XNamespace.Get(reader.NamespaceURI); 498 var attribNamespace = XNamespace.Get(reader.NamespaceURI);
507 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; 499 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
@@ -524,12 +516,12 @@ namespace WixToolset.Core
524 break; 516 break;
525 517
526 case XmlNodeType.Text: 518 case XmlNodeType.Text:
527 var postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value); 519 var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value);
528 currentContainer.Add(postprocessedText); 520 currentContainer.Add(postprocessedText);
529 break; 521 break;
530 522
531 case XmlNodeType.CDATA: 523 case XmlNodeType.CDATA:
532 var postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value); 524 var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value);
533 currentContainer.Add(new XCData(postprocessedValue)); 525 currentContainer.Add(new XCData(postprocessedValue));
534 break; 526 break;
535 527
@@ -540,13 +532,13 @@ namespace WixToolset.Core
540 532
541 if (0 != ifStack.Count) 533 if (0 != ifStack.Count)
542 { 534 {
543 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "if", "endif")); 535 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif"));
544 } 536 }
545 537
546 // TODO: can this actually happen? 538 // TODO: can this actually happen?
547 if (0 != containerStack.Count) 539 if (0 != containerStack.Count)
548 { 540 {
549 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "nodes", "nodes")); 541 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes"));
550 } 542 }
551 } 543 }
552 544
@@ -554,37 +546,37 @@ namespace WixToolset.Core
554 /// Processes an error processing instruction. 546 /// Processes an error processing instruction.
555 /// </summary> 547 /// </summary>
556 /// <param name="errorMessage">Text from source.</param> 548 /// <param name="errorMessage">Text from source.</param>
557 private void PreprocessError(string errorMessage) 549 private void PreprocessError(ProcessingState state, string errorMessage)
558 { 550 {
559 // Resolve other variables in the error message. 551 // Resolve other variables in the error message.
560 errorMessage = this.Helper.PreprocessString(this.Context, errorMessage); 552 errorMessage = state.Helper.PreprocessString(state.Context, errorMessage);
561 553
562 throw new WixException(ErrorMessages.PreprocessorError(this.Context.CurrentSourceLineNumber, errorMessage)); 554 throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage));
563 } 555 }
564 556
565 /// <summary> 557 /// <summary>
566 /// Processes a warning processing instruction. 558 /// Processes a warning processing instruction.
567 /// </summary> 559 /// </summary>
568 /// <param name="warningMessage">Text from source.</param> 560 /// <param name="warningMessage">Text from source.</param>
569 private void PreprocessWarning(string warningMessage) 561 private void PreprocessWarning(ProcessingState state, string warningMessage)
570 { 562 {
571 // Resolve other variables in the warning message. 563 // Resolve other variables in the warning message.
572 warningMessage = this.Helper.PreprocessString(this.Context, warningMessage); 564 warningMessage = state.Helper.PreprocessString(state.Context, warningMessage);
573 565
574 this.Messaging.Write(WarningMessages.PreprocessorWarning(this.Context.CurrentSourceLineNumber, warningMessage)); 566 this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage));
575 } 567 }
576 568
577 /// <summary> 569 /// <summary>
578 /// Processes a define processing instruction and creates the appropriate parameter. 570 /// Processes a define processing instruction and creates the appropriate parameter.
579 /// </summary> 571 /// </summary>
580 /// <param name="originalDefine">Text from source.</param> 572 /// <param name="originalDefine">Text from source.</param>
581 private void PreprocessDefine(string originalDefine) 573 private void PreprocessDefine(ProcessingState state, string originalDefine)
582 { 574 {
583 var match = DefineRegex.Match(originalDefine); 575 var match = DefineRegex.Match(originalDefine);
584 576
585 if (!match.Success) 577 if (!match.Success)
586 { 578 {
587 throw new WixException(ErrorMessages.IllegalDefineStatement(this.Context.CurrentSourceLineNumber, originalDefine)); 579 throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine));
588 } 580 }
589 581
590 var defineName = match.Groups["varName"].Value; 582 var defineName = match.Groups["varName"].Value;
@@ -599,15 +591,15 @@ namespace WixToolset.Core
599 } 591 }
600 592
601 // resolve other variables in the variable value 593 // resolve other variables in the variable value
602 defineValue = this.Helper.PreprocessString(this.Context, defineValue); 594 defineValue = state.Helper.PreprocessString(state.Context, defineValue);
603 595
604 if (defineName.StartsWith("var.", StringComparison.Ordinal)) 596 if (defineName.StartsWith("var.", StringComparison.Ordinal))
605 { 597 {
606 this.Helper.AddVariable(this.Context, defineName.Substring(4), defineValue); 598 state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue);
607 } 599 }
608 else 600 else
609 { 601 {
610 this.Helper.AddVariable(this.Context, defineName, defineValue); 602 state.Helper.AddVariable(state.Context, defineName, defineValue);
611 } 603 }
612 } 604 }
613 605
@@ -615,17 +607,17 @@ namespace WixToolset.Core
615 /// Processes an undef processing instruction and creates the appropriate parameter. 607 /// Processes an undef processing instruction and creates the appropriate parameter.
616 /// </summary> 608 /// </summary>
617 /// <param name="originalDefine">Text from source.</param> 609 /// <param name="originalDefine">Text from source.</param>
618 private void PreprocessUndef(string originalDefine) 610 private void PreprocessUndef(ProcessingState state, string originalDefine)
619 { 611 {
620 var name = this.Helper.PreprocessString(this.Context, originalDefine.Trim()); 612 var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim());
621 613
622 if (name.StartsWith("var.", StringComparison.Ordinal)) 614 if (name.StartsWith("var.", StringComparison.Ordinal))
623 { 615 {
624 this.Helper.RemoveVariable(this.Context, name.Substring(4)); 616 state.Helper.RemoveVariable(state.Context, name.Substring(4));
625 } 617 }
626 else 618 else
627 { 619 {
628 this.Helper.RemoveVariable(this.Context, name); 620 state.Helper.RemoveVariable(state.Context, name);
629 } 621 }
630 } 622 }
631 623
@@ -634,14 +626,14 @@ namespace WixToolset.Core
634 /// </summary> 626 /// </summary>
635 /// <param name="includePath">Path to included file.</param> 627 /// <param name="includePath">Path to included file.</param>
636 /// <param name="parent">Parent container for included content.</param> 628 /// <param name="parent">Parent container for included content.</param>
637 private void PreprocessInclude(string includePath, XContainer parent) 629 private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent)
638 { 630 {
639 var sourceLineNumbers = this.Context.CurrentSourceLineNumber; 631 var sourceLineNumbers = state.Context.CurrentSourceLineNumber;
640 632
641 // Preprocess variables in the path. 633 // Preprocess variables in the path.
642 includePath = this.Helper.PreprocessString(this.Context, includePath); 634 includePath = state.Helper.PreprocessString(state.Context, includePath);
643 635
644 var includeFile = this.GetIncludeFile(includePath); 636 var includeFile = this.GetIncludeFile(state, includePath);
645 637
646 if (null == includeFile) 638 if (null == includeFile)
647 { 639 {
@@ -650,22 +642,28 @@ namespace WixToolset.Core
650 642
651 using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) 643 using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
652 { 644 {
653 this.PushInclude(includeFile); 645 this.PushInclude(state, includeFile);
654 646
655 // process the included reader into the writer 647 // process the included reader into the writer
656 try 648 try
657 { 649 {
658 this.PreprocessReader(true, reader, parent, 0); 650 this.PreprocessReader(state, true, reader, parent, 0);
659 } 651 }
660 catch (XmlException e) 652 catch (XmlException e)
661 { 653 {
662 this.UpdateCurrentLineNumber(reader, 0); 654 this.UpdateCurrentLineNumber(state, reader, 0);
663 throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); 655 throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message));
664 } 656 }
665 657
666 this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); 658 this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile));
667 659
668 this.PopInclude(); 660 var includedFile = this.ServiceProvider.GetService<IIncludedFile>();
661 includedFile.Path = includeFile;
662 includedFile.SourceLineNumbers = sourceLineNumbers;
663
664 state.IncludedFiles.Add(includedFile);
665
666 this.PopInclude(state);
669 } 667 }
670 } 668 }
671 669
@@ -675,13 +673,13 @@ namespace WixToolset.Core
675 /// <param name="reader">The xml reader.</param> 673 /// <param name="reader">The xml reader.</param>
676 /// <param name="container">The container where to output processed data.</param> 674 /// <param name="container">The container where to output processed data.</param>
677 /// <param name="offset">Offset for the line numbers.</param> 675 /// <param name="offset">Offset for the line numbers.</param>
678 private void PreprocessForeach(XmlReader reader, XContainer container, int offset) 676 private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset)
679 { 677 {
680 // Find the "in" token. 678 // Find the "in" token.
681 var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); 679 var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
682 if (0 > indexOfInToken) 680 if (0 > indexOfInToken)
683 { 681 {
684 throw new WixException(ErrorMessages.IllegalForeach(this.Context.CurrentSourceLineNumber, reader.Value)); 682 throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value));
685 } 683 }
686 684
687 // parse out the variable name 685 // parse out the variable name
@@ -689,7 +687,7 @@ namespace WixToolset.Core
689 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); 687 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
690 688
691 // preprocess the variable values string because it might be a variable itself 689 // preprocess the variable values string because it might be a variable itself
692 varValuesString = this.Helper.PreprocessString(this.Context, varValuesString); 690 varValuesString = state.Helper.PreprocessString(state.Context, varValuesString);
693 691
694 var varValues = varValuesString.Split(';'); 692 var varValues = varValuesString.Split(';');
695 693
@@ -751,7 +749,7 @@ namespace WixToolset.Core
751 } 749 }
752 else if (reader.NodeType == XmlNodeType.None) 750 else if (reader.NodeType == XmlNodeType.None)
753 { 751 {
754 throw new WixException(ErrorMessages.ExpectedEndforeach(this.Context.CurrentSourceLineNumber)); 752 throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber));
755 } 753 }
756 754
757 reader.Read(); 755 reader.Read();
@@ -765,16 +763,16 @@ namespace WixToolset.Core
765 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) 763 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
766 { 764 {
767 // Always overwrite foreach variables. 765 // Always overwrite foreach variables.
768 this.Helper.AddVariable(this.Context, varName, varValue, false); 766 state.Helper.AddVariable(state.Context, varName, varValue, false);
769 767
770 try 768 try
771 { 769 {
772 this.PreprocessReader(false, loopReader, container, offset); 770 this.PreprocessReader(state, false, loopReader, container, offset);
773 } 771 }
774 catch (XmlException e) 772 catch (XmlException e)
775 { 773 {
776 this.UpdateCurrentLineNumber(loopReader, offset); 774 this.UpdateCurrentLineNumber(state, loopReader, offset);
777 throw new WixException(ErrorMessages.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); 775 throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message));
778 } 776 }
779 777
780 fragmentStream.Position = 0; // seek back to the beginning for the next loop. 778 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
@@ -787,25 +785,25 @@ namespace WixToolset.Core
787 /// Processes a pragma processing instruction 785 /// Processes a pragma processing instruction
788 /// </summary> 786 /// </summary>
789 /// <param name="pragmaText">Text from source.</param> 787 /// <param name="pragmaText">Text from source.</param>
790 private void PreprocessPragma(string pragmaText, XContainer parent) 788 private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent)
791 { 789 {
792 var match = PragmaRegex.Match(pragmaText); 790 var match = PragmaRegex.Match(pragmaText);
793 791
794 if (!match.Success) 792 if (!match.Success)
795 { 793 {
796 throw new WixException(ErrorMessages.InvalidPreprocessorPragma(this.Context.CurrentSourceLineNumber, pragmaText)); 794 throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText));
797 } 795 }
798 796
799 // resolve other variables in the pragma argument(s) 797 // resolve other variables in the pragma argument(s)
800 var pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim(); 798 var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim();
801 799
802 try 800 try
803 { 801 {
804 this.Helper.PreprocessPragma(this.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); 802 state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent);
805 } 803 }
806 catch (Exception e) 804 catch (Exception e)
807 { 805 {
808 throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(this.Context.CurrentSourceLineNumber, pragmaText, e.Message)); 806 throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message));
809 } 807 }
810 } 808 }
811 809
@@ -816,7 +814,7 @@ namespace WixToolset.Core
816 /// <param name="expression">Expression with token removed.</param> 814 /// <param name="expression">Expression with token removed.</param>
817 /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param> 815 /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param>
818 /// <returns>Next token.</returns> 816 /// <returns>Next token.</returns>
819 private string GetNextToken(string originalExpression, ref string expression, out bool stringLiteral) 817 private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral)
820 { 818 {
821 stringLiteral = false; 819 stringLiteral = false;
822 var token = String.Empty; 820 var token = String.Empty;
@@ -832,11 +830,11 @@ namespace WixToolset.Core
832 var endingQuotes = expression.IndexOf('\"', 1); 830 var endingQuotes = expression.IndexOf('\"', 1);
833 if (-1 == endingQuotes) 831 if (-1 == endingQuotes)
834 { 832 {
835 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 833 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
836 } 834 }
837 835
838 // cut the quotes off the string 836 // cut the quotes off the string
839 token = this.Helper.PreprocessString(this.Context, expression.Substring(1, endingQuotes - 1)); 837 token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1));
840 838
841 // advance past this string 839 // advance past this string
842 expression = expression.Substring(endingQuotes + 1).Trim(); 840 expression = expression.Substring(endingQuotes + 1).Trim();
@@ -866,7 +864,7 @@ namespace WixToolset.Core
866 864
867 if (-1 == endingParen) 865 if (-1 == endingParen)
868 { 866 {
869 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 867 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
870 } 868 }
871 token = expression.Substring(0, endingParen + 1); 869 token = expression.Substring(0, endingParen + 1);
872 870
@@ -962,7 +960,7 @@ namespace WixToolset.Core
962 /// <param name="originalExpression">Original expression for error message.</param> 960 /// <param name="originalExpression">Original expression for error message.</param>
963 /// <param name="variable">Variable to evaluate.</param> 961 /// <param name="variable">Variable to evaluate.</param>
964 /// <returns>Value of variable.</returns> 962 /// <returns>Value of variable.</returns>
965 private string EvaluateVariable(string originalExpression, string variable) 963 private string EvaluateVariable(ProcessingState state, string originalExpression, string variable)
966 { 964 {
967 // By default it's a literal and will only be evaluated if it 965 // By default it's a literal and will only be evaluated if it
968 // matches the variable format 966 // matches the variable format
@@ -972,7 +970,7 @@ namespace WixToolset.Core
972 { 970 {
973 try 971 try
974 { 972 {
975 varValue = this.Helper.PreprocessString(this.Context, variable); 973 varValue = state.Helper.PreprocessString(state.Context, variable);
976 } 974 }
977 catch (ArgumentNullException) 975 catch (ArgumentNullException)
978 { 976 {
@@ -983,12 +981,12 @@ namespace WixToolset.Core
983 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) 981 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
984 { 982 {
985 // make sure it doesn't contain parenthesis 983 // make sure it doesn't contain parenthesis
986 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 984 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
987 } 985 }
988 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) 986 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
989 { 987 {
990 // shouldn't contain quotes 988 // shouldn't contain quotes
991 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 989 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
992 } 990 }
993 991
994 return varValue; 992 return varValue;
@@ -1002,31 +1000,31 @@ namespace WixToolset.Core
1002 /// <param name="leftValue">Left side value from expression.</param> 1000 /// <param name="leftValue">Left side value from expression.</param>
1003 /// <param name="operation">Operation in expression.</param> 1001 /// <param name="operation">Operation in expression.</param>
1004 /// <param name="rightValue">Right side value from expression.</param> 1002 /// <param name="rightValue">Right side value from expression.</param>
1005 private void GetNameValuePair(string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) 1003 private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue)
1006 { 1004 {
1007 leftValue = this.GetNextToken(originalExpression, ref expression, out var stringLiteral); 1005 leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral);
1008 1006
1009 // If it wasn't a string literal, evaluate it 1007 // If it wasn't a string literal, evaluate it
1010 if (!stringLiteral) 1008 if (!stringLiteral)
1011 { 1009 {
1012 leftValue = this.EvaluateVariable(originalExpression, leftValue); 1010 leftValue = this.EvaluateVariable(state, originalExpression, leftValue);
1013 } 1011 }
1014 1012
1015 // Get the operation 1013 // Get the operation
1016 operation = this.GetNextToken(originalExpression, ref expression, out stringLiteral); 1014 operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral);
1017 if (IsOperator(operation)) 1015 if (IsOperator(operation))
1018 { 1016 {
1019 if (stringLiteral) 1017 if (stringLiteral)
1020 { 1018 {
1021 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 1019 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1022 } 1020 }
1023 1021
1024 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral); 1022 rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral);
1025 1023
1026 // If it wasn't a string literal, evaluate it 1024 // If it wasn't a string literal, evaluate it
1027 if (!stringLiteral) 1025 if (!stringLiteral)
1028 { 1026 {
1029 rightValue = this.EvaluateVariable(originalExpression, rightValue); 1027 rightValue = this.EvaluateVariable(state, originalExpression, rightValue);
1030 } 1028 }
1031 } 1029 }
1032 else 1030 else
@@ -1052,11 +1050,11 @@ namespace WixToolset.Core
1052 /// <param name="originalExpression">Original expression to evaluate.</param> 1050 /// <param name="originalExpression">Original expression to evaluate.</param>
1053 /// <param name="expression">Expression modified while processing.</param> 1051 /// <param name="expression">Expression modified while processing.</param>
1054 /// <returns>true if expression evaluates to true.</returns> 1052 /// <returns>true if expression evaluates to true.</returns>
1055 private bool EvaluateAtomicExpression(string originalExpression, ref string expression) 1053 private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression)
1056 { 1054 {
1057 // Quick test to see if the first token is a variable 1055 // Quick test to see if the first token is a variable
1058 var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); 1056 var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal);
1059 this.GetNameValuePair(originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); 1057 this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue);
1060 1058
1061 var expressionValue = false; 1059 var expressionValue = false;
1062 1060
@@ -1065,7 +1063,7 @@ namespace WixToolset.Core
1065 { 1063 {
1066 if (operation.Length > 0) 1064 if (operation.Length > 0)
1067 { 1065 {
1068 throw new WixException(ErrorMessages.ExpectedVariable(this.Context.CurrentSourceLineNumber, originalExpression)); 1066 throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression));
1069 } 1067 }
1070 1068
1071 // false expression 1069 // false expression
@@ -1080,7 +1078,7 @@ namespace WixToolset.Core
1080 } 1078 }
1081 else 1079 else
1082 { 1080 {
1083 throw new WixException(ErrorMessages.UnexpectedLiteral(this.Context.CurrentSourceLineNumber, originalExpression)); 1081 throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression));
1084 } 1082 }
1085 } 1083 }
1086 else 1084 else
@@ -1120,11 +1118,11 @@ namespace WixToolset.Core
1120 } 1118 }
1121 catch (FormatException) 1119 catch (FormatException)
1122 { 1120 {
1123 throw new WixException(ErrorMessages.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 1121 throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1124 } 1122 }
1125 catch (OverflowException) 1123 catch (OverflowException)
1126 { 1124 {
1127 throw new WixException(ErrorMessages.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 1125 throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1128 } 1126 }
1129 1127
1130 // Compare the numbers 1128 // Compare the numbers
@@ -1148,7 +1146,7 @@ namespace WixToolset.Core
1148 /// <param name="expression">Expression modified while processing.</param> 1146 /// <param name="expression">Expression modified while processing.</param>
1149 /// <param name="endSubExpression">Index of end of sub-expression.</param> 1147 /// <param name="endSubExpression">Index of end of sub-expression.</param>
1150 /// <returns>Sub-expression in parenthesis.</returns> 1148 /// <returns>Sub-expression in parenthesis.</returns>
1151 private string GetParenthesisExpression(string originalExpression, string expression, out int endSubExpression) 1149 private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression)
1152 { 1150 {
1153 endSubExpression = 0; 1151 endSubExpression = 0;
1154 1152
@@ -1166,7 +1164,7 @@ namespace WixToolset.Core
1166 closeParenIndex = expression.IndexOf(')', closeParenIndex); 1164 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1167 if (closeParenIndex == -1) 1165 if (closeParenIndex == -1)
1168 { 1166 {
1169 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 1167 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1170 } 1168 }
1171 1169
1172 if (InsideQuotes(expression, closeParenIndex)) 1170 if (InsideQuotes(expression, closeParenIndex))
@@ -1201,7 +1199,7 @@ namespace WixToolset.Core
1201 /// <param name="currentValue">State to update.</param> 1199 /// <param name="currentValue">State to update.</param>
1202 /// <param name="operation">Operation to apply to current value.</param> 1200 /// <param name="operation">Operation to apply to current value.</param>
1203 /// <param name="prevResult">Previous result.</param> 1201 /// <param name="prevResult">Previous result.</param>
1204 private void UpdateExpressionValue(ref bool currentValue, PreprocessorOperation operation, bool prevResult) 1202 private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult)
1205 { 1203 {
1206 switch (operation) 1204 switch (operation)
1207 { 1205 {
@@ -1215,7 +1213,7 @@ namespace WixToolset.Core
1215 currentValue = !currentValue; 1213 currentValue = !currentValue;
1216 break; 1214 break;
1217 default: 1215 default:
1218 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString())); 1216 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString()));
1219 } 1217 }
1220 } 1218 }
1221 1219
@@ -1224,10 +1222,10 @@ namespace WixToolset.Core
1224 /// </summary> 1222 /// </summary>
1225 /// <param name="expression">Expression to evaluate.</param> 1223 /// <param name="expression">Expression to evaluate.</param>
1226 /// <returns>Boolean result of expression.</returns> 1224 /// <returns>Boolean result of expression.</returns>
1227 private bool EvaluateExpression(string expression) 1225 private bool EvaluateExpression(ProcessingState state, string expression)
1228 { 1226 {
1229 var tmpExpression = expression; 1227 var tmpExpression = expression;
1230 return this.EvaluateExpressionRecurse(expression, ref tmpExpression, PreprocessorOperation.And, true); 1228 return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true);
1231 } 1229 }
1232 1230
1233 /// <summary> 1231 /// <summary>
@@ -1258,20 +1256,20 @@ namespace WixToolset.Core
1258 /// <param name="prevResultOperation">The operation to apply to this result</param> 1256 /// <param name="prevResultOperation">The operation to apply to this result</param>
1259 /// <param name="prevResult">The previous result to apply to this result</param> 1257 /// <param name="prevResult">The previous result to apply to this result</param>
1260 /// <returns>Boolean to indicate if the expression is true or false</returns> 1258 /// <returns>Boolean to indicate if the expression is true or false</returns>
1261 private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) 1259 private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult)
1262 { 1260 {
1263 var expressionValue = false; 1261 var expressionValue = false;
1264 expression = expression.Trim(); 1262 expression = expression.Trim();
1265 if (expression.Length == 0) 1263 if (expression.Length == 0)
1266 { 1264 {
1267 throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(this.Context.CurrentSourceLineNumber, originalExpression)); 1265 throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression));
1268 } 1266 }
1269 1267
1270 // If the expression starts with parenthesis, evaluate it 1268 // If the expression starts with parenthesis, evaluate it
1271 if (expression.IndexOf('(') == 0) 1269 if (expression.IndexOf('(') == 0)
1272 { 1270 {
1273 var subExpression = this.GetParenthesisExpression(originalExpression, expression, out var endSubExpressionIndex); 1271 var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex);
1274 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true); 1272 expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true);
1275 1273
1276 // Now get the rest of the expression that hasn't been evaluated 1274 // Now get the rest of the expression that hasn't been evaluated
1277 expression = expression.Substring(endSubExpressionIndex).Trim(); 1275 expression = expression.Substring(endSubExpressionIndex).Trim();
@@ -1284,19 +1282,19 @@ namespace WixToolset.Core
1284 expression = expression.Substring(3).Trim(); 1282 expression = expression.Substring(3).Trim();
1285 if (expression.Length == 0) 1283 if (expression.Length == 0)
1286 { 1284 {
1287 throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(this.Context.CurrentSourceLineNumber, originalExpression)); 1285 throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression));
1288 } 1286 }
1289 1287
1290 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true); 1288 expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true);
1291 } 1289 }
1292 else // Expect a literal 1290 else // Expect a literal
1293 { 1291 {
1294 expressionValue = this.EvaluateAtomicExpression(originalExpression, ref expression); 1292 expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression);
1295 1293
1296 // Expect the literal that was just evaluated to already be cut off 1294 // Expect the literal that was just evaluated to already be cut off
1297 } 1295 }
1298 } 1296 }
1299 this.UpdateExpressionValue(ref expressionValue, prevResultOperation, prevResult); 1297 this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult);
1300 1298
1301 // If there's still an expression left, it must start with AND or OR. 1299 // If there's still an expression left, it must start with AND or OR.
1302 if (expression.Trim().Length > 0) 1300 if (expression.Trim().Length > 0)
@@ -1304,16 +1302,16 @@ namespace WixToolset.Core
1304 if (StartsWithKeyword(expression, PreprocessorOperation.And)) 1302 if (StartsWithKeyword(expression, PreprocessorOperation.And))
1305 { 1303 {
1306 expression = expression.Substring(3); 1304 expression = expression.Substring(3);
1307 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.And, expressionValue); 1305 return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue);
1308 } 1306 }
1309 else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) 1307 else if (StartsWithKeyword(expression, PreprocessorOperation.Or))
1310 { 1308 {
1311 expression = expression.Substring(2); 1309 expression = expression.Substring(2);
1312 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); 1310 return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue);
1313 } 1311 }
1314 else 1312 else
1315 { 1313 {
1316 throw new WixException(ErrorMessages.InvalidSubExpression(this.Context.CurrentSourceLineNumber, expression, originalExpression)); 1314 throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression));
1317 } 1315 }
1318 } 1316 }
1319 1317
@@ -1325,16 +1323,16 @@ namespace WixToolset.Core
1325 /// </summary> 1323 /// </summary>
1326 /// <param name="reader">The xml reader for the preprocessor.</param> 1324 /// <param name="reader">The xml reader for the preprocessor.</param>
1327 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param> 1325 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param>
1328 private void UpdateCurrentLineNumber(XmlReader reader, int offset) 1326 private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset)
1329 { 1327 {
1330 var lineInfoReader = reader as IXmlLineInfo; 1328 var lineInfoReader = reader as IXmlLineInfo;
1331 if (null != lineInfoReader) 1329 if (null != lineInfoReader)
1332 { 1330 {
1333 var newLine = lineInfoReader.LineNumber + offset; 1331 var newLine = lineInfoReader.LineNumber + offset;
1334 1332
1335 if (this.Context.CurrentSourceLineNumber.LineNumber != newLine) 1333 if (state.Context.CurrentSourceLineNumber.LineNumber != newLine)
1336 { 1334 {
1337 this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.CurrentSourceLineNumber.FileName, newLine); 1335 state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, newLine);
1338 } 1336 }
1339 } 1337 }
1340 } 1338 }
@@ -1343,28 +1341,28 @@ namespace WixToolset.Core
1343 /// Pushes a file name on the stack of included files. 1341 /// Pushes a file name on the stack of included files.
1344 /// </summary> 1342 /// </summary>
1345 /// <param name="fileName">Name to push on to the stack of included files.</param> 1343 /// <param name="fileName">Name to push on to the stack of included files.</param>
1346 private void PushInclude(string fileName) 1344 private void PushInclude(ProcessingState state, string fileName)
1347 { 1345 {
1348 if (1023 < this.CurrentFileStack.Count) 1346 if (1023 < state.CurrentFileStack.Count)
1349 { 1347 {
1350 throw new WixException(ErrorMessages.TooDeeplyIncluded(this.Context.CurrentSourceLineNumber, this.CurrentFileStack.Count)); 1348 throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count));
1351 } 1349 }
1352 1350
1353 this.CurrentFileStack.Push(fileName); 1351 state.CurrentFileStack.Push(fileName);
1354 this.SourceStack.Push(this.Context.CurrentSourceLineNumber); 1352 state.SourceStack.Push(state.Context.CurrentSourceLineNumber);
1355 this.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName); 1353 state.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName);
1356 this.IncludeNextStack.Push(true); 1354 state.IncludeNextStack.Push(true);
1357 } 1355 }
1358 1356
1359 /// <summary> 1357 /// <summary>
1360 /// Pops a file name from the stack of included files. 1358 /// Pops a file name from the stack of included files.
1361 /// </summary> 1359 /// </summary>
1362 private void PopInclude() 1360 private void PopInclude(ProcessingState state)
1363 { 1361 {
1364 this.Context.CurrentSourceLineNumber = this.SourceStack.Pop(); 1362 state.Context.CurrentSourceLineNumber = state.SourceStack.Pop();
1365 1363
1366 this.CurrentFileStack.Pop(); 1364 state.CurrentFileStack.Pop();
1367 this.IncludeNextStack.Pop(); 1365 state.IncludeNextStack.Pop();
1368 } 1366 }
1369 1367
1370 /// <summary> 1368 /// <summary>
@@ -1375,7 +1373,7 @@ namespace WixToolset.Core
1375 /// </summary> 1373 /// </summary>
1376 /// <param name="includePath">User-specified path to the included file (usually just the file name).</param> 1374 /// <param name="includePath">User-specified path to the included file (usually just the file name).</param>
1377 /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns> 1375 /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns>
1378 private string GetIncludeFile(string includePath) 1376 private string GetIncludeFile(ProcessingState state, string includePath)
1379 { 1377 {
1380 string finalIncludePath = null; 1378 string finalIncludePath = null;
1381 1379
@@ -1399,7 +1397,7 @@ namespace WixToolset.Core
1399 else // relative path 1397 else // relative path
1400 { 1398 {
1401 // build a string to test the directory containing the source file first 1399 // build a string to test the directory containing the source file first
1402 var currentFolder = this.CurrentFileStack.Peek(); 1400 var currentFolder = state.CurrentFileStack.Peek();
1403 var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); 1401 var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath);
1404 1402
1405 // test the source file directory 1403 // test the source file directory
@@ -1407,9 +1405,9 @@ namespace WixToolset.Core
1407 { 1405 {
1408 finalIncludePath = includeTestPath; 1406 finalIncludePath = includeTestPath;
1409 } 1407 }
1410 else // test all search paths in the order specified on the command line 1408 else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line
1411 { 1409 {
1412 foreach (var includeSearchPath in this.Context.IncludeSearchPaths) 1410 foreach (var includeSearchPath in state.Context.IncludeSearchPaths)
1413 { 1411 {
1414 // if the path exists, we have found the final string 1412 // if the path exists, we have found the final string
1415 includeTestPath = Path.Combine(includeSearchPath, includePath); 1413 includeTestPath = Path.Combine(includeSearchPath, includePath);
@@ -1425,17 +1423,22 @@ namespace WixToolset.Core
1425 return finalIncludePath; 1423 return finalIncludePath;
1426 } 1424 }
1427 1425
1428 private void PreProcess() 1426 private void PreProcess(ProcessingState state)
1429 { 1427 {
1430 foreach (var extension in this.Context.Extensions) 1428 if (state.Context.Extensions == null)
1429 {
1430 return;
1431 }
1432
1433 foreach (var extension in state.Context.Extensions)
1431 { 1434 {
1432 if (extension.Prefixes != null) 1435 if (extension.Prefixes != null)
1433 { 1436 {
1434 foreach (var prefix in extension.Prefixes) 1437 foreach (var prefix in extension.Prefixes)
1435 { 1438 {
1436 if (!this.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) 1439 if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension))
1437 { 1440 {
1438 this.ExtensionsByPrefix.Add(prefix, extension); 1441 state.ExtensionsByPrefix.Add(prefix, extension);
1439 } 1442 }
1440 else 1443 else
1441 { 1444 {
@@ -1444,18 +1447,49 @@ namespace WixToolset.Core
1444 } 1447 }
1445 } 1448 }
1446 1449
1447 extension.PrePreprocess(this.Context); 1450 extension.PrePreprocess(state.Context);
1448 } 1451 }
1449 } 1452 }
1450 1453
1451 private XDocument PostProcess(XDocument document) 1454 private void PostProcess(ProcessingState state, IPreprocessResult result)
1452 { 1455 {
1453 foreach (var extension in this.Context.Extensions) 1456 if (state.Context.Extensions == null)
1454 { 1457 {
1455 extension.PostPreprocess(document); 1458 return;
1456 } 1459 }
1457 1460
1458 return document; 1461 foreach (var extension in state.Context.Extensions)
1462 {
1463 extension.PostPreprocess(result);
1464 }
1465 }
1466
1467 private class ProcessingState
1468 {
1469 public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context)
1470 {
1471 this.Context = context;
1472 this.Context.CurrentSourceLineNumber = new SourceLineNumber(context.SourcePath);
1473 this.Context.Variables = this.Context.Variables == null ? new Dictionary<string, string>() : new Dictionary<string, string>(this.Context.Variables);
1474
1475 this.Helper = serviceProvider.GetService<IPreprocessHelper>();
1476 }
1477
1478 public IPreprocessContext Context { get; }
1479
1480 public IPreprocessHelper Helper { get; }
1481
1482 public List<IIncludedFile> IncludedFiles { get; } = new List<IIncludedFile>();
1483
1484 public XDocument Output { get; } = new XDocument();
1485
1486 public Stack<string> CurrentFileStack { get; } = new Stack<string>();
1487
1488 public Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>();
1489
1490 public Stack<bool> IncludeNextStack { get; } = new Stack<bool>();
1491
1492 public Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>();
1459 } 1493 }
1460 } 1494 }
1461} 1495}
diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs
index e03be0b2..267e4524 100644
--- a/src/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -45,6 +45,8 @@ namespace WixToolset.Core
45 this.AddService<IBindResult>((provider, singletons) => new BindResult()); 45 this.AddService<IBindResult>((provider, singletons) => new BindResult());
46 this.AddService<IComponentKeyPath>((provider, singletons) => new ComponentKeyPath()); 46 this.AddService<IComponentKeyPath>((provider, singletons) => new ComponentKeyPath());
47 this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult()); 47 this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult());
48 this.AddService<IIncludedFile>((provider, singletons) => new IncludedFile());
49 this.AddService<IPreprocessResult>((provider, singletons) => new PreprocessResult());
48 this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult()); 50 this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult());
49 this.AddService<IResolveResult>((provider, singletons) => new ResolveResult()); 51 this.AddService<IResolveResult>((provider, singletons) => new ResolveResult());
50 this.AddService<IResolvedCabinet>((provider, singletons) => new ResolvedCabinet()); 52 this.AddService<IResolvedCabinet>((provider, singletons) => new ResolvedCabinet());