Horizontally merge adjacent cells in one or more rows.
Compatibility
The macro can be used in the highlighted input document types only. A greyed-out icon indicates not supported.
Usage
Merges adjacent cells within a row that either have identical text, contain specified text, or do not contain specified text. Use 'matchType' to control how cells are matched. Cells already part of a merge are skipped by default (controlled by 'skipMergedCells'). The leftmost cell's style is always retained. Row targeting: use 'rowIndices' for specific rows, or 'rowIndexFrom'/'rowIndexTo' for a range. These are mutually exclusive. Column scope: use 'startColumnIndex'/'endColumnIndex' to restrict which columns participate.
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 | |
| Boolean | Optional | Whether to skip the header (first) row when processing all rows. Has no effect when 'rowIndices' or 'rowIndexFrom' is specified. |
|
true | |
| MacroMode | Optional | The mode in which variables are stored. In the legacy mode (default for Schedules), the variable created is a string and formatted. In the normal mode (default for Report Studio), the output variable is stored as a strongly-typed theObject, e.g. an Int32 or a List |
|
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 | |
| Boolean | Optional | Whether to skip cells that are already part of a merge (acting as barriers). If false, encountering a pre-merged cell will throw an error. |
|
true | |
| 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 |
Filtering & Sorting (2)
| Parameter | Type | Presence | Purpose | Options | Default |
| String | Optional | The text to match against (or a regular expression when 'matchType' is 'Regex'). Required when 'matchType' is 'Contains', 'NotContains', or 'Regex'. | N/A | N/A | |
| MergeRowMatchType | Optional | How cells are matched for merging. 'Identical' merges adjacent cells with the same text. 'Contains' merges adjacent cells whose text contains the 'matchText' value. 'NotContains' merges adjacent cells whose text does not contain the 'matchText' value. 'Regex' merges adjacent cells whose text matches the 'matchText' regular expression. |
|
Identical |
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 delimiter to use when 'mergeValue' is 'Joined'. | N/A | , |
General (7)
| Parameter | Type | Presence | Purpose | Options | Default |
| String | Optional | Add a comment to make your document template more readable. The comment is discarded in the output document. | N/A | N/A | |
| Int32 | Optional | The 1-based rightmost column to consider for merging. If omitted, uses the last column. | N/A | N/A | |
| MergeRowValueBehavior | Optional | What content the merged cell should contain. 'First' keeps the leftmost cell's text. 'Last' keeps the rightmost cell's text. 'Empty' clears the content. 'Joined' joins all cell texts with the 'joinDelimiter' delimiter. 'MatchText' uses the 'matchText' value as the content (only valid with 'Contains' or 'Regex' match type). |
|
First | |
| Int32 | Optional | The starting row index (1-based) for a range of rows to process. Mutually exclusive with 'rowIndices'. If specified without 'rowIndexTo', processes from this row to the last row. | N/A | N/A | |
| Int32 | Optional | The ending row index (1-based, inclusive) for a range of rows to process. Only valid with 'rowIndexFrom'. | N/A | N/A | |
| List<Int32> | Optional | The row indices (1-based, separated by a semi-colon) to process. Mutually exclusive with 'rowIndexFrom'/'rowIndexTo'. If omitted (and no range specified), all rows are processed. | N/A | N/A | |
| Int32 | Optional | The 1-based leftmost column to consider for merging. |
|
1 |
Examples (8)
Example 1
This example merges adjacent cells with identical text in all rows (header row is skipped by default):
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">H1</th>
<th style="border: 1px solid black;">H2</th>
<th style="border: 1px solid black;">H3</th>
<th style="border: 1px solid black;">H4</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">b</td>
</tr>
<tr>
<td style="border: 1px solid black;">c</td>
<td style="border: 1px solid black;">c</td>
<td style="border: 1px solid black;">d</td>
<td style="border: 1px solid black;">d</td>
</tr>
</tbody>
</table>
[Table.MergeRows:]Example 2
This example merges cells containing '2025' in the header row only, replacing content with the match text:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">Region</th>
<th style="border: 1px solid black;">Q1 2025</th>
<th style="border: 1px solid black;">Q2 2025</th>
<th style="border: 1px solid black;">Q3 2025</th>
<th style="border: 1px solid black;">Q1 2026</th>
<th style="border: 1px solid black;">Q2 2026</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">North</td>
<td style="border: 1px solid black;">10</td>
<td style="border: 1px solid black;">20</td>
<td style="border: 1px solid black;">30</td>
<td style="border: 1px solid black;">40</td>
<td style="border: 1px solid black;">50</td>
</tr>
</tbody>
</table>
[Table.MergeRows: rowIndices=1, matchType=Contains, matchText=2025, mergeValue=MatchText]Example 3
This example merges identical cells in columns 2 to 6 for rows 2 through 4:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">Name</th>
<th style="border: 1px solid black;">Mon</th>
<th style="border: 1px solid black;">Tue</th>
<th style="border: 1px solid black;">Wed</th>
<th style="border: 1px solid black;">Thu</th>
<th style="border: 1px solid black;">Fri</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">Alice</td>
<td style="border: 1px solid black;">x</td>
<td style="border: 1px solid black;">x</td>
<td style="border: 1px solid black;">x</td>
<td style="border: 1px solid black;">y</td>
<td style="border: 1px solid black;">y</td>
</tr>
<tr>
<td style="border: 1px solid black;">Bob</td>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">b</td>
<td style="border: 1px solid black;">b</td>
<td style="border: 1px solid black;">b</td>
</tr>
<tr>
<td style="border: 1px solid black;">Carol</td>
<td style="border: 1px solid black;">z</td>
<td style="border: 1px solid black;">z</td>
<td style="border: 1px solid black;">z</td>
<td style="border: 1px solid black;">z</td>
<td style="border: 1px solid black;">z</td>
</tr>
</tbody>
</table>
[Table.MergeRows: rowIndexFrom=2, rowIndexTo=4, startColumnIndex=2, endColumnIndex=6]Example 4
This example merges non-'Total' cells and joins their values with ' + ':
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">C1</th>
<th style="border: 1px solid black;">C2</th>
<th style="border: 1px solid black;">C3</th>
<th style="border: 1px solid black;">C4</th>
<th style="border: 1px solid black;">C5</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">Total</td>
<td style="border: 1px solid black;">100</td>
<td style="border: 1px solid black;">200</td>
<td style="border: 1px solid black;">Total</td>
<td style="border: 1px solid black;">300</td>
</tr>
</tbody>
</table>
[Table.MergeRows: rowIndices=2, matchType=NotContains, matchText=Total, mergeValue=Joined, joinDelimiter=" + "]Example 5
This example clears the content of merged cells using mergeValue=Empty:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">H1</th>
<th style="border: 1px solid black;">H2</th>
<th style="border: 1px solid black;">H3</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">a</td>
<td style="border: 1px solid black;">b</td>
</tr>
<tr>
<td style="border: 1px solid black;">c</td>
<td style="border: 1px solid black;">c</td>
<td style="border: 1px solid black;">c</td>
</tr>
</tbody>
</table>
[Table.MergeRows: mergeValue=Empty]Example 6
This example merges cells matching a regex pattern (starting with 'Q' followed by a digit) in the header row:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">Q1 2025</th>
<th style="border: 1px solid black;">Q2 2025</th>
<th style="border: 1px solid black;">H1 2025</th>
<th style="border: 1px solid black;">Q1 2026</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;">10</td>
<td style="border: 1px solid black;">20</td>
<td style="border: 1px solid black;">30</td>
<td style="border: 1px solid black;">40</td>
</tr>
</tbody>
</table>
[Table.MergeRows: rowIndices=1, matchType=Regex, matchText=^Q\d, mergeValue=First]Example 7
This example preserves bold formatting and background colors when merging identical cells:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black;">Status</th>
<th style="border: 1px solid black;">Q1</th>
<th style="border: 1px solid black;">Q2</th>
<th style="border: 1px solid black;">Q3</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black; background-color: #d4edda;"><b>Green</b></td>
<td style="border: 1px solid black; background-color: #d4edda;"><b>Green</b></td>
<td style="border: 1px solid black; background-color: #d4edda;"><b>Green</b></td>
<td style="border: 1px solid black; background-color: #f8d7da;"><b>Red</b></td>
</tr>
</tbody>
</table>
[Table.MergeRows:]Example 8
This example merges '2025' headers using startColumnIndex to skip an existing colspan:
<table style="border: 1px solid black; border-collapse: collapse;">
<thead>
<tr>
<th style="border: 1px solid black; background-color: #333; color: white;" colspan="2">Info</th>
<th style="border: 1px solid black; background-color: #0f3460; color: white;">Jan 2025</th>
<th style="border: 1px solid black; background-color: #0f3460; color: white;">Feb 2025</th>
<th style="border: 1px solid black; background-color: #0f3460; color: white;">Mar 2025</th>
<th style="border: 1px solid black;">Jan 2026</th>
</tr>
</thead>
<tbody>
<tr>
<td style="border: 1px solid black;" colspan="2">Region</td>
<td style="border: 1px solid black;">100</td>
<td style="border: 1px solid black;">150</td>
<td style="border: 1px solid black;">200</td>
<td style="border: 1px solid black;">300</td>
</tr>
</tbody>
</table>
[Table.MergeRows: rowIndices=1, matchType=Contains, matchText=2025, mergeValue=MatchText, startColumnIndex=3]