合并表格单元格

有时,表格中的某些行需要标题或占据表格整个宽度的大文本块。为了正确设计表格,用户可以将多个表格单元格合并为一个。 Aspose.Words 在处理所有输入格式(包括导入 HTML 内容)时支持合并单元格。

如何合并表格单元格

在 Aspose.Words 中,合并单元格由 CellFormat 类的以下属性表示:

  • HorizontalMerge 描述单元格是否是单元格水平合并的一部分
  • VerticalMerge 描述单元格是否是单元格垂直合并的一部分

这些属性的值决定单元格的合并行为:

检查单元格是否合并

要检查单元格是否是合并单元格序列的一部分,我们只需检查 HorizontalMergeVerticalMerge 属性。

以下代码示例显示如何打印水平和垂直单元格合并类型:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document(MyDir + "Table with merged cells.docx");
Table table = (Table) doc.GetChild(NodeType.Table, 0, true);
foreach (Row row in table.Rows)
{
foreach (Cell cell in row.Cells)
{
Console.WriteLine(PrintCellMergeType(cell));
}
}

使用 DocumentBuilder 时合并表格单元格

要合并使用 DocumentBuilder 创建的表中的单元格,您需要为需要合并的每个单元格设置适当的合并类型 - 首先是 CellMerge.First,然后是 CellMerge.Previous

此外,您必须记住清除那些不需要合并的单元格的合并设置 - 这可以通过将第一个非合并单元格设置为 CellMerge.None 来完成。如果不这样做,表中的所有单元格将被合并。

以下代码示例演示如何创建一个包含两行的表格,其中第一行中的单元格水平合并:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document();
DocumentBuilder builder = new DocumentBuilder(doc);
builder.InsertCell();
builder.CellFormat.HorizontalMerge = CellMerge.First;
builder.Write("Text in merged cells.");
builder.InsertCell();
// This cell is merged to the previous and should be empty.
builder.CellFormat.HorizontalMerge = CellMerge.Previous;
builder.EndRow();
builder.InsertCell();
builder.CellFormat.HorizontalMerge = CellMerge.None;
builder.Write("Text in one cell.");
builder.InsertCell();
builder.Write("Text in another cell.");
builder.EndRow();
builder.EndTable();
doc.Save(ArtifactsDir + "WorkingWithTables.HorizontalMerge.docx");

以下代码示例演示如何创建一个两列表格,其中第一列中的单元格垂直合并:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document();
DocumentBuilder builder = new DocumentBuilder(doc);
builder.InsertCell();
builder.CellFormat.VerticalMerge = CellMerge.First;
builder.Write("Text in merged cells.");
builder.InsertCell();
builder.CellFormat.VerticalMerge = CellMerge.None;
builder.Write("Text in one cell");
builder.EndRow();
builder.InsertCell();
// This cell is vertically merged to the cell above and should be empty.
builder.CellFormat.VerticalMerge = CellMerge.Previous;
builder.InsertCell();
builder.CellFormat.VerticalMerge = CellMerge.None;
builder.Write("Text in another cell");
builder.EndRow();
builder.EndTable();
doc.Save(ArtifactsDir + "WorkingWithTables.VerticalMerge.docx");

其他情况下合并表格单元格

在不使用 DocumentBuilder 的其他情况下,例如在现有表格中,以以前的方式合并单元格可能不那么容易。相反,我们可以将涉及将合并属性应用于单元格的基本操作包装在一种方法中,从而使任务变得更加容易。此方法类似于合并自动化方法,调用该方法来合并表中的一系列单元格。

下面的代码将合并指定范围内的表格单元格,从给定单元格开始,到结束单元格结束。在这种情况下,范围可以跨越多行或多列:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
internal void MergeCells(Cell startCell, Cell endCell)
{
Table parentTable = startCell.ParentRow.ParentTable;
// Find the row and cell indices for the start and end cell.
Point startCellPos = new Point(startCell.ParentRow.IndexOf(startCell),
parentTable.IndexOf(startCell.ParentRow));
Point endCellPos = new Point(endCell.ParentRow.IndexOf(endCell), parentTable.IndexOf(endCell.ParentRow));
// Create a range of cells to be merged based on these indices.
// Inverse each index if the end cell is before the start cell.
Rectangle mergeRange = new Rectangle(Math.Min(startCellPos.X, endCellPos.X),
Math.Min(startCellPos.Y, endCellPos.Y),
Math.Abs(endCellPos.X - startCellPos.X) + 1, Math.Abs(endCellPos.Y - startCellPos.Y) + 1);
foreach (Row row in parentTable.Rows)
{
foreach (Cell cell in row.Cells)
{
Point currentPos = new Point(row.IndexOf(cell), parentTable.IndexOf(row));
// Check if the current cell is inside our merge range, then merge it.
if (mergeRange.Contains(currentPos))
{
cell.CellFormat.HorizontalMerge = currentPos.X == mergeRange.X ? CellMerge.First : CellMerge.Previous;
cell.CellFormat.VerticalMerge = currentPos.Y == mergeRange.Y ? CellMerge.First : CellMerge.Previous;
}
}
}
}
view raw merge-cells.cs hosted with ❤ by GitHub

以下代码示例演示如何合并两个指定单元格之间的单元格区域:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document(MyDir + "Table with merged cells.docx");
Table table = doc.FirstSection.Body.Tables[0];
// We want to merge the range of cells found inbetween these two cells.
Cell cellStartRange = table.Rows[0].Cells[0];
Cell cellEndRange = table.Rows[1].Cells[1];
// Merge all the cells between the two specified cells into one.
MergeCells(cellStartRange, cellEndRange);
doc.Save(ArtifactsDir + "WorkingWithTables.MergeCellRange.docx");

