package main import ( "crypto/sha256" "encoding/hex" "strings" "unicode" "golang.org/x/net/html" ) // The site writes the team separator as NBSP-hyphen-NBSP wrapped in newlines, // so every string that leaves the parser goes through normalize and downstream // comparisons and diffs stay stable. func normalize(s string) string { return strings.Join(strings.FieldsFunc(s, isSpaceRune), " ") } func isSpaceRune(r rune) bool { if unicode.IsSpace(r) { return true } // No-break and zero-width variants the site emits around team names. switch r { case '\u00a0', '\u2007', '\u202f', '\u200b', '\ufeff': return true } return false } // textOf returns the normalized text of a node subtree. func textOf(n *html.Node) string { if n == nil { return "" } var b strings.Builder var walk func(*html.Node) walk = func(n *html.Node) { if n.Type == html.TextNode { b.WriteString(n.Data) } for c := n.FirstChild; c != nil; c = c.NextSibling { walk(c) } } walk(n) return normalize(b.String()) } // textExcluding returns the normalized text of a node subtree with the first // descendant matching skipTag (and its subtree) left out. The schedule uses // this to read the mobile cell's start time without its date. func textExcluding(n *html.Node, skipTag string) string { if n == nil { return "" } var b strings.Builder var walk func(*html.Node) bool walk = func(n *html.Node) bool { if n.Type == html.ElementNode && n.Data == skipTag { return false } if n.Type == html.TextNode { b.WriteString(n.Data) } for c := n.FirstChild; c != nil; c = c.NextSibling { walk(c) } return true } walk(n) return normalize(b.String()) } // childElements returns the direct children of n with the given tag name. func childElements(n *html.Node, tag string) []*html.Node { if n == nil { return nil } var out []*html.Node for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == html.ElementNode && c.Data == tag { out = append(out, c) } } return out } // descendants returns every element with the given tag name, in document order. func descendants(n *html.Node, tag string) []*html.Node { var out []*html.Node var walk func(*html.Node) walk = func(n *html.Node) { if n.Type == html.ElementNode && n.Data == tag { out = append(out, n) } for c := n.FirstChild; c != nil; c = c.NextSibling { walk(c) } } walk(n) return out } func attr(n *html.Node, key string) string { if n == nil { return "" } for _, a := range n.Attr { if a.Key == key { return a.Val } } return "" } func hasClass(n *html.Node, class string) bool { for _, f := range strings.Fields(attr(n, "class")) { if f == class { return true } } return false } func sha256Hex(b []byte) string { sum := sha256.Sum256(b) return hex.EncodeToString(sum[:]) }