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.cs597
1 files changed, 287 insertions, 310 deletions
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
index 9f0ab1bb..5a9cf115 100644
--- a/src/WixToolset.Core/Preprocessor.cs
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -6,7 +6,6 @@ namespace WixToolset.Core
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Globalization; 7 using System.Globalization;
8 using System.IO; 8 using System.IO;
9 using System.Linq;
10 using System.Text; 9 using System.Text;
11 using System.Text.RegularExpressions; 10 using System.Text.RegularExpressions;
12 using System.Xml; 11 using System.Xml;
@@ -20,7 +19,7 @@ namespace WixToolset.Core
20 /// <summary> 19 /// <summary>
21 /// Preprocessor object 20 /// Preprocessor object
22 /// </summary> 21 /// </summary>
23 internal class Preprocessor 22 internal class Preprocessor : IPreprocessor
24 { 23 {
25 private static readonly Regex DefineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); 24 private static readonly Regex DefineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
26 private static readonly Regex PragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); 25 private static readonly Regex PragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
@@ -30,6 +29,7 @@ namespace WixToolset.Core
30 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, 29 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
31 XmlResolver = null, 30 XmlResolver = null,
32 }; 31 };
32
33 private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() 33 private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
34 { 34 {
35 ConformanceLevel = ConformanceLevel.Fragment, 35 ConformanceLevel = ConformanceLevel.Fragment,
@@ -44,14 +44,6 @@ namespace WixToolset.Core
44 this.Messaging = this.ServiceProvider.GetService<IMessaging>(); 44 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
45 } 45 }
46 46
47 public IEnumerable<string> IncludeSearchPaths { get; set; }
48
49 public Platform Platform { get; set; }
50
51 public string SourcePath { get; set; }
52
53 public IDictionary<string, string> Variables { get; set; }
54
55 private IServiceProvider ServiceProvider { get; } 47 private IServiceProvider ServiceProvider { get; }
56 48
57 private IMessaging Messaging { get; } 49 private IMessaging Messaging { get; }
@@ -108,19 +100,21 @@ namespace WixToolset.Core
108 /// </summary> 100 /// </summary>
109 /// <param name="context">The preprocessing context.</param> 101 /// <param name="context">The preprocessing context.</param>
110 /// <returns>XDocument with the postprocessed data.</returns> 102 /// <returns>XDocument with the postprocessed data.</returns>
111 public XDocument Execute() 103 public XDocument Preprocess(IPreprocessContext context)
112 { 104 {
113 this.Context = this.CreateContext(); 105 this.Context = 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);
114 108
115 this.PreProcess(); 109 this.PreProcess();
116 110
117 XDocument document; 111 XDocument document;
118 using (XmlReader reader = XmlReader.Create(this.Context.SourceFile, DocumentXmlReaderSettings)) 112 using (var reader = XmlReader.Create(this.Context.SourcePath, DocumentXmlReaderSettings))
119 { 113 {
120 document = this.Process(reader); 114 document = this.Process(reader);
121 } 115 }
122 116
123 return PostProcess(document); 117 return this.PostProcess(document);
124 } 118 }
125 119
126 /// <summary> 120 /// <summary>
@@ -129,21 +123,23 @@ namespace WixToolset.Core
129 /// <param name="context">The preprocessing context.</param> 123 /// <param name="context">The preprocessing context.</param>
130 /// <param name="reader">XmlReader to processing the context.</param> 124 /// <param name="reader">XmlReader to processing the context.</param>
131 /// <returns>XDocument with the postprocessed data.</returns> 125 /// <returns>XDocument with the postprocessed data.</returns>
132 public XDocument Execute(XmlReader reader) 126 public XDocument Preprocess(IPreprocessContext context, XmlReader reader)
133 { 127 {
134 if (String.IsNullOrEmpty(this.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) 128 if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI))
135 { 129 {
136 var uri = new Uri(reader.BaseURI); 130 var uri = new Uri(reader.BaseURI);
137 this.SourcePath = uri.AbsolutePath; 131 context.SourcePath = uri.AbsolutePath;
138 } 132 }
139 133
140 this.Context = this.CreateContext(); 134 this.Context = 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);
141 137
142 this.PreProcess(); 138 this.PreProcess();
143 139
144 var document = this.Process(reader); 140 var document = this.Process(reader);
145 141
146 return PostProcess(document); 142 return this.PostProcess(document);
147 } 143 }
148 144
149 /// <summary> 145 /// <summary>
@@ -160,13 +156,13 @@ namespace WixToolset.Core
160 this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR")); 156 this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR"));
161 157
162 // Process the reader into the output. 158 // Process the reader into the output.
163 XDocument output = new XDocument(); 159 var output = new XDocument();
164 try 160 try
165 { 161 {
166 this.PreprocessReader(false, reader, output, 0); 162 this.PreprocessReader(false, reader, output, 0);
167 163
168 // Fire event with post-processed document. 164 // Fire event with post-processed document.
169 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourceFile, output)); 165 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourcePath, output));
170 } 166 }
171 catch (XmlException e) 167 catch (XmlException e)
172 { 168 {
@@ -221,8 +217,8 @@ namespace WixToolset.Core
221 return false; 217 return false;
222 } 218 }
223 219
224 int numQuotes = 0; 220 var numQuotes = 0;
225 int tmpIndex = 0; 221 var tmpIndex = 0;
226 while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) 222 while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex)))
227 { 223 {
228 numQuotes++; 224 numQuotes++;
@@ -250,26 +246,26 @@ namespace WixToolset.Core
250 expression = expression.ToUpperInvariant(); 246 expression = expression.ToUpperInvariant();
251 switch (operation) 247 switch (operation)
252 { 248 {
253 case PreprocessorOperation.Not: 249 case PreprocessorOperation.Not:
254 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) 250 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal))
255 { 251 {
256 return true; 252 return true;
257 } 253 }
258 break; 254 break;
259 case PreprocessorOperation.And: 255 case PreprocessorOperation.And:
260 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) 256 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal))
261 { 257 {
262 return true; 258 return true;
263 } 259 }
264 break; 260 break;
265 case PreprocessorOperation.Or: 261 case PreprocessorOperation.Or:
266 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) 262 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal))
267 { 263 {
268 return true; 264 return true;
269 } 265 }
270 break; 266 break;
271 default: 267 default:
272 break; 268 break;
273 } 269 }
274 return false; 270 return false;
275 } 271 }
@@ -283,11 +279,11 @@ namespace WixToolset.Core
283 /// <param name="offset">Original offset for the line numbers being processed.</param> 279 /// <param name="offset">Original offset for the line numbers being processed.</param>
284 private void PreprocessReader(bool include, XmlReader reader, XContainer container, int offset) 280 private void PreprocessReader(bool include, XmlReader reader, XContainer container, int offset)
285 { 281 {
286 XContainer currentContainer = container; 282 var currentContainer = container;
287 Stack<XContainer> containerStack = new Stack<XContainer>(); 283 var containerStack = new Stack<XContainer>();
288 284
289 IfContext ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code 285 var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code
290 Stack<IfContext> ifStack = new Stack<IfContext>(); 286 var ifStack = new Stack<IfContext>();
291 287
292 // process the reader into the writer 288 // process the reader into the writer
293 while (reader.Read()) 289 while (reader.Read())
@@ -300,102 +296,102 @@ namespace WixToolset.Core
300 // check for changes in conditional processing 296 // check for changes in conditional processing
301 if (XmlNodeType.ProcessingInstruction == reader.NodeType) 297 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
302 { 298 {
303 bool ignore = false; 299 var ignore = false;
304 string name = null; 300 string name = null;
305 301
306 switch (reader.LocalName) 302 switch (reader.LocalName)
307 { 303 {
308 case "if": 304 case "if":
309 ifStack.Push(ifContext); 305 ifStack.Push(ifContext);
310 if (ifContext.IsTrue) 306 if (ifContext.IsTrue)
311 { 307 {
312 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(reader.Value), IfState.If); 308 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(reader.Value), IfState.If);
313 } 309 }
314 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 310 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
315 { 311 {
316 ifContext = new IfContext(); 312 ifContext = new IfContext();
317 } 313 }
318 ignore = true; 314 ignore = true;
319 break; 315 break;
320 316
321 case "ifdef": 317 case "ifdef":
322 ifStack.Push(ifContext); 318 ifStack.Push(ifContext);
323 name = reader.Value.Trim(); 319 name = reader.Value.Trim();
324 if (ifContext.IsTrue) 320 if (ifContext.IsTrue)
325 { 321 {
326 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); 322 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If);
327 } 323 }
328 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 324 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
329 { 325 {
330 ifContext = new IfContext(); 326 ifContext = new IfContext();
331 } 327 }
332 ignore = true; 328 ignore = true;
333 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); 329 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
334 break; 330 break;
335 331
336 case "ifndef": 332 case "ifndef":
337 ifStack.Push(ifContext); 333 ifStack.Push(ifContext);
338 name = reader.Value.Trim(); 334 name = reader.Value.Trim();
339 if (ifContext.IsTrue) 335 if (ifContext.IsTrue)
340 { 336 {
341 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); 337 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If);
342 } 338 }
343 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 339 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
344 { 340 {
345 ifContext = new IfContext(); 341 ifContext = new IfContext();
346 } 342 }
347 ignore = true; 343 ignore = true;
348 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); 344 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
349 break; 345 break;
350 346
351 case "elseif": 347 case "elseif":
352 if (0 == ifStack.Count) 348 if (0 == ifStack.Count)
353 { 349 {
354 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); 350 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
355 } 351 }
356 352
357 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 353 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
358 { 354 {
359 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); 355 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
360 } 356 }
361 357
362 ifContext.IfState = IfState.ElseIf; // we're now in an elseif 358 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
363 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test 359 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
364 { 360 {
365 ifContext.IsTrue = this.EvaluateExpression(reader.Value); 361 ifContext.IsTrue = this.EvaluateExpression(reader.Value);
366 } 362 }
367 else if (ifContext.IsTrue) 363 else if (ifContext.IsTrue)
368 { 364 {
369 ifContext.IsTrue = false; 365 ifContext.IsTrue = false;
370 } 366 }
371 ignore = true; 367 ignore = true;
372 break; 368 break;
373 369
374 case "else": 370 case "else":
375 if (0 == ifStack.Count) 371 if (0 == ifStack.Count)
376 { 372 {
377 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); 373 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
378 } 374 }
379 375
380 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 376 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
381 { 377 {
382 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); 378 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
383 } 379 }
384 380
385 ifContext.IfState = IfState.Else; // we're now in an else 381 ifContext.IfState = IfState.Else; // we're now in an else
386 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now 382 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now
387 ignore = true; 383 ignore = true;
388 break; 384 break;
389 385
390 case "endif": 386 case "endif":
391 if (0 == ifStack.Count) 387 if (0 == ifStack.Count)
392 { 388 {
393 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); 389 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif"));
394 } 390 }
395 391
396 ifContext = (IfContext)ifStack.Pop(); 392 ifContext = ifStack.Pop();
397 ignore = true; 393 ignore = true;
398 break; 394 break;
399 } 395 }
400 396
401 if (ignore) // ignore this node since we just handled it above 397 if (ignore) // ignore this node since we just handled it above
@@ -411,134 +407,134 @@ namespace WixToolset.Core
411 407
412 switch (reader.NodeType) 408 switch (reader.NodeType)
413 { 409 {
414 case XmlNodeType.XmlDeclaration: 410 case XmlNodeType.XmlDeclaration:
415 XDocument document = currentContainer as XDocument; 411 var document = currentContainer as XDocument;
416 if (null != document) 412 if (null != document)
413 {
414 document.Declaration = new XDeclaration(null, null, null);
415 while (reader.MoveToNextAttribute())
417 { 416 {
418 document.Declaration = new XDeclaration(null, null, null); 417 switch (reader.LocalName)
419 while (reader.MoveToNextAttribute())
420 { 418 {
421 switch (reader.LocalName) 419 case "version":
422 { 420 document.Declaration.Version = reader.Value;
423 case "version":
424 document.Declaration.Version = reader.Value;
425 break;
426
427 case "encoding":
428 document.Declaration.Encoding = reader.Value;
429 break;
430
431 case "standalone":
432 document.Declaration.Standalone = reader.Value;
433 break;
434 }
435 }
436
437 }
438 //else
439 //{
440 // display an error? Can this happen?
441 //}
442 break;
443
444 case XmlNodeType.ProcessingInstruction:
445 switch (reader.LocalName)
446 {
447 case "define":
448 this.PreprocessDefine(reader.Value);
449 break;
450
451 case "error":
452 this.PreprocessError(reader.Value);
453 break;
454
455 case "warning":
456 this.PreprocessWarning(reader.Value);
457 break; 421 break;
458 422
459 case "undef": 423 case "encoding":
460 this.PreprocessUndef(reader.Value); 424 document.Declaration.Encoding = reader.Value;
461 break; 425 break;
462 426
463 case "include": 427 case "standalone":
464 this.UpdateCurrentLineNumber(reader, offset); 428 document.Declaration.Standalone = reader.Value;
465 this.PreprocessInclude(reader.Value, currentContainer);
466 break; 429 break;
430 }
431 }
467 432
468 case "foreach": 433 }
469 this.PreprocessForeach(reader, currentContainer, offset); 434 //else
470 break; 435 //{
471 436 // display an error? Can this happen?
472 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error 437 //}
473 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); 438 break;
474
475 case "pragma":
476 this.PreprocessPragma(reader.Value, currentContainer);
477 break;
478 439
479 default: 440 case XmlNodeType.ProcessingInstruction:
480 // unknown processing instructions are currently ignored 441 switch (reader.LocalName)
481 break; 442 {
482 } 443 case "define":
444 this.PreprocessDefine(reader.Value);
483 break; 445 break;
484 446
485 case XmlNodeType.Element: 447 case "error":
486 if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek()) 448 this.PreprocessError(reader.Value);
487 { 449 break;
488 if ("Include" != reader.LocalName)
489 {
490 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
491 }
492 450
493 this.IncludeNextStack.Pop(); 451 case "warning":
494 this.IncludeNextStack.Push(false); 452 this.PreprocessWarning(reader.Value);
495 break; 453 break;
496 }
497 454
498 var empty = reader.IsEmptyElement; 455 case "undef":
499 var ns = XNamespace.Get(reader.NamespaceURI); 456 this.PreprocessUndef(reader.Value);
500 var element = new XElement(ns + reader.LocalName); 457 break;
501 currentContainer.Add(element);
502 458
459 case "include":
503 this.UpdateCurrentLineNumber(reader, offset); 460 this.UpdateCurrentLineNumber(reader, offset);
504 element.AddAnnotation(sourceLineNumbers); 461 this.PreprocessInclude(reader.Value, currentContainer);
462 break;
505 463
506 while (reader.MoveToNextAttribute()) 464 case "foreach":
507 { 465 this.PreprocessForeach(reader, currentContainer, offset);
508 var value = this.Helper.PreprocessString(this.Context, reader.Value); 466 break;
509 467
510 var attribNamespace = XNamespace.Get(reader.NamespaceURI); 468 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
511 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; 469 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach"));
512 470
513 element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); 471 case "pragma":
514 } 472 this.PreprocessPragma(reader.Value, currentContainer);
473 break;
515 474
516 if (!empty) 475 default:
517 { 476 // unknown processing instructions are currently ignored
518 containerStack.Push(currentContainer);
519 currentContainer = element;
520 }
521 break; 477 break;
478 }
479 break;
522 480
523 case XmlNodeType.EndElement: 481 case XmlNodeType.Element:
524 if (0 < reader.Depth || !include) 482 if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek())
483 {
484 if ("Include" != reader.LocalName)
525 { 485 {
526 currentContainer = containerStack.Pop(); 486 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
527 } 487 }
528 break;
529 488
530 case XmlNodeType.Text: 489 this.IncludeNextStack.Pop();
531 string postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value); 490 this.IncludeNextStack.Push(false);
532 currentContainer.Add(postprocessedText);
533 break; 491 break;
492 }
534 493
535 case XmlNodeType.CDATA: 494 var empty = reader.IsEmptyElement;
536 string postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value); 495 var ns = XNamespace.Get(reader.NamespaceURI);
537 currentContainer.Add(new XCData(postprocessedValue)); 496 var element = new XElement(ns + reader.LocalName);
538 break; 497 currentContainer.Add(element);
539 498
540 default: 499 this.UpdateCurrentLineNumber(reader, offset);
541 break; 500 element.AddAnnotation(sourceLineNumbers);
501
502 while (reader.MoveToNextAttribute())
503 {
504 var value = this.Helper.PreprocessString(this.Context, reader.Value);
505
506 var attribNamespace = XNamespace.Get(reader.NamespaceURI);
507 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
508
509 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
510 }
511
512 if (!empty)
513 {
514 containerStack.Push(currentContainer);
515 currentContainer = element;
516 }
517 break;
518
519 case XmlNodeType.EndElement:
520 if (0 < reader.Depth || !include)
521 {
522 currentContainer = containerStack.Pop();
523 }
524 break;
525
526 case XmlNodeType.Text:
527 var postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value);
528 currentContainer.Add(postprocessedText);
529 break;
530
531 case XmlNodeType.CDATA:
532 var postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value);
533 currentContainer.Add(new XCData(postprocessedValue));
534 break;
535
536 default:
537 break;
542 } 538 }
543 } 539 }
544 540
@@ -652,7 +648,7 @@ namespace WixToolset.Core
652 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); 648 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include"));
653 } 649 }
654 650
655 using (XmlReader reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) 651 using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
656 { 652 {
657 this.PushInclude(includeFile); 653 this.PushInclude(includeFile);
658 654
@@ -689,13 +685,13 @@ namespace WixToolset.Core
689 } 685 }
690 686
691 // parse out the variable name 687 // parse out the variable name
692 string varName = reader.Value.Substring(0, indexOfInToken).Trim(); 688 var varName = reader.Value.Substring(0, indexOfInToken).Trim();
693 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); 689 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
694 690
695 // preprocess the variable values string because it might be a variable itself 691 // preprocess the variable values string because it might be a variable itself
696 varValuesString = this.Helper.PreprocessString(this.Context, varValuesString); 692 varValuesString = this.Helper.PreprocessString(this.Context, varValuesString);
697 693
698 string[] varValues = varValuesString.Split(';'); 694 var varValues = varValuesString.Split(';');
699 695
700 // go through all the empty strings 696 // go through all the empty strings
701 while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) 697 while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType)
@@ -703,44 +699,44 @@ namespace WixToolset.Core
703 } 699 }
704 700
705 // get the offset of this xml fragment (for some reason its always off by 1) 701 // get the offset of this xml fragment (for some reason its always off by 1)
706 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo; 702 var lineInfoReader = reader as IXmlLineInfo;
707 if (null != lineInfoReader) 703 if (null != lineInfoReader)
708 { 704 {
709 offset += lineInfoReader.LineNumber - 1; 705 offset += lineInfoReader.LineNumber - 1;
710 } 706 }
711 707
712 XmlTextReader textReader = reader as XmlTextReader; 708 var textReader = reader as XmlTextReader;
713 // dump the xml to a string (maintaining whitespace if possible) 709 // dump the xml to a string (maintaining whitespace if possible)
714 if (null != textReader) 710 if (null != textReader)
715 { 711 {
716 textReader.WhitespaceHandling = WhitespaceHandling.All; 712 textReader.WhitespaceHandling = WhitespaceHandling.All;
717 } 713 }
718 714
719 StringBuilder fragmentBuilder = new StringBuilder(); 715 var fragmentBuilder = new StringBuilder();
720 int nestedForeachCount = 1; 716 var nestedForeachCount = 1;
721 while (nestedForeachCount != 0) 717 while (nestedForeachCount != 0)
722 { 718 {
723 if (reader.NodeType == XmlNodeType.ProcessingInstruction) 719 if (reader.NodeType == XmlNodeType.ProcessingInstruction)
724 { 720 {
725 switch (reader.LocalName) 721 switch (reader.LocalName)
726 { 722 {
727 case "foreach": 723 case "foreach":
728 ++nestedForeachCount; 724 ++nestedForeachCount;
729 // Output the foreach statement 725 // Output the foreach statement
730 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value); 726 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value);
731 break; 727 break;
732 728
733 case "endforeach": 729 case "endforeach":
734 --nestedForeachCount; 730 --nestedForeachCount;
735 if (0 != nestedForeachCount) 731 if (0 != nestedForeachCount)
736 { 732 {
737 fragmentBuilder.Append("<?endforeach ?>"); 733 fragmentBuilder.Append("<?endforeach ?>");
738 } 734 }
739 break; 735 break;
740 736
741 default: 737 default:
742 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value); 738 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value);
743 break; 739 break;
744 } 740 }
745 } 741 }
746 else if (reader.NodeType == XmlNodeType.Element) 742 else if (reader.NodeType == XmlNodeType.Element)
@@ -764,7 +760,7 @@ namespace WixToolset.Core
764 using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) 760 using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
765 { 761 {
766 // process each iteration, updating the variable's value each time 762 // process each iteration, updating the variable's value each time
767 foreach (string varValue in varValues) 763 foreach (var varValue in varValues)
768 { 764 {
769 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) 765 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
770 { 766 {
@@ -801,7 +797,7 @@ namespace WixToolset.Core
801 } 797 }
802 798
803 // resolve other variables in the pragma argument(s) 799 // resolve other variables in the pragma argument(s)
804 string pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim(); 800 var pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim();
805 801
806 try 802 try
807 { 803 {
@@ -823,7 +819,7 @@ namespace WixToolset.Core
823 private string GetNextToken(string originalExpression, ref string expression, out bool stringLiteral) 819 private string GetNextToken(string originalExpression, ref string expression, out bool stringLiteral)
824 { 820 {
825 stringLiteral = false; 821 stringLiteral = false;
826 string token = String.Empty; 822 var token = String.Empty;
827 expression = expression.Trim(); 823 expression = expression.Trim();
828 if (0 == expression.Length) 824 if (0 == expression.Length)
829 { 825 {
@@ -833,7 +829,7 @@ namespace WixToolset.Core
833 if (expression.StartsWith("\"", StringComparison.Ordinal)) 829 if (expression.StartsWith("\"", StringComparison.Ordinal))
834 { 830 {
835 stringLiteral = true; 831 stringLiteral = true;
836 int endingQuotes = expression.IndexOf('\"', 1); 832 var endingQuotes = expression.IndexOf('\"', 1);
837 if (-1 == endingQuotes) 833 if (-1 == endingQuotes)
838 { 834 {
839 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); 835 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression));
@@ -848,9 +844,9 @@ namespace WixToolset.Core
848 else if (expression.StartsWith("$(", StringComparison.Ordinal)) 844 else if (expression.StartsWith("$(", StringComparison.Ordinal))
849 { 845 {
850 // Find the ending paren of the expression 846 // Find the ending paren of the expression
851 int endingParen = -1; 847 var endingParen = -1;
852 int openedCount = 1; 848 var openedCount = 1;
853 for (int i = 2; i < expression.Length; i++) 849 for (var i = 2; i < expression.Length; i++)
854 { 850 {
855 if ('(' == expression[i]) 851 if ('(' == expression[i])
856 { 852 {
@@ -881,14 +877,14 @@ namespace WixToolset.Core
881 { 877 {
882 // Cut the token off at the next equal, space, inequality operator, 878 // Cut the token off at the next equal, space, inequality operator,
883 // or end of string, whichever comes first 879 // or end of string, whichever comes first
884 int space = expression.IndexOf(" ", StringComparison.Ordinal); 880 var space = expression.IndexOf(" ", StringComparison.Ordinal);
885 int equals = expression.IndexOf("=", StringComparison.Ordinal); 881 var equals = expression.IndexOf("=", StringComparison.Ordinal);
886 int lessThan = expression.IndexOf("<", StringComparison.Ordinal); 882 var lessThan = expression.IndexOf("<", StringComparison.Ordinal);
887 int lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); 883 var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal);
888 int greaterThan = expression.IndexOf(">", StringComparison.Ordinal); 884 var greaterThan = expression.IndexOf(">", StringComparison.Ordinal);
889 int greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); 885 var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal);
890 int notEquals = expression.IndexOf("!=", StringComparison.Ordinal); 886 var notEquals = expression.IndexOf("!=", StringComparison.Ordinal);
891 int equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); 887 var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal);
892 int closingIndex; 888 int closingIndex;
893 889
894 if (space == -1) 890 if (space == -1)
@@ -970,7 +966,7 @@ namespace WixToolset.Core
970 { 966 {
971 // By default it's a literal and will only be evaluated if it 967 // By default it's a literal and will only be evaluated if it
972 // matches the variable format 968 // matches the variable format
973 string varValue = variable; 969 var varValue = variable;
974 970
975 if (variable.StartsWith("$(", StringComparison.Ordinal)) 971 if (variable.StartsWith("$(", StringComparison.Ordinal))
976 { 972 {
@@ -1008,8 +1004,7 @@ namespace WixToolset.Core
1008 /// <param name="rightValue">Right side value from expression.</param> 1004 /// <param name="rightValue">Right side value from expression.</param>
1009 private void GetNameValuePair(string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) 1005 private void GetNameValuePair(string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue)
1010 { 1006 {
1011 bool stringLiteral; 1007 leftValue = this.GetNextToken(originalExpression, ref expression, out var stringLiteral);
1012 leftValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1013 1008
1014 // If it wasn't a string literal, evaluate it 1009 // If it wasn't a string literal, evaluate it
1015 if (!stringLiteral) 1010 if (!stringLiteral)
@@ -1060,14 +1055,10 @@ namespace WixToolset.Core
1060 private bool EvaluateAtomicExpression(string originalExpression, ref string expression) 1055 private bool EvaluateAtomicExpression(string originalExpression, ref string expression)
1061 { 1056 {
1062 // Quick test to see if the first token is a variable 1057 // Quick test to see if the first token is a variable
1063 bool startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); 1058 var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal);
1064 1059 this.GetNameValuePair(originalExpression, ref expression, out var leftValue, out var operation, out var rightValue);
1065 string leftValue;
1066 string rightValue;
1067 string operation;
1068 this.GetNameValuePair(originalExpression, ref expression, out leftValue, out operation, out rightValue);
1069 1060
1070 bool expressionValue = false; 1061 var expressionValue = false;
1071 1062
1072 // If the variables don't exist, they were evaluated to null 1063 // If the variables don't exist, they were evaluated to null
1073 if (null == leftValue || null == rightValue) 1064 if (null == leftValue || null == rightValue)
@@ -1168,8 +1159,8 @@ namespace WixToolset.Core
1168 } 1159 }
1169 1160
1170 // search for the end of the expression with the matching paren 1161 // search for the end of the expression with the matching paren
1171 int openParenIndex = 0; 1162 var openParenIndex = 0;
1172 int closeParenIndex = 1; 1163 var closeParenIndex = 1;
1173 while (openParenIndex != -1 && openParenIndex < closeParenIndex) 1164 while (openParenIndex != -1 && openParenIndex < closeParenIndex)
1174 { 1165 {
1175 closeParenIndex = expression.IndexOf(')', closeParenIndex); 1166 closeParenIndex = expression.IndexOf(')', closeParenIndex);
@@ -1214,17 +1205,17 @@ namespace WixToolset.Core
1214 { 1205 {
1215 switch (operation) 1206 switch (operation)
1216 { 1207 {
1217 case PreprocessorOperation.And: 1208 case PreprocessorOperation.And:
1218 currentValue = currentValue && prevResult; 1209 currentValue = currentValue && prevResult;
1219 break; 1210 break;
1220 case PreprocessorOperation.Or: 1211 case PreprocessorOperation.Or:
1221 currentValue = currentValue || prevResult; 1212 currentValue = currentValue || prevResult;
1222 break; 1213 break;
1223 case PreprocessorOperation.Not: 1214 case PreprocessorOperation.Not:
1224 currentValue = !currentValue; 1215 currentValue = !currentValue;
1225 break; 1216 break;
1226 default: 1217 default:
1227 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString())); 1218 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString()));
1228 } 1219 }
1229 } 1220 }
1230 1221
@@ -1235,7 +1226,7 @@ namespace WixToolset.Core
1235 /// <returns>Boolean result of expression.</returns> 1226 /// <returns>Boolean result of expression.</returns>
1236 private bool EvaluateExpression(string expression) 1227 private bool EvaluateExpression(string expression)
1237 { 1228 {
1238 string tmpExpression = expression; 1229 var tmpExpression = expression;
1239 return this.EvaluateExpressionRecurse(expression, ref tmpExpression, PreprocessorOperation.And, true); 1230 return this.EvaluateExpressionRecurse(expression, ref tmpExpression, PreprocessorOperation.And, true);
1240 } 1231 }
1241 1232
@@ -1269,7 +1260,7 @@ namespace WixToolset.Core
1269 /// <returns>Boolean to indicate if the expression is true or false</returns> 1260 /// <returns>Boolean to indicate if the expression is true or false</returns>
1270 private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) 1261 private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult)
1271 { 1262 {
1272 bool expressionValue = false; 1263 var expressionValue = false;
1273 expression = expression.Trim(); 1264 expression = expression.Trim();
1274 if (expression.Length == 0) 1265 if (expression.Length == 0)
1275 { 1266 {
@@ -1279,8 +1270,7 @@ namespace WixToolset.Core
1279 // If the expression starts with parenthesis, evaluate it 1270 // If the expression starts with parenthesis, evaluate it
1280 if (expression.IndexOf('(') == 0) 1271 if (expression.IndexOf('(') == 0)
1281 { 1272 {
1282 int endSubExpressionIndex; 1273 var subExpression = this.GetParenthesisExpression(originalExpression, expression, out var endSubExpressionIndex);
1283 string subExpression = this.GetParenthesisExpression(originalExpression, expression, out endSubExpressionIndex);
1284 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true); 1274 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true);
1285 1275
1286 // Now get the rest of the expression that hasn't been evaluated 1276 // Now get the rest of the expression that hasn't been evaluated
@@ -1337,10 +1327,10 @@ namespace WixToolset.Core
1337 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param> 1327 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param>
1338 private void UpdateCurrentLineNumber(XmlReader reader, int offset) 1328 private void UpdateCurrentLineNumber(XmlReader reader, int offset)
1339 { 1329 {
1340 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo; 1330 var lineInfoReader = reader as IXmlLineInfo;
1341 if (null != lineInfoReader) 1331 if (null != lineInfoReader)
1342 { 1332 {
1343 int newLine = lineInfoReader.LineNumber + offset; 1333 var newLine = lineInfoReader.LineNumber + offset;
1344 1334
1345 if (this.Context.CurrentSourceLineNumber.LineNumber != newLine) 1335 if (this.Context.CurrentSourceLineNumber.LineNumber != newLine)
1346 { 1336 {
@@ -1435,19 +1425,6 @@ namespace WixToolset.Core
1435 return finalIncludePath; 1425 return finalIncludePath;
1436 } 1426 }
1437 1427
1438 private IPreprocessContext CreateContext()
1439 {
1440 var context = this.ServiceProvider.GetService<IPreprocessContext>();
1441 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IPreprocessorExtension>();
1442 context.CurrentSourceLineNumber = new SourceLineNumber(this.SourcePath);
1443 context.Platform = this.Platform;
1444 context.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List<string>();
1445 context.SourceFile = this.SourcePath;
1446 context.Variables = new Dictionary<string, string>(this.Variables);
1447
1448 return context;
1449 }
1450
1451 private void PreProcess() 1428 private void PreProcess()
1452 { 1429 {
1453 foreach (var extension in this.Context.Extensions) 1430 foreach (var extension in this.Context.Extensions)