1
2
3
4
5
6
7
8
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
30 type matchFunc func(*html.Node) bool
31
32
33
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
45 func matchAtom(a atom.Atom) matchFunc {
46 return func(n *html.Node) bool {
47 return n.DataAtom == a
48 }
49 }
50
51
52
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
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
70 var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`)
71
72
73
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
91
92 func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) {
93 ret := make(map[string]color.RGBA)
94
95
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
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
107
108 if len(nameSpan) != 1 || len(valueSpan) != 1 {
109 continue
110 }
111 n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild
112
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
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
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 }
198
View as plain text