diff --git a/CHANGELOG b/CHANGELOG index 560fe03..cb04142 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ v0.6 (unreleased): -- ... +- Fixed Wikicode transformation methods (replace(), remove(), etc.) when passed + an empty section as an argument. (#212) v0.5.2 (released November 1, 2018): diff --git a/docs/changelog.rst b/docs/changelog.rst index 24b671c..2e4ec9d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,7 +7,9 @@ v0.6 Unreleased (`changes `__): -- ... +- Fixed :class:`.Wikicode` transformation methods (:meth:`.Wikicode.replace`, + :meth:`.Wikicode.remove`, etc.) when passed an empty section as an argument. + (`#212 `_) v0.5.2 ------ diff --git a/mwparserfromhell/wikicode.py b/mwparserfromhell/wikicode.py index 4379b0a..14b7368 100644 --- a/mwparserfromhell/wikicode.py +++ b/mwparserfromhell/wikicode.py @@ -27,6 +27,7 @@ import re from .compat import bytes, py3k, range, str from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, Node, Tag, Template, Text, Wikilink) +from .smart_list import _ListProxy from .string_mixin import StringMixIn from .utils import parse_anything @@ -108,6 +109,23 @@ class Wikicode(StringMixIn): if (not forcetype or isinstance(node, forcetype)) and match(node): yield (i, node) + def _is_child_wikicode(self, obj, recursive=True): + """Return whether the given :class:`.Wikicode` is a descendant.""" + nodes = obj.nodes + if isinstance(nodes, _ListProxy): + nodes = nodes._parent # pylint: disable=protected-access + if nodes is self.nodes: + return True + if recursive: + todo = [self] + while todo: + code = todo.pop() + if nodes is code.nodes: + return True + for node in code.nodes: + todo += list(node.__children__()) + return False + def _do_strong_search(self, obj, recursive=True): """Search for the specific element *obj* within the node list. @@ -120,11 +138,16 @@ class Wikicode(StringMixIn): :class:`.Wikicode` contained by a node within ``self``. If *obj* is not found, :exc:`ValueError` is raised. """ + if isinstance(obj, Wikicode): + if not self._is_child_wikicode(obj, recursive): + raise ValueError(obj) + return obj, slice(0, len(obj.nodes)) + if isinstance(obj, Node): mkslice = lambda i: slice(i, i + 1) if not recursive: return self, mkslice(self.index(obj)) - for i, node in enumerate(self.nodes): + for node in self.nodes: for context, child in self._get_children(node, contexts=True): if obj is child: if not context: @@ -132,11 +155,7 @@ class Wikicode(StringMixIn): return context, mkslice(context.index(child)) raise ValueError(obj) - context, ind = self._do_strong_search(obj.get(0), recursive) - for i in range(1, len(obj.nodes)): - if obj.get(i) is not context.get(ind.start + i): - raise ValueError(obj) - return context, slice(ind.start, ind.start + len(obj.nodes)) + raise TypeError(obj) def _do_weak_search(self, obj, recursive): """Search for an element that looks like *obj* within the node list. diff --git a/tests/test_wikicode.py b/tests/test_wikicode.py index 12a1761..2e2436e 100644 --- a/tests/test_wikicode.py +++ b/tests/test_wikicode.py @@ -188,8 +188,8 @@ class TestWikicode(TreeEqualityTestCase): self.assertRaises(ValueError, func, fake, "q", recursive=True) func("{{b}}{{c}}", "w", recursive=False) func("{{d}}{{e}}", "x", recursive=True) - func(wrap(code4.nodes[-2:]), "y", recursive=False) - func(wrap(code4.nodes[-2:]), "z", recursive=True) + func(Wikicode(code4.nodes[-2:]), "y", recursive=False) + func(Wikicode(code4.nodes[-2:]), "z", recursive=True) self.assertEqual(expected[3], code4) self.assertRaises(ValueError, func, "{{c}}{{d}}", "q", recursive=False) self.assertRaises(ValueError, func, "{{c}}{{d}}", "q", recursive=True) @@ -218,6 +218,13 @@ class TestWikicode(TreeEqualityTestCase): func("{{foo}}{{baz}}", "{{lol}}") self.assertEqual(expected[6], code7) + code8 = parse("== header ==") + func = partial(meth, code8) + sec1, sec2 = code8.get_sections(include_headings=False) + func(sec1, "lead\n") + func(sec2, "\nbody") + self.assertEqual(expected[7], code8) + def test_insert_before(self): """test Wikicode.insert_before()""" meth = lambda code, *args, **kw: code.insert_before(*args, **kw) @@ -228,7 +235,9 @@ class TestWikicode(TreeEqualityTestCase): "{{a}}w{{b}}{{c}}x{{d}}{{e}}{{f}}{{g}}{{h}}yz{{i}}{{j}}", "{{a|x{{b}}{{c}}|{{f|{{g}}=y{{h}}{{i}}}}}}", "here cdis {{some abtext and a {{template}}}}", - "{{foo}}{{bar}}{{baz}}{{lol}}{{foo}}{{baz}}"] + "{{foo}}{{bar}}{{baz}}{{lol}}{{foo}}{{baz}}", + "lead\n== header ==\nbody", + ] self._test_search(meth, expected) def test_insert_after(self): @@ -241,16 +250,24 @@ class TestWikicode(TreeEqualityTestCase): "{{a}}{{b}}{{c}}w{{d}}{{e}}x{{f}}{{g}}{{h}}{{i}}{{j}}yz", "{{a|{{b}}{{c}}x|{{f|{{g}}={{h}}{{i}}y}}}}", "here is {{somecd text andab a {{template}}}}", - "{{foo}}{{bar}}{{baz}}{{foo}}{{baz}}{{lol}}"] + "{{foo}}{{bar}}{{baz}}{{foo}}{{baz}}{{lol}}", + "lead\n== header ==\nbody", + ] self._test_search(meth, expected) def test_replace(self): """test Wikicode.replace()""" meth = lambda code, *args, **kw: code.replace(*args, **kw) expected = [ - "{{a}}xz[[y]]{{e}}", "dcdffe", "{{a|x|{{c|d=y}}}}", - "{{a}}wx{{f}}{{g}}z", "{{a|x|{{f|{{g}}=y}}}}", - "here cd ab a {{template}}}}", "{{foo}}{{bar}}{{baz}}{{lol}}"] + "{{a}}xz[[y]]{{e}}", + "dcdffe", + "{{a|x|{{c|d=y}}}}", + "{{a}}wx{{f}}{{g}}z", + "{{a|x|{{f|{{g}}=y}}}}", + "here cd ab a {{template}}}}", + "{{foo}}{{bar}}{{baz}}{{lol}}", + "lead\n== header ==\nbody", + ] self._test_search(meth, expected) def test_append(self): @@ -269,9 +286,15 @@ class TestWikicode(TreeEqualityTestCase): """test Wikicode.remove()""" meth = lambda code, obj, value, **kw: code.remove(obj, **kw) expected = [ - "{{a}}{{c}}", "", "{{a||{{c|d=}}}}", "{{a}}{{f}}", - "{{a||{{f|{{g}}=}}}}", "here a {{template}}}}", - "{{foo}}{{bar}}{{baz}}"] + "{{a}}{{c}}", + "", + "{{a||{{c|d=}}}}", + "{{a}}{{f}}", + "{{a||{{f|{{g}}=}}}}", + "here a {{template}}}}", + "{{foo}}{{bar}}{{baz}}", + "== header ==", + ] self._test_search(meth, expected) def test_matches(self):