package documenttext import ( "context" "fmt" "log/slog" "strings" documenttypes "queryorchestration/internal/document/types" "github.com/aws/aws-sdk-go-v2/service/textract" "github.com/aws/aws-sdk-go-v2/service/textract/types" "github.com/google/uuid" ) func (s *Service) getPageText(ctx context.Context, document documenttypes.File, index int) (string, error) { res, err := s.GetBasePage(ctx, document, index) if err != nil { return "", err } var pageBlock *types.Block for _, block := range res.Blocks { if block.BlockType == types.BlockTypePage { pageBlock = &block break } } if pageBlock == nil { return "", fmt.Errorf("no page block found for index: %d", index) } blockMap := s.getBlockMap(res.Blocks) lines, tables, tableBlockMap, err := s.getPageElements(ctx, document, index, *pageBlock, blockMap) if err != nil { return "", err } return s.buildPageText(ctx, lines, blockMap, index, tables, tableBlockMap) } func (s *Service) getPageElements(ctx context.Context, document documenttypes.File, index int, pageBlock types.Block, blockMap map[uuid.UUID]types.Block) ([]uuid.UUID, []uuid.UUID, map[uuid.UUID]types.Block, error) { directChildren := s.getDirectChildren(pageBlock, blockMap) hasTable := false for _, id := range directChildren { if blockMap[id].BlockType == types.BlockTypeLayoutTable { hasTable = true } } var tables []uuid.UUID var tableBlockMap map[uuid.UUID]types.Block if hasTable { ids, blockMap, err := s.getTables(ctx, document, index) if err != nil { return nil, nil, nil, err } tables = ids tableBlockMap = blockMap } return directChildren, tables, tableBlockMap, nil } type buildParams struct { numOfSignatures int currentTable int } func (s *Service) buildPageText(ctx context.Context, lines []uuid.UUID, blockMap map[uuid.UUID]types.Block, index int, tables []uuid.UUID, tableBlockMap map[uuid.UUID]types.Block) (string, error) { var builder strings.Builder params := buildParams{ numOfSignatures: 0, currentTable: 0, } for _, id := range lines { err := s.addComponentText(ctx, blockMap[id], blockMap, tables, tableBlockMap, ¶ms, &builder) if err != nil { return "", err } } for params.currentTable < len(tables) { id := tables[params.currentTable] err := s.addComponentText(ctx, tableBlockMap[id], blockMap, tables, tableBlockMap, ¶ms, &builder) if err != nil { return "", err } } if params.numOfSignatures > 0 { builder.WriteString(fmt.Sprintf("\nThis page has %d signature.\n", params.numOfSignatures)) } return builder.String(), nil } func (s *Service) addComponentText(ctx context.Context, block types.Block, blockMap map[uuid.UUID]types.Block, tables []uuid.UUID, tableBlockMap map[uuid.UUID]types.Block, params *buildParams, builder *strings.Builder) error { switch block.BlockType { case types.BlockTypeLayoutText, types.BlockTypeLayoutTitle, types.BlockTypeLayoutHeader, types.BlockTypeLayoutFooter, types.BlockTypeLayoutSectionHeader, types.BlockTypeLayoutPageNumber, types.BlockTypeLayoutList, types.BlockTypeLayoutFigure, types.BlockTypeLayoutKeyValue: for _, id := range s.getChildIds(block) { err := s.addComponentText(ctx, blockMap[id], blockMap, tables, tableBlockMap, params, builder) if err != nil { return err } } case types.BlockTypeLine: _, err := builder.WriteString(fmt.Sprintf("%s\n", *block.Text)) if err != nil { slog.Error("unable to write string", "error", err) return err } case types.BlockTypeWord: _, err := builder.WriteString(fmt.Sprintf(" %s", *block.Text)) if err != nil { slog.Error("unable to write string", "error", err) return err } case types.BlockTypeSignature: params.numOfSignatures++ case types.BlockTypeLayoutTable, types.BlockTypeTable: if block.BlockType == types.BlockTypeLayoutTable && len(tables) <= params.currentTable { for _, child := range s.getChildIds(block) { err := s.addComponentText(ctx, blockMap[child], blockMap, tables, tableBlockMap, params, builder) if err != nil { return err } } params.currentTable++ break } tableId := tables[params.currentTable] params.currentTable++ tableBlock := tableBlockMap[tableId] err := s.addTable(ctx, tableBlock, tableBlockMap, builder) if err != nil { return err } default: slog.Debug("uncaught component", "type", block.BlockType) } return nil } func (s *Service) GetBasePage(ctx context.Context, doc documenttypes.File, index int) (textract.AnalyzeDocumentOutput, error) { pageBytes, err := doc.GetPageAsPNG(ctx, index) if err != nil { return textract.AnalyzeDocumentOutput{}, err } res, err := s.cfg.GetTextractClient().AnalyzeDocument(ctx, &textract.AnalyzeDocumentInput{ Document: &types.Document{ Bytes: pageBytes, }, FeatureTypes: []types.FeatureType{ types.FeatureTypeLayout, types.FeatureTypeSignatures, }, }) if err != nil { return textract.AnalyzeDocumentOutput{}, err } return *res, err }