package gliffy2drawio import ( "bufio" "encoding/json" "os" "path/filepath" "strconv" "strings" "sync" ) const DefaultTranslationPath = "java/gliffy/importer/gliffyTranslation.properties" type StencilTranslator struct { once sync.Once path string translations map[string]string customFile string } func NewStencilTranslator(path string) *StencilTranslator { return &StencilTranslator{path: path} } // WithCustomMapping allows specifying an optional JSON file (map[string]string) for extra translations. func (s *StencilTranslator) WithCustomMapping(jsonPath string) *StencilTranslator { s.customFile = jsonPath return s } func (s *StencilTranslator) Translate(uid, tid string) string { s.once.Do(s.load) if s.translations == nil { return "" } if v, ok := s.translations[uid]; ok { return v } if tid != "" { if v, ok := s.translations[tid]; ok { return v } } return "" } func (s *StencilTranslator) load() { path := s.path if path == "" { path = DefaultTranslationPath } file, err := os.Open(filepath.Clean(path)) if err != nil { return } defer file.Close() s.translations = make(map[string]string) scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } s.translations[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) } // Hard-coded additions for shapes absent from properties. for k, v := range map[string]string{ "gshape.aws_v3.database.Amazon_RDS_PostgreSQL_instance_light_bg": "mxgraph.aws4.rds_postgresql_instance;fillColor=#3334B9", "gstencil.aws_v3.database.Amazon_RDS_PostgreSQL_instance_light_bg": "mxgraph.aws4.rds_postgresql_instance;fillColor=#3334B9", } { if _, ok := s.translations[k]; !ok { s.translations[k] = v } } // Load optional custom JSON mapping file if provided. if s.customFile != "" { if data, err := os.ReadFile(filepath.Clean(s.customFile)); err == nil { var extra map[string]string if err := json.Unmarshal(data, &extra); err == nil { for k, v := range extra { if _, exists := s.translations[k]; !exists { s.translations[k] = v } } } } } } func DashStyleMapping(value string, width int) string { if value == "" { return "" } parts := strings.Split(value, ",") for i, p := range parts { if f, err := strconv.ParseFloat(strings.TrimSpace(p), 64); err == nil { parts[i] = floatToString(f * float64(width)) } } return "dashed=1;fixDash=1;dashPattern=" + strings.Join(parts, " ") + ";" } var lineMapping = map[string]string{ "linear": "", "orthogonal": "edgeStyle=orthogonal;", "quadratic": "curved=1;edgeStyle=orthogonalEdgeStyle;", } func LineMapping(style string) string { return lineMapping[style] } type ArrowStyle struct { Name string Fill bool Size int Spacing int } func (a ArrowStyle) ToString(start bool) string { fill := 0 if a.Fill { fill = 1 } if start { return "startArrow=" + a.Name + ";startFill=" + strconv.Itoa(fill) + ";startSize=" + strconv.Itoa(a.Size) + optionalSpacing("sourcePerimeterSpacing", a.Spacing) + ";" } return "endArrow=" + a.Name + ";endFill=" + strconv.Itoa(fill) + ";endSize=" + strconv.Itoa(a.Size) + optionalSpacing("targetPerimeterSpacing", a.Spacing) + ";" } func optionalSpacing(key string, v int) string { if v == 0 { return "" } return ";" + key + "=" + strconv.Itoa(v) + ";" } var arrowMapping = map[int]ArrowStyle{ 0: {Name: "none", Fill: false, Size: 6}, 1: {Name: "open", Fill: false, Size: 6}, 2: {Name: "block", Fill: true, Size: 6}, 3: {Name: "block", Fill: false, Size: 6}, 4: {Name: "block", Fill: false, Size: 10}, 5: {Name: "diamond", Fill: false, Size: 12}, 6: {Name: "open", Fill: false, Size: 10}, 7: {Name: "diamond", Fill: true, Size: 12}, 8: {Name: "classic", Fill: true, Size: 6}, 9: {Name: "ERzeroToMany", Fill: true, Size: 10}, 10: {Name: "ERoneToMany", Fill: true, Size: 10}, 11: {Name: "ERmandOne", Fill: true, Size: 10}, 12: {Name: "ERone", Fill: true, Size: 10}, 13: {Name: "ERzeroToOne", Fill: true, Size: 10}, 14: {Name: "ERmany", Fill: true, Size: 10}, 15: {Name: "oval", Fill: false, Size: 10, Spacing: 6}, 16: {Name: "dash", Fill: false, Size: 6}, 17: {Name: "block", Fill: true, Size: 6}, 18: {Name: "classic", Fill: true, Size: 6}, 19: {Name: "openAsync", Fill: false, Size: 10}, } func ArrowMapping(id *int) ArrowStyle { if id == nil { return arrowMapping[0] } if a, ok := arrowMapping[*id]; ok { return a } return arrowMapping[0] }