aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-12-06 14:11:59 -0800
committerRob Mensching <rob@firegiant.com>2022-12-06 17:29:07 -0800
commit07fbc3561fb66dba1502305ba4aff1db905c84e4 (patch)
treeb58287c4badb0fce8ee23429b1346e34244c77d0 /src
parentf2e5bdc263b8f6def149c918c332bd0d66fb6c1f (diff)
downloadwix-07fbc3561fb66dba1502305ba4aff1db905c84e4.tar.gz
wix-07fbc3561fb66dba1502305ba4aff1db905c84e4.tar.bz2
wix-07fbc3561fb66dba1502305ba4aff1db905c84e4.zip
Allow quoted values in foreach
Fixes 7039
Diffstat (limited to 'src')
-rw-r--r--src/wix/WixToolset.Core/Preprocessor.cs463
-rw-r--r--src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs50
2 files changed, 287 insertions, 226 deletions
diff --git a/src/wix/WixToolset.Core/Preprocessor.cs b/src/wix/WixToolset.Core/Preprocessor.cs
index a71e5bb7..fbf70b6a 100644
--- a/src/wix/WixToolset.Core/Preprocessor.cs
+++ b/src/wix/WixToolset.Core/Preprocessor.cs
@@ -242,26 +242,26 @@ namespace WixToolset.Core
242 expression = expression.ToUpperInvariant(); 242 expression = expression.ToUpperInvariant();
243 switch (operation) 243 switch (operation)
244 { 244 {
245 case PreprocessorOperation.Not: 245 case PreprocessorOperation.Not:
246 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) 246 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal))
247 { 247 {
248 return true; 248 return true;
249 } 249 }
250 break; 250 break;
251 case PreprocessorOperation.And: 251 case PreprocessorOperation.And:
252 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) 252 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal))
253 { 253 {
254 return true; 254 return true;
255 } 255 }
256 break; 256 break;
257 case PreprocessorOperation.Or: 257 case PreprocessorOperation.Or:
258 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) 258 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal))
259 { 259 {
260 return true; 260 return true;
261 } 261 }
262 break; 262 break;
263 default: 263 default:
264 break; 264 break;
265 } 265 }
266 return false; 266 return false;
267 } 267 }
@@ -298,97 +298,97 @@ namespace WixToolset.Core
298 298
299 switch (reader.LocalName) 299 switch (reader.LocalName)
300 { 300 {
301 case "if": 301 case "if":
302 ifStack.Push(ifContext); 302 ifStack.Push(ifContext);
303 if (ifContext.IsTrue) 303 if (ifContext.IsTrue)
304 { 304 {
305 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); 305 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If);
306 } 306 }
307 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 307 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
308 { 308 {
309 ifContext = new IfContext(); 309 ifContext = new IfContext();
310 } 310 }
311 ignore = true; 311 ignore = true;
312 break; 312 break;
313 313
314 case "ifdef": 314 case "ifdef":
315 ifStack.Push(ifContext); 315 ifStack.Push(ifContext);
316 name = reader.Value.Trim(); 316 name = reader.Value.Trim();
317 if (ifContext.IsTrue) 317 if (ifContext.IsTrue)
318 { 318 {
319 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); 319 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
320 } 320 }
321 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 321 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
322 { 322 {
323 ifContext = new IfContext(); 323 ifContext = new IfContext();
324 } 324 }
325 ignore = true; 325 ignore = true;
326 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); 326 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
327 break; 327 break;
328 328
329 case "ifndef": 329 case "ifndef":
330 ifStack.Push(ifContext); 330 ifStack.Push(ifContext);
331 name = reader.Value.Trim(); 331 name = reader.Value.Trim();
332 if (ifContext.IsTrue) 332 if (ifContext.IsTrue)
333 { 333 {
334 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); 334 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
335 } 335 }
336 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true 336 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
337 { 337 {
338 ifContext = new IfContext(); 338 ifContext = new IfContext();
339 } 339 }
340 ignore = true; 340 ignore = true;
341 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); 341 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
342 break; 342 break;
343 343
344 case "elseif": 344 case "elseif":
345 if (0 == ifStack.Count) 345 if (0 == ifStack.Count)
346 { 346 {
347 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); 347 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
348 } 348 }
349 349
350 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 350 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
351 { 351 {
352 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); 352 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
353 } 353 }
354 354
355 ifContext.IfState = IfState.ElseIf; // we're now in an elseif 355 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
356 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test 356 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
357 { 357 {
358 ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); 358 ifContext.IsTrue = this.EvaluateExpression(state, reader.Value);
359 } 359 }
360 else if (ifContext.IsTrue) 360 else if (ifContext.IsTrue)
361 { 361 {
362 ifContext.IsTrue = false; 362 ifContext.IsTrue = false;
363 } 363 }
364 ignore = true; 364 ignore = true;
365 break; 365 break;
366 366
367 case "else": 367 case "else":
368 if (0 == ifStack.Count) 368 if (0 == ifStack.Count)
369 { 369 {
370 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); 370 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
371 } 371 }
372 372
373 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) 373 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
374 { 374 {
375 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); 375 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
376 } 376 }
377 377
378 ifContext.IfState = IfState.Else; // we're now in an else 378 ifContext.IfState = IfState.Else; // we're now in an else
379 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now 379 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now
380 ignore = true; 380 ignore = true;
381 break; 381 break;
382 382
383 case "endif": 383 case "endif":
384 if (0 == ifStack.Count) 384 if (0 == ifStack.Count)
385 { 385 {
386 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); 386 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif"));
387 } 387 }
388 388
389 ifContext = ifStack.Pop(); 389 ifContext = ifStack.Pop();
390 ignore = true; 390 ignore = true;
391 break; 391 break;
392 } 392 }
393 393
394 if (ignore) // ignore this node since we just handled it above 394 if (ignore) // ignore this node since we just handled it above
@@ -404,132 +404,132 @@ namespace WixToolset.Core
404 404
405 switch (reader.NodeType) 405 switch (reader.NodeType)
406 { 406 {
407 case XmlNodeType.XmlDeclaration: 407 case XmlNodeType.XmlDeclaration:
408 if (currentContainer is XDocument document) 408 if (currentContainer is XDocument document)
409 {
410 document.Declaration = new XDeclaration(null, null, null);
411 while (reader.MoveToNextAttribute())
412 { 409 {
413 switch (reader.LocalName) 410 document.Declaration = new XDeclaration(null, null, null);
411 while (reader.MoveToNextAttribute())
414 { 412 {
415 case "version": 413 switch (reader.LocalName)
416 document.Declaration.Version = reader.Value; 414 {
417 break; 415 case "version":
418 416 document.Declaration.Version = reader.Value;
419 case "encoding": 417 break;
420 document.Declaration.Encoding = reader.Value; 418
421 break; 419 case "encoding":
422 420 document.Declaration.Encoding = reader.Value;
423 case "standalone": 421 break;
424 document.Declaration.Standalone = reader.Value; 422
425 break; 423 case "standalone":
424 document.Declaration.Standalone = reader.Value;
425 break;
426 }
426 } 427 }
427 } 428 }
428 } 429 //else
429 //else 430 //{
430 //{ 431 // display an error? Can this happen?
431 // display an error? Can this happen? 432 //}
432 //}
433 break;
434
435 case XmlNodeType.ProcessingInstruction:
436 switch (reader.LocalName)
437 {
438 case "define":
439 this.PreprocessDefine(state, reader.Value);
440 break; 433 break;
441 434
442 case "error": 435 case XmlNodeType.ProcessingInstruction:
443 this.PreprocessError(state, reader.Value); 436 switch (reader.LocalName)
444 break; 437 {
438 case "define":
439 this.PreprocessDefine(state, reader.Value);
440 break;
445 441
446 case "warning": 442 case "error":
447 this.PreprocessWarning(state, reader.Value); 443 this.PreprocessError(state, reader.Value);
448 break; 444 break;
449 445
450 case "undef": 446 case "warning":
451 this.PreprocessUndef(state, reader.Value); 447 this.PreprocessWarning(state, reader.Value);
452 break; 448 break;
453 449
454 case "include": 450 case "undef":
455 this.UpdateCurrentLineNumber(state, reader, offset); 451 this.PreprocessUndef(state, reader.Value);
456 this.PreprocessInclude(state, reader.Value, currentContainer); 452 break;
457 break;
458 453
459 case "foreach": 454 case "include":
460 this.PreprocessForeach(state, reader, currentContainer, offset); 455 this.UpdateCurrentLineNumber(state, reader, offset);
461 break; 456 this.PreprocessInclude(state, reader.Value, currentContainer);
457 break;
462 458
463 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error 459 case "foreach":
464 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); 460 this.PreprocessForeach(state, reader, currentContainer, offset);
461 break;
465 462
466 case "pragma": 463 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
467 this.PreprocessPragma(state, reader.Value, currentContainer); 464 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach"));
468 break;
469 465
470 default: 466 case "pragma":
471 // unknown processing instructions are currently ignored 467 this.PreprocessPragma(state, reader.Value, currentContainer);
468 break;
469
470 default:
471 // unknown processing instructions are currently ignored
472 break;
473 }
472 break; 474 break;
473 }
474 break;
475 475
476 case XmlNodeType.Element: 476 case XmlNodeType.Element:
477 if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) 477 if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek())
478 {
479 if ("Include" != reader.LocalName)
480 { 478 {
481 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); 479 if ("Include" != reader.LocalName)
482 } 480 {
481 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
482 }
483 483
484 state.IncludeNextStack.Pop(); 484 state.IncludeNextStack.Pop();
485 state.IncludeNextStack.Push(false); 485 state.IncludeNextStack.Push(false);
486 break; 486 break;
487 } 487 }
488 488
489 var empty = reader.IsEmptyElement; 489 var empty = reader.IsEmptyElement;
490 var ns = XNamespace.Get(reader.NamespaceURI); 490 var ns = XNamespace.Get(reader.NamespaceURI);
491 var element = new XElement(ns + reader.LocalName); 491 var element = new XElement(ns + reader.LocalName);
492 currentContainer.Add(element); 492 currentContainer.Add(element);
493 493
494 this.UpdateCurrentLineNumber(state, reader, offset); 494 this.UpdateCurrentLineNumber(state, reader, offset);
495 element.AddAnnotation(sourceLineNumbers); 495 element.AddAnnotation(sourceLineNumbers);
496 496
497 while (reader.MoveToNextAttribute()) 497 while (reader.MoveToNextAttribute())
498 { 498 {
499 var value = state.Helper.PreprocessString(state.Context, reader.Value); 499 var value = state.Helper.PreprocessString(state.Context, reader.Value);
500 500
501 var attribNamespace = XNamespace.Get(reader.NamespaceURI); 501 var attribNamespace = XNamespace.Get(reader.NamespaceURI);
502 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; 502 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
503 503
504 element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); 504 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
505 } 505 }
506 506
507 if (!empty) 507 if (!empty)
508 { 508 {
509 containerStack.Push(currentContainer); 509 containerStack.Push(currentContainer);
510 currentContainer = element; 510 currentContainer = element;
511 } 511 }
512 break; 512 break;
513 513
514 case XmlNodeType.EndElement: 514 case XmlNodeType.EndElement:
515 if (0 < reader.Depth || !include) 515 if (0 < reader.Depth || !include)
516 { 516 {
517 currentContainer = containerStack.Pop(); 517 currentContainer = containerStack.Pop();
518 } 518 }
519 break; 519 break;
520 520
521 case XmlNodeType.Text: 521 case XmlNodeType.Text:
522 var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); 522 var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value);
523 currentContainer.Add(postprocessedText); 523 currentContainer.Add(postprocessedText);
524 break; 524 break;
525 525
526 case XmlNodeType.CDATA: 526 case XmlNodeType.CDATA:
527 var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); 527 var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value);
528 currentContainer.Add(new XCData(postprocessedValue)); 528 currentContainer.Add(new XCData(postprocessedValue));
529 break; 529 break;
530 530
531 default: 531 default:
532 break; 532 break;
533 } 533 }
534 } 534 }
535 535
@@ -695,6 +695,17 @@ namespace WixToolset.Core
695 var varName = reader.Value.Substring(0, indexOfInToken).Trim(); 695 var varName = reader.Value.Substring(0, indexOfInToken).Trim();
696 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); 696 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
697 697
698 if (varValuesString.StartsWith("\"", StringComparison.Ordinal))
699 {
700 if (!varValuesString.EndsWith("\"", StringComparison.Ordinal))
701 {
702 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, varValuesString));
703 }
704
705 // cut the quotes off the string
706 varValuesString = varValuesString.Substring(1, varValuesString.Length - 2);
707 }
708
698 // preprocess the variable values string because it might be a variable itself 709 // preprocess the variable values string because it might be a variable itself
699 varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); 710 varValuesString = state.Helper.PreprocessString(state.Context, varValuesString);
700 711
@@ -725,23 +736,23 @@ namespace WixToolset.Core
725 { 736 {
726 switch (reader.LocalName) 737 switch (reader.LocalName)
727 { 738 {
728 case "foreach": 739 case "foreach":
729 ++nestedForeachCount; 740 ++nestedForeachCount;
730 // Output the foreach statement 741 // Output the foreach statement
731 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value); 742 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value);
732 break; 743 break;
733 744
734 case "endforeach": 745 case "endforeach":
735 --nestedForeachCount; 746 --nestedForeachCount;
736 if (0 != nestedForeachCount) 747 if (0 != nestedForeachCount)
737 { 748 {
738 fragmentBuilder.Append("<?endforeach ?>"); 749 fragmentBuilder.Append("<?endforeach ?>");
739 } 750 }
740 break; 751 break;
741 752
742 default: 753 default:
743 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value); 754 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value);
744 break; 755 break;
745 } 756 }
746 } 757 }
747 else if (reader.NodeType == XmlNodeType.Element) 758 else if (reader.NodeType == XmlNodeType.Element)
@@ -1224,17 +1235,17 @@ namespace WixToolset.Core
1224 { 1235 {
1225 switch (operation) 1236 switch (operation)
1226 { 1237 {
1227 case PreprocessorOperation.And: 1238 case PreprocessorOperation.And:
1228 currentValue = currentValue && prevResult; 1239 currentValue = currentValue && prevResult;
1229 break; 1240 break;
1230 case PreprocessorOperation.Or: 1241 case PreprocessorOperation.Or:
1231 currentValue = currentValue || prevResult; 1242 currentValue = currentValue || prevResult;
1232 break; 1243 break;
1233 case PreprocessorOperation.Not: 1244 case PreprocessorOperation.Not:
1234 currentValue = !currentValue; 1245 currentValue = !currentValue;
1235 break; 1246 break;
1236 default: 1247 default:
1237 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); 1248 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString()));
1238 } 1249 }
1239 } 1250 }
1240 1251
diff --git a/src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs b/src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs
index 4be37ea1..1988614a 100644
--- a/src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs
+++ b/src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs
@@ -266,6 +266,56 @@ namespace WixToolsetTest.Core
266 WixAssert.CompareLineByLine(expected, actual); 266 WixAssert.CompareLineByLine(expected, actual);
267 } 267 }
268 268
269 [Fact]
270 public void CanPreprocessForeach()
271 {
272 var input = String.Join(Environment.NewLine,
273 "<Wix>",
274 "<?foreach value in A ; B ; C ?>",
275 " <Fragment Id='$(value)' />",
276 "<?endforeach?>",
277 "</Wix>"
278 );
279 var expected = new[]
280 {
281 "<Wix>",
282 " <Fragment Id=\"A \" />",
283 " <Fragment Id=\" B \" />",
284 " <Fragment Id=\" C\" />",
285 "</Wix>"
286 };
287
288 var result = PreprocessFromString(input);
289
290 var actual = result.Document.ToString().Split("\r\n");
291 WixAssert.CompareLineByLine(expected, actual);
292 }
293
294 [Fact]
295 public void CanPreprocessForeachWithQuotes()
296 {
297 var input = String.Join(Environment.NewLine,
298 "<Wix>",
299 "<?foreach value in \" A ; B ; C \" ?>",
300 " <Fragment Id='$(value)' />",
301 "<?endforeach?>",
302 "</Wix>"
303 );
304 var expected = new[]
305 {
306 "<Wix>",
307 " <Fragment Id=\" A \" />",
308 " <Fragment Id=\" B \" />",
309 " <Fragment Id=\" C \" />",
310 "</Wix>"
311 };
312
313 var result = PreprocessFromString(input);
314
315 var actual = result.Document.ToString().Split("\r\n");
316 WixAssert.CompareLineByLine(expected, actual);
317 }
318
269 private static IPreprocessResult PreprocessFromString(string xml) 319 private static IPreprocessResult PreprocessFromString(string xml)
270 { 320 {
271 using var stringReader = new StringReader(xml); 321 using var stringReader = new StringReader(xml);