mirror of
				https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
				synced 2025-10-30 17:45:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			463 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			463 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package viewmodels
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| 
 | |
| 	"github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels"
 | |
| )
 | |
| 
 | |
| type PiecePageEntry struct {
 | |
| 	PageNumber     int
 | |
| 	IssueYear      int
 | |
| 	IssueNumber    int
 | |
| 	ImagePath      string
 | |
| 	IsContinuation bool
 | |
| 	IssueContext   string // "1764 Nr. 37" for display
 | |
| 	Available      bool
 | |
| 	OtherPieces    []IndividualPieceByIssue // Other pieces on the same page
 | |
| 	PartNumber     int                      // Which part of the piece (1, 2, 3, etc.)
 | |
| }
 | |
| 
 | |
| type PieceImages struct {
 | |
| 	AllPages  []IssuePage // Sequential list of all pages across issues
 | |
| 	HasImages bool
 | |
| }
 | |
| 
 | |
| type PieceVM struct {
 | |
| 	xmlmodels.Piece
 | |
| 	AllIssueRefs    []xmlmodels.IssueRef   // All issues containing this piece
 | |
| 	AllPages        []PiecePageEntry       // Flattened chronological page list
 | |
| 	ContinuousPages IndividualPiecesByPage // For template compatibility
 | |
| 	Images          PieceImages            // All page images across issues
 | |
| 	TotalPageCount  int
 | |
| 	Title           string   // Extracted piece title
 | |
| 	MainCategory    string   // Primary category
 | |
| 	IssueContexts   []string // List of issue contexts for display
 | |
| }
 | |
| 
 | |
