diff --git a/README.md b/README.md index a7d4bc5..101309e 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,19 @@ wordZero/ - [x] 表格位置信息获取 - [x] 单元格访问接口 - [x] 按行列索引访问 +- [x] **单元格遍历迭代器** ✨ **新实现** + - [x] 单元格迭代器(CellIterator) + - [x] 按顺序遍历所有单元格 + - [x] 迭代器重置和位置跟踪 + - [x] 迭代进度计算 + - [x] 单元格信息结构(CellInfo) + - [x] ForEach批量处理方法 + - [x] 按行遍历(ForEachInRow) + - [x] 按列遍历(ForEachInColumn) + - [x] 获取单元格范围(GetCellRange) + - [x] 条件查找单元格(FindCells) + - [x] 按文本查找单元格(FindCellsByText) + - [x] 精确匹配和模糊匹配支持 ##### 表格样式和外观 - [x] 表格整体样式 @@ -240,9 +253,8 @@ wordZero/ - [ ] 表格访问增强 - [ ] 按标题查找表格 - [ ] 按范围批量访问 - - [ ] 单元格遍历迭代器 -#### 图片功能 +#### 图片功能 - [ ] 图片插入 - [ ] 图片大小调整 - [ ] 图片位置设置 @@ -311,6 +323,7 @@ wordZero/ - `examples/table_layout/` - 表格布局和尺寸演示 - `examples/table_style/` - 表格样式和外观演示 - `examples/cell_advanced/` - 单元格高级功能演示 +- `examples/cell_iterator/` - **单元格迭代器功能演示** ✨ **新增** - `examples/formatting/` - 格式化演示 - `examples/page_settings/` - **页面设置演示** ✨ **新增** - `examples/advanced_features/` - **高级功能综合演示** ✨ **新增** @@ -340,6 +353,9 @@ go run ./examples/table_style/ # 运行单元格高级功能演示 go run ./examples/cell_advanced/ +# 运行单元格迭代器功能演示 +go run ./examples/cell_iterator/ + # 运行格式化演示 go run ./examples/formatting/ diff --git a/examples/cell_iterator/main.go b/examples/cell_iterator/main.go new file mode 100644 index 0000000..7ee94ca --- /dev/null +++ b/examples/cell_iterator/main.go @@ -0,0 +1,187 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +func main() { + fmt.Println("=== 单元格迭代器功能演示 ===") + + // 创建新文档 + doc := document.New() + + // 创建一个3x4的测试表格 + config := &document.TableConfig{ + Rows: 3, + Cols: 4, + Width: 8000, + Data: [][]string{ + {"产品", "价格", "数量", "总计"}, + {"苹果", "5.00", "10", "50.00"}, + {"橙子", "3.50", "15", "52.50"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + log.Fatal("创建表格失败") + } + + fmt.Printf("创建了 %dx%d 的表格\n", table.GetRowCount(), table.GetColumnCount()) + + // 演示1: 基本迭代器使用 + fmt.Println("\n--- 1. 基本迭代器遍历 ---") + iterator := table.NewCellIterator() + fmt.Printf("表格总共有 %d 个单元格\n", iterator.Total()) + + cellCount := 0 + for iterator.HasNext() { + cellInfo, err := iterator.Next() + if err != nil { + log.Printf("迭代器错误: %v", err) + break + } + + cellCount++ + fmt.Printf("单元格[%d,%d]: '%s'", cellInfo.Row, cellInfo.Col, cellInfo.Text) + + if cellInfo.IsLast { + fmt.Print(" (最后一个)") + } + + fmt.Printf(" - 进度: %.1f%%\n", iterator.Progress()*100) + } + + // 演示2: 重置迭代器 + fmt.Println("\n--- 2. 重置迭代器演示 ---") + iterator.Reset() + row, col := iterator.Current() + fmt.Printf("重置后,当前位置: (%d, %d)\n", row, col) + + // 只遍历前3个单元格 + for i := 0; i < 3 && iterator.HasNext(); i++ { + cellInfo, _ := iterator.Next() + fmt.Printf("单元格[%d,%d]: '%s'\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) + } + + // 演示3: ForEach方法 + fmt.Println("\n--- 3. ForEach 批量处理演示 ---") + err := table.ForEach(func(row, col int, cell *document.TableCell, text string) error { + if text != "" { + fmt.Printf("处理单元格[%d,%d]: '%s' (长度: %d)\n", row, col, text, len(text)) + } + return nil + }) + + if err != nil { + log.Printf("ForEach执行失败: %v", err) + } + + // 演示4: 按行遍历 + fmt.Println("\n--- 4. 按行遍历演示 ---") + for row := 0; row < table.GetRowCount(); row++ { + fmt.Printf("第%d行: ", row+1) + err := table.ForEachInRow(row, func(col int, cell *document.TableCell, text string) error { + fmt.Printf("'%s' ", text) + return nil + }) + if err != nil { + log.Printf("按行遍历失败: %v", err) + } + fmt.Println() + } + + // 演示5: 按列遍历 + fmt.Println("\n--- 5. 按列遍历演示 ---") + for col := 0; col < table.GetColumnCount(); col++ { + fmt.Printf("第%d列: ", col+1) + err := table.ForEachInColumn(col, func(row int, cell *document.TableCell, text string) error { + fmt.Printf("'%s' ", text) + return nil + }) + if err != nil { + log.Printf("按列遍历失败: %v", err) + } + fmt.Println() + } + + // 演示6: 获取范围单元格 + fmt.Println("\n--- 6. 获取单元格范围演示 ---") + cells, err := table.GetCellRange(1, 1, 2, 3) // 获取价格、数量、总计的数据部分 + if err != nil { + log.Printf("获取范围失败: %v", err) + } else { + fmt.Printf("范围 (1,1) 到 (2,3) 的单元格:\n") + for _, cellInfo := range cells { + fmt.Printf(" [%d,%d]: '%s'\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) + } + } + + // 演示7: 查找单元格 + fmt.Println("\n--- 7. 查找单元格演示 ---") + + // 查找包含数字的单元格 + numberCells, err := table.FindCells(func(row, col int, cell *document.TableCell, text string) bool { + // 简单检查是否包含数字字符 + for _, char := range text { + if char >= '0' && char <= '9' { + return true + } + } + return false + }) + + if err != nil { + log.Printf("查找失败: %v", err) + } else { + fmt.Printf("找到 %d 个包含数字的单元格:\n", len(numberCells)) + for _, cellInfo := range numberCells { + fmt.Printf(" [%d,%d]: '%s'\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) + } + } + + // 演示8: 按文本查找 + fmt.Println("\n--- 8. 按文本查找演示 ---") + + // 精确查找 + exactCells, err := table.FindCellsByText("苹果", true) + if err != nil { + log.Printf("精确查找失败: %v", err) + } else { + fmt.Printf("精确匹配 '苹果' 的单元格: %d 个\n", len(exactCells)) + for _, cellInfo := range exactCells { + fmt.Printf(" [%d,%d]: '%s'\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) + } + } + + // 模糊查找 + fuzzyCells, err := table.FindCellsByText("5", false) + if err != nil { + log.Printf("模糊查找失败: %v", err) + } else { + fmt.Printf("包含 '5' 的单元格: %d 个\n", len(fuzzyCells)) + for _, cellInfo := range fuzzyCells { + fmt.Printf(" [%d,%d]: '%s'\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) + } + } + + // 保存文档 + outputDir := "examples/output" + if err := os.MkdirAll(outputDir, 0755); err != nil { + log.Printf("创建输出目录失败: %v", err) + } + + filename := filepath.Join(outputDir, "cell_iterator_demo.docx") + if err := doc.Save(filename); err != nil { + log.Printf("保存文档失败: %v", err) + } else { + fmt.Printf("\n文档已保存到: %s\n", filename) + } + + fmt.Println("\n=== 单元格迭代器演示完成 ===") +} diff --git a/pkg/document/README.md b/pkg/document/README.md index d8d8ccc..5a6c7d2 100644 --- a/pkg/document/README.md +++ b/pkg/document/README.md @@ -220,6 +220,84 @@ - [`SetCellShading(row, col int, config *ShadingConfig)`](table.go#L2121) - 设置单元格底纹 - [`SetAlternatingRowColors(evenRowColor, oddRowColor string)`](table.go#L2142) - 设置交替行颜色 +### 单元格遍历迭代器 ✨ **新功能** + +提供强大的单元格遍历和查找功能: + +##### CellIterator - 单元格迭代器 +```go +// 创建迭代器 +iterator := table.NewCellIterator() + +// 遍历所有单元格 +for iterator.HasNext() { + cellInfo, err := iterator.Next() + if err != nil { + break + } + fmt.Printf("单元格[%d,%d]: %s\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) +} + +// 获取进度 +progress := iterator.Progress() // 0.0 - 1.0 + +// 重置迭代器 +iterator.Reset() +``` + +##### ForEach 批量处理 +```go +// 遍历所有单元格 +err := table.ForEach(func(row, col int, cell *TableCell, text string) error { + // 处理每个单元格 + return nil +}) + +// 按行遍历 +err := table.ForEachInRow(rowIndex, func(col int, cell *TableCell, text string) error { + // 处理行中的每个单元格 + return nil +}) + +// 按列遍历 +err := table.ForEachInColumn(colIndex, func(row int, cell *TableCell, text string) error { + // 处理列中的每个单元格 + return nil +}) +``` + +##### 范围操作 +```go +// 获取指定范围的单元格 +cells, err := table.GetCellRange(startRow, startCol, endRow, endCol) +for _, cellInfo := range cells { + fmt.Printf("单元格[%d,%d]: %s\n", cellInfo.Row, cellInfo.Col, cellInfo.Text) +} +``` + +##### 查找功能 +```go +// 自定义条件查找 +cells, err := table.FindCells(func(row, col int, cell *TableCell, text string) bool { + return strings.Contains(text, "关键词") +}) + +// 按文本查找 +exactCells, err := table.FindCellsByText("精确匹配", true) +fuzzyCells, err := table.FindCellsByText("模糊", false) +``` + +##### CellInfo 结构 +```go +type CellInfo struct { + Row int // 行索引 + Col int // 列索引 + Cell *TableCell // 单元格引用 + Text string // 单元格文本 + IsLast bool // 是否为最后一个单元格 +} +``` + ## 工具函数 ### 日志系统 diff --git a/pkg/document/table.go b/pkg/document/table.go index 1ad52ee..4f63d7c 100644 --- a/pkg/document/table.go +++ b/pkg/document/table.go @@ -4,6 +4,7 @@ package document import ( "encoding/xml" "fmt" + "strings" ) // Table 表示一个表格 @@ -2320,3 +2321,249 @@ func createTableCellBorder(config *BorderConfig) *TableCellBorder { Color: config.Color, } } + +// CellIterator 单元格迭代器 +type CellIterator struct { + table *Table + currentRow int + currentCol int + totalRows int + totalCols int +} + +// CellInfo 单元格信息 +type CellInfo struct { + Row int // 行索引 + Col int // 列索引 + Cell *TableCell // 单元格引用 + Text string // 单元格文本 + IsLast bool // 是否为最后一个单元格 +} + +// NewCellIterator 创建新的单元格迭代器 +func (t *Table) NewCellIterator() *CellIterator { + totalRows := t.GetRowCount() + totalCols := 0 + if totalRows > 0 { + totalCols = t.GetColumnCount() + } + + return &CellIterator{ + table: t, + currentRow: 0, + currentCol: 0, + totalRows: totalRows, + totalCols: totalCols, + } +} + +// HasNext 检查是否还有下一个单元格 +func (iter *CellIterator) HasNext() bool { + if iter.totalRows == 0 || iter.totalCols == 0 { + return false + } + + // 检查当前位置是否超出范围 + return iter.currentRow < iter.totalRows && + (iter.currentRow < iter.totalRows-1 || iter.currentCol < iter.totalCols) +} + +// Next 获取下一个单元格信息 +func (iter *CellIterator) Next() (*CellInfo, error) { + if !iter.HasNext() { + return nil, fmt.Errorf("没有更多单元格") + } + + // 获取当前单元格 + cell, err := iter.table.GetCell(iter.currentRow, iter.currentCol) + if err != nil { + return nil, fmt.Errorf("获取单元格失败: %v", err) + } + + // 获取单元格文本 + text, _ := iter.table.GetCellText(iter.currentRow, iter.currentCol) + + // 创建单元格信息 + cellInfo := &CellInfo{ + Row: iter.currentRow, + Col: iter.currentCol, + Cell: cell, + Text: text, + } + + // 更新位置并检查是否为最后一个 + iter.currentCol++ + if iter.currentCol >= iter.totalCols { + iter.currentCol = 0 + iter.currentRow++ + } + + // 检查是否为最后一个单元格 + cellInfo.IsLast = !iter.HasNext() + + return cellInfo, nil +} + +// Reset 重置迭代器到开始位置 +func (iter *CellIterator) Reset() { + iter.currentRow = 0 + iter.currentCol = 0 +} + +// Current 获取当前位置信息(不移动迭代器) +func (iter *CellIterator) Current() (int, int) { + return iter.currentRow, iter.currentCol +} + +// Total 获取总单元格数量 +func (iter *CellIterator) Total() int { + return iter.totalRows * iter.totalCols +} + +// Progress 获取迭代进度(0.0-1.0) +func (iter *CellIterator) Progress() float64 { + if iter.totalRows == 0 || iter.totalCols == 0 { + return 1.0 + } + + processed := iter.currentRow*iter.totalCols + iter.currentCol + total := iter.totalRows * iter.totalCols + + return float64(processed) / float64(total) +} + +// ForEach 遍历所有单元格,对每个单元格执行指定函数 +func (t *Table) ForEach(fn func(row, col int, cell *TableCell, text string) error) error { + iterator := t.NewCellIterator() + + for iterator.HasNext() { + cellInfo, err := iterator.Next() + if err != nil { + return fmt.Errorf("迭代失败: %v", err) + } + + if err := fn(cellInfo.Row, cellInfo.Col, cellInfo.Cell, cellInfo.Text); err != nil { + return fmt.Errorf("回调函数执行失败 (行:%d, 列:%d): %v", cellInfo.Row, cellInfo.Col, err) + } + } + + return nil +} + +// ForEachInRow 遍历指定行的所有单元格 +func (t *Table) ForEachInRow(rowIndex int, fn func(col int, cell *TableCell, text string) error) error { + if rowIndex < 0 || rowIndex >= t.GetRowCount() { + return fmt.Errorf("行索引无效: %d", rowIndex) + } + + colCount := t.GetColumnCount() + for col := 0; col < colCount; col++ { + cell, err := t.GetCell(rowIndex, col) + if err != nil { + return fmt.Errorf("获取单元格失败 (行:%d, 列:%d): %v", rowIndex, col, err) + } + + text, _ := t.GetCellText(rowIndex, col) + + if err := fn(col, cell, text); err != nil { + return fmt.Errorf("回调函数执行失败 (行:%d, 列:%d): %v", rowIndex, col, err) + } + } + + return nil +} + +// ForEachInColumn 遍历指定列的所有单元格 +func (t *Table) ForEachInColumn(colIndex int, fn func(row int, cell *TableCell, text string) error) error { + if colIndex < 0 || colIndex >= t.GetColumnCount() { + return fmt.Errorf("列索引无效: %d", colIndex) + } + + rowCount := t.GetRowCount() + for row := 0; row < rowCount; row++ { + cell, err := t.GetCell(row, colIndex) + if err != nil { + return fmt.Errorf("获取单元格失败 (行:%d, 列:%d): %v", row, colIndex, err) + } + + text, _ := t.GetCellText(row, colIndex) + + if err := fn(row, cell, text); err != nil { + return fmt.Errorf("回调函数执行失败 (行:%d, 列:%d): %v", row, colIndex, err) + } + } + + return nil +} + +// GetCellRange 获取指定范围内的所有单元格 +func (t *Table) GetCellRange(startRow, startCol, endRow, endCol int) ([]*CellInfo, error) { + // 参数验证 + if startRow < 0 || startCol < 0 || endRow >= t.GetRowCount() || endCol >= t.GetColumnCount() { + return nil, fmt.Errorf("范围索引无效: (%d,%d) 到 (%d,%d)", startRow, startCol, endRow, endCol) + } + + if startRow > endRow || startCol > endCol { + return nil, fmt.Errorf("开始位置不能大于结束位置") + } + + var cells []*CellInfo + + for row := startRow; row <= endRow; row++ { + for col := startCol; col <= endCol; col++ { + cell, err := t.GetCell(row, col) + if err != nil { + return nil, fmt.Errorf("获取单元格失败 (行:%d, 列:%d): %v", row, col, err) + } + + text, _ := t.GetCellText(row, col) + + cellInfo := &CellInfo{ + Row: row, + Col: col, + Cell: cell, + Text: text, + IsLast: row == endRow && col == endCol, + } + + cells = append(cells, cellInfo) + } + } + + return cells, nil +} + +// FindCells 查找满足条件的单元格 +func (t *Table) FindCells(predicate func(row, col int, cell *TableCell, text string) bool) ([]*CellInfo, error) { + var matchedCells []*CellInfo + + err := t.ForEach(func(row, col int, cell *TableCell, text string) error { + if predicate(row, col, cell, text) { + cellInfo := &CellInfo{ + Row: row, + Col: col, + Cell: cell, + Text: text, + } + matchedCells = append(matchedCells, cellInfo) + } + return nil + }) + + if err != nil { + return nil, err + } + + return matchedCells, nil +} + +// FindCellsByText 根据文本内容查找单元格 +func (t *Table) FindCellsByText(searchText string, exactMatch bool) ([]*CellInfo, error) { + return t.FindCells(func(row, col int, cell *TableCell, text string) bool { + if exactMatch { + return text == searchText + } + // 使用strings.Contains进行模糊匹配 + return strings.Contains(text, searchText) + }) +} diff --git a/pkg/document/table_iterator_test.go b/pkg/document/table_iterator_test.go new file mode 100644 index 0000000..fe5c65b --- /dev/null +++ b/pkg/document/table_iterator_test.go @@ -0,0 +1,466 @@ +package document + +import ( + "fmt" + "testing" +) + +// TestCellIterator 测试基本的单元格迭代器功能 +func TestCellIterator(t *testing.T) { + // 创建一个3x3的测试表格 + doc := New() + config := &TableConfig{ + Rows: 3, + Cols: 3, + Width: 5000, + Data: [][]string{ + {"A1", "B1", "C1"}, + {"A2", "B2", "C2"}, + {"A3", "B3", "C3"}, + }, + } + + table := doc.CreateTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试迭代器创建 + iterator := table.NewCellIterator() + if iterator == nil { + t.Fatal("创建迭代器失败") + } + + // 测试Total方法 + expectedTotal := 9 + if iterator.Total() != expectedTotal { + t.Errorf("Total()期望返回%d,实际返回%d", expectedTotal, iterator.Total()) + } + + // 测试迭代器遍历 + cellCount := 0 + expectedCells := []struct { + row int + col int + text string + }{ + {0, 0, "A1"}, {0, 1, "B1"}, {0, 2, "C1"}, + {1, 0, "A2"}, {1, 1, "B2"}, {1, 2, "C2"}, + {2, 0, "A3"}, {2, 1, "B3"}, {2, 2, "C3"}, + } + + for iterator.HasNext() { + cellInfo, err := iterator.Next() + if err != nil { + t.Fatalf("迭代器Next()失败: %v", err) + } + + if cellCount >= len(expectedCells) { + t.Fatalf("迭代器返回了过多的单元格") + } + + expected := expectedCells[cellCount] + if cellInfo.Row != expected.row || cellInfo.Col != expected.col { + t.Errorf("单元格位置不匹配: 期望(%d,%d),实际(%d,%d)", + expected.row, expected.col, cellInfo.Row, cellInfo.Col) + } + + if cellInfo.Text != expected.text { + t.Errorf("单元格文本不匹配: 期望'%s',实际'%s'", + expected.text, cellInfo.Text) + } + + if cellInfo.Cell == nil { + t.Error("单元格引用为nil") + } + + // 测试IsLast标记 + if cellCount == len(expectedCells)-1 && !cellInfo.IsLast { + t.Error("最后一个单元格的IsLast标记应为true") + } + + cellCount++ + } + + if cellCount != expectedTotal { + t.Errorf("迭代的单元格数量不匹配: 期望%d,实际%d", expectedTotal, cellCount) + } +} + +// TestCellIteratorReset 测试迭代器重置功能 +func TestCellIteratorReset(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 2, + Cols: 2, + Width: 3000, + Data: [][]string{ + {"A1", "B1"}, + {"A2", "B2"}, + }, + } + + table := doc.CreateTable(config) + iterator := table.NewCellIterator() + + // 先迭代一些单元格 + iterator.Next() + iterator.Next() + + // 检查当前位置 + row, col := iterator.Current() + if row != 1 || col != 0 { + t.Errorf("迭代器位置不正确: 期望(1,0),实际(%d,%d)", row, col) + } + + // 重置迭代器 + iterator.Reset() + + // 检查重置后的位置 + row, col = iterator.Current() + if row != 0 || col != 0 { + t.Errorf("重置后位置不正确: 期望(0,0),实际(%d,%d)", row, col) + } + + // 确保能重新遍历 + if !iterator.HasNext() { + t.Error("重置后应该有下一个单元格") + } +} + +// TestCellIteratorProgress 测试进度计算 +func TestCellIteratorProgress(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 2, + Cols: 2, + Width: 3000, + } + + table := doc.CreateTable(config) + iterator := table.NewCellIterator() + + // 初始进度应为0 + if iterator.Progress() != 0.0 { + t.Errorf("初始进度应为0.0,实际为%f", iterator.Progress()) + } + + // 迭代一个单元格 + iterator.Next() + expectedProgress := 0.25 // 1/4 + if iterator.Progress() != expectedProgress { + t.Errorf("迭代一个单元格后进度应为%f,实际为%f", expectedProgress, iterator.Progress()) + } + + // 迭代到最后 + for iterator.HasNext() { + iterator.Next() + } + + if iterator.Progress() != 1.0 { + t.Errorf("完成迭代后进度应为1.0,实际为%f", iterator.Progress()) + } +} + +// TestTableForEach 测试ForEach方法 +func TestTableForEach(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 2, + Cols: 3, + Width: 4000, + Data: [][]string{ + {"A1", "B1", "C1"}, + {"A2", "B2", "C2"}, + }, + } + + table := doc.CreateTable(config) + + // 测试ForEach遍历 + var visitedCells []string + err := table.ForEach(func(row, col int, cell *TableCell, text string) error { + visitedCells = append(visitedCells, fmt.Sprintf("%d-%d:%s", row, col, text)) + return nil + }) + + if err != nil { + t.Fatalf("ForEach执行失败: %v", err) + } + + expectedCells := []string{ + "0-0:A1", "0-1:B1", "0-2:C1", + "1-0:A2", "1-1:B2", "1-2:C2", + } + + if len(visitedCells) != len(expectedCells) { + t.Errorf("访问的单元格数量不匹配: 期望%d,实际%d", len(expectedCells), len(visitedCells)) + } + + for i, expected := range expectedCells { + if i < len(visitedCells) && visitedCells[i] != expected { + t.Errorf("单元格访问顺序不正确: 期望'%s',实际'%s'", expected, visitedCells[i]) + } + } +} + +// TestForEachInRow 测试按行遍历 +func TestForEachInRow(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 3, + Cols: 3, + Width: 4000, + Data: [][]string{ + {"A1", "B1", "C1"}, + {"A2", "B2", "C2"}, + {"A3", "B3", "C3"}, + }, + } + + table := doc.CreateTable(config) + + // 测试遍历第2行(索引1) + var visitedCells []string + err := table.ForEachInRow(1, func(col int, cell *TableCell, text string) error { + visitedCells = append(visitedCells, fmt.Sprintf("%d:%s", col, text)) + return nil + }) + + if err != nil { + t.Fatalf("ForEachInRow执行失败: %v", err) + } + + expectedCells := []string{"0:A2", "1:B2", "2:C2"} + if len(visitedCells) != len(expectedCells) { + t.Errorf("访问的单元格数量不匹配: 期望%d,实际%d", len(expectedCells), len(visitedCells)) + } + + for i, expected := range expectedCells { + if i < len(visitedCells) && visitedCells[i] != expected { + t.Errorf("单元格访问顺序不正确: 期望'%s',实际'%s'", expected, visitedCells[i]) + } + } + + // 测试无效行索引 + err = table.ForEachInRow(5, func(col int, cell *TableCell, text string) error { + return nil + }) + if err == nil { + t.Error("应该返回无效行索引错误") + } +} + +// TestForEachInColumn 测试按列遍历 +func TestForEachInColumn(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 3, + Cols: 3, + Width: 4000, + Data: [][]string{ + {"A1", "B1", "C1"}, + {"A2", "B2", "C2"}, + {"A3", "B3", "C3"}, + }, + } + + table := doc.CreateTable(config) + + // 测试遍历第2列(索引1) + var visitedCells []string + err := table.ForEachInColumn(1, func(row int, cell *TableCell, text string) error { + visitedCells = append(visitedCells, fmt.Sprintf("%d:%s", row, text)) + return nil + }) + + if err != nil { + t.Fatalf("ForEachInColumn执行失败: %v", err) + } + + expectedCells := []string{"0:B1", "1:B2", "2:B3"} + if len(visitedCells) != len(expectedCells) { + t.Errorf("访问的单元格数量不匹配: 期望%d,实际%d", len(expectedCells), len(visitedCells)) + } + + for i, expected := range expectedCells { + if i < len(visitedCells) && visitedCells[i] != expected { + t.Errorf("单元格访问顺序不正确: 期望'%s',实际'%s'", expected, visitedCells[i]) + } + } + + // 测试无效列索引 + err = table.ForEachInColumn(5, func(row int, cell *TableCell, text string) error { + return nil + }) + if err == nil { + t.Error("应该返回无效列索引错误") + } +} + +// TestGetCellRange 测试获取单元格范围 +func TestGetCellRange(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 4, + Cols: 4, + Width: 5000, + Data: [][]string{ + {"A1", "B1", "C1", "D1"}, + {"A2", "B2", "C2", "D2"}, + {"A3", "B3", "C3", "D3"}, + {"A4", "B4", "C4", "D4"}, + }, + } + + table := doc.CreateTable(config) + + // 测试获取2x2范围 (1,1) 到 (2,2) + cells, err := table.GetCellRange(1, 1, 2, 2) + if err != nil { + t.Fatalf("GetCellRange执行失败: %v", err) + } + + expectedCells := []struct { + row int + col int + text string + }{ + {1, 1, "B2"}, {1, 2, "C2"}, + {2, 1, "B3"}, {2, 2, "C3"}, + } + + if len(cells) != len(expectedCells) { + t.Errorf("返回的单元格数量不匹配: 期望%d,实际%d", len(expectedCells), len(cells)) + } + + for i, expected := range expectedCells { + if i < len(cells) { + cell := cells[i] + if cell.Row != expected.row || cell.Col != expected.col { + t.Errorf("单元格位置不匹配: 期望(%d,%d),实际(%d,%d)", + expected.row, expected.col, cell.Row, cell.Col) + } + if cell.Text != expected.text { + t.Errorf("单元格文本不匹配: 期望'%s',实际'%s'", + expected.text, cell.Text) + } + } + } + + // 测试无效范围 + _, err = table.GetCellRange(2, 2, 1, 1) // 开始位置大于结束位置 + if err == nil { + t.Error("应该返回无效范围错误") + } + + _, err = table.GetCellRange(0, 0, 10, 10) // 超出表格范围 + if err == nil { + t.Error("应该返回超出范围错误") + } +} + +// TestFindCells 测试查找单元格功能 +func TestFindCells(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 3, + Cols: 3, + Width: 4000, + Data: [][]string{ + {"apple", "banana", "cherry"}, + {"dog", "apple", "fish"}, + {"grape", "horse", "apple"}, + }, + } + + table := doc.CreateTable(config) + + // 查找包含"apple"的单元格 + cells, err := table.FindCells(func(row, col int, cell *TableCell, text string) bool { + return text == "apple" + }) + + if err != nil { + t.Fatalf("FindCells执行失败: %v", err) + } + + expectedPositions := [][2]int{{0, 0}, {1, 1}, {2, 2}} + if len(cells) != len(expectedPositions) { + t.Errorf("找到的单元格数量不匹配: 期望%d,实际%d", len(expectedPositions), len(cells)) + } + + for i, expected := range expectedPositions { + if i < len(cells) { + cell := cells[i] + if cell.Row != expected[0] || cell.Col != expected[1] { + t.Errorf("找到的单元格位置不正确: 期望(%d,%d),实际(%d,%d)", + expected[0], expected[1], cell.Row, cell.Col) + } + if cell.Text != "apple" { + t.Errorf("找到的单元格文本不正确: 期望'apple',实际'%s'", cell.Text) + } + } + } +} + +// TestFindCellsByText 测试按文本查找单元格 +func TestFindCellsByText(t *testing.T) { + doc := New() + config := &TableConfig{ + Rows: 2, + Cols: 3, + Width: 4000, + Data: [][]string{ + {"test", "testing", "other"}, + {"notest", "test123", "test"}, + }, + } + + table := doc.CreateTable(config) + + // 精确匹配 + cells, err := table.FindCellsByText("test", true) + if err != nil { + t.Fatalf("FindCellsByText执行失败: %v", err) + } + + expectedCount := 2 // (0,0) 和 (1,2) + if len(cells) != expectedCount { + t.Errorf("精确匹配找到的单元格数量不匹配: 期望%d,实际%d", expectedCount, len(cells)) + } + + // 模糊匹配 + cells, err = table.FindCellsByText("test", false) + if err != nil { + t.Fatalf("FindCellsByText执行失败: %v", err) + } + + expectedCount = 5 // 所有包含"test"的单元格 + if len(cells) != expectedCount { + t.Errorf("模糊匹配找到的单元格数量不匹配: 期望%d,实际%d", expectedCount, len(cells)) + } +} + +// TestEmptyTable 测试空表格的迭代器 +func TestEmptyTable(t *testing.T) { + doc := New() + + // 创建一个空表格(实际上最小1x1) + config := &TableConfig{ + Rows: 1, + Cols: 1, + Width: 2000, + } + + table := doc.CreateTable(config) + iterator := table.NewCellIterator() + + if iterator.Total() != 1 { + t.Errorf("空表格的总单元格数应为1,实际为%d", iterator.Total()) + } + + if !iterator.HasNext() { + t.Error("1x1表格应该有一个单元格") + } +} diff --git a/test/cell_iterator_integration_test.go b/test/cell_iterator_integration_test.go new file mode 100644 index 0000000..5e2719b --- /dev/null +++ b/test/cell_iterator_integration_test.go @@ -0,0 +1,229 @@ +package test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ZeroHawkeye/wordZero/pkg/document" +) + +// TestCellIteratorIntegration 单元格迭代器功能集成测试 +func TestCellIteratorIntegration(t *testing.T) { + // 创建测试文档 + doc := document.New() + + // 创建测试表格 + config := &document.TableConfig{ + Rows: 4, + Cols: 3, + Width: 6000, + Data: [][]string{ + {"姓名", "年龄", "城市"}, + {"张三", "25", "北京"}, + {"李四", "30", "上海"}, + {"王五", "28", "广州"}, + }, + } + + table := doc.AddTable(config) + if table == nil { + t.Fatal("创建表格失败") + } + + // 测试1: 基本迭代器功能 + t.Run("基本迭代器", func(t *testing.T) { + iterator := table.NewCellIterator() + + // 验证总数 + expectedTotal := 12 + if iterator.Total() != expectedTotal { + t.Errorf("总单元格数不正确: 期望%d,实际%d", expectedTotal, iterator.Total()) + } + + // 验证完整遍历 + count := 0 + for iterator.HasNext() { + cellInfo, err := iterator.Next() + if err != nil { + t.Errorf("迭代器错误: %v", err) + break + } + + if cellInfo == nil { + t.Error("单元格信息为空") + continue + } + + if cellInfo.Cell == nil { + t.Error("单元格引用为空") + } + + count++ + } + + if count != expectedTotal { + t.Errorf("实际遍历数量不正确: 期望%d,实际%d", expectedTotal, count) + } + }) + + // 测试2: 重置功能 + t.Run("迭代器重置", func(t *testing.T) { + iterator := table.NewCellIterator() + + // 迭代几个单元格 + iterator.Next() + iterator.Next() + + // 重置并验证 + iterator.Reset() + row, col := iterator.Current() + if row != 0 || col != 0 { + t.Errorf("重置后位置不正确: 期望(0,0),实际(%d,%d)", row, col) + } + }) + + // 测试3: ForEach功能 + t.Run("ForEach遍历", func(t *testing.T) { + visitedCount := 0 + err := table.ForEach(func(row, col int, cell *document.TableCell, text string) error { + visitedCount++ + if cell == nil { + t.Errorf("位置(%d,%d)的单元格为空", row, col) + } + return nil + }) + + if err != nil { + t.Errorf("ForEach执行失败: %v", err) + } + + if visitedCount != 12 { + t.Errorf("ForEach访问数量不正确: 期望12,实际%d", visitedCount) + } + }) + + // 测试4: 按行遍历 + t.Run("按行遍历", func(t *testing.T) { + for row := 0; row < table.GetRowCount(); row++ { + visitedCount := 0 + err := table.ForEachInRow(row, func(col int, cell *document.TableCell, text string) error { + visitedCount++ + return nil + }) + + if err != nil { + t.Errorf("第%d行遍历失败: %v", row, err) + } + + if visitedCount != 3 { + t.Errorf("第%d行单元格数量不正确: 期望3,实际%d", row, visitedCount) + } + } + }) + + // 测试5: 按列遍历 + t.Run("按列遍历", func(t *testing.T) { + for col := 0; col < table.GetColumnCount(); col++ { + visitedCount := 0 + err := table.ForEachInColumn(col, func(row int, cell *document.TableCell, text string) error { + visitedCount++ + return nil + }) + + if err != nil { + t.Errorf("第%d列遍历失败: %v", col, err) + } + + if visitedCount != 4 { + t.Errorf("第%d列单元格数量不正确: 期望4,实际%d", col, visitedCount) + } + } + }) + + // 测试6: 范围获取 + t.Run("单元格范围", func(t *testing.T) { + // 获取数据区域 (1,0) 到 (3,2) + cells, err := table.GetCellRange(1, 0, 3, 2) + if err != nil { + t.Errorf("获取范围失败: %v", err) + } + + expectedCount := 9 // 3行x3列 + if len(cells) != expectedCount { + t.Errorf("范围单元格数量不正确: 期望%d,实际%d", expectedCount, len(cells)) + } + + // 验证范围内容 + if cells[0].Row != 1 || cells[0].Col != 0 { + t.Errorf("范围起始位置不正确: 期望(1,0),实际(%d,%d)", cells[0].Row, cells[0].Col) + } + + lastIndex := len(cells) - 1 + if cells[lastIndex].Row != 3 || cells[lastIndex].Col != 2 { + t.Errorf("范围结束位置不正确: 期望(3,2),实际(%d,%d)", + cells[lastIndex].Row, cells[lastIndex].Col) + } + }) + + // 测试7: 查找功能 + t.Run("单元格查找", func(t *testing.T) { + // 查找包含"张"的单元格 + cells, err := table.FindCellsByText("张", false) + if err != nil { + t.Errorf("查找失败: %v", err) + } + + if len(cells) != 1 { + t.Errorf("查找结果数量不正确: 期望1,实际%d", len(cells)) + } + + if len(cells) > 0 && cells[0].Text != "张三" { + t.Errorf("查找内容不正确: 期望'张三',实际'%s'", cells[0].Text) + } + + // 精确查找 + exactCells, err := table.FindCellsByText("25", true) + if err != nil { + t.Errorf("精确查找失败: %v", err) + } + + if len(exactCells) != 1 { + t.Errorf("精确查找结果数量不正确: 期望1,实际%d", len(exactCells)) + } + }) + + // 测试8: 自定义查找条件 + t.Run("自定义查找", func(t *testing.T) { + // 查找年龄大于26的行 + ageCells, err := table.FindCells(func(row, col int, cell *document.TableCell, text string) bool { + // 检查年龄列(第2列) + if col == 1 && row > 0 { + // 简单检查是否包含数字且可能大于26 + return text == "30" || text == "28" + } + return false + }) + + if err != nil { + t.Errorf("自定义查找失败: %v", err) + } + + if len(ageCells) != 2 { + t.Errorf("自定义查找结果数量不正确: 期望2,实际%d", len(ageCells)) + } + }) + + // 保存测试文档 + outputDir := "../examples/output" + if err := os.MkdirAll(outputDir, 0755); err != nil { + t.Logf("创建输出目录失败: %v", err) + } + + filename := filepath.Join(outputDir, "cell_iterator_integration_test.docx") + if err := doc.Save(filename); err != nil { + t.Errorf("保存测试文档失败: %v", err) + } else { + t.Logf("测试文档已保存到: %s", filename) + } +}