Merge lp:~facelessuser/beautifulsoup/multi-append-insert into lp:beautifulsoup

Proposed by Isaac Muse
Status: Merged
Merged at revision: 485
Proposed branch: lp:~facelessuser/beautifulsoup/multi-append-insert
Merge into: lp:beautifulsoup
Diff against target: 143 lines (+71/-33)
2 files modified
bs4/element.py (+43/-33)
bs4/tests/test_tree.py (+28/-0)
To merge this branch: bzr merge lp:~facelessuser/beautifulsoup/multi-append-insert
Reviewer Review Type Date Requested Status
Leonard Richardson Approve
Review via email: mp+361340@code.launchpad.net

Commit message

Add conveniences for multi append and inserting
Add extend method to append multiple nodes
Allow insert_before and insert_after to accept multiple arguments.

Description of the change

Resolves issue #1514970.

To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

The self.parent checks can be moved out of the loops, but everything else looks good. I'll clean that up when I write the documentation.

review: Approve
Revision history for this message
Isaac Muse (facelessuser) wrote :

Yeah that's a good point.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bs4/element.py'
2--- bs4/element.py 2018-12-31 02:13:03 +0000
3+++ bs4/element.py 2018-12-31 05:22:03 +0000
4@@ -440,43 +440,53 @@
5 """Appends the given tag to the contents of this tag."""
6 self.insert(len(self.contents), tag)
7
8- def insert_before(self, predecessor):
9- """Makes the given element the immediate predecessor of this one.
10-
11- The two elements will have the same parent, and the given element
12+ def extend(self, tags):
13+ """Appends the given tags to the contents of this tag."""
14+ for tag in tags:
15+ self.append(tag)
16+
17+ def insert_before(self, *args):
18+ """Makes the given element(s) the immediate predecessor of this one.
19+
20+ The elements will have the same parent, and the given elements
21 will be immediately before this one.
22 """
23- if self is predecessor:
24- raise ValueError("Can't insert an element before itself.")
25- parent = self.parent
26- if parent is None:
27- raise ValueError(
28- "Element has no parent, so 'before' has no meaning.")
29- # Extract first so that the index won't be screwed up if they
30- # are siblings.
31- if isinstance(predecessor, PageElement):
32- predecessor.extract()
33- index = parent.index(self)
34- parent.insert(index, predecessor)
35-
36- def insert_after(self, successor):
37- """Makes the given element the immediate successor of this one.
38-
39- The two elements will have the same parent, and the given element
40+ for predecessor in args:
41+ if self is predecessor:
42+ raise ValueError("Can't insert an element before itself.")
43+ parent = self.parent
44+ if parent is None:
45+ raise ValueError(
46+ "Element has no parent, so 'before' has no meaning.")
47+ # Extract first so that the index won't be screwed up if they
48+ # are siblings.
49+ if isinstance(predecessor, PageElement):
50+ predecessor.extract()
51+ index = parent.index(self)
52+ parent.insert(index, predecessor)
53+
54+ def insert_after(self, *args):
55+ """Makes the given element(s) the immediate successor of this one.
56+
57+ The elements will have the same parent, and the given elements
58 will be immediately after this one.
59 """
60- if self is successor:
61- raise ValueError("Can't insert an element after itself.")
62- parent = self.parent
63- if parent is None:
64- raise ValueError(
65- "Element has no parent, so 'after' has no meaning.")
66- # Extract first so that the index won't be screwed up if they
67- # are siblings.
68- if isinstance(successor, PageElement):
69- successor.extract()
70- index = parent.index(self)
71- parent.insert(index+1, successor)
72+
73+ offset = 0
74+ for successor in args:
75+ if self is successor:
76+ raise ValueError("Can't insert an element after itself.")
77+ parent = self.parent
78+ if parent is None:
79+ raise ValueError(
80+ "Element has no parent, so 'after' has no meaning.")
81+ # Extract first so that the index won't be screwed up if they
82+ # are siblings.
83+ if isinstance(successor, PageElement):
84+ successor.extract()
85+ index = parent.index(self)
86+ parent.insert(index+1+offset, successor)
87+ offset += 1
88
89 def find_next(self, name=None, attrs={}, text=None, **kwargs):
90 """Returns the first item that matches the given criteria and
91
92=== modified file 'bs4/tests/test_tree.py'
93--- bs4/tests/test_tree.py 2018-12-20 02:13:02 +0000
94+++ bs4/tests/test_tree.py 2018-12-31 05:22:03 +0000
95@@ -931,6 +931,13 @@
96 soup.a.append(soup.b)
97 self.assertEqual(data, soup.decode())
98
99+ def test_extend(self):
100+ data = "<a><b><c><d><e><f><g></g></f></e></d></c></b></a>"
101+ soup = self.soup(data)
102+ l = [soup.g, soup.f, soup.e, soup.d, soup.c, soup.b]
103+ soup.a.extend(l)
104+ self.assertEqual("<a><g></g><f></f><e></e><d></d><c></c><b></b></a>", soup.decode())
105+
106 def test_move_tag_to_beginning_of_parent(self):
107 data = "<a><b></b><c></c><d></d></a>"
108 soup = self.soup(data)
109@@ -957,6 +964,17 @@
110 self.assertEqual(
111 soup.decode(), self.document_for("QUUX<b>bar</b><a>foo</a>BAZ"))
112
113+ def test_insert_multiple_before(self):
114+ soup = self.soup("<a>foo</a><b>bar</b>")
115+ soup.b.insert_before("BAZ", " ", "QUUX")
116+ soup.a.insert_before("QUUX", " ", "BAZ")
117+ self.assertEqual(
118+ soup.decode(), self.document_for("QUUX BAZ<a>foo</a>BAZ QUUX<b>bar</b>"))
119+
120+ soup.a.insert_before(soup.b, "FOO")
121+ self.assertEqual(
122+ soup.decode(), self.document_for("QUUX BAZ<b>bar</b>FOO<a>foo</a>BAZ QUUX"))
123+
124 def test_insert_after(self):
125 soup = self.soup("<a>foo</a><b>bar</b>")
126 soup.b.insert_after("BAZ")
127@@ -967,6 +985,16 @@
128 self.assertEqual(
129 soup.decode(), self.document_for("QUUX<b>bar</b><a>foo</a>BAZ"))
130
131+ def test_insert_multiple_after(self):
132+ soup = self.soup("<a>foo</a><b>bar</b>")
133+ soup.b.insert_after("BAZ", " ", "QUUX")
134+ soup.a.insert_after("QUUX", " ", "BAZ")
135+ self.assertEqual(
136+ soup.decode(), self.document_for("<a>foo</a>QUUX BAZ<b>bar</b>BAZ QUUX"))
137+ soup.b.insert_after(soup.a, "FOO ")
138+ self.assertEqual(
139+ soup.decode(), self.document_for("QUUX BAZ<b>bar</b><a>foo</a>FOO BAZ QUUX"))
140+
141 def test_insert_after_raises_exception_if_after_has_no_meaning(self):
142 soup = self.soup("")
143 tag = soup.new_tag("a")

Subscribers

People subscribed via source and target branches

to status/vote changes: