Объединить ячейки таблицы

Иногда для определенных строк в таблице требуется заголовок или большие блоки текста, занимающие всю ширину таблицы. Для правильного оформления таблицы пользователь может объединить несколько ячеек таблицы в одну. Aspose.Words поддерживает объединение ячеек при работе со всеми форматами ввода, включая импорт содержимого HTML.

Как объединить ячейки таблицы

В Aspose.Words объединенные ячейки представлены следующими свойствами класса CellFormat:

  • HorizontalMerge, который описывает, является ли ячейка частью горизонтального слияния ячеек
  • VerticalMerge, который описывает, является ли ячейка частью вертикального объединения ячеек

Значения этих свойств определяют поведение ячеек при слиянии:

  • Первая ячейка в последовательности объединенных ячеек будет иметь CellMerge.First
  • Все последующие объединенные ячейки будут иметь CellMerge.Previous
  • Ячейка, которая не была объединена, будет иметь CellMerge.None

Проверьте, объединена ли ячейка

Чтобы проверить, является ли ячейка частью последовательности объединенных ячеек, мы просто проверяем свойства HorizontalMerge и VerticalMerge.

В следующем примере кода показано, как напечатать тип слияния горизонтальных и вертикальных ячеек:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
auto doc = MakeObject<Document>(MyDir + u"Table with merged cells.docx");
auto table = System::ExplicitCast<Table>(doc->GetChild(NodeType::Table, 0, true));
for (const auto& row : System::IterateOver<Row>(table->get_Rows()))
{
for (const auto& cell : System::IterateOver<Cell>(row->get_Cells()))
{
std::cout << PrintCellMergeType(cell) << std::endl;
}
}

Объединенные ячейки в таблице

Чтобы объединить ячейки в таблице, созданной с помощью DocumentBuilder, вам необходимо задать соответствующий тип слияния для каждой ячейки, в которой ожидается слияние – сначала CellMerge.First, а затем CellMerge.Previous.

Кроме того, вы должны помнить, что для тех ячеек, где слияние не требуется, необходимо отменить настройку слияния – это можно сделать, установив для первой ячейки, не подлежащей слиянию, значение CellMerge.None. Если этого не сделать, все ячейки в таблице будут объединены.

В следующем примере кода показано, как создать таблицу с двумя строками, в которой ячейки в первой строке объединяются по горизонтали:

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

В следующем примере кода показано, как создать таблицу с двумя столбцами, в которой ячейки в первом столбце объединяются по вертикали:

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

Объединяйте ячейки таблицы в других случаях

В других ситуациях, когда DocumentBuilder не используется, например, в существующей таблице, объединение ячеек предыдущим способом может оказаться не таким простым. Вместо этого мы можем обернуть основные операции, связанные с применением свойств объединения к ячейкам, в метод, который значительно упрощает задачу. Этот метод аналогичен методу автоматизации слияния, который вызывается для объединения диапазона ячеек в таблице.

