1package goquery
  2
  3import (
  4	"bytes"
  5	"io"
  6
  7	"golang.org/x/net/html"
  8)
  9
 10// used to determine if a set (map[*html.Node]bool) should be used
 11// instead of iterating over a slice. The set uses more memory and
 12// is slower than slice iteration for small N.
 13const minNodesForSet = 1000
 14
 15var nodeNames = []string{
 16	html.ErrorNode:    "#error",
 17	html.TextNode:     "#text",
 18	html.DocumentNode: "#document",
 19	html.CommentNode:  "#comment",
 20}
 21
 22// NodeName returns the node name of the first element in the selection.
 23// It tries to behave in a similar way as the DOM's nodeName property
 24// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
 25//
 26// Go's net/html package defines the following node types, listed with
 27// the corresponding returned value from this function:
 28//
 29//     ErrorNode : #error
 30//     TextNode : #text
 31//     DocumentNode : #document
 32//     ElementNode : the element's tag name
 33//     CommentNode : #comment
 34//     DoctypeNode : the name of the document type
 35//
 36func NodeName(s *Selection) string {
 37	if s.Length() == 0 {
 38		return ""
 39	}
 40	return nodeName(s.Get(0))
 41}
 42
 43// nodeName returns the node name of the given html node.
 44// See NodeName for additional details on behaviour.
 45func nodeName(node *html.Node) string {
 46	if node == nil {
 47		return ""
 48	}
 49
 50	switch node.Type {
 51	case html.ElementNode, html.DoctypeNode:
 52		return node.Data
 53	default:
 54		if int(node.Type) < len(nodeNames) {
 55			return nodeNames[node.Type]
 56		}
 57		return ""
 58	}
 59}
 60
 61// Render renders the HTML of the first item in the selection and writes it to
 62// the writer. It behaves the same as OuterHtml but writes to w instead of
 63// returning the string.
 64func Render(w io.Writer, s *Selection) error {
 65	if s.Length() == 0 {
 66		return nil
 67	}
 68	n := s.Get(0)
 69	return html.Render(w, n)
 70}
 71
 72// OuterHtml returns the outer HTML rendering of the first item in
 73// the selection - that is, the HTML including the first element's
 74// tag and attributes.
 75//
 76// Unlike Html, this is a function and not a method on the Selection,
 77// because this is not a jQuery method (in javascript-land, this is
 78// a property provided by the DOM).
 79func OuterHtml(s *Selection) (string, error) {
 80	var buf bytes.Buffer
 81	if err := Render(&buf, s); err != nil {
 82		return "", err
 83	}
 84	return buf.String(), nil
 85}
 86
 87// Loop through all container nodes to search for the target node.
 88func sliceContains(container []*html.Node, contained *html.Node) bool {
 89	for _, n := range container {
 90		if nodeContains(n, contained) {
 91			return true
 92		}
 93	}
 94
 95	return false
 96}
 97
 98// Checks if the contained node is within the container node.
 99func nodeContains(container *html.Node, contained *html.Node) bool {
100	// Check if the parent of the contained node is the container node, traversing
101	// upward until the top is reached, or the container is found.
102	for contained = contained.Parent; contained != nil; contained = contained.Parent {
103		if container == contained {
104			return true
105		}
106	}
107	return false
108}
109
110// Checks if the target node is in the slice of nodes.
111func isInSlice(slice []*html.Node, node *html.Node) bool {
112	return indexInSlice(slice, node) > -1
113}
114
115// Returns the index of the target node in the slice, or -1.
116func indexInSlice(slice []*html.Node, node *html.Node) int {
117	if node != nil {
118		for i, n := range slice {
119			if n == node {
120				return i
121			}
122		}
123	}
124	return -1
125}
126
127// Appends the new nodes to the target slice, making sure no duplicate is added.
128// There is no check to the original state of the target slice, so it may still
129// contain duplicates. The target slice is returned because append() may create
130// a new underlying array. If targetSet is nil, a local set is created with the
131// target if len(target) + len(nodes) is greater than minNodesForSet.
132func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
133	// if there are not that many nodes, don't use the map, faster to just use nested loops
134	// (unless a non-nil targetSet is passed, in which case the caller knows better).
135	if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
136		for _, n := range nodes {
137			if !isInSlice(target, n) {
138				target = append(target, n)
139			}
140		}
141		return target
142	}
143
144	// if a targetSet is passed, then assume it is reliable, otherwise create one
145	// and initialize it with the current target contents.
146	if targetSet == nil {
147		targetSet = make(map[*html.Node]bool, len(target))
148		for _, n := range target {
149			targetSet[n] = true
150		}
151	}
152	for _, n := range nodes {
153		if !targetSet[n] {
154			target = append(target, n)
155			targetSet[n] = true
156		}
157	}
158
159	return target
160}
161
162// Loop through a selection, returning only those nodes that pass the predicate
163// function.
164func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) {
165	for i, n := range sel.Nodes {
166		if predicate(i, newSingleSelection(n, sel.document)) {
167			result = append(result, n)
168		}
169	}
170	return result
171}
172
173// Creates a new Selection object based on the specified nodes, and keeps the
174// source Selection object on the stack (linked list).
175func pushStack(fromSel *Selection, nodes []*html.Node) *Selection {
176	result := &Selection{nodes, fromSel.document, fromSel}
177	return result
178}