|
|
@@ -60,19 +60,6 @@ class Wikicode(StringMixIn): |
|
|
|
for context, child in node.__iternodes__(self._get_all_nodes): |
|
|
|
yield child |
|
|
|
|
|
|
|
def _get_context(self, node, obj): |
|
|
|
"""Return a ``Wikicode`` that contains *obj* in its descendants. |
|
|
|
|
|
|
|
The closest (shortest distance from *node*) suitable ``Wikicode`` will |
|
|
|
be returned, or ``None`` if the *obj* is the *node* itself. |
|
|
|
|
|
|
|
Raises ``ValueError`` if *obj* is not within *node*. |
|
|
|
""" |
|
|
|
for context, child in node.__iternodes__(self._get_all_nodes): |
|
|
|
if self._is_equivalent(obj, child): |
|
|
|
return context |
|
|
|
raise ValueError(obj) |
|
|
|
|
|
|
|
def _get_all_nodes(self, code): |
|
|
|
"""Iterate over all of our descendant nodes. |
|
|
|
|
|
|
@@ -105,26 +92,54 @@ class Wikicode(StringMixIn): |
|
|
|
return False |
|
|
|
return obj in nodes |
|
|
|
|
|
|
|
def _do_search(self, obj, recursive, callback, context, *args, **kwargs): |
|
|
|
"""Look within *context* for *obj*, executing *callback* if found. |
|
|
|
def _do_search(self, obj, recursive, context=None, literal=None): |
|
|
|
"""Return some info about the location of *obj* within *context*. |
|
|
|
|
|
|
|
If *recursive* is ``True``, we'll look within *context* and its |
|
|
|
descendants, otherwise we'll just execute *callback*. We raise |
|
|
|
:py:exc:`ValueError` if *obj* isn't in our node list or context. If |
|
|
|
found, *callback* is passed the context, the index of the node within |
|
|
|
the context, and whatever were passed as ``*args`` and ``**kwargs``. |
|
|
|
If *recursive* is ``True``, we'll look within *context* (``self`` by |
|
|
|
default) and its descendants, otherwise just *context*. We raise |
|
|
|
:py:exc:`ValueError` if *obj* isn't found. The return data is a list of |
|
|
|
3-tuples (*type*, *context*, *data*) where *type* is *obj*\ 's best |
|
|
|
type resolution (either ``Node``, ``Wikicode``, or ``str``), *context* |
|
|
|
is the closest ``Wikicode`` encompassing it, and *data* is either a |
|
|
|
``Node``, a list of ``Node``\ s, or ``None`` depending on *type*. |
|
|
|
""" |
|
|
|
if recursive: |
|
|
|
for i, node in enumerate(context.nodes): |
|
|
|
if self._is_equivalent(obj, node): |
|
|
|
return callback(context, i, *args, **kwargs) |
|
|
|
if self._contains(self._get_children(node), obj): |
|
|
|
context = self._get_context(node, obj) |
|
|
|
return self._do_search(obj, recursive, callback, context, |
|
|
|
*args, **kwargs) |
|
|
|
raise ValueError(obj) |
|
|
|
if not context: |
|
|
|
context = self |
|
|
|
literal = isinstance(obj, (Node, Wikicode)) |
|
|
|
obj = parse_anything(obj) |
|
|
|
if not obj or obj not in self: |
|
|
|
raise ValueError(obj) |
|
|
|
if len(obj.nodes) == 1: |
|
|
|
obj = obj.get(0) |
|
|
|
|
|
|
|
compare = lambda a, b: (a is b) if literal else (a == b) |
|
|
|
results = [] |
|
|
|
i = 0 |
|
|
|
while i < len(context.nodes): |
|
|
|
node = context.get(i) |
|
|
|
if isinstance(obj, Node) and compare(obj, node): |
|
|
|
results.append((Node, context, node)) |
|
|
|
elif isinstance(obj, Wikicode) and compare(obj.get(0), node): |
|
|
|
for j in range(1, len(obj.nodes)): |
|
|
|
if not compare(obj.get(j), context.get(i + j)): |
|
|
|
break |
|
|
|
else: |
|
|
|
nodes = list(context.nodes[i:i + len(obj.nodes)]) |
|
|
|
results.append((Wikicode, context, nodes)) |
|
|
|
i += len(obj.nodes) - 1 |
|
|
|
elif recursive: |
|
|
|
contexts = node.__iternodes__(self._get_all_nodes) |
|
|
|
for code in {ctx for ctx, child in contexts}: |
|
|
|
if code and obj in code: |
|
|
|
search = self._do_search(obj, recursive, code, literal) |
|
|
|
results.extend(search) |
|
|
|
i += 1 |
|
|
|
|
|
|
|
callback(context, self.index(obj, recursive=False), *args, **kwargs) |
|
|
|
if not results and not literal and recursive: |
|
|
|
results.append((str, context, None)) |
|
|
|
if not results and context is self: |
|
|
|
raise ValueError(obj) |
|
|
|
return results |
|
|
|
|
|
|
|
def _get_tree(self, code, lines, marker, indent): |
|
|
|
"""Build a tree to illustrate the way the Wikicode object was parsed. |
|
|
@@ -253,41 +268,64 @@ class Wikicode(StringMixIn): |
|
|
|
def insert_before(self, obj, value, recursive=True): |
|
|
|
"""Insert *value* immediately before *obj* in the list of nodes. |
|
|
|
|
|
|
|
*obj* can be either a string or a :py:class:`~.Node`. *value* can be |
|
|
|
anything parasable by :py:func:`.parse_anything`. If *recursive* is |
|
|
|
``True``, we will try to find *obj* within our child nodes even if it |
|
|
|
is not a direct descendant of this :py:class:`~.Wikicode` object. If |
|
|
|
*obj* is not in the node list, :py:exc:`ValueError` is raised. |
|
|
|
*obj* can be either a string, a :py:class:`~.Node`, or other |
|
|
|
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, |
|
|
|
for example). *value* can be anything parasable by |
|
|
|
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to |
|
|
|
find *obj* within our child nodes even if it is not a direct descendant |
|
|
|
of this :py:class:`~.Wikicode` object. If *obj* is not found, |
|
|
|
:py:exc:`ValueError` is raised. |
|
|
|
""" |
|
|
|
callback = lambda self, i, value: self.insert(i, value) |
|
|
|
self._do_search(obj, recursive, callback, self, value) |
|
|
|
for restype, context, data in self._do_search(obj, recursive): |
|
|
|
if restype in (Node, Wikicode): |
|
|
|
i = context.index(data if restype is Node else data[0], False) |
|
|
|
context.insert(i, value) |
|
|
|
else: |
|
|
|
obj = str(obj) |
|
|
|
context.nodes = str(context).replace(obj, str(value) + obj) |
|
|
|
|
|
|
|
def insert_after(self, obj, value, recursive=True): |
|
|
|
"""Insert *value* immediately after *obj* in the list of nodes. |
|
|
|
|
|
|
|
*obj* can be either a string or a :py:class:`~.Node`. *value* can be |
|
|
|
anything parasable by :py:func:`.parse_anything`. If *recursive* is |
|
|
|
``True``, we will try to find *obj* within our child nodes even if it |
|
|
|
is not a direct descendant of this :py:class:`~.Wikicode` object. If |
|
|
|
*obj* is not in the node list, :py:exc:`ValueError` is raised. |
|
|
|
*obj* can be either a string, a :py:class:`~.Node`, or other |
|
|
|
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, |
|
|
|
for example). *value* can be anything parasable by |
|
|
|
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to |
|
|
|
find *obj* within our child nodes even if it is not a direct descendant |
|
|
|
of this :py:class:`~.Wikicode` object. If *obj* is not found, |
|
|
|
:py:exc:`ValueError` is raised. |
|
|
|
""" |
|
|
|
callback = lambda self, i, value: self.insert(i + 1, value) |
|
|
|
self._do_search(obj, recursive, callback, self, value) |
|
|
|
for restype, context, data in self._do_search(obj, recursive): |
|
|
|
if restype in (Node, Wikicode): |
|
|
|
i = context.index(data if restype is Node else data[-1], False) |
|
|
|
context.insert(i + 1, value) |
|
|
|
else: |
|
|
|
obj = str(obj) |
|
|
|
context.nodes = str(context).replace(obj, obj + str(value)) |
|
|
|
|
|
|
|
def replace(self, obj, value, recursive=True): |
|
|
|
"""Replace *obj* with *value* in the list of nodes. |
|
|
|
|
|
|
|
*obj* can be either a string or a :py:class:`~.Node`. *value* can be |
|
|
|
anything parasable by :py:func:`.parse_anything`. If *recursive* is |
|
|
|
``True``, we will try to find *obj* within our child nodes even if it |
|
|
|
is not a direct descendant of this :py:class:`~.Wikicode` object. If |
|
|
|
*obj* is not in the node list, :py:exc:`ValueError` is raised. |
|
|
|
*obj* can be either a string, a :py:class:`~.Node`, or other |
|
|
|
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, |
|
|
|
for example). *value* can be anything parasable by |
|
|
|
:py:func:`.parse_anything`. If *recursive* is ``True``, we will try to |
|
|
|
find *obj* within our child nodes even if it is not a direct descendant |
|
|
|
of this :py:class:`~.Wikicode` object. If *obj* is not found, |
|
|
|
:py:exc:`ValueError` is raised. |
|
|
|
""" |
|
|
|
def callback(self, i, value): |
|
|
|
self.nodes.pop(i) |
|
|
|
self.insert(i, value) |
|
|
|
|
|
|
|
self._do_search(obj, recursive, callback, self, value) |
|
|
|
for restype, context, data in self._do_search(obj, recursive): |
|
|
|
if restype is Node: |
|
|
|
i = context.index(data, False) |
|
|
|
context.nodes.pop(i) |
|
|
|
context.insert(i, value) |
|
|
|
elif restype is Wikicode: |
|
|
|
i = context.index(data[0], False) |
|
|
|
for _ in data: |
|
|
|
context.nodes.pop(i) |
|
|
|
context.insert(i, value) |
|
|
|
else: |
|
|
|
context.nodes = str(context).replace(str(obj), str(value)) |
|
|
|
|
|
|
|
def append(self, value): |
|
|
|
"""Insert *value* at the end of the list of nodes. |
|
|
@@ -301,13 +339,22 @@ class Wikicode(StringMixIn): |
|
|
|
def remove(self, obj, recursive=True): |
|
|
|
"""Remove *obj* from the list of nodes. |
|
|
|
|
|
|
|
*obj* can be either a string or a :py:class:`~.Node`. If *recursive* is |
|
|
|
``True``, we will try to find *obj* within our child nodes even if it |
|
|
|
is not a direct descendant of this :py:class:`~.Wikicode` object. If |
|
|
|
*obj* is not in the node list, :py:exc:`ValueError` is raised. |
|
|
|
*obj* can be either a string, a :py:class:`~.Node`, or other |
|
|
|
:py:class:`~.Wikicode` object (as created by :py:meth:`get_sections`, |
|
|
|
for example). If *recursive* is ``True``, we will try to find *obj* |
|
|
|
within our child nodes even if it is not a direct descendant of this |
|
|
|
:py:class:`~.Wikicode` object. If *obj* is not found, |
|
|
|
:py:exc:`ValueError` is raised. |
|
|
|
""" |
|
|
|
callback = lambda self, i: self.nodes.pop(i) |
|
|
|
self._do_search(obj, recursive, callback, self) |
|
|
|
for restype, context, data in self._do_search(obj, recursive): |
|
|
|
if restype is Node: |
|
|
|
context.nodes.pop(context.index(data, False)) |
|
|
|
elif restype is Wikicode: |
|
|
|
i = context.index(data[0], False) |
|
|
|
for _ in data: |
|
|
|
context.nodes.pop(i) |
|
|
|
else: |
|
|
|
context.nodes = str(context).replace(str(obj), "") |
|
|
|
|
|
|
|
def matches(self, other): |
|
|
|
"""Do a loose equivalency test suitable for comparing page names. |
|
|
|