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 /src | |
parent | f2e5bdc263b8f6def149c918c332bd0d66fb6c1f (diff) | |
download | wix-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.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); |