| func NewPieceView(piece xmlmodels.Piece, lib *xmlmodels.Library) (*PieceVM, error) {
 | |
| 	pvm := &PieceVM{
 | |
| 		Piece:         piece,
 | |
| 		AllIssueRefs:  piece.IssueRefs,
 | |
| 		AllPages:      []PiecePageEntry{},
 | |
| 		IssueContexts: []string{},
 | |
| 	}
 | |
| 
 | |
| 	// DEBUG: Log piece details
 | |
| 	fmt.Printf("DEBUG PieceView: Creating view for piece ID=%s\n", piece.Identifier.ID)
 | |
| 	fmt.Printf("DEBUG PieceView: Piece has %d IssueRefs\n", len(piece.IssueRefs))
 | |
| 	for i, ref := range piece.IssueRefs {
 | |
| 		fmt.Printf("DEBUG PieceView: IssueRef[%d]: Year=%d, Nr=%d, Von=%d, Bis=%d, Beilage=%d\n", i, ref.When.Year, ref.Nr, ref.Von, ref.Bis, ref.Beilage)
 | |
| 	}
 | |
| 
 | |
| 	// Extract title from piece
 | |
| 	if len(piece.Title) > 0 {
 | |
| 		pvm.Title = piece.Title[0]
 | |
| 	}
 | |
| 
 | |
| 	// Extract main category
 | |
| 	if len(piece.CategoryRefs) > 0 {
 | |
| 		pvm.MainCategory = piece.CategoryRefs[0].Ref
 | |
| 	}
 | |
| 
 | |
| 	// Sort issue refs chronologically
 | |
| 	sort.Slice(pvm.AllIssueRefs, func(i, j int) bool {
 | |
| 		refA := pvm.AllIssueRefs[i]
 | |
| 		refB := pvm.AllIssueRefs[j]
 | |
| 
 | |
| 		if refA.When.Year != refB.When.Year {
 | |
| 			return refA.When.Year < refB.When.Year
 | |
| 		}
 | |
| 		return refA.Nr < refB.Nr
 | |
| 	})
 | |
| 
 | |
| 	// Process each issue reference
 | |
| 	for partIndex, issueRef := range pvm.AllIssueRefs {
 | |
| 		issueContext := fmt.Sprintf("%d Nr. %d", issueRef.When.Year, issueRef.Nr)
 | |
| 		pvm.IssueContexts = append(pvm.IssueContexts, issueContext)
 | |
| 
 | |
| 		// Add pages for this issue reference
 | |
| 		// Handle case where Bis=0 (should be treated as single page at Von)
 | |
| 		bis := issueRef.Bis
 | |
| 		if bis == 0 {
 | |
| 			bis = issueRef.Von
 | |
| 		}
 | |
| 
 | |
| 		for pageNum := issueRef.Von; pageNum <= bis; pageNum++ {
 | |
| 			pageEntry := PiecePageEntry{
 | |
| 				PageNumber:     pageNum,
 | |
| 				IssueYear:      issueRef.When.Year,
 | |
| 				IssueNumber:    issueRef.Nr,
 | |
| 				IsContinuation: pageNum > issueRef.Von || partIndex > 0,
 | |
| 				IssueContext:   issueContext,
 | |
| 				Available:      true,                       // Will be updated when we load images
 | |
| 				OtherPieces:    []IndividualPieceByIssue{}, // Will be populated later
 | |
| 				PartNumber:     partIndex + 1,              // Part number (1-based)
 | |
| 			}
 | |
| 
 | |
| 			// Get actual image path from registry
 | |
| 			pageEntry.ImagePath = getImagePathFromRegistryWithBeilage(issueRef.When.Year, issueRef.Nr, pageNum, issueRef.Beilage > 0)
 | |
| 
 | |
| 			pvm.AllPages = append(pvm.AllPages, pageEntry)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	pvm.TotalPageCount = len(pvm.AllPages)
 | |
| 
 | |
| 	// DEBUG: Log final counts
 | |
| 	fmt.Printf("DEBUG PieceView: Final counts - %d issue contexts, %d total pages\n", len(pvm.IssueContexts), pvm.TotalPageCount)
 | |
| 	fmt.Printf("DEBUG PieceView: Issue contexts: %v\n", pvm.IssueContexts)
 | |
| 
 | |
| 	// Load images and update availability
 | |
| 	if err := pvm.loadImages(); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to load images: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Create template-compatible structure
 | |
| 	if err := pvm.createContinuousPages(lib); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to create continuous pages: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// Populate other pieces on each page
 | |
| 	if err := pvm.populateOtherPieces(lib); err != nil {
 | |
| 		return nil, fmt.Errorf("failed to populate other pieces: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return pvm, nil
 | |
| }
 | |
| 
 | |
| // loadImages loads and validates all page images for the piece
 | |
| func (pvm *PieceVM) loadImages() error {
 | |
| 	// Initialize image registry if needed
 | |
| 	if err := initImageRegistry(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	issuePages := []IssuePage{}
 | |
| 	hasAnyImages := false
 | |
| 
 | |
| 	for i, pageEntry := range pvm.AllPages {
 | |
| 		// Create IssuePage for template compatibility
 | |
| 		issuePage := IssuePage{
 | |
| 			PageNumber: pageEntry.PageNumber,
 | |
| 			ImagePath:  pageEntry.ImagePath,
 | |
| 			Available:  true,     // Assume available for now
 | |
| 			PageIcon:   "single", // Simplified icon for piece view
 | |
| 		}
 | |
| 
 | |
| 		// Check if image actually exists using the registry
 | |
| 		key := fmt.Sprintf("%d-%d", pageEntry.IssueYear, pageEntry.PageNumber)
 | |
| 		if imageRegistry != nil {
 | |
| 			if _, exists := imageRegistry.ByYearPage[key]; exists {
 | |
| 				hasAnyImages = true
 | |
| 			} else {
 | |
| 				issuePage.Available = false
 | |
| 				pvm.AllPages[i].Available = false
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		issuePages = append(issuePages, issuePage)
 | |
| 	}
 | |
| 
 | |
| 	pvm.Images = PieceImages{
 | |
| 		AllPages:  issuePages,
 | |
| 		HasImages: hasAnyImages,
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // createContinuousPages creates the template-compatible IndividualPiecesByPage structure
 | |
| func (pvm *PieceVM) createContinuousPages(lib *xmlmodels.Library) error {
 | |
| 	individual := IndividualPiecesByPage{
 | |
| 		Items: make(map[int][]IndividualPieceByIssue),
 | |
| 		Pages: []int{},
 | |
| 	}
 | |
| 
 | |
| 	// Create a virtual piece entry for each page
 | |
| 	for _, pageEntry := range pvm.AllPages {
 | |
| 		// Create IssueRef for this specific page
 | |
| 		issueRef := xmlmodels.IssueRef{
 | |
| 			Nr:  pageEntry.IssueNumber,
 | |
| 			Von: pageEntry.PageNumber,
 | |
| 			Bis: pageEntry.PageNumber,
 | |
| 		}
 | |
| 		issueRef.When.Year = pageEntry.IssueYear
 | |
| 
 | |
| 		// Create PieceByIssue
 | |
| 		pieceByIssue := PieceByIssue{
 | |
| 			Piece:          pvm.Piece,
 | |
| 			Reference:      issueRef,
 | |
| 			IsContinuation: pageEntry.IsContinuation,
 | |
| 		}
 | |
| 
 | |
| 		// Create IndividualPieceByIssue
 | |
| 		individualPiece := IndividualPieceByIssue{
 | |
| 			PieceByIssue: pieceByIssue,
 | |
| 			IssueRefs:    pvm.AllIssueRefs,
 | |
| 			PageIcon:     "single", // Simplified icon for piece view
 | |
| 		}
 | |
| 
 | |
| 		// Add to the page map
 | |
| 		if individual.Items[pageEntry.PageNumber] == nil {
 | |
| 			individual.Items[pageEntry.PageNumber] = []IndividualPieceByIssue{}
 | |
| 			individual.Pages = append(individual.Pages, pageEntry.PageNumber)
 | |
| 		}
 | |
| 		individual.Items[pageEntry.PageNumber] = append(individual.Items[pageEntry.PageNumber], individualPiece)
 | |
| 	}
 | |
| 
 | |
| 	// Sort pages
 | |
| 	sort.Ints(individual.Pages)
 | |
| 
 | |
| 	pvm.ContinuousPages = individual
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // getImagePathFromRegistry gets the actual image path from the image registry
 | |
| func getImagePathFromRegistry(year, page int) string {
 | |
| 	// Initialize registry if needed
 | |
| 	if err := initImageRegistry(); err != nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	// Look up the image by year and page
 | |
| 	key := fmt.Sprintf("%d-%d", year, page)
 | |
| 	if imageFile, exists := imageRegistry.ByYearPage[key]; exists {
 | |
| 		return imageFile.Path
 | |
| 	}
 | |
| 
 | |
| 	// Fallback: generate a default path (though this probably won't exist)
 | |
| 	return fmt.Sprintf("/static/pictures/%d/seite_%d.jpg", year, page)
 | |
| }
 | |
| 
 | |
| // getImagePathFromRegistryWithBeilage gets the actual image path, handling both regular and Beilage pages
 | |
| func getImagePathFromRegistryWithBeilage(year, issue, page int, isBeilage bool) string {
 | |
| 	// Initialize registry if needed
 | |
| 	if err := initImageRegistry(); err != nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	// For regular pages, use the old method
 | |
| 	if !isBeilage {
 | |
| 		key := fmt.Sprintf("%d-%d", year, page)
 | |
| 		if imageFile, exists := imageRegistry.ByYearPage[key]; exists {
 | |
| 			return imageFile.Path
 | |
| 		}
 | |
| 		// Fallback for regular pages
 | |
| 		return fmt.Sprintf("/static/pictures/%d/seite_%d.jpg", year, page)
 | |
| 	}
 | |
| 
 | |
| 	// For Beilage pages, search through all files for this year-issue
 | |
| 	yearIssueKey := fmt.Sprintf("%d-%d", year, issue)
 | |
| 	if issueFiles, exists := imageRegistry.ByYearIssue[yearIssueKey]; exists {
 | |
| 		for _, file := range issueFiles {
 | |
| 			if file.IsBeilage && file.Page == page {
 | |
| 				fmt.Printf("DEBUG: Found Beilage image for year=%d, issue=%d, page=%d: %s\n", year, issue, page, file.Path)
 | |
| 				return file.Path
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Fallback for Beilage pages
 | |
| 	fmt.Printf("DEBUG: No Beilage image found for year=%d, issue=%d, page=%d\n", year, issue, page)
 | |
| 	return fmt.Sprintf("/static/pictures/%d/%db-beilage-seite_%d.jpg", year, issue, page)
 | |
| }
 | |
| 
 | |
| // populateOtherPieces finds and populates other pieces that appear on the same pages as this piece
 | |
| func (pvm *PieceVM) populateOtherPieces(lib *xmlmodels.Library) error {
 | |
| 	fmt.Printf("DEBUG: Starting populateOtherPieces for piece %s\n", pvm.Piece.Identifier.ID)
 | |
| 	for i, pageEntry := range pvm.AllPages {
 | |
| 		fmt.Printf("DEBUG: Processing page %d from issue %d/%d\n", pageEntry.PageNumber, pageEntry.IssueYear, pageEntry.IssueNumber)
 | |
| 
 | |
| 		// Find the issue this page belongs to
 | |
| 		var issue *xmlmodels.Issue
 | |
| 		lib.Issues.Lock()
 | |
| 		for _, iss := range lib.Issues.Array {
 | |
| 			if iss.Datum.When.Year == pageEntry.IssueYear && iss.Number.No == pageEntry.IssueNumber {
 | |
| 				issue = &iss
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		lib.Issues.Unlock()
 | |
| 
 | |
| 		if issue == nil {
 | |
| 			fmt.Printf("DEBUG: Issue not found for %d/%d\n", pageEntry.IssueYear, pageEntry.IssueNumber)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Get all pieces for this issue using the same approach as the ausgabe view
 | |
| 		piecesForIssue, _, err := PiecesForIssue(lib, *issue)
 | |
| 		if err != nil {
 | |
| 			fmt.Printf("DEBUG: Error getting pieces for issue: %v\n", err)
 | |
| 			continue
 | |
| 		}
 | |
| 		fmt.Printf("DEBUG: Found %d total pieces for issue %d/%d\n", len(piecesForIssue.Pages), pageEntry.IssueYear, pageEntry.IssueNumber)
 | |
| 		fmt.Printf("DEBUG: PiecesForIssue.Pages = %v\n", piecesForIssue.Pages)
 | |
| 
 | |
| 		// Create IndividualPiecesByPage using the same function as ausgabe view
 | |
| 		individualPieces := CreateIndividualPagesWithMetadata(piecesForIssue, lib)
 | |
| 		fmt.Printf("DEBUG: CreateIndividualPagesWithMetadata created %d pages with pieces\n", len(individualPieces.Pages))
 | |
| 		fmt.Printf("DEBUG: Pages with pieces: %v\n", individualPieces.Pages)
 | |
| 
 | |
| 		// DEBUG: Show what pages are available in the map
 | |
| 		if pageEntry.PageNumber == 113 {
 | |
| 			fmt.Printf("DEBUG: Available pages in individualPieces.Items: %v\n", individualPieces.Pages)
 | |
| 			for pageNum := range individualPieces.Items {
 | |
| 				fmt.Printf("DEBUG: Page %d has %d pieces\n", pageNum, len(individualPieces.Items[pageNum]))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Get pieces that appear on this specific page
 | |
| 		if individualPiecesOnPage, exists := individualPieces.Items[pageEntry.PageNumber]; exists {
 | |
| 			fmt.Printf("DEBUG: Found %d pieces on page %d\n", len(individualPiecesOnPage), pageEntry.PageNumber)
 | |
| 			otherPieces := []IndividualPieceByIssue{}
 | |
| 
 | |
| 			for _, individualPiece := range individualPiecesOnPage {
 | |
| 				// Skip the current piece itself using comprehensive comparison
 | |
| 				if IsSamePiece(individualPiece.PieceByIssue.Piece, pvm.Piece) {
 | |
| 					fmt.Printf("DEBUG: Skipping current piece (comprehensive field match)\n")
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				// Debug piece information
 | |
| 				pieceTitle := "no title"
 | |
| 				if len(individualPiece.PieceByIssue.Piece.Title) > 0 {
 | |
| 					pieceTitle = individualPiece.PieceByIssue.Piece.Title[0]
 | |
| 				} else if len(individualPiece.PieceByIssue.Piece.Incipit) > 0 {
 | |
| 					pieceTitle = individualPiece.PieceByIssue.Piece.Incipit[0]
 | |
| 				}
 | |
| 
 | |
| 				fmt.Printf("DEBUG: Adding other piece title='%s'\n", pieceTitle)
 | |
| 				otherPieces = append(otherPieces, individualPiece)
 | |
| 			}
 | |
| 
 | |
| 			fmt.Printf("DEBUG: Found %d other pieces on page %d\n", len(otherPieces), pageEntry.PageNumber)
 | |
| 			pvm.AllPages[i].OtherPieces = otherPieces
 | |
| 		} else {
 | |
| 			fmt.Printf("DEBUG: No pieces found on page %d\n", pageEntry.PageNumber)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // IsSamePiece compares two pieces using comprehensive field-by-field heuristics
 | |
| // This is a universal method that can be used anywhere in the codebase
 | |
| func IsSamePiece(piece1, piece2 xmlmodels.Piece) bool {
 | |
| 	// 1. Compare titles (all variants)
 | |
| 	if !equalStringSlices(piece1.Title, piece2.Title) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 2. Compare incipits (all variants)
 | |
| 	if !equalStringSlices(piece1.Incipit, piece2.Incipit) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 3. Compare issue references (must have identical coverage)
 | |
| 	if !equalIssueRefs(piece1.IssueRefs, piece2.IssueRefs) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 4. Compare category references
 | |
| 	if !equalCategoryRefs(piece1.CategoryRefs, piece2.CategoryRefs) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 5. Compare agent references (authors, etc.)
 | |
| 	if !equalAgentRefs(piece1.AgentRefs, piece2.AgentRefs) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 6. Compare work references
 | |
| 	if !equalWorkRefs(piece1.WorkRefs, piece2.WorkRefs) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// 7. Compare place references
 | |
| 	if !equalPlaceRefs(piece1.PlaceRefs, piece2.PlaceRefs) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // Helper functions for comparing slices and references
 | |
| 
 | |
| func equalStringSlices(a, b []string) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i] != b[i] {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func equalIssueRefs(a, b []xmlmodels.IssueRef) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i].When.Year != b[i].When.Year ||
 | |
| 			a[i].Nr != b[i].Nr ||
 | |
| 			a[i].Von != b[i].Von ||
 | |
| 			a[i].Bis != b[i].Bis {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func equalCategoryRefs(a, b []xmlmodels.CategoryRef) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i].Ref != b[i].Ref {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func equalAgentRefs(a, b []xmlmodels.AgentRef) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i].Ref != b[i].Ref || a[i].Category != b[i].Category {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func equalWorkRefs(a, b []xmlmodels.WorkRef) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i].Ref != b[i].Ref {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func equalPlaceRefs(a, b []xmlmodels.PlaceRef) bool {
 | |
| 	if len(a) != len(b) {
 | |
| 		return false
 | |
| 	}
 | |
| 	for i := range a {
 | |
| 		if a[i].Ref != b[i].Ref {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | 