根据您使用的 .NET Framework 版本,您可能希望通过将其转换为扩展方法来完善此方法。在这种情况下,您可以直接在单元格上调用此方法来合并一系列单元格,例如cell1.Merge(cell2)

HTML 表格中的垂直和水平合并单元格

正如我们在之前的文章中所说,Microsoft Word 中的表是一组独立的行。每行都有一组独立于其他行的单元格的单元格。因此,在 Microsoft Word 表中不存在"列"这样的对象,“第一列"类似于"表中每行的第一个单元格的集合”。例如,这允许用户拥有一个表格,其中第一行由两个单元格组成 - 2 厘米和 1 厘米,第二行由两个不同的单元格组成 - 1 厘米和 2 厘米宽。 Aspose.Words 支持这种表的概念。

HTML 中的表格具有本质上不同的结构:每行具有相同数量的单元格,并且(这对于任务很重要)每个单元格具有相应列的宽度,一列中的所有单元格都相同。因此,如果 HorizontalMergeVerticalMerge 返回不正确的值,请使用以下代码示例:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document(MyDir + "Table with merged cells.docx");
SpanVisitor visitor = new SpanVisitor(doc);
doc.Accept(visitor);
// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
/// <summary>
/// Helper class that contains collection of rowinfo for each row.
/// </summary>
public class TableInfo
{
public List<RowInfo> Rows { get; } = new List<RowInfo>();
}
/// <summary>
/// Helper class that contains collection of cellinfo for each cell.
/// </summary>
public class RowInfo
{
public List<CellInfo> Cells { get; } = new List<CellInfo>();
}
/// <summary>
/// Helper class that contains info about cell. currently here is only colspan and rowspan.
/// </summary>
public class CellInfo
{
public CellInfo(int colSpan, int rowSpan)
{
ColSpan = colSpan;
RowSpan = rowSpan;
}
public int ColSpan { get; }
public int RowSpan { get; }
}
public class SpanVisitor : DocumentVisitor
{
/// <summary>
/// Creates new SpanVisitor instance.
/// </summary>
/// <param name="doc">
/// Is document which we should parse.
/// </param>
public SpanVisitor(Document doc)
{
mWordTables = doc.GetChildNodes(NodeType.Table, true);
// We will parse HTML to determine the rowspan and colspan of each cell.
MemoryStream htmlStream = new MemoryStream();
Aspose.Words.Saving.HtmlSaveOptions options = new Aspose.Words.Saving.HtmlSaveOptions
{
ImagesFolder = Path.GetTempPath()
};
doc.Save(htmlStream, options);
// Load HTML into the XML document.
XmlDocument xmlDoc = new XmlDocument();
htmlStream.Position = 0;
xmlDoc.Load(htmlStream);
// Get collection of tables in the HTML document.
XmlNodeList tables = xmlDoc.DocumentElement.GetElementsByTagName("table");
foreach (XmlNode table in tables)
{
TableInfo tableInf = new TableInfo();
// Get collection of rows in the table.
XmlNodeList rows = table.SelectNodes("tr");
foreach (XmlNode row in rows)
{
RowInfo rowInf = new RowInfo();
// Get collection of cells.
XmlNodeList cells = row.SelectNodes("td");
foreach (XmlNode cell in cells)
{
// Determine row span and colspan of the current cell.
XmlAttribute colSpanAttr = cell.Attributes["colspan"];
XmlAttribute rowSpanAttr = cell.Attributes["rowspan"];
int colSpan = colSpanAttr == null ? 0 : int.Parse(colSpanAttr.Value);
int rowSpan = rowSpanAttr == null ? 0 : int.Parse(rowSpanAttr.Value);
CellInfo cellInf = new CellInfo(colSpan, rowSpan);
rowInf.Cells.Add(cellInf);
}
tableInf.Rows.Add(rowInf);
}
mTables.Add(tableInf);
}
}
public override VisitorAction VisitCellStart(Cell cell)
{
int tabIdx = mWordTables.IndexOf(cell.ParentRow.ParentTable);
int rowIdx = cell.ParentRow.ParentTable.IndexOf(cell.ParentRow);
int cellIdx = cell.ParentRow.IndexOf(cell);
int colSpan = 0;
int rowSpan = 0;
if (tabIdx < mTables.Count &&
rowIdx < mTables[tabIdx].Rows.Count &&
cellIdx < mTables[tabIdx].Rows[rowIdx].Cells.Count)
{
colSpan = mTables[tabIdx].Rows[rowIdx].Cells[cellIdx].ColSpan;
rowSpan = mTables[tabIdx].Rows[rowIdx].Cells[cellIdx].RowSpan;
}
Console.WriteLine("{0}.{1}.{2} colspan={3}\t rowspan={4}", tabIdx, rowIdx, cellIdx, colSpan, rowSpan);
return VisitorAction.Continue;
}
private readonly List<TableInfo> mTables = new List<TableInfo>();
private readonly NodeCollection mWordTables;
}

转换为水平合并单元格

有时无法检测哪些单元格被合并,因为某些较新版本的 Microsoft Word 在水平合并单元格时不再使用合并标志。但对于使用合并标志将单元格按宽度水平合并为单元格的情况,Aspose.Words 提供了 ConvertToHorizontallyMergedCells 方法来转换单元格。此方法只是转换表格并根据需要添加新单元格。

下面的代码示例展示了上述方法的运行情况:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-.NET.git.
Document doc = new Document(MyDir + "Table with merged cells.docx");
Table table = doc.FirstSection.Body.Tables[0];
// Now merged cells have appropriate merge flags.
table.ConvertToHorizontallyMergedCells();