@@ -2,7 +2,10 @@ package kfx
22
33import (
44 "context"
5+ "crypto/rand"
6+ "crypto/sha256"
57 "fmt"
8+ "math/big"
69 "os"
710 "sort"
811 "strings"
@@ -17,6 +20,7 @@ import (
1720 "fbc/convert/kfx/ionutil"
1821 "fbc/convert/kfx/model"
1922 "fbc/convert/kfx/symbols"
23+ "fbc/misc"
2024)
2125
2226// Generate creates the KFX output file.
@@ -31,7 +35,7 @@ func Generate(ctx context.Context, c *content.Content, outputPath string, cfg *c
3135
3236 log .Info ("Generating KFX" , zap .String ("output" , outputPath ))
3337
34- containerID := "CR!" + c .Book .Description .DocumentInfo .ID
38+ containerID := "CR!" + hashTo28Alphanumeric ( c .Book .Description .DocumentInfo .ID )
3539
3640 // Minimal content fragment.
3741 type contentFragment struct {
@@ -611,7 +615,7 @@ func Generate(ctx context.Context, c *content.Content, outputPath string, cfg *c
611615
612616 data , err := container .Pack (& container.PackParams {
613617 ContainerID : containerID ,
614- KfxgenApplicationVersion : "kfxlib-20251012" ,
618+ KfxgenApplicationVersion : misc . GetAppName () + "-" + misc . GetVersion () ,
615619 KfxgenPackageVersion : "" ,
616620 DocumentSymbols : prolog .DocSymbols ,
617621 FormatCapabilities : builders .BuildFormatCapabilities (),
@@ -628,3 +632,47 @@ func Generate(ctx context.Context, c *content.Content, outputPath string, cfg *c
628632
629633 return nil
630634}
635+
636+ const charsetCR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
637+
638+ // randomAlphanumeric28 generates a random string of exactly 28 bytes
639+ // containing only uppercase Latin letters (A-Z) and digits (0-9).
640+ func randomAlphanumeric28 () string {
641+ const length = 28
642+
643+ result := make ([]byte , length )
644+ charsetLen := big .NewInt (int64 (len (charsetCR )))
645+
646+ for i := range length {
647+ num , err := rand .Int (rand .Reader , charsetLen )
648+ if err != nil {
649+ panic (err )
650+ }
651+ result [i ] = charsetCR [num .Int64 ()]
652+ }
653+ return string (result )
654+ }
655+
656+ // hashTo28Alphanumeric hashes the input string to produce exactly 28 bytes
657+ // containing only uppercase Latin letters (A-Z) and digits (0-9). It does this
658+ // deterministically - same input string will always produce the same output
659+ // string. If the input is empty, a random string is generated instead.
660+ func hashTo28Alphanumeric (input string ) string {
661+ if input == "" {
662+ return randomAlphanumeric28 ()
663+ }
664+
665+ // Use SHA-256 to hash the input
666+ hash := sha256 .Sum256 ([]byte (input ))
667+
668+ // Map hash bytes to full alphanumeric charset (A-Z, 0-9)
669+ result := make ([]byte , 28 )
670+
671+ for i := range 28 {
672+ // Use hash bytes to deterministically select from charset
673+ idx := hash [i ] % byte (len (charsetCR ))
674+ result [i ] = charsetCR [idx ]
675+ }
676+
677+ return string (result )
678+ }
0 commit comments