199 lines
5.5 KiB
Go
199 lines
5.5 KiB
Go
package pdf
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"tyapi-server/internal/domains/product/entities"
|
||
|
||
"github.com/jung-kurt/gofpdf/v2"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// TableBlock 表格块(用于向后兼容)
|
||
type TableBlock struct {
|
||
BeforeText string
|
||
TableData [][]string
|
||
AfterText string
|
||
}
|
||
|
||
// TableParser 表格解析器
|
||
// 从数据库读取数据并转换为表格格式
|
||
type TableParser struct {
|
||
logger *zap.Logger
|
||
fontManager *FontManager
|
||
databaseReader *DatabaseTableReader
|
||
databaseRenderer *DatabaseTableRenderer
|
||
}
|
||
|
||
// NewTableParser 创建表格解析器
|
||
func NewTableParser(logger *zap.Logger, fontManager *FontManager) *TableParser {
|
||
reader := NewDatabaseTableReader(logger)
|
||
renderer := NewDatabaseTableRenderer(logger, fontManager)
|
||
return &TableParser{
|
||
logger: logger,
|
||
fontManager: fontManager,
|
||
databaseReader: reader,
|
||
databaseRenderer: renderer,
|
||
}
|
||
}
|
||
|
||
// ParseAndRenderTable 从产品文档中解析并渲染表格(支持多个表格,带标题)
|
||
func (tp *TableParser) ParseAndRenderTable(ctx context.Context, pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, fieldType string) error {
|
||
// 从数据库读取表格数据(支持多个表格)
|
||
tableData, err := tp.databaseReader.ReadTableFromDocumentation(ctx, doc, fieldType)
|
||
if err != nil {
|
||
// 如果内容为空,不渲染,也不报错(静默跳过)
|
||
if strings.Contains(err.Error(), "内容为空") {
|
||
tp.logger.Debug("表格内容为空,跳过渲染", zap.String("field_type", fieldType))
|
||
return nil
|
||
}
|
||
return fmt.Errorf("读取表格数据失败: %w", err)
|
||
}
|
||
|
||
// 检查表格数据是否有效
|
||
if tableData == nil || len(tableData.Headers) == 0 {
|
||
tp.logger.Warn("表格数据无效,跳过渲染",
|
||
zap.String("field_type", fieldType),
|
||
zap.Bool("is_nil", tableData == nil))
|
||
return nil
|
||
}
|
||
|
||
|
||
// 渲染表格到PDF
|
||
if err := tp.databaseRenderer.RenderTable(pdf, tableData); err != nil {
|
||
// 错误已返回,不记录日志
|
||
return fmt.Errorf("渲染表格失败: %w", err)
|
||
}
|
||
|
||
|
||
return nil
|
||
}
|
||
|
||
// ParseAndRenderTablesWithTitles 从产品文档中解析并渲染多个表格(带标题)
|
||
func (tp *TableParser) ParseAndRenderTablesWithTitles(ctx context.Context, pdf *gofpdf.Fpdf, doc *entities.ProductDocumentation, fieldType string) error {
|
||
var content string
|
||
|
||
switch fieldType {
|
||
case "request_params":
|
||
content = doc.RequestParams
|
||
case "response_fields":
|
||
content = doc.ResponseFields
|
||
case "response_example":
|
||
content = doc.ResponseExample
|
||
case "error_codes":
|
||
content = doc.ErrorCodes
|
||
default:
|
||
return fmt.Errorf("未知的字段类型: %s", fieldType)
|
||
}
|
||
|
||
if strings.TrimSpace(content) == "" {
|
||
return nil
|
||
}
|
||
|
||
// 解析多个表格(带标题)
|
||
tablesWithTitles, err := tp.databaseReader.parseMarkdownTablesWithTitles(content)
|
||
if err != nil {
|
||
tp.logger.Warn("解析表格失败,回退到单个表格", zap.Error(err))
|
||
// 回退到单个表格渲染
|
||
return tp.ParseAndRenderTable(ctx, pdf, doc, fieldType)
|
||
}
|
||
|
||
if len(tablesWithTitles) == 0 {
|
||
return nil
|
||
}
|
||
|
||
// 分别渲染每个表格,并在表格前显示标题
|
||
_, lineHt := pdf.GetFontSize()
|
||
for i, twt := range tablesWithTitles {
|
||
if twt.Table == nil || len(twt.Table.Headers) == 0 {
|
||
continue
|
||
}
|
||
|
||
// 如果不是第一个表格,添加间距
|
||
if i > 0 {
|
||
pdf.Ln(5)
|
||
}
|
||
|
||
// 如果有标题,显示标题
|
||
if strings.TrimSpace(twt.Title) != "" {
|
||
pdf.SetTextColor(0, 0, 0)
|
||
tp.fontManager.SetFont(pdf, "B", 12)
|
||
_, lineHt = pdf.GetFontSize()
|
||
pdf.CellFormat(0, lineHt, twt.Title, "", 1, "L", false, 0, "")
|
||
pdf.Ln(2)
|
||
}
|
||
|
||
// 渲染表格
|
||
if err := tp.databaseRenderer.RenderTable(pdf, twt.Table); err != nil {
|
||
tp.logger.Warn("渲染表格失败", zap.Error(err), zap.String("title", twt.Title))
|
||
continue
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ParseTableData 仅解析表格数据,不渲染
|
||
func (tp *TableParser) ParseTableData(ctx context.Context, doc *entities.ProductDocumentation, fieldType string) (*TableData, error) {
|
||
return tp.databaseReader.ReadTableFromDocumentation(ctx, doc, fieldType)
|
||
}
|
||
|
||
// ParseMarkdownTable 解析Markdown表格(兼容方法)
|
||
func (tp *TableParser) ParseMarkdownTable(text string) [][]string {
|
||
// 使用数据库读取器的markdown解析功能
|
||
tableData, err := tp.databaseReader.parseMarkdownTable(text)
|
||
if err != nil {
|
||
tp.logger.Warn("解析markdown表格失败", zap.Error(err))
|
||
return nil
|
||
}
|
||
|
||
// 转换为旧格式 [][]string
|
||
result := make([][]string, 0, len(tableData.Rows)+1)
|
||
result = append(result, tableData.Headers)
|
||
result = append(result, tableData.Rows...)
|
||
return result
|
||
}
|
||
|
||
// ExtractAllTables 提取所有表格块(兼容方法)
|
||
func (tp *TableParser) ExtractAllTables(content string) []TableBlock {
|
||
// 使用数据库读取器解析markdown表格
|
||
tableData, err := tp.databaseReader.parseMarkdownTable(content)
|
||
if err != nil {
|
||
return []TableBlock{}
|
||
}
|
||
|
||
// 转换为TableBlock格式
|
||
if len(tableData.Headers) > 0 {
|
||
rows := make([][]string, 0, len(tableData.Rows)+1)
|
||
rows = append(rows, tableData.Headers)
|
||
rows = append(rows, tableData.Rows...)
|
||
return []TableBlock{
|
||
{
|
||
BeforeText: "",
|
||
TableData: rows,
|
||
AfterText: "",
|
||
},
|
||
}
|
||
}
|
||
return []TableBlock{}
|
||
}
|
||
|
||
// IsValidTable 验证表格是否有效(兼容方法)
|
||
func (tp *TableParser) IsValidTable(tableData [][]string) bool {
|
||
if len(tableData) == 0 {
|
||
return false
|
||
}
|
||
if len(tableData[0]) == 0 {
|
||
return false
|
||
}
|
||
// 检查表头是否有有效内容
|
||
for _, cell := range tableData[0] {
|
||
if strings.TrimSpace(cell) != "" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|