package output import ( "bytes" "encoding/json" "encoding/xml" "fmt" "reflect" "strings" "text/template" ) // TextEncoder converts a struct to plain text based on struct tags, handling nested structs with indentation. func Text(v interface{}) string { var result strings.Builder val := reflect.ValueOf(v) typ := val.Type() // Handle pointer types if val.Kind() == reflect.Ptr { val = val.Elem() typ = val.Type() } processValue(val, typ, 0, &result) return result.String() } func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *strings.Builder) { indent := strings.Repeat("\t", indentLevel) switch val.Kind() { case reflect.Struct: for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) // Get the 'text' tag, fallback to 'xml' tag if not present tag := fieldType.Tag.Get("text") if tag == "" { tag = fieldType.Tag.Get("xml") } // Skip if tag is "-" if tag == "-" { continue } // Use field name if no tag fieldName := fieldType.Name if tag == "" { tag = fieldName } currentTag := tag // Handle slices if field.Kind() == reflect.Slice { for j := 0; j < field.Len(); j++ { item := field.Index(j) itemType := item.Type() if item.Kind() == reflect.Struct || (item.Kind() == reflect.Ptr && item.Elem().Kind() == reflect.Struct) { // Nested struct or pointer to struct in slice if strings.Contains(tag, "omitempty") && isEmptyValue(item) { continue } else { currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "") result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit)) if item.Kind() == reflect.Ptr { processValue(item.Elem(), itemType.Elem(), indentLevel+1, result) } else { processValue(item, itemType, indentLevel+1, result) } } } else { lines := strings.TrimSpace(fmt.Sprintf("%v", item.Interface())) if strings.Contains(tag, "omitempty") && lines == "" { continue } else { currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "") result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines)) } } } } else if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) { // Nested struct or pointer to struct if strings.Contains(tag, "omitempty") && isEmptyValue(field) { continue } else { currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "") result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit)) fieldTypeToPass := fieldType.Type fieldValToPass := field if field.Kind() == reflect.Ptr { fieldTypeToPass = fieldType.Type.Elem() fieldValToPass = field.Elem() } processValue(fieldValToPass, fieldTypeToPass, indentLevel+1, result) } } else { // Basic type lines := strings.TrimSpace(fmt.Sprintf("%v", field.Interface())) if strings.Contains(tag, "omitempty") && lines == "" { continue } else { currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "") result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines)) } } } default: result.WriteString(fmt.Sprintf("%v", val.Interface())) } } func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() case reflect.Struct: for i := 0; i < v.NumField(); i++ { if !isEmptyValue(v.Field(i)) { return false } } return true } return false } func JSON(v interface{}) string { b, _ := json.Marshal(v) return string(b) } func XML(v interface{}) string { b, _ := xml.Marshal(v) return string(b) } func FormatPath(path string, data interface{}) string { var output bytes.Buffer templ := template.Must(template.New("fp").Parse(path)) templ.Execute(&output, data) return output.String() }