From 3b02903f230e07233f5e1bb315257fced993e86e Mon Sep 17 00:00:00 2001 From: zero <166997982@qq.com> Date: Thu, 19 Jun 2025 16:53:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=A1=B5=E7=9C=89=E5=92=8C?= =?UTF-8?q?=E9=A1=B5=E8=84=9A=E6=94=AF=E6=8C=81=EF=BC=8C=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=A1=A8=E6=A0=BC=E8=A1=8C=E5=A4=8D=E5=88=B6=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E6=B7=B1=E6=8B=B7=E8=B4=9D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/advanced_features/main.go | 18 +- pkg/document/table.go | 23 ++- test/table_insert_merge_fix_test.go | 305 ++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+), 10 deletions(-) create mode 100644 test/table_insert_merge_fix_test.go diff --git a/examples/advanced_features/main.go b/examples/advanced_features/main.go index c780ba4..a0aa731 100644 --- a/examples/advanced_features/main.go +++ b/examples/advanced_features/main.go @@ -180,17 +180,17 @@ func main() { // log.Printf("设置页面边距失败: %v", err) // } - // 5. 添加页眉页脚 - 暂时跳过,因为API可能尚未实现 + // 5. 添加页眉页脚 fmt.Println("添加页眉页脚...") - // err = doc.AddHeader("高级功能演示文档", "") - // if err != nil { - // log.Printf("添加页眉失败: %v", err) - // } + err = doc.AddHeader(document.HeaderFooterTypeDefault, "高级功能演示文档") + if err != nil { + log.Printf("添加页眉失败: %v", err) + } - // err = doc.AddFooter("", "第{页码}页 共{总页数}页") - // if err != nil { - // log.Printf("添加页脚失败: %v", err) - // } + err = doc.AddFooterWithPageNumber(document.HeaderFooterTypeDefault, "", true) + if err != nil { + log.Printf("添加页脚失败: %v", err) + } // 6. 创建演示表格 fmt.Println("创建演示表格...") diff --git a/pkg/document/table.go b/pkg/document/table.go index 9abf28b..3d6bae5 100644 --- a/pkg/document/table.go +++ b/pkg/document/table.go @@ -345,8 +345,29 @@ func (t *Table) InsertRow(position int, data []string) error { // 复制第一行的单元格属性作为模板 templateRow := t.Rows[0] for i := 0; i < colCount; i++ { + // 深拷贝单元格属性 + var cellProps *TableCellProperties + if templateRow.Cells[i].Properties != nil { + cellProps = &TableCellProperties{} + // 复制宽度 + if templateRow.Cells[i].Properties.TableCellW != nil { + cellProps.TableCellW = &TableCellW{ + W: templateRow.Cells[i].Properties.TableCellW.W, + Type: templateRow.Cells[i].Properties.TableCellW.Type, + } + } + // 复制垂直对齐 + if templateRow.Cells[i].Properties.VAlign != nil { + cellProps.VAlign = &VAlign{ + Val: templateRow.Cells[i].Properties.VAlign.Val, + } + } + // 复制其他必要的属性 + // 注意:不要复制GridSpan和VMerge,因为这些是合并相关的属性 + } + newRow.Cells[i] = TableCell{ - Properties: templateRow.Cells[i].Properties, // 复用属性 + Properties: cellProps, Paragraphs: []Paragraph{ { Runs: []Run{ diff --git a/test/table_insert_merge_fix_test.go b/test/table_insert_merge_fix_test.go new file mode 100644 index 0000000..4a6883e --- /dev/null +++ b/test/table_insert_merge_fix_test.go @@ -0,0 +1,305 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +// TestTableInsertAndMergeFix 测试动态添加行后合并单元格的修复 +func TestTableInsertAndMergeFix(t *testing.T) { + // 开启日志 + document.SetGlobalLevel(document.LogLevelInfo) + + t.Run("验证属性深拷贝", func(t *testing.T) { + doc := document.New() + + // 创建初始表格 + config := &document.TableConfig{ + Rows: 2, + Cols: 3, + Width: 6000, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 设置第一行的单元格文本 + table.SetCellText(0, 0, "Header1") + table.SetCellText(0, 1, "Header2") + table.SetCellText(0, 2, "Header3") + + // 添加新行 + err := table.AppendRow([]string{"Row2-1", "Row2-2", "Row2-3"}) + if err != nil { + t.Fatalf("添加行失败: %v", err) + } + + // 获取第一行和新添加行的单元格属性 + cell1, _ := table.GetCell(0, 0) + cell2, _ := table.GetCell(2, 0) + + // 验证属性是独立的(不是同一个指针) + if cell1.Properties == cell2.Properties { + t.Error("单元格属性应该是独立的副本,而不是共享的指针") + } + + // 修改新行的属性,不应影响第一行 + if cell2.Properties != nil && cell2.Properties.TableCellW != nil { + cell2.Properties.TableCellW.W = "3000" + } + + // 验证第一行的属性没有被改变 + if cell1.Properties != nil && cell1.Properties.TableCellW != nil { + if cell1.Properties.TableCellW.W == "3000" { + t.Error("修改新行的属性不应该影响第一行") + } + } + }) + + t.Run("大表格动态添加和合并", func(t *testing.T) { + doc := document.New() + + // 创建28行的表格 + config := &document.TableConfig{ + Rows: 28, + Cols: 5, + Width: 10000, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 填充数据 + for i := 0; i < 28; i++ { + for j := 0; j < 5; j++ { + table.SetCellText(i, j, fmt.Sprintf("Cell-%d-%d", i+1, j+1)) + } + } + + // 动态添加多行 + for i := 29; i <= 35; i++ { + rowData := make([]string, 5) + for j := 0; j < 5; j++ { + rowData[j] = fmt.Sprintf("Cell-%d-%d", i, j+1) + } + err := table.AppendRow(rowData) + if err != nil { + t.Fatalf("添加第%d行失败: %v", i, err) + } + } + + // 在不同位置进行合并 + testCases := []struct { + name string + row int + startCol int + endCol int + }{ + {"合并第1行", 0, 1, 3}, + {"合并第15行", 14, 0, 2}, + {"合并第28行", 27, 2, 4}, + {"合并第30行(动态添加的)", 29, 1, 3}, + {"合并最后一行", 34, 0, 1}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := table.MergeCellsHorizontal(tc.row, tc.startCol, tc.endCol) + if err != nil { + t.Errorf("%s失败: %v", tc.name, err) + } + + // 验证合并后的单元格数 + row := table.Rows[tc.row] + expectedCells := 5 - (tc.endCol - tc.startCol) + if len(row.Cells) != expectedCells { + t.Errorf("%s后单元格数不正确: 期望%d,实际%d", + tc.name, expectedCells, len(row.Cells)) + } + + // 验证GridSpan设置 + cell, _ := table.GetCell(tc.row, tc.startCol) + if cell.Properties == nil || cell.Properties.GridSpan == nil { + t.Errorf("%s后GridSpan未设置", tc.name) + } else { + expectedSpan := fmt.Sprintf("%d", tc.endCol-tc.startCol+1) + if cell.Properties.GridSpan.Val != expectedSpan { + t.Errorf("%s后GridSpan值不正确: 期望%s,实际%s", + tc.name, expectedSpan, cell.Properties.GridSpan.Val) + } + } + }) + } + + // 保存文档 + err := doc.Save("test/output/large_table_merge_test.docx") + if err != nil { + t.Errorf("保存文档失败: %v", err) + } + }) + + t.Run("混合操作测试", func(t *testing.T) { + doc := document.New() + + // 创建初始表格 + config := &document.TableConfig{ + Rows: 5, + Cols: 4, + Width: 8000, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 先合并一些单元格 + err := table.MergeCellsHorizontal(1, 1, 2) + if err != nil { + t.Fatalf("初始合并失败: %v", err) + } + + // 添加新行 + for i := 0; i < 3; i++ { + err := table.AppendRow([]string{"New1", "New2", "New3", "New4"}) + if err != nil { + t.Fatalf("添加行失败: %v", err) + } + } + + // 在新添加的行上进行合并 + err = table.MergeCellsHorizontal(6, 0, 1) + if err != nil { + t.Fatalf("合并新行失败: %v", err) + } + + // 验证表格结构完整性 + if table.GetRowCount() != 8 { + t.Errorf("表格行数不正确: 期望8,实际%d", table.GetRowCount()) + } + + // 验证每行的单元格数是否正确 + expectedCellCounts := []int{4, 3, 4, 4, 4, 4, 3, 4} // 第1行和第6行有合并 + for i, row := range table.Rows { + if len(row.Cells) != expectedCellCounts[i] { + t.Errorf("第%d行单元格数不正确: 期望%d,实际%d", + i, expectedCellCounts[i], len(row.Cells)) + } + } + + // 保存文档 + err = doc.Save("test/output/mixed_operations_test.docx") + if err != nil { + t.Errorf("保存文档失败: %v", err) + } + }) +} + +// TestTableGridConsistencyAfterFix 测试修复后的表格网格一致性 +func TestTableGridConsistencyAfterFix(t *testing.T) { + doc := document.New() + + // 创建带有自定义列宽的表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 4, + Width: 8000, + ColWidths: []int{1500, 2000, 2500, 2000}, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 记录原始列宽 + originalWidths := make([]string, len(table.Grid.Cols)) + for i, col := range table.Grid.Cols { + originalWidths[i] = col.W + } + + // 动态添加10行 + for i := 0; i < 10; i++ { + err := table.AppendRow([]string{ + fmt.Sprintf("A%d", i+4), + fmt.Sprintf("B%d", i+4), + fmt.Sprintf("C%d", i+4), + fmt.Sprintf("D%d", i+4), + }) + if err != nil { + t.Fatalf("添加第%d行失败: %v", i+4, err) + } + } + + // 验证所有行的单元格宽度与网格定义一致 + for i, row := range table.Rows { + for j, cell := range row.Cells { + if cell.Properties == nil || cell.Properties.TableCellW == nil { + t.Errorf("行%d列%d缺少宽度属性", i, j) + continue + } + + expectedWidth := originalWidths[j] + actualWidth := cell.Properties.TableCellW.W + if actualWidth != expectedWidth { + t.Errorf("行%d列%d宽度不一致: 期望%s,实际%s", + i, j, expectedWidth, actualWidth) + } + } + } + + // 进行一些合并操作 + err := table.MergeCellsHorizontal(5, 1, 2) + if err != nil { + t.Fatalf("合并失败: %v", err) + } + + err = table.MergeCellsVertical(8, 10, 0) + if err != nil { + t.Fatalf("垂直合并失败: %v", err) + } + + // 再次验证未合并单元格的宽度保持一致 + for i, row := range table.Rows { + cellIndex := 0 + for j := 0; j < len(originalWidths); j++ { + if cellIndex >= len(row.Cells) { + break + } + + cell := row.Cells[cellIndex] + + // 跳过被合并掉的单元格 + if i == 5 && (j == 2 || j == 3) { + // 这些单元格在第5行被水平合并了 + continue + } + + if cell.Properties != nil && cell.Properties.TableCellW != nil { + expectedWidth := originalWidths[j] + actualWidth := cell.Properties.TableCellW.W + if actualWidth != expectedWidth { + // 合并的单元格可能有不同的宽度 + if cell.Properties.GridSpan == nil && cell.Properties.VMerge == nil { + t.Errorf("行%d单元格%d宽度不一致: 期望%s,实际%s", + i, cellIndex, expectedWidth, actualWidth) + } + } + } + + cellIndex++ + } + } + + // 保存文档 + err = doc.Save("test/output/grid_consistency_after_fix.docx") + if err != nil { + t.Errorf("保存文档失败: %v", err) + } +}