using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniMaxAIDocx.Core.Samples;
///
/// Comprehensive reference for OpenXML table creation and formatting.
///
/// KEY GOTCHA: Every TableCell MUST contain at least one Paragraph, even if empty.
/// Omitting it produces a corrupt document that Word will attempt to repair.
///
/// Border size units: eighth-points (1/8 pt). Size="12" = 1.5pt line.
/// Width units: DXA (twentieths of a point). 1440 DXA = 1 inch.
/// Pct width: fiftieths of a percent. 5000 = 100%.
///
/// XML structure:
///
/// — table-level properties (width, alignment, borders, layout)
/// — column definitions
/// — table row
/// — row properties (height, header repeat)
/// — table cell
/// — cell properties (width, merge, shading, borders)
/// — paragraph (REQUIRED, at least one)
///
public static class TableSamples
{
// ──────────────────────────────────────────────────────────────
// 1. CreateSimpleTable — basic table with single-line borders
// ──────────────────────────────────────────────────────────────
///
/// Creates a simple table with uniform single borders.
///
/// XML produced:
///
///
///
///
///
///
///
///
///
/// ...
/// Header1 ...
/// ...
///
///
/// The document body to append the table to.
/// Column header strings.
/// Rows of data; each inner array matches headers length.
public static Table CreateSimpleTable(Body body, string[] headers, string[][] data)
{
var table = new Table();
// -- Table Properties --
var tblPr = new TableProperties();
// Full-width table: Pct 5000 = 100%
tblPr.Append(new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct });
// Single borders all around + inside gridlines
// Border Size is in eighth-points: 4 = 0.5pt (thin), 12 = 1.5pt, 24 = 3pt
var borders = new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new LeftBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new BottomBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new RightBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" }
);
tblPr.Append(borders);
table.Append(tblPr);
// -- Table Grid: equal column widths --
// Grid columns define the default width in DXA.
// Total page width ~9360 DXA for letter with 1" margins (8.5" - 2" = 6.5" = 9360 DXA)
var grid = new TableGrid();
int colWidth = 9360 / headers.Length;
foreach (var _ in headers)
{
grid.Append(new GridColumn { Width = colWidth.ToString() });
}
table.Append(grid);
// -- Header Row --
var headerRow = new TableRow();
foreach (var h in headers)
{
var cell = new TableCell();
// GOTCHA: every cell MUST have at least one Paragraph
cell.Append(new Paragraph(
new Run(
new RunProperties(new Bold()),
new Text(h) { Space = SpaceProcessingModeValues.Preserve })));
headerRow.Append(cell);
}
table.Append(headerRow);
// -- Data Rows --
foreach (var rowData in data)
{
var row = new TableRow();
foreach (var cellText in rowData)
{
var cell = new TableCell();
cell.Append(new Paragraph(
new Run(new Text(cellText) { Space = SpaceProcessingModeValues.Preserve })));
row.Append(cell);
}
table.Append(row);
}
body.Append(table);
// Add an empty paragraph after the table (Word best practice)
body.Append(new Paragraph());
return table;
}
// ──────────────────────────────────────────────────────────────
// 2. CreateStyledTable — header shading, zebra striping, totals
// ──────────────────────────────────────────────────────────────
///
/// Creates a professionally styled table with:
/// - Dark header row (navy background, white bold text)
/// - Alternating row shading (zebra striping)
/// - Bold totals row at the bottom
///
/// XML for shaded cell:
///
///
///
///
///
///
///
///
/// Header
///
///
///
///
public static Table CreateStyledTable(Body body)
{
var table = new Table();
// Table properties: full width, single borders
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" },
new LeftBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" },
new BottomBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" },
new RightBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "1F3864" }
));
table.Append(tblPr);
// Grid: 3 columns
var grid = new TableGrid(
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" });
table.Append(grid);
string[] headers = ["Item", "Quantity", "Price"];
string[][] data =
[
["Widget A", "10", "$5.00"],
["Widget B", "25", "$3.50"],
["Widget C", "15", "$7.25"],
];
// -- Header row: dark navy background, white bold text --
var headerRow = new TableRow();
foreach (var h in headers)
{
var cell = new TableCell(
new TableCellProperties(
new Shading
{
Val = ShadingPatternValues.Clear,
Color = "auto",
Fill = "1F3864" // Dark navy
}),
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(
new Bold(),
new Color { Val = "FFFFFF" }), // White text
new Text(h))));
headerRow.Append(cell);
}
table.Append(headerRow);
// -- Data rows with zebra striping --
for (int i = 0; i < data.Length; i++)
{
var row = new TableRow();
// Alternate rows get light gray background
string? fillColor = (i % 2 == 1) ? "D9E2F3" : null;
foreach (var cellText in data[i])
{
var tcPr = new TableCellProperties();
if (fillColor != null)
{
tcPr.Append(new Shading
{
Val = ShadingPatternValues.Clear,
Color = "auto",
Fill = fillColor
});
}
var cell = new TableCell(
tcPr,
new Paragraph(
new Run(new Text(cellText) { Space = SpaceProcessingModeValues.Preserve })));
row.Append(cell);
}
table.Append(row);
}
// -- Totals row: bold text, top border emphasis --
var totalsRow = new TableRow();
string[] totals = ["Total", "50", "$257.50"];
foreach (var t in totals)
{
var cell = new TableCell(
new TableCellProperties(
new Shading
{
Val = ShadingPatternValues.Clear,
Color = "auto",
Fill = "1F3864"
}),
new Paragraph(
new Run(
new RunProperties(
new Bold(),
new Color { Val = "FFFFFF" }),
new Text(t) { Space = SpaceProcessingModeValues.Preserve })));
totalsRow.Append(cell);
}
table.Append(totalsRow);
body.Append(table);
body.Append(new Paragraph());
return table;
}
// ──────────────────────────────────────────────────────────────
// 3. CreateThreeLineTable — academic 三线表
// ──────────────────────────────────────────────────────────────
///
/// Creates an academic three-line table (三线表):
/// - Thick top border (1.5pt = Size 12)
/// - Thin border below header row (0.75pt = Size 6)
/// - Thick bottom border (1.5pt = Size 12)
/// - NO vertical borders, NO inside vertical borders
/// - NO left/right borders
///
/// This is the standard table style for Chinese academic papers (GB/T 7713).
///
/// XML produced:
///
///
///
///
///
///
///
///
///
///
/// ...
///
///
///
public static Table CreateThreeLineTable(Body body)
{
var table = new Table();
// Table borders: only top and bottom (thick), no sides, no inside
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 12, Space = 0, Color = "000000" },
new BottomBorder { Val = BorderValues.Single, Size = 12, Space = 0, Color = "000000" },
// Explicitly set left/right/insideH/insideV to none
new LeftBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new RightBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideHorizontalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new InsideVerticalBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" }
));
table.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" });
table.Append(grid);
string[] headers = ["Variable", "Mean", "SD"];
// -- Header row: centered, bold, with thin bottom border on each cell --
var headerRow = new TableRow();
foreach (var h in headers)
{
// Per-cell bottom border creates the thin "second line" of the three-line table
var tcPr = new TableCellProperties(
new TableCellBorders(
new BottomBorder { Val = BorderValues.Single, Size = 6, Space = 0, Color = "000000" }
));
var cell = new TableCell(
tcPr,
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(
new RunProperties(new Bold()),
new Text(h))));
headerRow.Append(cell);
}
table.Append(headerRow);
// -- Data rows: centered text, no borders --
string[][] data =
[
["Age", "25.3", "4.2"],
["Height", "170.5", "8.1"],
["Weight", "65.2", "10.3"],
];
foreach (var rowData in data)
{
var row = new TableRow();
foreach (var cellText in rowData)
{
var cell = new TableCell(
new Paragraph(
new ParagraphProperties(
new Justification { Val = JustificationValues.Center }),
new Run(new Text(cellText))));
row.Append(cell);
}
table.Append(row);
}
body.Append(table);
body.Append(new Paragraph());
return table;
}
// ──────────────────────────────────────────────────────────────
// 4. CreateBorderedTable — multiple border styles
// ──────────────────────────────────────────────────────────────
///
/// Creates a table demonstrating different border styles.
///
/// Border Val options:
/// BorderValues.Single — normal single line
/// BorderValues.Double — double line
/// BorderValues.Thick — thick single line
/// BorderValues.Dashed — dashed line
/// BorderValues.DashSmallGap — dashed with small gaps
/// BorderValues.DotDash — dot-dash pattern
/// BorderValues.Dotted — dotted line
/// BorderValues.Wave — wavy line
/// BorderValues.None — no border
///
/// Size is in eighth-points: 4 = 0.5pt, 8 = 1pt, 12 = 1.5pt, 24 = 3pt
///
public static Table CreateBorderedTable(Body body)
{
var table = new Table();
// Use different border styles on each side to demonstrate the options
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.Double, Size = 4, Space = 0, Color = "000000" },
new BottomBorder { Val = BorderValues.Double, Size = 4, Space = 0, Color = "000000" },
new LeftBorder { Val = BorderValues.Thick, Size = 12, Space = 0, Color = "333333" },
new RightBorder { Val = BorderValues.Thick, Size = 12, Space = 0, Color = "333333" },
new InsideHorizontalBorder { Val = BorderValues.Dashed, Size = 4, Space = 0, Color = "999999" },
new InsideVerticalBorder { Val = BorderValues.Dotted, Size = 4, Space = 0, Color = "999999" }
));
table.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "4680" },
new GridColumn { Width = "4680" });
table.Append(grid);
// Sample data
string[][] rows =
[
["Double top / Thick sides", "Dotted vertical inside"],
["Dashed horizontal inside", "Double bottom"],
];
foreach (var rowData in rows)
{
var row = new TableRow();
foreach (var cellText in rowData)
{
var cell = new TableCell(
new Paragraph(new Run(new Text(cellText))));
row.Append(cell);
}
table.Append(row);
}
body.Append(table);
body.Append(new Paragraph());
return table;
}
// ──────────────────────────────────────────────────────────────
// 5. SetTableWidth — Pct, DXA, Auto
// ──────────────────────────────────────────────────────────────
///
/// Demonstrates three table width modes:
///
/// 1. Pct (percentage): Value in fiftieths of a percent. 5000 = 100%, 2500 = 50%.
/// XML:
///
/// 2. Dxa (absolute): Value in twentieths of a point. 1440 = 1 inch, 9360 = 6.5 inches.
/// XML:
///
/// 3. Auto: Word determines width from content.
/// XML:
///
public static void SetTableWidth(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
// --- Option A: Percentage width (100%) ---
// Pct value is in fiftieths of a percent: 5000 = 100%
tblPr.TableWidth = new TableWidth
{
Width = "5000",
Type = TableWidthUnitValues.Pct
};
// --- Option B: Absolute width (6.5 inches = 9360 DXA) ---
// tblPr.TableWidth = new TableWidth
// {
// Width = "9360", // 6.5 * 1440 = 9360 DXA
// Type = TableWidthUnitValues.Dxa
// };
// --- Option C: Auto width ---
// tblPr.TableWidth = new TableWidth
// {
// Width = "0",
// Type = TableWidthUnitValues.Auto
// };
}
// ──────────────────────────────────────────────────────────────
// 6. SetTableLayout — Fixed vs AutoFit
// ──────────────────────────────────────────────────────────────
///
/// Controls whether the table uses fixed column widths or auto-fits to content.
///
/// Fixed layout: columns keep their exact width from TableGrid; content wraps.
/// XML:
///
/// AutoFit layout: Word adjusts column widths based on content. This is the default.
/// XML:
///
/// GOTCHA: Fixed layout is required for predictable column widths; AutoFit
/// may override your GridColumn values.
///
public static void SetTableLayout(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
// Fixed layout — columns respect GridColumn widths exactly
tblPr.TableLayout = new TableLayout
{
Type = TableLayoutValues.Fixed
};
// AutoFit layout (default) — Word adjusts widths to content
// tblPr.TableLayout = new TableLayout
// {
// Type = TableLayoutValues.Autofit
// };
}
// ──────────────────────────────────────────────────────────────
// 7. SetTableAlignment — center, right, left with indent
// ──────────────────────────────────────────────────────────────
///
/// Controls horizontal alignment of the table on the page.
///
/// XML:
///
/// For left alignment with indent:
/// XML:
///
/// GOTCHA: TableJustification (w:jc) inside tblPr is DIFFERENT from paragraph Justification.
///
public static void SetTableAlignment(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
// --- Option A: Center the table ---
tblPr.TableJustification = new TableJustification
{
Val = TableRowAlignmentValues.Center
};
// --- Option B: Right-align ---
// tblPr.TableJustification = new TableJustification
// {
// Val = TableRowAlignmentValues.Right
// };
// --- Option C: Left with indent (0.5 inch = 720 DXA) ---
// tblPr.TableJustification = new TableJustification
// {
// Val = TableRowAlignmentValues.Left
// };
// tblPr.TableIndentation = new TableIndentation
// {
// Width = 720,
// Type = TableWidthUnitValues.Dxa
// };
}
// ──────────────────────────────────────────────────────────────
// 8. ConfigureTableGrid — column widths
// ──────────────────────────────────────────────────────────────
///
/// Sets explicit column widths via TableGrid / GridColumn elements.
///
/// GridColumn.Width is in DXA (twentieths of a point).
/// 1440 DXA = 1 inch. Common page width = 9360 DXA (6.5" printable on letter).
///
/// XML:
///
///
///
///
///
///
/// GOTCHA: The number of GridColumn elements should match the maximum number
/// of cells in any row (before merging). Merged cells still correspond to
/// multiple grid columns via GridSpan.
///
public static void ConfigureTableGrid(Table table)
{
// Remove existing grid if present
var existingGrid = table.GetFirstChild();
existingGrid?.Remove();
// 3 columns: narrow (1"), wide (3.25"), medium (2.25") = 6.5" total
var grid = new TableGrid(
new GridColumn { Width = "1440" }, // 1 inch
new GridColumn { Width = "4680" }, // 3.25 inches
new GridColumn { Width = "3240" } // 2.25 inches
);
// Grid must come after TableProperties, before any TableRow
var tblPr = table.GetFirstChild();
if (tblPr != null)
tblPr.InsertAfterSelf(grid);
else
table.PrependChild(grid);
}
// ──────────────────────────────────────────────────────────────
// 9. SetCellProperties — width, vAlign, text direction, no-wrap, shading
// ──────────────────────────────────────────────────────────────
///
/// Demonstrates the key TableCellProperties settings.
///
/// XML:
///
///
///
///
///
///
///
///
/// Vertical alignment values:
/// TableVerticalAlignmentValues.Top — default
/// TableVerticalAlignmentValues.Center — vertically centered
/// TableVerticalAlignmentValues.Bottom — bottom-aligned
///
/// Text direction values:
/// TextDirectionValues.LefToRightTopToBottom — normal horizontal (default)
/// TextDirectionValues.TopToBottomRightToLeft — vertical (CJK style)
/// TextDirectionValues.BottomToTopLeftToRight — rotated 90 CCW
///
public static void SetCellProperties(TableCell cell)
{
var tcPr = cell.GetFirstChild()
?? cell.PrependChild(new TableCellProperties());
// Cell width: 2 inches = 2880 DXA
tcPr.TableCellWidth = new TableCellWidth
{
Width = "2880",
Type = TableWidthUnitValues.Dxa
};
// Vertical alignment: center content vertically in cell
tcPr.TableCellVerticalAlignment = new TableCellVerticalAlignment
{
Val = TableVerticalAlignmentValues.Center
};
// Text direction: bottom-to-top (rotated 90 degrees counterclockwise)
// Useful for narrow column headers
tcPr.TextDirection = new TextDirection
{
Val = TextDirectionValues.BottomToTopLeftToRight
};
// No-wrap: prevent text wrapping, force cell to expand horizontally
tcPr.NoWrap = new NoWrap();
// Shading (background color): light green
// Fill is the background color; Color is the pattern color (usually "auto")
tcPr.Shading = new Shading
{
Val = ShadingPatternValues.Clear,
Color = "auto",
Fill = "E2EFDA"
};
}
// ──────────────────────────────────────────────────────────────
// 10. SetTableCellMargins — table-level default cell margins
// ──────────────────────────────────────────────────────────────
///
/// Sets default cell margins (padding) for the entire table.
/// These apply to ALL cells unless overridden per-cell.
///
/// XML:
///
///
///
///
///
///
///
///
///
/// Units: DXA. Default Word margins are approximately top/bottom=0, left/right=108 DXA.
///
/// GOTCHA: Use StartMargin/EndMargin (not LeftMargin/RightMargin) for OOXML Strict
/// compliance, but Word also accepts Left/Right.
///
public static void SetTableCellMargins(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
tblPr.TableCellMarginDefault = new TableCellMarginDefault(
new TopMargin { Width = "72", Type = TableWidthUnitValues.Dxa }, // ~0.05 inch
new StartMargin { Width = "115", Type = TableWidthUnitValues.Dxa }, // ~0.08 inch
new BottomMargin { Width = "72", Type = TableWidthUnitValues.Dxa },
new EndMargin { Width = "115", Type = TableWidthUnitValues.Dxa }
);
}
// ──────────────────────────────────────────────────────────────
// 11. SetPerCellMargins — per-cell override
// ──────────────────────────────────────────────────────────────
///
/// Overrides the table-level cell margins for a specific cell.
///
/// XML:
///
///
///
///
///
///
///
///
///
/// GOTCHA: Per-cell margins fully replace the table defaults for that cell.
/// You must specify all four sides; omitted sides get zero margin (not the table default).
///
public static void SetPerCellMargins(TableCell cell)
{
var tcPr = cell.GetFirstChild()
?? cell.PrependChild(new TableCellProperties());
tcPr.TableCellMargin = new TableCellMargin(
new TopMargin { Width = "144", Type = TableWidthUnitValues.Dxa }, // 0.1 inch
new StartMargin { Width = "216", Type = TableWidthUnitValues.Dxa }, // 0.15 inch
new BottomMargin { Width = "144", Type = TableWidthUnitValues.Dxa },
new EndMargin { Width = "216", Type = TableWidthUnitValues.Dxa }
);
}
// ──────────────────────────────────────────────────────────────
// 12. SetRowHeight — exact, atLeast, auto
// ──────────────────────────────────────────────────────────────
///
/// Controls the height of a table row.
///
/// XML:
///
/// Height rule values:
/// HeightRuleValues.Exact — row is exactly the specified height; content may clip
/// HeightRuleValues.AtLeast — row is at least the specified height; expands for content
/// HeightRuleValues.Auto — row height determined by content (default)
///
/// Height value is in DXA (twentieths of a point). 1440 DXA = 1 inch.
///
public static void SetRowHeight(TableRow row)
{
var trPr = row.GetFirstChild()
?? row.PrependChild(new TableRowProperties());
// Option A: Exact height of 0.5 inch (720 DXA)
trPr.Append(new TableRowHeight
{
Val = 720, // 0.5 inch in DXA
HeightType = HeightRuleValues.Exact
});
// Option B: Minimum height (grows if content needs more)
// trPr.Append(new TableRowHeight
// {
// Val = 360, // 0.25 inch minimum
// HeightType = HeightRuleValues.AtLeast
// });
}
// ──────────────────────────────────────────────────────────────
// 13. SetHeaderRowRepeat — repeat on each page
// ──────────────────────────────────────────────────────────────
///
/// Marks a row as a header row that repeats at the top of each page
/// when the table spans multiple pages.
///
/// XML:
///
///
///
///
/// GOTCHA: Only works on contiguous rows starting from the FIRST row of the table.
/// If row 1 and row 2 are both headers, both must have TableHeader set.
/// You cannot make row 3 a repeating header if row 2 is not.
///
public static void SetHeaderRowRepeat(TableRow row)
{
var trPr = row.GetFirstChild()
?? row.PrependChild(new TableRowProperties());
trPr.Append(new TableHeader());
}
// ──────────────────────────────────────────────────────────────
// 14. SetPerCellBorders — override table borders on specific cells
// ──────────────────────────────────────────────────────────────
///
/// Overrides the table-level borders for a specific cell.
/// Per-cell borders take precedence over table-level borders.
///
/// XML:
///
///
///
///
///
///
///
///
///
/// GOTCHA: When two adjacent cells define conflicting borders, the conflict
/// resolution follows the ECMA-376 spec: wider borders win; if same width,
/// the cell on the "end" side wins.
///
public static void SetPerCellBorders(TableCell cell)
{
var tcPr = cell.GetFirstChild()
?? cell.PrependChild(new TableCellProperties());
tcPr.TableCellBorders = new TableCellBorders(
new TopBorder { Val = BorderValues.Double, Size = 4, Space = 0, Color = "FF0000" },
new BottomBorder { Val = BorderValues.Single, Size = 12, Space = 0, Color = "0000FF" },
new LeftBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" },
new RightBorder { Val = BorderValues.None, Size = 0, Space = 0, Color = "auto" }
);
}
// ──────────────────────────────────────────────────────────────
// 15. CreateHorizontalMerge — GridSpan (column span)
// ──────────────────────────────────────────────────────────────
///
/// Creates a row with horizontal cell merging using GridSpan.
///
/// To merge 3 columns into one cell, set GridSpan.Val = 3 on that cell.
/// The row still references the same grid columns, but one cell spans multiple.
///
/// XML for a row in a 4-column table where first cell spans columns 1-3:
///
///
///
///
///
/// Merged across 3 columns
///
///
/// Normal cell
///
///
///
/// GOTCHA: The total GridSpan values across all cells in a row must equal
/// the number of GridColumn elements in TblGrid.
///
public static TableRow CreateHorizontalMerge(TableRow row)
{
// Assume a 4-column grid: first cell spans 3 columns, second cell is normal
// Cell spanning 3 columns
var mergedCell = new TableCell(
new TableCellProperties(
new GridSpan { Val = 3 }),
new Paragraph(
new Run(new Text("This cell spans 3 columns"))));
row.Append(mergedCell);
// Normal cell (1 column, GridSpan defaults to 1 when omitted)
var normalCell = new TableCell(
new Paragraph(
new Run(new Text("Normal cell"))));
row.Append(normalCell);
return row;
}
// ──────────────────────────────────────────────────────────────
// 16. CreateVerticalMerge — VerticalMerge Restart + Continue
// ──────────────────────────────────────────────────────────────
///
/// Creates vertical cell merging using VerticalMerge with Restart/Continue pattern.
///
/// Vertical merge pattern:
/// - First row: VerticalMerge.Val = MergedCellValues.Restart (starts the merge)
/// - Subsequent rows: VerticalMerge.Val = MergedCellValues.Continue (continues)
/// - Last continuation can also use VerticalMerge with no Val attribute
///
/// XML:
/// Row 1:
/// Visible content
/// Row 2:
///
/// Row 3:
///
///
/// GOTCHA: The "continue" cells MUST still contain at least one empty Paragraph.
/// They also MUST have the same column position in the grid as the "restart" cell.
///
public static Table CreateVerticalMerge(Table table)
{
var grid = new TableGrid(
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" },
new GridColumn { Width = "3120" });
// Only add grid if not already present
if (table.GetFirstChild() == null)
{
var tblPr = table.GetFirstChild();
if (tblPr != null)
tblPr.InsertAfterSelf(grid);
else
table.PrependChild(grid);
}
// Row 1: first cell starts vertical merge
var row1 = new TableRow(
new TableCell(
new TableCellProperties(
new VerticalMerge { Val = MergedCellValues.Restart }), // Start merge
new Paragraph(new Run(new Text("Spans 3 rows")))),
new TableCell(
new Paragraph(new Run(new Text("Row 1, Col 2")))),
new TableCell(
new Paragraph(new Run(new Text("Row 1, Col 3")))));
table.Append(row1);
// Row 2: first cell continues vertical merge
var row2 = new TableRow(
new TableCell(
new TableCellProperties(
new VerticalMerge()), // Continue (no Val)
new Paragraph()), // Empty paragraph required!
new TableCell(
new Paragraph(new Run(new Text("Row 2, Col 2")))),
new TableCell(
new Paragraph(new Run(new Text("Row 2, Col 3")))));
table.Append(row2);
// Row 3: first cell continues vertical merge
var row3 = new TableRow(
new TableCell(
new TableCellProperties(
new VerticalMerge()), // Continue
new Paragraph()), // Empty paragraph required!
new TableCell(
new Paragraph(new Run(new Text("Row 3, Col 2")))),
new TableCell(
new Paragraph(new Run(new Text("Row 3, Col 3")))));
table.Append(row3);
return table;
}
// ──────────────────────────────────────────────────────────────
// 17. CreateNestedTable — table inside a table cell
// ──────────────────────────────────────────────────────────────
///
/// Inserts a table inside a table cell.
///
/// GOTCHA: The nested table is a direct child of the TableCell, placed BEFORE
/// the required trailing Paragraph. The cell structure is:
///
///
/// ...
///
/// ...
/// ...
/// ...
///
///
///
///
/// GOTCHA: The parent cell still MUST end with a Paragraph after the nested table.
///
public static Table CreateNestedTable(TableCell parentCell)
{
var nestedTable = new Table();
var tblPr = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct },
new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" },
new LeftBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" },
new BottomBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" },
new RightBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "999999" }
));
nestedTable.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "2000" },
new GridColumn { Width = "2000" });
nestedTable.Append(grid);
// 2x2 nested table
for (int r = 0; r < 2; r++)
{
var row = new TableRow();
for (int c = 0; c < 2; c++)
{
var cell = new TableCell(
new Paragraph(
new Run(new Text($"Nested R{r + 1}C{c + 1}"))));
row.Append(cell);
}
nestedTable.Append(row);
}
// Insert the nested table in the parent cell.
// The parent cell must still end with a paragraph.
parentCell.Append(nestedTable);
parentCell.Append(new Paragraph()); // REQUIRED trailing paragraph
return nestedTable;
}
// ──────────────────────────────────────────────────────────────
// 18. CreateFloatingTable — absolute positioning
// ──────────────────────────────────────────────────────────────
///
/// Creates a floating (absolutely positioned) table using TablePositionProperties.
///
/// XML:
///
///
///
///
/// Anchor values:
/// VerticalAnchorValues.Page / Margin / Text
/// HorizontalAnchorValues.Page / Margin / Text
///
/// Position values (tblpX, tblpY) are in DXA from the anchor.
/// FromText values are spacing between table and surrounding text, in DXA.
///
/// GOTCHA: Floating tables allow text to wrap around them, similar to
/// text-wrapped images. This can produce unexpected layouts.
///
public static Table CreateFloatingTable(Body body)
{
var table = new Table();
var tblPr = new TableProperties();
// Floating position: 2 inches from left of page, 3 inches from top of page
tblPr.TablePositionProperties = new TablePositionProperties
{
LeftFromText = 180, // 0.125" spacing from text
RightFromText = 180,
TopFromText = 180,
BottomFromText = 180,
VerticalAnchor = VerticalAnchorValues.Page,
HorizontalAnchor = HorizontalAnchorValues.Page,
TablePositionX = 2880, // 2 inches from left edge of page
TablePositionY = 4320 // 3 inches from top of page
};
tblPr.Append(new TableWidth { Width = "3600", Type = TableWidthUnitValues.Dxa });
tblPr.Append(new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new LeftBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new BottomBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new RightBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4, Space = 0, Color = "000000" }
));
table.Append(tblPr);
var grid = new TableGrid(
new GridColumn { Width = "1800" },
new GridColumn { Width = "1800" });
table.Append(grid);
// Simple 2x2 floating table
for (int r = 0; r < 2; r++)
{
var row = new TableRow();
for (int c = 0; c < 2; c++)
{
var cell = new TableCell(
new Paragraph(
new Run(new Text($"Float R{r + 1}C{c + 1}"))));
row.Append(cell);
}
table.Append(row);
}
body.Append(table);
return table;
}
// ──────────────────────────────────────────────────────────────
// 19. ApplyTableLook — conditional formatting flags
// ──────────────────────────────────────────────────────────────
///
/// Sets the TableLook element which controls which parts of a table style are applied.
///
/// XML:
///
///
///
///
/// These flags control conditional formatting from the applied table style:
/// FirstRow = apply special header row formatting
/// LastRow = apply special last row formatting
/// FirstColumn = apply special first column formatting
/// LastColumn = apply special last column formatting
/// NoHorizontalBand = disable banded row shading
/// NoVerticalBand = disable banded column shading
///
/// The Val attribute is a bitmask but the individual boolean attributes are preferred.
///
public static void ApplyTableLook(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
tblPr.TableLook = new TableLook
{
Val = "04A0",
FirstRow = true,
LastRow = false,
FirstColumn = true,
LastColumn = false,
NoHorizontalBand = false, // false = DO apply banded row shading
NoVerticalBand = true // true = do NOT apply banded column shading
};
}
// ──────────────────────────────────────────────────────────────
// 20. ApplyTableStyle — reference a named style
// ──────────────────────────────────────────────────────────────
///
/// References a named table style defined in the styles part.
///
/// XML:
///
///
///
///
/// Common built-in style IDs:
/// "TableGrid" — basic grid with all borders
/// "TableNormal" — no borders or shading
/// "LightShading" — light shading style
/// "MediumShading1" — medium shading
/// "GridTable4-Accent1" — colorful banded table (Office 2013+)
///
/// GOTCHA: The style ID must exist in the StyleDefinitionsPart. If you reference
/// a style that doesn't exist, Word will silently ignore it and use defaults.
/// Built-in styles are only available if they have been added to the styles part
/// (Word adds them lazily on first use).
///
/// GOTCHA: Combine with TableLook to control which conditional parts of the
/// style are applied (header row, banded rows, etc.).
///
public static void ApplyTableStyle(Table table)
{
var tblPr = table.GetFirstChild()
?? table.PrependChild(new TableProperties());
// Reference the "TableGrid" built-in style
tblPr.TableStyle = new TableStyle { Val = "TableGrid" };
// Combine with TableLook for conditional formatting
tblPr.TableLook = new TableLook
{
Val = "04A0",
FirstRow = true,
LastRow = false,
FirstColumn = true,
LastColumn = false,
NoHorizontalBand = false,
NoVerticalBand = true
};
}
}