property.go

  1package goquery
  2
  3import (
  4	"bytes"
  5	"regexp"
  6	"strings"
  7
  8	"golang.org/x/net/html"
  9)
 10
 11var rxClassTrim = regexp.MustCompile("[\t\r\n]")
 12
 13// Attr gets the specified attribute's value for the first element in the
 14// Selection. To get the value for each element individually, use a looping
 15// construct such as Each or Map method.
 16func (s *Selection) Attr(attrName string) (val string, exists bool) {
 17	if len(s.Nodes) == 0 {
 18		return
 19	}
 20	return getAttributeValue(attrName, s.Nodes[0])
 21}
 22
 23// AttrOr works like Attr but returns default value if attribute is not present.
 24func (s *Selection) AttrOr(attrName, defaultValue string) string {
 25	if len(s.Nodes) == 0 {
 26		return defaultValue
 27	}
 28
 29	val, exists := getAttributeValue(attrName, s.Nodes[0])
 30	if !exists {
 31		return defaultValue
 32	}
 33
 34	return val
 35}
 36
 37// RemoveAttr removes the named attribute from each element in the set of matched elements.
 38func (s *Selection) RemoveAttr(attrName string) *Selection {
 39	for _, n := range s.Nodes {
 40		removeAttr(n, attrName)
 41	}
 42
 43	return s
 44}
 45
 46// SetAttr sets the given attribute on each element in the set of matched elements.
 47func (s *Selection) SetAttr(attrName, val string) *Selection {
 48	for _, n := range s.Nodes {
 49		attr := getAttributePtr(attrName, n)
 50		if attr == nil {
 51			n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
 52		} else {
 53			attr.Val = val
 54		}
 55	}
 56
 57	return s
 58}
 59
 60// Text gets the combined text contents of each element in the set of matched
 61// elements, including their descendants.
 62func (s *Selection) Text() string {
 63	var buf bytes.Buffer
 64
 65	// Slightly optimized vs calling Each: no single selection object created
 66	var f func(*html.Node)
 67	f = func(n *html.Node) {
 68		if n.Type == html.TextNode {
 69			// Keep newlines and spaces, like jQuery
 70			buf.WriteString(n.Data)
 71		}
 72		if n.FirstChild != nil {
 73			for c := n.FirstChild; c != nil; c = c.NextSibling {
 74				f(c)
 75			}
 76		}
 77	}
 78	for _, n := range s.Nodes {
 79		f(n)
 80	}
 81
 82	return buf.String()
 83}
 84
 85// Size is an alias for Length.
 86func (s *Selection) Size() int {
 87	return s.Length()
 88}
 89
 90// Length returns the number of elements in the Selection object.
 91func (s *Selection) Length() int {
 92	return len(s.Nodes)
 93}
 94
 95// Html gets the HTML contents of the first element in the set of matched
 96// elements. It includes text and comment nodes.
 97func (s *Selection) Html() (ret string, e error) {
 98	// Since there is no .innerHtml, the HTML content must be re-created from
 99	// the nodes using html.Render.
100	var buf bytes.Buffer
101
102	if len(s.Nodes) > 0 {
103		for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling {
104			e = html.Render(&buf, c)
105			if e != nil {
106				return
107			}
108		}
109		ret = buf.String()
110	}
111
112	return
113}
114
115// AddClass adds the given class(es) to each element in the set of matched elements.
116// Multiple class names can be specified, separated by a space or via multiple arguments.
117func (s *Selection) AddClass(class ...string) *Selection {
118	classStr := strings.TrimSpace(strings.Join(class, " "))
119
120	if classStr == "" {
121		return s
122	}
123
124	tcls := getClassesSlice(classStr)
125	for _, n := range s.Nodes {
126		curClasses, attr := getClassesAndAttr(n, true)
127		for _, newClass := range tcls {
128			if !strings.Contains(curClasses, " "+newClass+" ") {
129				curClasses += newClass + " "
130			}
131		}
132
133		setClasses(n, attr, curClasses)
134	}
135
136	return s
137}
138
139// HasClass determines whether any of the matched elements are assigned the
140// given class.
141func (s *Selection) HasClass(class string) bool {
142	class = " " + class + " "
143	for _, n := range s.Nodes {
144		classes, _ := getClassesAndAttr(n, false)
145		if strings.Contains(classes, class) {
146			return true
147		}
148	}
149	return false
150}
151
152// RemoveClass removes the given class(es) from each element in the set of matched elements.
153// Multiple class names can be specified, separated by a space or via multiple arguments.
154// If no class name is provided, all classes are removed.
155func (s *Selection) RemoveClass(class ...string) *Selection {
156	var rclasses []string
157
158	classStr := strings.TrimSpace(strings.Join(class, " "))
159	remove := classStr == ""
160
161	if !remove {
162		rclasses = getClassesSlice(classStr)
163	}
164
165	for _, n := range s.Nodes {
166		if remove {
167			removeAttr(n, "class")
168		} else {
169			classes, attr := getClassesAndAttr(n, true)
170			for _, rcl := range rclasses {
171				classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
172			}
173
174			setClasses(n, attr, classes)
175		}
176	}
177
178	return s
179}
180
181// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
182// Multiple class names can be specified, separated by a space or via multiple arguments.
183func (s *Selection) ToggleClass(class ...string) *Selection {
184	classStr := strings.TrimSpace(strings.Join(class, " "))
185
186	if classStr == "" {
187		return s
188	}
189
190	tcls := getClassesSlice(classStr)
191
192	for _, n := range s.Nodes {
193		classes, attr := getClassesAndAttr(n, true)
194		for _, tcl := range tcls {
195			if strings.Contains(classes, " "+tcl+" ") {
196				classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
197			} else {
198				classes += tcl + " "
199			}
200		}
201
202		setClasses(n, attr, classes)
203	}
204
205	return s
206}
207
208func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
209	if n == nil {
210		return nil
211	}
212
213	for i, a := range n.Attr {
214		if a.Key == attrName {
215			return &n.Attr[i]
216		}
217	}
218	return nil
219}
220
221// Private function to get the specified attribute's value from a node.
222func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
223	if a := getAttributePtr(attrName, n); a != nil {
224		val = a.Val
225		exists = true
226	}
227	return
228}
229
230// Get and normalize the "class" attribute from the node.
231func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
232	// Applies only to element nodes
233	if n.Type == html.ElementNode {
234		attr = getAttributePtr("class", n)
235		if attr == nil && create {
236			n.Attr = append(n.Attr, html.Attribute{
237				Key: "class",
238				Val: "",
239			})
240			attr = &n.Attr[len(n.Attr)-1]
241		}
242	}
243
244	if attr == nil {
245		classes = " "
246	} else {
247		classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
248	}
249
250	return
251}
252
253func getClassesSlice(classes string) []string {
254	return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
255}
256
257func removeAttr(n *html.Node, attrName string) {
258	for i, a := range n.Attr {
259		if a.Key == attrName {
260			n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
261				n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
262			return
263		}
264	}
265}
266
267func setClasses(n *html.Node, attr *html.Attribute, classes string) {
268	classes = strings.TrimSpace(classes)
269	if classes == "" {
270		removeAttr(n, "class")
271		return
272	}
273
274	attr.Val = classes
275}