package uiaselector import ( "bytes" "encoding/json" "fmt" "os" "strings" ) type DumpResponse struct { OK bool `json:"ok"` Data *DumpData `json:"data"` Error any `json:"error"` } type DumpData struct { Root *Node `json:"root"` } type Node struct { Name string `json:"name"` ControlType string `json:"control_type"` AutomationID string `json:"automation_id"` ClassName string `json:"class_name"` FrameworkID string `json:"framework_id"` IsEnabled *bool `json:"is_enabled"` IsOffscreen *bool `json:"is_offscreen"` Bounds any `json:"bounds"` Children []Node `json:"children"` } func LoadDumpFile(path string) (*Node, error) { content, err := os.ReadFile(path) if err != nil { return nil, err } content = normalizeDumpJSON(content) var response DumpResponse if err := json.Unmarshal(content, &response); err != nil { return nil, err } if !response.OK { return nil, fmt.Errorf("dump response ok=false: %v", response.Error) } if response.Data == nil || response.Data.Root == nil { return nil, fmt.Errorf("dump response has no data.root") } return response.Data.Root, nil } func CountNodes(root *Node) int { if root == nil { return 0 } count := 1 for i := range root.Children { count += CountNodes(&root.Children[i]) } return count } func normalizeDumpJSON(content []byte) []byte { content = bytes.TrimPrefix(content, []byte{0xEF, 0xBB, 0xBF}) source := string(content) var out strings.Builder out.Grow(len(source)) inString := false escaped := false for i := 0; i < len(source); { ch := source[i] if inString { out.WriteByte(ch) if escaped { escaped = false } else if ch == '\\' { escaped = true } else if ch == '"' { inString = false } i++ continue } if ch == '"' { inString = true out.WriteByte(ch) i++ continue } switch { case strings.HasPrefix(source[i:], "-Infinity"): out.WriteString("null") i += len("-Infinity") case strings.HasPrefix(source[i:], "Infinity"): out.WriteString("null") i += len("Infinity") case strings.HasPrefix(source[i:], "NaN"): out.WriteString("null") i += len("NaN") default: out.WriteByte(ch) i++ } } return []byte(out.String()) }