github.com/godaddy-x/freego@v1.0.156/goquery/property.go (about)

     1  package goquery
     2  
     3  import (
     4  	"bytes"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"golang.org/x/net/html"
     9  )
    10  
    11  var 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.
    16  func (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.
    24  func (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.
    38  func (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.
    47  func (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.
    62  func (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.
    86  func (s *Selection) Size() int {
    87  	return s.Length()
    88  }
    89  
    90  // Length returns the number of elements in the Selection object.
    91  func (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.
    97  func (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.
   117  func (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.
   141  func (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.
   155  func (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.
   183  func (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  
   208  func 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.
   222  func 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.
   231  func 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  
   253  func getClassesSlice(classes string) []string {
   254  	return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
   255  }
   256  
   257  func 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  
   267  func 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  }