git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

colornames/gen.go (5.7K)

  1 // Copyright 2015 The Go Authors. All rights reserved.
  2 // Use of this source code is governed by a BSD-style
  3 // license that can be found in the LICENSE file.
  4 
  5 //go:build ignore
  6 
  7 // This program generates table.go from
  8 // https://www.w3.org/TR/SVG11/types.html#ColorKeywords
  9 package main
 10 
 11 import (
 12 	"bytes"
 13 	"fmt"
 14 	"go/format"
 15 	"image/color"
 16 	"io"
 17 	"io/ioutil"
 18 	"log"
 19 	"net/http"
 20 	"regexp"
 21 	"sort"
 22 	"strconv"
 23 	"strings"
 24 
 25 	"golang.org/x/net/html"
 26 	"golang.org/x/net/html/atom"
 27 )
 28 
 29 // matchFunc matches HTML nodes.
 30 type matchFunc func(*html.Node) bool
 31 
 32 // appendAll recursively traverses the parse tree rooted under the provided
 33 // node and appends all nodes matched by the matchFunc to dst.
 34 func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
 35 	if mf(n) {
 36 		dst = append(dst, n)
 37 	}
 38 	for c := n.FirstChild; c != nil; c = c.NextSibling {
 39 		dst = appendAll(dst, c, mf)
 40 	}
 41 	return dst
 42 }
 43 
 44 // matchAtom returns a matchFunc that matches a Node with the specified Atom.
 45 func matchAtom(a atom.Atom) matchFunc {
 46 	return func(n *html.Node) bool {
 47 		return n.DataAtom == a
 48 	}
 49 }
 50 
 51 // matchAtomAttr returns a matchFunc that matches a Node with the specified
 52 // Atom and a html.Attribute's namespace, key and value.
 53 func matchAtomAttr(a atom.Atom, namespace, key, value string) matchFunc {
 54 	return func(n *html.Node) bool {
 55 		return n.DataAtom == a && getAttr(n, namespace, key) == value
 56 	}
 57 }
 58 
 59 // getAttr fetches the value of a html.Attribute for a given namespace and key.
 60 func getAttr(n *html.Node, namespace, key string) string {
 61 	for _, attr := range n.Attr {
 62 		if attr.Namespace == namespace && attr.Key == key {
 63 			return attr.Val
 64 		}
 65 	}
 66 	return ""
 67 }
 68 
 69 // re extracts RGB values from strings like "rgb( 0, 223, 128)".
 70 var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`)
 71 
 72 // parseRGB parses a color from a string like "rgb( 0, 233, 128)". It sets
 73 // the alpha value of the color to full opacity.
 74 func parseRGB(s string) (color.RGBA, error) {
 75 	m := re.FindStringSubmatch(s)
 76 	if m == nil {
 77 		return color.RGBA{}, fmt.Errorf("malformed color: %q", s)
 78 	}
 79 	var rgb [3]uint8
 80 	for i, t := range m[1:] {
 81 		num, err := strconv.ParseUint(t, 10, 8)
 82 		if err != nil {
 83 			return color.RGBA{}, fmt.Errorf("malformed value %q in %q: %s", t, s, err)
 84 		}
 85 		rgb[i] = uint8(num)
 86 	}
 87 	return color.RGBA{rgb[0], rgb[1], rgb[2], 0xFF}, nil
 88 }
 89 
 90 // extractSVGColors extracts named colors from the parse tree of the SVG 1.1
 91 // spec HTML document "Chapter 4: Basic data types and interfaces".
 92 func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) {
 93 	ret := make(map[string]color.RGBA)
 94 
 95 	// Find the tables which store the color keywords in the parse tree.
 96 	colorTables := appendAll(nil, tree, func(n *html.Node) bool {
 97 		return n.DataAtom == atom.Table && strings.Contains(getAttr(n, "", "summary"), "color keywords part")
 98 	})
 99 
100 	for _, table := range colorTables {
101 		// Color names and values are stored in TextNodes within spans in each row.
102 		for _, tr := range appendAll(nil, table, matchAtom(atom.Tr)) {
103 			nameSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "prop-value"))
104 			valueSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "color-keyword-value"))
105 
106 			// Since SVG 1.1 defines an odd number of colors, the last row
107 			// in the second table does not have contents. We skip it.
108 			if len(nameSpan) != 1 || len(valueSpan) != 1 {
109 				continue
110 			}
111 			n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild
112 			// This sanity checks for the existence of TextNodes under spans.
113 			if n == nil || n.Type != html.TextNode || v == nil || v.Type != html.TextNode {
114 				return nil, fmt.Errorf("extractSVGColors: couldn't find name/value text nodes")
115 			}
116 			val, err := parseRGB(v.Data)
117 			if err != nil {
118 				return nil, fmt.Errorf("extractSVGColors: couldn't parse name/value %q/%q: %s", n.Data, v.Data, err)
119 			}
120 			ret[n.Data] = val
121 		}
122 	}
123 	return ret, nil
124 }
125 
126 const preamble = `// generated by go generate; DO NOT EDIT.
127 
128 package colornames
129 
130 import "image/color"
131 
132 `
133 
134 // WriteColorNames writes table.go.
135 func writeColorNames(w io.Writer, m map[string]color.RGBA) {
136 	keys := make([]string, 0, len(m))
137 	for k := range m {
138 		keys = append(keys, k)
139 	}
140 	sort.Strings(keys)
141 
142 	fmt.Fprintln(w, preamble)
143 	fmt.Fprintln(w, "// Map contains named colors defined in the SVG 1.1 spec.")
144 	fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
145 	for _, k := range keys {
146 		c := m[k]
147 		fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
148 			k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
149 	}
150 	fmt.Fprintln(w, "}\n")
151 	fmt.Fprintln(w, "// Names contains the color names defined in the SVG 1.1 spec.")
152 	fmt.Fprintln(w, "var Names = []string{")
153 	for _, k := range keys {
154 		fmt.Fprintf(w, "%q,\n", k)
155 	}
156 	fmt.Fprintln(w, "}\n")
157 	fmt.Fprintln(w, "var (")
158 	for _, k := range keys {
159 		c := m[k]
160 		// Make the upper case version of k: "Darkred" instead of "darkred".
161 		k = string(k[0]-0x20) + k[1:]
162 		fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n",
163 			k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
164 	}
165 	fmt.Fprintln(w, ")")
166 }
167 
168 const url = "https://www.w3.org/TR/SVG11/types.html"
169 
170 func main() {
171 	res, err := http.Get(url)
172 	if err != nil {
173 		log.Fatalf("Couldn't read from %s: %s\n", url, err)
174 	}
175 	defer res.Body.Close()
176 
177 	tree, err := html.Parse(res.Body)
178 	if err != nil {
179 		log.Fatalf("Couldn't parse %s: %s\n", url, err)
180 	}
181 
182 	colors, err := extractSVGColors(tree)
183 	if err != nil {
184 		log.Fatalf("Couldn't extract colors: %s\n", err)
185 	}
186 
187 	buf := &bytes.Buffer{}
188 	writeColorNames(buf, colors)
189 	fmted, err := format.Source(buf.Bytes())
190 	if err != nil {
191 		log.Fatalf("Error while formatting code: %s\n", err)
192 	}
193 
194 	if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil {
195 		log.Fatalf("Error writing table.go: %s\n", err)
196 	}
197 }