| // 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 |
| // https://www.w3.org/TR/SVG11/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 = "https://www.w3.org/TR/SVG11/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) |
| } |
| } |