| |
| |
| |
| package parser |
|
|
| import ( |
| "fmt" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| "unicode/utf8" |
|
|
| pdf "github.com/ledongthuc/pdf" |
| ) |
|
|
| |
| const ( |
| PageDigital = "digital" |
| PageScanned = "scanned" |
| PagePartial = "partial" |
| PageBlank = "blank" |
| ) |
|
|
| |
| type PageResult struct { |
| PageNum int `json:"page_num"` |
| Text string `json:"text"` |
| PageType string `json:"page_type"` |
| CharLen int `json:"char_len"` |
| } |
|
|
| |
| type PDFResult struct { |
| SourceFile string `json:"source_file"` |
| TotalPages int `json:"total_pages"` |
| DigitalPages int `json:"digital_pages"` |
| ScannedPages []int `json:"scanned_pages"` |
| Pages []PageResult `json:"pages"` |
| Error string `json:"error,omitempty"` |
| } |
|
|
| |
| func ParsePDF(pdfPath string) (*PDFResult, error) { |
| filename := filepath.Base(pdfPath) |
|
|
| f, reader, err := pdf.Open(pdfPath) |
| if err != nil { |
| return &PDFResult{ |
| SourceFile: filename, |
| Error: fmt.Sprintf("failed to open PDF: %v", err), |
| }, err |
| } |
| defer f.Close() |
|
|
| totalPages := reader.NumPage() |
|
|
| result := &PDFResult{ |
| SourceFile: filename, |
| TotalPages: totalPages, |
| ScannedPages: []int{}, |
| Pages: make([]PageResult, 0, totalPages), |
| } |
|
|
| for i := 1; i <= totalPages; i++ { |
| page := reader.Page(i) |
| if page.V.IsNull() { |
| result.Pages = append(result.Pages, PageResult{ |
| PageNum: i, |
| Text: "", |
| PageType: PageBlank, |
| CharLen: 0, |
| }) |
| continue |
| } |
|
|
| text, err := page.GetPlainText(nil) |
| if err != nil { |
| log.Printf("[parser] page %d text extraction error: %v", i, err) |
| text = "" |
| } |
| text = strings.TrimSpace(text) |
| charLen := utf8.RuneCountInString(text) |
|
|
| pageType := classifyPage(charLen) |
|
|
| result.Pages = append(result.Pages, PageResult{ |
| PageNum: i, |
| Text: text, |
| PageType: pageType, |
| CharLen: charLen, |
| }) |
|
|
| if pageType == PageDigital { |
| result.DigitalPages++ |
| } |
| if pageType == PageScanned || pageType == PagePartial { |
| result.ScannedPages = append(result.ScannedPages, i) |
| } |
| } |
|
|
| return result, nil |
| } |
|
|
| |
| func classifyPage(charLen int) string { |
| switch { |
| case charLen > 200: |
| return PageDigital |
| case charLen >= 50: |
| return PagePartial |
| case charLen > 0: |
| return PageScanned |
| default: |
| return PageBlank |
| } |
| } |
|
|
| |
| func ParseAllPDFs(dirPath string) (map[string]*PDFResult, error) { |
| entries, err := os.ReadDir(dirPath) |
| if err != nil { |
| return nil, fmt.Errorf("failed to read directory %s: %w", dirPath, err) |
| } |
|
|
| results := make(map[string]*PDFResult) |
| for _, entry := range entries { |
| if entry.IsDir() { |
| continue |
| } |
| name := entry.Name() |
| if !strings.HasSuffix(strings.ToLower(name), ".pdf") { |
| continue |
| } |
| fullPath := filepath.Join(dirPath, name) |
| res, err := ParsePDF(fullPath) |
| if err != nil { |
| log.Printf("[parser] error parsing %s: %v", name, err) |
| results[name] = &PDFResult{ |
| SourceFile: name, |
| Error: err.Error(), |
| } |
| continue |
| } |
| results[name] = res |
| } |
|
|
| return results, nil |
| } |
|
|