CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
// Package batchsplit teilt einen Scan-Stapel (Mehrseiten-PDF) an Trennseiten
|
|
// in Einzeldokumente (INT-06). Die Erkennung der Trennseite steckt hinter
|
|
// Detector, damit Leerseiten- und Barcode-Erkennung (INT-10) ohne Umbau
|
|
// des Aufteilens angeschlossen werden koennen.
|
|
package batchsplit
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/pdfcpu/pdfcpu/pkg/api"
|
|
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
|
)
|
|
|
|
const DefaultMarker = "NEXARCH-TRENNSEITE"
|
|
|
|
var ErrOnlySeparators = errors.New("batchsplit: stapel enthaelt nur trennseiten")
|
|
|
|
type Detector interface {
|
|
IsSeparator(pageContent []byte) bool
|
|
}
|
|
|
|
// MarkerDetector erkennt eine Trennseite an einem Textmarker im Seiteninhalt.
|
|
type MarkerDetector struct{ Marker string }
|
|
|
|
func (d MarkerDetector) IsSeparator(pageContent []byte) bool {
|
|
m := d.Marker
|
|
if m == "" {
|
|
m = DefaultMarker
|
|
}
|
|
return bytes.Contains(pageContent, []byte("("+m+")"))
|
|
}
|
|
|
|
var pagePattern = regexp.MustCompile(`_Content_page_(\d+)\.txt$`)
|
|
|
|
// Split liefert die Einzeldokumente in Seitenreihenfolge; Trennseiten
|
|
// selbst entfallen. Ohne Trennseite kommt der Eingang unveraendert als
|
|
// einziges Element zurueck.
|
|
func Split(content []byte, d Detector) ([][]byte, error) {
|
|
dir, err := os.MkdirTemp("", "batchsplit-*")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("batchsplit: temp-verzeichnis anlegen: %w", err)
|
|
}
|
|
defer func() { _ = os.RemoveAll(dir) }()
|
|
|
|
in := filepath.Join(dir, "in.pdf")
|
|
if err := os.WriteFile(in, content, 0o600); err != nil {
|
|
return nil, fmt.Errorf("batchsplit: quelldatei schreiben: %w", err)
|
|
}
|
|
conf := model.NewDefaultConfiguration()
|
|
pages, err := api.PageCountFile(in)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("batchsplit: seitenzahl ermitteln: %w", err)
|
|
}
|
|
contentDir := filepath.Join(dir, "content")
|
|
if err := os.Mkdir(contentDir, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := api.ExtractContentFile(in, contentDir, nil, conf); err != nil {
|
|
return nil, fmt.Errorf("batchsplit: seiteninhalt lesen: %w", err)
|
|
}
|
|
files, err := filepath.Glob(filepath.Join(contentDir, "*_Content_page_*.txt"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
separator := make([]bool, pages+1)
|
|
for _, f := range files {
|
|
m := pagePattern.FindStringSubmatch(f)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
n, _ := strconv.Atoi(m[1])
|
|
if n < 1 || n > pages {
|
|
continue
|
|
}
|
|
b, err := os.ReadFile(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("batchsplit: seiteninhalt %d lesen: %w", n, err)
|
|
}
|
|
separator[n] = d.IsSeparator(b)
|
|
}
|
|
|
|
ranges := runs(separator, pages)
|
|
anySeparator := false
|
|
for i := 1; i <= pages; i++ {
|
|
anySeparator = anySeparator || separator[i]
|
|
}
|
|
if !anySeparator {
|
|
return [][]byte{content}, nil
|
|
}
|
|
if len(ranges) == 0 {
|
|
return nil, ErrOnlySeparators
|
|
}
|
|
|
|
out := make([][]byte, 0, len(ranges))
|
|
for i, r := range ranges {
|
|
dst := filepath.Join(dir, fmt.Sprintf("out-%d.pdf", i))
|
|
if err := api.TrimFile(in, dst, []string{fmt.Sprintf("%d-%d", r[0], r[1])}, conf); err != nil {
|
|
return nil, fmt.Errorf("batchsplit: teildokument %d-%d erzeugen: %w", r[0], r[1], err)
|
|
}
|
|
b, err := os.ReadFile(dst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, b)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func runs(separator []bool, pages int) [][2]int {
|
|
var out [][2]int
|
|
start := 0
|
|
for p := 1; p <= pages; p++ {
|
|
if separator[p] {
|
|
if start != 0 {
|
|
out = append(out, [2]int{start, p - 1})
|
|
start = 0
|
|
}
|
|
continue
|
|
}
|
|
if start == 0 {
|
|
start = p
|
|
}
|
|
}
|
|
if start != 0 {
|
|
out = append(out, [2]int{start, pages})
|
|
}
|
|
return out
|
|
}
|