package uiaselector import "strings" type MatchResult struct { OK bool `json:"ok"` SelectorID string `json:"selector_id"` Matched bool `json:"matched"` MatchCount int `json:"match_count"` Matches []NodeMatch `json:"matches"` Warnings []string `json:"warnings"` Error MatchError `json:"error,omitempty"` } type MatchError struct { Code string `json:"code"` Message string `json:"message"` } type NodeMatch struct { Path string `json:"path"` ControlType string `json:"control_type"` AutomationID string `json:"automation_id"` ClassName string `json:"class_name"` FrameworkID string `json:"framework_id"` HasName bool `json:"has_name"` NameLength int `json:"name_length"` Name string `json:"-"` } type nodeRef struct { path string node *Node } func MatchSelector(root *Node, catalog []Selector, selectorID string) MatchResult { selector, ok := findSelector(catalog, selectorID) if !ok { return MatchResult{OK: false, SelectorID: selectorID, Error: MatchError{Code: "UNKNOWN_SELECTOR", Message: "selector is not in catalog"}} } if selector.AllowedUse != AllowedUseReadOnlyLocate { return MatchResult{OK: false, SelectorID: selectorID, Error: MatchError{Code: "INVALID_SELECTOR", Message: "selector is not read-only"}} } refs := flatten(root) matches := matchCriteria(refs, selector.Required) if len(matches) == 0 { for _, fallback := range selector.Fallback { matches = matchFallback(refs, fallback) if len(matches) > 0 { break } } } result := MatchResult{OK: true, SelectorID: selectorID, Matched: len(matches) > 0, MatchCount: len(matches), Warnings: []string{}} for _, ref := range matches { result.Matches = append(result.Matches, summarizeNode(ref)) } if !result.Matched { result.Warnings = append(result.Warnings, "selector_not_found") } return result } func findSelector(catalog []Selector, selectorID string) (Selector, bool) { for _, selector := range catalog { if selector.ID == selectorID { return selector, true } } return Selector{}, false } func flatten(root *Node) []nodeRef { refs := []nodeRef{} var walk func(node *Node, path string) walk = func(node *Node, path string) { if node == nil { return } refs = append(refs, nodeRef{path: path, node: node}) for i := range node.Children { walk(&node.Children[i], path+"/"+itoa(i)) } } walk(root, "root") return refs } func itoa(value int) string { if value == 0 { return "0" } digits := []byte{} for value > 0 { digits = append([]byte{byte('0' + value%10)}, digits...) value = value / 10 } return string(digits) } func matchCriteria(refs []nodeRef, criteria Criteria) []nodeRef { matches := []nodeRef{} for _, ref := range refs { if criteria.AutomationID != "" && ref.node.AutomationID != criteria.AutomationID { continue } if criteria.ControlType != "" && ref.node.ControlType != criteria.ControlType { continue } if criteria.FrameworkID != "" && ref.node.FrameworkID != criteria.FrameworkID { continue } if criteria.ClassName != "" && ref.node.ClassName != criteria.ClassName { continue } if criteria.ClassPrefix != "" && !strings.HasPrefix(ref.node.ClassName, criteria.ClassPrefix) { continue } matches = append(matches, ref) } return matches } func matchFallback(refs []nodeRef, fallback FallbackCriteria) []nodeRef { matches := []nodeRef{} for _, ref := range refs { if fallback.ControlType != "" && ref.node.ControlType != fallback.ControlType { continue } if fallback.ClassPrefix != "" && !strings.HasPrefix(ref.node.ClassName, fallback.ClassPrefix) { continue } if !hasChildAutomationIDs(ref.node, fallback.ChildAutomationIDContains) { continue } if !hasChildControlTypes(ref.node, fallback.ChildControlTypeContains) { continue } matches = append(matches, ref) } return matches } func hasChildAutomationIDs(node *Node, ids []string) bool { for _, id := range ids { found := false for i := range node.Children { if node.Children[i].AutomationID == id { found = true break } } if !found { return false } } return true } func hasChildControlTypes(node *Node, controlTypes []string) bool { for _, controlType := range controlTypes { found := false for i := range node.Children { if node.Children[i].ControlType == controlType { found = true break } } if !found { return false } } return true } func summarizeNode(ref nodeRef) NodeMatch { return NodeMatch{ Path: ref.path, ControlType: ref.node.ControlType, AutomationID: ref.node.AutomationID, ClassName: ref.node.ClassName, FrameworkID: ref.node.FrameworkID, HasName: ref.node.Name != "", NameLength: len(ref.node.Name), } }