1package goquery 2 3import ( 4 "strings" 5 6 "github.com/andybalholm/cascadia" 7 "golang.org/x/net/html" 8) 9 10// After applies the selector from the root document and inserts the matched elements 11// after the elements in the set of matched elements. 12// 13// If one of the matched elements in the selection is not currently in the 14// document, it's impossible to insert nodes after it, so it will be ignored. 15// 16// This follows the same rules as Selection.Append. 17func (s *Selection) After(selector string) *Selection { 18 return s.AfterMatcher(cascadia.MustCompile(selector)) 19} 20 21// AfterMatcher applies the matcher from the root document and inserts the matched elements 22// after the elements in the set of matched elements. 23// 24// If one of the matched elements in the selection is not currently in the 25// document, it's impossible to insert nodes after it, so it will be ignored. 26// 27// This follows the same rules as Selection.Append. 28func (s *Selection) AfterMatcher(m Matcher) *Selection { 29 return s.AfterNodes(m.MatchAll(s.document.rootNode)...) 30} 31 32// AfterSelection inserts the elements in the selection after each element in the set of matched 33// elements. 34// 35// This follows the same rules as Selection.Append. 36func (s *Selection) AfterSelection(sel *Selection) *Selection { 37 return s.AfterNodes(sel.Nodes...) 38} 39 40// AfterHtml parses the html and inserts it after the set of matched elements. 41// 42// This follows the same rules as Selection.Append. 43func (s *Selection) AfterHtml(html string) *Selection { 44 return s.AfterNodes(parseHtml(html)...) 45} 46 47// AfterNodes inserts the nodes after each element in the set of matched elements. 48// 49// This follows the same rules as Selection.Append. 50func (s *Selection) AfterNodes(ns ...*html.Node) *Selection { 51 return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) { 52 if sn.Parent != nil { 53 sn.Parent.InsertBefore(n, sn.NextSibling) 54 } 55 }) 56} 57 58// Append appends the elements specified by the selector to the end of each element 59// in the set of matched elements, following those rules: 60// 61// 1) The selector is applied to the root document. 62// 63// 2) Elements that are part of the document will be moved to the new location. 64// 65// 3) If there are multiple locations to append to, cloned nodes will be 66// appended to all target locations except the last one, which will be moved 67// as noted in (2). 68func (s *Selection) Append(selector string) *Selection { 69 return s.AppendMatcher(cascadia.MustCompile(selector)) 70} 71 72// AppendMatcher appends the elements specified by the matcher to the end of each element 73// in the set of matched elements. 74// 75// This follows the same rules as Selection.Append. 76func (s *Selection) AppendMatcher(m Matcher) *Selection { 77 return s.AppendNodes(m.MatchAll(s.document.rootNode)...) 78} 79 80// AppendSelection appends the elements in the selection to the end of each element 81// in the set of matched elements. 82// 83// This follows the same rules as Selection.Append. 84func (s *Selection) AppendSelection(sel *Selection) *Selection { 85 return s.AppendNodes(sel.Nodes...) 86} 87 88// AppendHtml parses the html and appends it to the set of matched elements. 89func (s *Selection) AppendHtml(html string) *Selection { 90 return s.AppendNodes(parseHtml(html)...) 91} 92 93// AppendNodes appends the specified nodes to each node in the set of matched elements. 94// 95// This follows the same rules as Selection.Append. 96func (s *Selection) AppendNodes(ns ...*html.Node) *Selection { 97 return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { 98 sn.AppendChild(n) 99 }) 100} 101 102// Before inserts the matched elements before each element in the set of matched elements. 103// 104// This follows the same rules as Selection.Append. 105func (s *Selection) Before(selector string) *Selection { 106 return s.BeforeMatcher(cascadia.MustCompile(selector)) 107} 108 109// BeforeMatcher inserts the matched elements before each element in the set of matched elements. 110// 111// This follows the same rules as Selection.Append. 112func (s *Selection) BeforeMatcher(m Matcher) *Selection { 113 return s.BeforeNodes(m.MatchAll(s.document.rootNode)...) 114} 115 116// BeforeSelection inserts the elements in the selection before each element in the set of matched 117// elements. 118// 119// This follows the same rules as Selection.Append. 120func (s *Selection) BeforeSelection(sel *Selection) *Selection { 121 return s.BeforeNodes(sel.Nodes...) 122} 123 124// BeforeHtml parses the html and inserts it before the set of matched elements. 125// 126// This follows the same rules as Selection.Append. 127func (s *Selection) BeforeHtml(html string) *Selection { 128 return s.BeforeNodes(parseHtml(html)...) 129} 130 131// BeforeNodes inserts the nodes before each element in the set of matched elements. 132// 133// This follows the same rules as Selection.Append. 134func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection { 135 return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) { 136 if sn.Parent != nil { 137 sn.Parent.InsertBefore(n, sn) 138 } 139 }) 140} 141 142// Clone creates a deep copy of the set of matched nodes. The new nodes will not be 143// attached to the document. 144func (s *Selection) Clone() *Selection { 145 ns := newEmptySelection(s.document) 146 ns.Nodes = cloneNodes(s.Nodes) 147 return ns 148} 149 150// Empty removes all children nodes from the set of matched elements. 151// It returns the children nodes in a new Selection. 152func (s *Selection) Empty() *Selection { 153 var nodes []*html.Node 154 155 for _, n := range s.Nodes { 156 for c := n.FirstChild; c != nil; c = n.FirstChild { 157 n.RemoveChild(c) 158 nodes = append(nodes, c) 159 } 160 } 161 162 return pushStack(s, nodes) 163} 164 165// Prepend prepends the elements specified by the selector to each element in 166// the set of matched elements, following the same rules as Append. 167func (s *Selection) Prepend(selector string) *Selection { 168 return s.PrependMatcher(cascadia.MustCompile(selector)) 169} 170 171// PrependMatcher prepends the elements specified by the matcher to each 172// element in the set of matched elements. 173// 174// This follows the same rules as Selection.Append. 175func (s *Selection) PrependMatcher(m Matcher) *Selection { 176 return s.PrependNodes(m.MatchAll(s.document.rootNode)...) 177} 178 179// PrependSelection prepends the elements in the selection to each element in 180// the set of matched elements. 181// 182// This follows the same rules as Selection.Append. 183func (s *Selection) PrependSelection(sel *Selection) *Selection { 184 return s.PrependNodes(sel.Nodes...) 185} 186 187// PrependHtml parses the html and prepends it to the set of matched elements. 188func (s *Selection) PrependHtml(html string) *Selection { 189 return s.PrependNodes(parseHtml(html)...) 190} 191 192// PrependNodes prepends the specified nodes to each node in the set of 193// matched elements. 194// 195// This follows the same rules as Selection.Append. 196func (s *Selection) PrependNodes(ns ...*html.Node) *Selection { 197 return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) { 198 // sn.FirstChild may be nil, in which case this functions like 199 // sn.AppendChild() 200 sn.InsertBefore(n, sn.FirstChild) 201 }) 202} 203 204// Remove removes the set of matched elements from the document. 205// It returns the same selection, now consisting of nodes not in the document. 206func (s *Selection) Remove() *Selection { 207 for _, n := range s.Nodes { 208 if n.Parent != nil { 209 n.Parent.RemoveChild(n) 210 } 211 } 212 213 return s 214} 215 216// RemoveFiltered removes the set of matched elements by selector. 217// It returns the Selection of removed nodes. 218func (s *Selection) RemoveFiltered(selector string) *Selection { 219 return s.RemoveMatcher(cascadia.MustCompile(selector)) 220} 221 222// RemoveMatcher removes the set of matched elements. 223// It returns the Selection of removed nodes. 224func (s *Selection) RemoveMatcher(m Matcher) *Selection { 225 return s.FilterMatcher(m).Remove() 226} 227 228// ReplaceWith replaces each element in the set of matched elements with the 229// nodes matched by the given selector. 230// It returns the removed elements. 231// 232// This follows the same rules as Selection.Append. 233func (s *Selection) ReplaceWith(selector string) *Selection { 234 return s.ReplaceWithMatcher(cascadia.MustCompile(selector)) 235} 236 237// ReplaceWithMatcher replaces each element in the set of matched elements with 238// the nodes matched by the given Matcher. 239// It returns the removed elements. 240// 241// This follows the same rules as Selection.Append. 242func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection { 243 return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...) 244} 245 246// ReplaceWithSelection replaces each element in the set of matched elements with 247// the nodes from the given Selection. 248// It returns the removed elements. 249// 250// This follows the same rules as Selection.Append. 251func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection { 252 return s.ReplaceWithNodes(sel.Nodes...) 253} 254 255// ReplaceWithHtml replaces each element in the set of matched elements with 256// the parsed HTML. 257// It returns the removed elements. 258// 259// This follows the same rules as Selection.Append. 260func (s *Selection) ReplaceWithHtml(html string) *Selection { 261 return s.ReplaceWithNodes(parseHtml(html)...) 262} 263 264// ReplaceWithNodes replaces each element in the set of matched elements with 265// the given nodes. 266// It returns the removed elements. 267// 268// This follows the same rules as Selection.Append. 269func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection { 270 s.AfterNodes(ns...) 271 return s.Remove() 272} 273 274// Unwrap removes the parents of the set of matched elements, leaving the matched 275// elements (and their siblings, if any) in their place. 276// It returns the original selection. 277func (s *Selection) Unwrap() *Selection { 278 s.Parent().Each(func(i int, ss *Selection) { 279 // For some reason, jquery allows unwrap to remove the <head> element, so 280 // allowing it here too. Same for <html>. Why it allows those elements to 281 // be unwrapped while not allowing body is a mystery to me. 282 if ss.Nodes[0].Data != "body" { 283 ss.ReplaceWithSelection(ss.Contents()) 284 } 285 }) 286 287 return s 288} 289 290// Wrap wraps each element in the set of matched elements inside the first 291// element matched by the given selector. The matched child is cloned before 292// being inserted into the document. 293// 294// It returns the original set of elements. 295func (s *Selection) Wrap(selector string) *Selection { 296 return s.WrapMatcher(cascadia.MustCompile(selector)) 297} 298 299// WrapMatcher wraps each element in the set of matched elements inside the 300// first element matched by the given matcher. The matched child is cloned 301// before being inserted into the document. 302// 303// It returns the original set of elements. 304func (s *Selection) WrapMatcher(m Matcher) *Selection { 305 return s.wrapNodes(m.MatchAll(s.document.rootNode)...) 306} 307 308// WrapSelection wraps each element in the set of matched elements inside the 309// first element in the given Selection. The element is cloned before being 310// inserted into the document. 311// 312// It returns the original set of elements. 313func (s *Selection) WrapSelection(sel *Selection) *Selection { 314 return s.wrapNodes(sel.Nodes...) 315} 316 317// WrapHtml wraps each element in the set of matched elements inside the inner- 318// most child of the given HTML. 319// 320// It returns the original set of elements. 321func (s *Selection) WrapHtml(html string) *Selection { 322 return s.wrapNodes(parseHtml(html)...) 323} 324 325// WrapNode wraps each element in the set of matched elements inside the inner- 326// most child of the given node. The given node is copied before being inserted 327// into the document. 328// 329// It returns the original set of elements. 330func (s *Selection) WrapNode(n *html.Node) *Selection { 331 return s.wrapNodes(n) 332} 333 334func (s *Selection) wrapNodes(ns ...*html.Node) *Selection { 335 s.Each(func(i int, ss *Selection) { 336 ss.wrapAllNodes(ns...) 337 }) 338 339 return s 340} 341 342// WrapAll wraps a single HTML structure, matched by the given selector, around 343// all elements in the set of matched elements. The matched child is cloned 344// before being inserted into the document. 345// 346// It returns the original set of elements. 347func (s *Selection) WrapAll(selector string) *Selection { 348 return s.WrapAllMatcher(cascadia.MustCompile(selector)) 349} 350 351// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher, 352// around all elements in the set of matched elements. The matched child is 353// cloned before being inserted into the document. 354// 355// It returns the original set of elements. 356func (s *Selection) WrapAllMatcher(m Matcher) *Selection { 357 return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...) 358} 359 360// WrapAllSelection wraps a single HTML structure, the first node of the given 361// Selection, around all elements in the set of matched elements. The matched 362// child is cloned before being inserted into the document. 363// 364// It returns the original set of elements. 365func (s *Selection) WrapAllSelection(sel *Selection) *Selection { 366 return s.wrapAllNodes(sel.Nodes...) 367} 368 369// WrapAllHtml wraps the given HTML structure around all elements in the set of 370// matched elements. The matched child is cloned before being inserted into the 371// document. 372// 373// It returns the original set of elements. 374func (s *Selection) WrapAllHtml(html string) *Selection { 375 return s.wrapAllNodes(parseHtml(html)...) 376} 377 378func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection { 379 if len(ns) > 0 { 380 return s.WrapAllNode(ns[0]) 381 } 382 return s 383} 384 385// WrapAllNode wraps the given node around the first element in the Selection, 386// making all other nodes in the Selection children of the given node. The node 387// is cloned before being inserted into the document. 388// 389// It returns the original set of elements. 390func (s *Selection) WrapAllNode(n *html.Node) *Selection { 391 if s.Size() == 0 { 392 return s 393 } 394 395 wrap := cloneNode(n) 396 397 first := s.Nodes[0] 398 if first.Parent != nil { 399 first.Parent.InsertBefore(wrap, first) 400 first.Parent.RemoveChild(first) 401 } 402 403 for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) { 404 wrap = c 405 } 406 407 newSingleSelection(wrap, s.document).AppendSelection(s) 408 409 return s 410} 411 412// WrapInner wraps an HTML structure, matched by the given selector, around the 413// content of element in the set of matched elements. The matched child is 414// cloned before being inserted into the document. 415// 416// It returns the original set of elements. 417func (s *Selection) WrapInner(selector string) *Selection { 418 return s.WrapInnerMatcher(cascadia.MustCompile(selector)) 419} 420 421// WrapInnerMatcher wraps an HTML structure, matched by the given selector, 422// around the content of element in the set of matched elements. The matched 423// child is cloned before being inserted into the document. 424// 425// It returns the original set of elements. 426func (s *Selection) WrapInnerMatcher(m Matcher) *Selection { 427 return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...) 428} 429 430// WrapInnerSelection wraps an HTML structure, matched by the given selector, 431// around the content of element in the set of matched elements. The matched 432// child is cloned before being inserted into the document. 433// 434// It returns the original set of elements. 435func (s *Selection) WrapInnerSelection(sel *Selection) *Selection { 436 return s.wrapInnerNodes(sel.Nodes...) 437} 438 439// WrapInnerHtml wraps an HTML structure, matched by the given selector, around 440// the content of element in the set of matched elements. The matched child is 441// cloned before being inserted into the document. 442// 443// It returns the original set of elements. 444func (s *Selection) WrapInnerHtml(html string) *Selection { 445 return s.wrapInnerNodes(parseHtml(html)...) 446} 447 448// WrapInnerNode wraps an HTML structure, matched by the given selector, around 449// the content of element in the set of matched elements. The matched child is 450// cloned before being inserted into the document. 451// 452// It returns the original set of elements. 453func (s *Selection) WrapInnerNode(n *html.Node) *Selection { 454 return s.wrapInnerNodes(n) 455} 456 457func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection { 458 if len(ns) == 0 { 459 return s 460 } 461 462 s.Each(func(i int, s *Selection) { 463 contents := s.Contents() 464 465 if contents.Size() > 0 { 466 contents.wrapAllNodes(ns...) 467 } else { 468 s.AppendNodes(cloneNode(ns[0])) 469 } 470 }) 471 472 return s 473} 474 475func parseHtml(h string) []*html.Node { 476 // Errors are only returned when the io.Reader returns any error besides 477 // EOF, but strings.Reader never will 478 nodes, err := html.ParseFragment(strings.NewReader(h), &html.Node{Type: html.ElementNode}) 479 if err != nil { 480 panic("goquery: failed to parse HTML: " + err.Error()) 481 } 482 return nodes 483} 484 485// Get the first child that is an ElementNode 486func getFirstChildEl(n *html.Node) *html.Node { 487 c := n.FirstChild 488 for c != nil && c.Type != html.ElementNode { 489 c = c.NextSibling 490 } 491 return c 492} 493 494// Deep copy a slice of nodes. 495func cloneNodes(ns []*html.Node) []*html.Node { 496 cns := make([]*html.Node, 0, len(ns)) 497 498 for _, n := range ns { 499 cns = append(cns, cloneNode(n)) 500 } 501 502 return cns 503} 504 505// Deep copy a node. The new node has clones of all the original node's 506// children but none of its parents or siblings. 507func cloneNode(n *html.Node) *html.Node { 508 nn := &html.Node{ 509 Type: n.Type, 510 DataAtom: n.DataAtom, 511 Data: n.Data, 512 Attr: make([]html.Attribute, len(n.Attr)), 513 } 514 515 copy(nn.Attr, n.Attr) 516 for c := n.FirstChild; c != nil; c = c.NextSibling { 517 nn.AppendChild(cloneNode(c)) 518 } 519 520 return nn 521} 522 523func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool, 524 f func(sn *html.Node, n *html.Node)) *Selection { 525 526 lasti := s.Size() - 1 527 528 // net.Html doesn't provide document fragments for insertion, so to get 529 // things in the correct order with After() and Prepend(), the callback 530 // needs to be called on the reverse of the nodes. 531 if reverse { 532 for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 { 533 ns[i], ns[j] = ns[j], ns[i] 534 } 535 } 536 537 for i, sn := range s.Nodes { 538 for _, n := range ns { 539 if i != lasti { 540 f(sn, cloneNode(n)) 541 } else { 542 if n.Parent != nil { 543 n.Parent.RemoveChild(n) 544 } 545 f(sn, n) 546 } 547 } 548 } 549 550 return s 551} 552