Об'єднати комірки таблиці

Іноді для певних рядків у таблиці потрібен заголовок або великі блоки тексту, що займають всю ширину таблиці. Для правильного оформлення таблиці користувач може об’єднати кілька осередків таблиці в одну. 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, а 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();