Loops while a condition is true.
Purpose
Use the [While:] / [EndWhile:] macros to repeat a section of the input template while a condition evaluates to true. This is useful for paginated API calls or other scenarios where the iteration count is not known upfront.
Compatibility
The macro can be used in all input document types and in Report Studio.
Usage
The [Break:] and [Continue:] macros can also be used with the [While:] / [EndWhile:] macros, to provide more control over your macro execution. The condition is re-evaluated at the end of each iteration. Variables updated inside the body will affect the next condition check. When nesting [While:] macros, use explicit 'storeAs' names for each loop to avoid counter variable collisions.
Updates
When [While:] and [ForEach:] loops are nested inside each other, a [Break:] or [Continue:] in the inner loop now works instead of failing with a 'guid' macro parameter error.
History (2)
When this macro removes part of a Word document, blank lines straight after its own line, and straight after a table in the removed part, are now removed with that part instead of being left behind.
A condition comparing text that contains a comma followed by a name and equals sign inside a quoted string, e.g. contains('some error, code=500 happened', 'error, code=500'), no longer silently stops the loop after its first iteration.
Behaviour (10)
| Parameter | Type | Presence | Purpose | Options | Default |
| List<ExecutionResult> | Optional | If specified, asserts the expected execution result of the macro. Accepts a single value or a semicolon-separated list of acceptable values (e.g. 'Success;Warning'). The macro executes normally; if the actual result matches any of the desired values, the result is converted to Success. If the actual result does not match, the result is converted to MacroError with a descriptive message. This is primarily used for testing and diagnostic purposes. Valid values are: 'Unknown', 'Success', 'MacroError', 'WorkerStopped', 'Running', 'Warning', 'NeverRun', 'Cancelled', 'Pending', 'Paused', 'SystemError', 'Deferred' and 'Stopped'. |
|
N/A | |
| Boolean | Optional | Should NCalc expression evaluation throw error on Overflow |
|
true | |
| String | Optional | If specified, asserts the expected output type of the macro result. The macro executes normally; if the actual type does not match, a macro error is generated. Requires 'storeAs', 'storeAsHidden', or 'storeFormattedValueAs' to be set for typed validation. Valid types include CLR names (e.g. Int32, Int64, Single, Double, Boolean, String, JArray, JObject) and C# keyword aliases (e.g. int, long, float, double, bool, string, uint, ulong, short, ushort, byte, sbyte, decimal, char, object). The special value 'Number' matches any numeric type. | N/A | N/A | |
| String | Optional | If specified, asserts the expected output value of the macro result. The macro executes normally; if the actual value does not match, a macro error is generated. When 'storeAs' or 'storeAsHidden' is set, the stored variable value is compared. Otherwise, the document output text is compared. | N/A | N/A | |
| String | Optional | The condition that must be true in order for the macro to be executed/evaluated. Must either evaluate to true or false, for example: "3+5=8" or "contains('abcd', 'z')". | N/A | true | |
| String | Optional | Text to output if the condition is false on the very first evaluation (zero iterations). | N/A | N/A | |
| Int32 | Optional | The maximum number of iterations permitted (1-10000). Defaults to 10000. Use this to prevent accidental infinite loops during development. |
|
10000 | |
| MacroMode | Optional | The mode in which variables are stored. In legacy mode (default for Schedules), the variable created is a string and formatted. In normal mode (default for Report Studio), the output variable is stored as a strongly-typed object, e.g. an Int32 or a JArray etc., rather than a formatted string. |
|
Legacy | |
| ObfuscationType | Optional | Obfuscation type. Use obfuscation to write reports where sensitive data is hidden. When used, ReportMagic guarantees that the same input string will map to the same output string for the whole of the report (but the next time the report runs, it will most likely map to a different value). If you use obfuscation, the property in your macro will not show up and instead, you will see a fake item of the obfuscation type chosen. |
|
None | |
| String | Optional | If specified, adds a warning message for this macro. This is processed as an NCalc, and the warning message will ALWAYS be present and will be the value of the evaluated NCalc expression. | N/A | N/A |
Output (3)
| Parameter | Type | Presence | Purpose | Options | Default |
| String | Optional | The text to display should the macro fail to execute. Note that a poorly-specified macro (e.g. omitting mandatory parameters) will still result in an error message. | N/A | N/A | |
| String | Optional | The name of a variable to create should the macro fail to execute. The variable will be a text variable, and will contain either the failure text (only if the failureText parameter is set), otherwise it will contain the exception / failure message. | N/A | N/A | |
| String | Optional | The name of the 0-based iteration counter variable. Defaults to '_whileIndex'. This variable is only accessible inside the loop body and is removed when the loop ends. When nesting While loops, use unique names to avoid variable collisions. | N/A | _whileIndex |
Examples (10)
Example 1
A simple countdown loop:
[String: value=3, =>counter]
[While: condition={counter}>0]
Counter is: [String: value={counter}]
[Calculate: value={counter}-1, =>counter]
[EndWhile:]Example 2
Using maxIterations to limit iterations:
[String: value=true, =>keepGoing]
[While: condition='{keepGoing}'=='true', maxIterations=5, storeAs=i]
Iteration [String: value={i}]
[EndWhile:]Example 3
Using ifNoIterations when condition is initially false:
[String: value=false, =>hasData]
[While: condition='{hasData}'=='true', ifNoIterations=No data available.]
Processing data...
[EndWhile:]Example 4
Nested While loops with explicit counter names:
[String: value=2, =>outerCount]
[While: condition={outerCount}>0, storeAs=outerIndex]
Outer [String: value={outerIndex}]:
[String: value=2, =>innerCount]
[While: condition={innerCount}>0, storeAs=innerIndex]
Inner [String: value={innerIndex}]
[Calculate: value={innerCount}-1, =>innerCount]
[EndWhile:]
[Calculate: value={outerCount}-1, =>outerCount]
[EndWhile:]Example 5
Using [Break:] to exit the loop early:
[String: value=1, =>n]
[While: condition={n}>0, storeAs=i]
[If: condition={i}>=3][Break:][EndIf:]
Item [String: value={i}]
[Calculate: value={n}+1, =>n]
[EndWhile:]Example 6
Using [Break:] with a countdown:
[String: value=10, =>counter]
[While: condition={counter}>0]
[Calculate: value={counter}-1, =>counter]
[If: condition={counter}=3][Break:][EndIf:]
Value [String: value={counter}]
[EndWhile:]Example 7
Using [Continue:] to skip an iteration:
[String: value=5, =>remaining]
[While: condition={remaining}>0]
[Calculate: value={remaining}-1, =>remaining]
[If: condition={remaining}=2][Continue:][EndIf:]
Value [String: value={remaining}]
[EndWhile:]Example 8
Using [Continue:] to skip even numbers:
[String: value=6, =>counter]
[While: condition={counter}>0]
[Calculate: value={counter}-1, =>counter]
[Calculate: value={counter}%2, =>isOdd]
[If: condition={isOdd}=0][Continue:][EndIf:]
Value [String: value={counter}]
[EndWhile:]Example 9
Accumulating results using [=:] shorthand. The [=:] syntax is shorthand for [Calculate:] and in this case stores integers rather than strings. Variables created inside the loop persist between iterations (unlike the storeAs counter which is cleaned up when the loop ends):
[=: 3, =>outer]
[=: 0, =>total]
[While: condition=`outer > 0`]
[=: `outer`, =>inner]
[While: condition=`inner > 0`]
[=: `total + 1`, =>total]
[=: `inner - 1`, =>inner]
[EndWhile:]
[=: `outer - 1`, =>outer]
[EndWhile:]
Total: [=: total]Example 10
Checking text that contains commas. With backticks around the condition, commas inside quoted text, such as 'error, code=500', have no effect on the loop:
[String: value=0, =>attempt]
[While: condition=`{attempt}<2 && contains('some error, code=500 happened', 'error, code=500')`]
Attempt [String: value={attempt}]
[Calculate: value={attempt}+1, =>attempt]
[EndWhile:]