// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build ignore // This program generates table.go from // http://www.w3.org/TR/SVG/types.html#ColorKeywords package main import ( "bytes" "fmt" "go/format" "image/color" "io" "io/ioutil" "log" "net/http" "regexp" "sort" "strconv" "strings" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) // matchFunc matches HTML nodes. type matchFunc func(*html.Node) bool // appendAll recursively traverses the parse tree rooted under the provided // node and appends all nodes matched by the matchFunc to dst. func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node { if mf(n) { dst = append(dst, n) } for c := n.FirstChild; c != nil; c = c.NextSibling { dst = appendAll(dst, c, mf) } return dst } // matchAtom returns a matchFunc that matches a Node with the specified Atom. func matchAtom(a atom.Atom) matchFunc { return func(n *html.Node) bool { return n.DataAtom == a } } // matchAtomAttr returns a matchFunc that matches a Node with the specified // Atom and a html.Attribute's namespace, key and value. func matchAtomAttr(a atom.Atom, namespace, key, value string) matchFunc { return func(n *html.Node) bool { return n.DataAtom == a && getAttr(n, namespace, key) == value } } // getAttr fetches the value of a html.Attribute for a given namespace and key. func getAttr(n *html.Node, namespace, key string) string { for _, attr := range n.Attr { if attr.Namespace == namespace && attr.Key == key { return attr.Val } } return "" } // re extracts RGB values from strings like "rgb( 0, 223, 128)". var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`) // parseRGB parses a color from a string like "rgb( 0, 233, 128)". It sets // the alpha value of the color to full opacity. func parseRGB(s string) (color.RGBA, error) { m := re.FindStringSubmatch(s) if m == nil { return color.RGBA{}, fmt.Errorf("malformed color: %q", s) } var rgb [3]uint8 for i, t := range m[1:] { num, err := strconv.ParseUint(t, 10, 8) if err != nil { return color.RGBA{}, fmt.Errorf("malformed value %q in %q: %s", t, s, err) } rgb[i] = uint8(num) } return color.RGBA{rgb[0], rgb[1], rgb[2], 0xFF}, nil } // extractSVGColors extracts named colors from the parse tree of the SVG 1.1 // spec HTML document "Chapter 4: Basic data types and interfaces". func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) { ret := make(map[string]color.RGBA) // Find the tables which store the color keywords in the parse tree. colorTables := appendAll(nil, tree, func(n *html.Node) bool { return n.DataAtom == atom.Table && strings.Contains(getAttr(n, "", "summary"), "color keywords part") }) for _, table := range colorTables { // Color names and values are stored in TextNodes within spans in each row. for _, tr := range appendAll(nil, table, matchAtom(atom.Tr)) { nameSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "prop-value")) valueSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "color-keyword-value")) // Since SVG 1.1 defines an odd number of colors, the last row // in the second table does not have contents. We skip it. if len(nameSpan) != 1 || len(valueSpan) != 1 { continue } n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild // This sanity checks for the existence of TextNodes under spans. if n == nil || n.Type != html.TextNode || v == nil || v.Type != html.TextNode { return nil, fmt.Errorf("extractSVGColors: couldn't find name/value text nodes") } val, err := parseRGB(v.Data) if err != nil { return nil, fmt.Errorf("extractSVGColors: couldn't parse name/value %q/%q: %s", n.Data, v.Data, err) } ret[n.Data] = val } } return ret, nil } const preamble = `// generated by go generate; DO NOT EDIT. package colornames import "image/color" ` // WriteColorNames writes table.go. func writeColorNames(w io.Writer, m map[string]color.RGBA) { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) fmt.Fprintln(w, preamble) fmt.Fprintln(w, "// Map contains named colors defined in the SVG 1.1 spec.") fmt.Fprintln(w, "var Map = map[string]color.RGBA{") for _, k := range keys { c := m[k] fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n", k, c.R, c.G, c.B, c.A, c.R, c.G, c.B) } fmt.Fprintln(w, "}\n") fmt.Fprintln(w, "// Names contains the color names defined in the SVG 1.1 spec.") fmt.Fprintln(w, "var Names = []string{") for _, k := range keys { fmt.Fprintf(w, "%q,\n", k) } fmt.Fprintln(w, "}\n") fmt.Fprintln(w, "var (") for _, k := range keys { c := m[k] // Make the upper case version of k: "Darkred" instead of "darkred". k = string(k[0]-0x20) + k[1:] fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n", k, c.R, c.G, c.B, c.A, c.R, c.G, c.B) } fmt.Fprintln(w, ")") } const url = "http://www.w3.org/TR/SVG/types.html" func main() { res, err := http.Get(url) if err != nil { log.Fatalf("Couldn't read from %s: %s\n", url, err) } defer res.Body.Close() tree, err := html.Parse(res.Body) if err != nil { log.Fatalf("Couldn't parse %s: %s\n", url, err) } colors, err := extractSVGColors(tree) if err != nil { log.Fatalf("Couldn't extract colors: %s\n", err) } buf := &bytes.Buffer{} writeColorNames(buf, colors) fmted, err := format.Source(buf.Bytes()) if err != nil { log.Fatalf("Error while formatting code: %s\n", err) } if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil { log.Fatalf("Error writing table.go: %s\n", err) } }