diff --git a/CHANGELOG b/CHANGELOG
index 41b5f636..eaf4baec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -76,6 +76,7 @@
### Other Improvements
+- DIV(..).elements(...replace=...), thanks Anthony
- new layout based on Twitter Bootstrap
- New generic views: generic.ics (Mac Mail Calendar) and generic.map (Google Maps)
- request.args(0,default=0, cast=int, otherwise=URL(...)), thanks Anthony
diff --git a/VERSION b/VERSION
index 31364eab..c517cf07 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.00.0 (2012-08-24 19:24:54) dev
+Version 2.00.0 (2012-08-24 22:17:44) dev
diff --git a/gluon/html.py b/gluon/html.py
index e88d8604..8522b7a5 100644
--- a/gluon/html.py
+++ b/gluon/html.py
@@ -962,6 +962,48 @@ class DIV(XmlComponent):
>>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
>>> a.xml()
'
'
+
+ Elements that are matched can also be replaced or removed by specifying
+ a "replace" argument (note, a list of the original matching elements
+ is still returned as usual).
+
+ >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
+ >>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
+ >>> print a
+
+
+ "replace" can be a callable, which will be passed the original element and
+ should return a new element to replace it.
+
+ >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
+ >>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
+ >>> print a
+
+
+ If replace=None, matching elements will be removed completely.
+
+ >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
+ >>> b = a.elements('span', find='y', replace=None)
+ >>> print a
+
+
+ If a "find_text" argument is specified, elements will be searched for text
+ components that match find_text, and any matching text components will be
+ replaced (find_text is ignored if "replace" is not also specified).
+ Like the "find" argument, "find_text" can be a string or a compiled regex.
+
+ >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
+ >>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
+ >>> print a
+
+
+ If other attributes are specified along with find_text, then only components
+ that match the specified attributes will be searched for find_text.
+
+ >>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
+ >>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
+ >>> print a
+
"""
if len(args)==1:
args = [a.strip() for a in args[0].split(',')]
@@ -990,49 +1032,60 @@ class DIV(XmlComponent):
return self.elements(*args,**kargs)
# make a copy of the components
matches = []
- first_only = False
- if kargs.has_key("first_only"):
- first_only = kargs["first_only"]
- del kargs["first_only"]
# check if the component has an attribute with the same
# value as provided
check = True
- tag = getattr(self,'tag').replace("/","")
+ tag = getattr(self,'tag').replace('/', '')
if args and tag not in args:
check = False
for (key, value) in kargs.items():
- if isinstance(value,(str,int)):
- if self[key] != str(value):
+ if key not in ['first_only', 'replace', 'find_text']:
+ if isinstance(value, (str, int)):
+ if self[key] != str(value):
+ check = False
+ elif key in self.attributes:
+ if not value.search(str(self[key])):
+ check = False
+ else:
check = False
- elif key in self.attributes:
- if not value.search(str(self[key])):
- check = False
- else:
- check = False
if 'find' in kargs:
find = kargs['find']
+ is_regex = not isinstance(find, (str, int))
for c in self.components:
- if isinstance(find,(str,int)):
- if isinstance(c,str) and str(find) in c:
- check = True
- else:
- if isinstance(c,str) and find.search(c):
- check = True
+ if (isinstance(c, str) and ((is_regex and find.search(c)) or
+ (str(find) in c))):
+ check = True
# if found, return the component
if check:
matches.append(self)
- if first_only:
- return matches
- # loop the copy
- for c in self.components:
- if isinstance(c, XmlComponent):
- kargs['first_only'] = first_only
- child_matches = c.elements( *args, **kargs )
- if first_only and len(child_matches) != 0:
- return child_matches
- matches.extend( child_matches )
- return matches
+ first_only = kargs.get('first_only', False)
+ replace = kargs.get('replace', False)
+ find_text = replace is not False and kargs.get('find_text', False)
+ is_regex = not isinstance(find_text, (str, int, bool))
+ find_components = not (check and first_only)
+ def replace_component(i):
+ if replace is None:
+ del self[i]
+ elif callable(replace):
+ self[i] = replace(self[i])
+ else:
+ self[i] = replace
+ # loop the components
+ if find_text or find_components:
+ for i, c in enumerate(self.components):
+ if check and find_text and isinstance(c, str) and \
+ ((is_regex and find_text.search(c)) or (str(find_text) in c)):
+ replace_component(i)
+ if find_components and isinstance(c, XmlComponent):
+ child_matches = c.elements(*args, **kargs)
+ if len(child_matches):
+ if not find_text and replace is not False and child_matches[0] is c:
+ replace_component(i)
+ if first_only:
+ return child_matches
+ matches.extend(child_matches)
+ return matches
def element(self, *args, **kargs):
"""