diff options
| author | Rob Mensching <rob@firegiant.com> | 2022-12-06 14:11:59 -0800 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2022-12-06 17:29:07 -0800 |
| commit | 07fbc3561fb66dba1502305ba4aff1db905c84e4 (patch) | |
| tree | b58287c4badb0fce8ee23429b1346e34244c77d0 | |
| parent | f2e5bdc263b8f6def149c918c332bd0d66fb6c1f (diff) | |
| download | wix-07fbc3561fb66dba1502305ba4aff1db905c84e4.tar.gz wix-07fbc3561fb66dba1502305ba4aff1db905c84e4.tar.bz2 wix-07fbc3561fb66dba1502305ba4aff1db905c84e4.zip | |
Allow quoted values in foreach
Fixes 7039
| -rw-r--r-- | src/wix/WixToolset.Core/Preprocessor.cs | 463 | ||||
| -rw-r--r-- | src/wix/test/WixToolsetTest.Core/PreprocessorFixture.cs | 50 |
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); |