Приведенный ниже код объединит ячейки таблицы в указанном диапазоне, начиная с указанной ячейки и заканчивая конечной ячейкой. В этом случае диапазон может охватывать несколько строк или столбцов:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
void MergeCells(SharedPtr<Cell> startCell, SharedPtr<Cell> endCell)
{
SharedPtr<Table> parentTable = startCell->get_ParentRow()->get_ParentTable();
// Find the row and cell indices for the start and end cell.
System::Drawing::Point startCellPos(startCell->get_ParentRow()->IndexOf(startCell), parentTable->IndexOf(startCell->get_ParentRow()));
System::Drawing::Point endCellPos(endCell->get_ParentRow()->IndexOf(endCell), parentTable->IndexOf(endCell->get_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.
System::Drawing::Rectangle mergeRange(
System::Math::Min(startCellPos.get_X(), endCellPos.get_X()), System::Math::Min(startCellPos.get_Y(), endCellPos.get_Y()),
System::Math::Abs(endCellPos.get_X() - startCellPos.get_X()) + 1, System::Math::Abs(endCellPos.get_Y() - startCellPos.get_Y()) + 1);
for (const auto& row : System::IterateOver<Row>(parentTable->get_Rows()))
{
for (const auto& cell : System::IterateOver<Cell>(row->get_Cells()))
{
System::Drawing::Point currentPos(row->IndexOf(cell), parentTable->IndexOf(row));
// Check if the current cell is inside our merge range, then merge it.
if (mergeRange.Contains(currentPos))
{
cell->get_CellFormat()->set_HorizontalMerge(currentPos.get_X() == mergeRange.get_X() ? CellMerge::First : CellMerge::Previous);
cell->get_CellFormat()->set_VerticalMerge(currentPos.get_Y() == mergeRange.get_Y() ? CellMerge::First : CellMerge::Previous);
}
}
}
}
view raw merge-cells.h hosted with ❤ by GitHub

В следующем примере кода показано, как объединить диапазон ячеек между двумя указанными ячейками:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
auto doc = MakeObject<Document>(MyDir + u"Table with merged cells.docx");
SharedPtr<Table> table = doc->get_FirstSection()->get_Body()->get_Tables()->idx_get(0);
// We want to merge the range of cells found inbetween these two cells.
SharedPtr<Cell> cellStartRange = table->get_Rows()->idx_get(0)->get_Cells()->idx_get(0);
SharedPtr<Cell> cellEndRange = table->get_Rows()->idx_get(1)->get_Cells()->idx_get(1);
// Merge all the cells between the two specified cells into one.
MergeCells(cellStartRange, cellEndRange);
doc->Save(ArtifactsDir + u"WorkingWithTables.MergeCellRange.docx");

В зависимости от используемой версии платформы, вы можете захотеть усовершенствовать этот метод, превратив его в метод расширения. В этом случае вы можете вызвать этот метод непосредственно для ячейки, чтобы объединить диапазон ячеек, например cell1.Merge(cell2).

Объединенные по вертикали и горизонтали ячейки в таблице

Как мы уже говорили в предыдущих статьях, таблица в Microsoft Word представляет собой набор независимых строк. Каждая строка содержит набор ячеек, которые не зависят от ячеек других строк. Таким образом, в таблице Microsoft Word нет такого объекта, как “столбец”, а “1-й столбец” - это что-то вроде “набора 1-х ячеек каждой строки таблицы”. Это позволяет пользователям иметь таблицу, в которой, например, 1–я строка состоит из двух ячеек - 2 см и 1 см, а 2-я строка состоит из двух разных ячеек – 1 см и 2 см шириной. И Aspose.Words поддерживает эту концепцию таблиц.

Таблица в HTML имеет принципиально иную структуру: в каждой строке одинаковое количество ячеек и (это важно для задачи) каждая ячейка имеет ширину соответствующего столбца, одинаковую для всех ячеек в одном столбце. Поэтому, если HorizontalMerge и VerticalMerge возвращают неверное значение, используйте следующий пример кода:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
auto doc = MakeObject<Document>(MyDir + u"Table with merged cells.docx");
auto visitor = MakeObject<WorkingWithTables::SpanVisitor>(doc);
doc->Accept(visitor);
// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
/// <summary>
/// Helper class that contains collection of rowinfo for each row.
/// </summary>
class TableInfo : public System::Object
{
public:
SharedPtr<System::Collections::Generic::List<SharedPtr<WorkingWithTables::RowInfo>>> get_Rows()
{
return mRows;
}
TableInfo() : mRows(MakeObject<System::Collections::Generic::List<SharedPtr<WorkingWithTables::RowInfo>>>())
{
}
private:
SharedPtr<System::Collections::Generic::List<SharedPtr<WorkingWithTables::RowInfo>>> mRows;
};
/// <summary>
/// Helper class that contains collection of cellinfo for each cell.
/// </summary>
class RowInfo : public System::Object
{
public:
SharedPtr<System::Collections::Generic::List<SharedPtr<WorkingWithTables::CellInfo>>> get_Cells()
{
return mCells;
}
RowInfo() : mCells(MakeObject<System::Collections::Generic::List<SharedPtr<WorkingWithTables::CellInfo>>>())
{
}
private:
SharedPtr<System::Collections::Generic::List<SharedPtr<WorkingWithTables::CellInfo>>> mCells;
};
/// <summary>
/// Helper class that contains info about cell. currently here is only colspan and rowspan.
/// </summary>
class CellInfo : public System::Object
{
public:
int get_ColSpan()
{
return pr_ColSpan;
}
int get_RowSpan()
{
return pr_RowSpan;
}
CellInfo(int colSpan, int rowSpan) : pr_ColSpan(0), pr_RowSpan(0)
{
pr_ColSpan = colSpan;
pr_RowSpan = rowSpan;
}
private:
int pr_ColSpan;
int pr_RowSpan;
};
class SpanVisitor : public DocumentVisitor
{
public:
/// <summary>
/// Creates new SpanVisitor instance.
/// </summary>
/// <param name="doc">
/// Is document which we should parse.
/// </param>
SpanVisitor(SharedPtr<Document> doc) : mTables(MakeObject<System::Collections::Generic::List<SharedPtr<WorkingWithTables::TableInfo>>>())
{
mWordTables = doc->GetChildNodes(NodeType::Table, true);
// We will parse HTML to determine the rowspan and colspan of each cell.
auto htmlStream = MakeObject<System::IO::MemoryStream>();
auto options = MakeObject<Aspose::Words::Saving::HtmlSaveOptions>();
options->set_ImagesFolder(System::IO::Path::GetTempPath());
doc->Save(htmlStream, options);
// Load HTML into the XML document.
auto xmlDoc = MakeObject<System::Xml::XmlDocument>();
htmlStream->set_Position(0);
xmlDoc->Load(htmlStream);
// Get collection of tables in the HTML document.
SharedPtr<System::Xml::XmlNodeList> tables = xmlDoc->get_DocumentElement()->GetElementsByTagName(u"table");
for (const auto& table : System::IterateOver(tables))
{
auto tableInf = MakeObject<WorkingWithTables::TableInfo>();
// Get collection of rows in the table.
SharedPtr<System::Xml::XmlNodeList> rows = table->SelectNodes(u"tr");
for (const auto& row : System::IterateOver(rows))
{
auto rowInf = MakeObject<WorkingWithTables::RowInfo>();
// Get collection of cells.
SharedPtr<System::Xml::XmlNodeList> cells = row->SelectNodes(u"td");
for (const auto& cell : System::IterateOver(cells))
{
// Determine row span and colspan of the current cell.
SharedPtr<System::Xml::XmlAttribute> colSpanAttr = cell->get_Attributes()->idx_get(u"colspan");
SharedPtr<System::Xml::XmlAttribute> rowSpanAttr = cell->get_Attributes()->idx_get(u"rowspan");
int colSpan = colSpanAttr == nullptr ? 0 : System::Int32::Parse(colSpanAttr->get_Value());
int rowSpan = rowSpanAttr == nullptr ? 0 : System::Int32::Parse(rowSpanAttr->get_Value());
auto cellInf = MakeObject<WorkingWithTables::CellInfo>(colSpan, rowSpan);
rowInf->get_Cells()->Add(cellInf);
}
tableInf->get_Rows()->Add(rowInf);
}
mTables->Add(tableInf);
}
}
VisitorAction VisitCellStart(SharedPtr<Cell> cell) override
{
int tabIdx = mWordTables->IndexOf(cell->get_ParentRow()->get_ParentTable());
int rowIdx = cell->get_ParentRow()->get_ParentTable()->IndexOf(cell->get_ParentRow());
int cellIdx = cell->get_ParentRow()->IndexOf(cell);
int colSpan = 0;
int rowSpan = 0;
if (tabIdx < mTables->get_Count() && rowIdx < mTables->idx_get(tabIdx)->get_Rows()->get_Count() &&
cellIdx < mTables->idx_get(tabIdx)->get_Rows()->idx_get(rowIdx)->get_Cells()->get_Count())
{
colSpan = mTables->idx_get(tabIdx)->get_Rows()->idx_get(rowIdx)->get_Cells()->idx_get(cellIdx)->get_ColSpan();
rowSpan = mTables->idx_get(tabIdx)->get_Rows()->idx_get(rowIdx)->get_Cells()->idx_get(cellIdx)->get_RowSpan();
}
std::cout << tabIdx << "." << rowIdx << "." << cellIdx << " colspan=" << colSpan << "\t rowspan=" << rowSpan << std::endl;
return VisitorAction::Continue;
}
private:
SharedPtr<System::Collections::Generic::List<SharedPtr<WorkingWithTables::TableInfo>>> mTables;
SharedPtr<NodeCollection> mWordTables;
};

Преобразовать в горизонтально объединенные ячейки

Иногда невозможно определить, какие ячейки объединены, потому что в некоторых новых версиях Microsoft Word больше не используются флаги объединения при объединении ячеек по горизонтали. Но для ситуаций, когда ячейки объединяются в ячейку по горизонтали по ширине с помощью флагов объединения, в Aspose.Words предусмотрен метод ConvertToHorizontallyMergedCells для преобразования ячеек. Этот метод просто преобразует таблицу и добавляет новые ячейки по мере необходимости.

Следующий пример кода показывает описанный выше метод в действии:

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-C.git.
auto doc = MakeObject<Document>(MyDir + u"Table with merged cells.docx");
SharedPtr<Table> table = doc->get_FirstSection()->get_Body()->get_Tables()->idx_get(0);
// Now merged cells have appropriate merge flags.
table->ConvertToHorizontallyMergedCells();