def _execute(self, modify_how, what): """Executes this BlipRefs object. Args: modify_how: What to do. Any of the operation declared at the top. what: Depending on the operation. For delete, has to be None. For the others it is a singleton, a list or a function returning what to do; for ANNOTATE tuples of (key, value), for the others either string or elements. If what is a function, it takes three parameters, the content of the blip, the beginning of the matching range and the end. Raises: IndexError when trying to access content outside of the blip. ValueError when called with the wrong values. Returns: self for chainability. """ blip = self._blip if modify_how != BlipRefs.DELETE: if type(what) != list: what = [what] next_index = 0 matched = [] # updated_elements is used to store the element type of the # element to update updated_elements = [] # For now, if we find one markup, we'll use it everywhere. next = None hit_found = False for start, end in self._hits(): hit_found = True if start < 0: start += len(blip) if end == 0: end += len(blip) if end < 0: end += len(blip) if len(blip) == 0: if start != 0 or end != 0: raise IndexError('Start and end have to be 0 for empty document') elif start < 0 or end < 1 or start >= len(blip) or end > len(blip): raise IndexError('Position outside the document') if modify_how == BlipRefs.DELETE: for i in range(start, end): if i in blip._elements: del blip._elements[i] blip._delete_annotations(start, end) blip._shift(end, start - end) blip._content = blip._content[:start] + blip._content[end:] else: if callable(what): next = what(blip._content, start, end) matched.append(next) else: next = what[next_index] next_index = (next_index + 1) % len(what) if isinstance(next, str): next = next.decode('utf-8') if modify_how == BlipRefs.ANNOTATE: key, value = next blip.annotations._add_internal(key, value, start, end) elif modify_how == BlipRefs.CLEAR_ANNOTATION: blip.annotations._delete_internal(next, start, end) elif modify_how == BlipRefs.UPDATE_ELEMENT: el = blip._elements.get(start) if not element: raise ValueError('No element found at index %s' % start) # the passing around of types this way feels a bit dirty: updated_elements.append(element.Element(el.type, properties=next)) for k, b in next.items(): setattr(el, k, b) else: if modify_how == BlipRefs.INSERT: end = start elif modify_how == BlipRefs.INSERT_AFTER: start = end elif modify_how == BlipRefs.REPLACE: pass else: raise ValueError('Unexpected modify_how: ' + modify_how) if isinstance(next, element.Element): text = ' ' else: text = next # in the case of a replace, and the replacement text is shorter, # delete the delta. if start != end and len(text) < end - start: blip._delete_annotations(start + len(text), end) blip._shift(end, len(text) + start - end) blip._content = blip._content[:start] + text + blip._content[end:] if isinstance(next, element.Element): blip._elements[start] = next # No match found, return immediately without generating op. if not hit_found: return operation = blip._operation_queue.document_modify(blip.wave_id, blip.wavelet_id, blip.blip_id) for param, value in self._params.items(): operation.set_param(param, value) modify_action = {'modifyHow': modify_how} if modify_how == BlipRefs.DELETE: pass elif modify_how == BlipRefs.UPDATE_ELEMENT: modify_action['elements'] = updated_elements elif (modify_how == BlipRefs.REPLACE or modify_how == BlipRefs.INSERT or modify_how == BlipRefs.INSERT_AFTER): if callable(what): what = matched if what: if not isinstance(next, element.Element): modify_action['values'] = [util.force_string(value) for value in what] else: modify_action['elements'] = what elif modify_how == BlipRefs.ANNOTATE: modify_action['values'] = [x[1] for x in what] modify_action['annotationKey'] = what[0][0] elif modify_how == BlipRefs.CLEAR_ANNOTATION: modify_action['annotationKey'] = what[0] operation.set_param('modifyAction', modify_action) return self
def testForceString(self): self.assertEquals("aaa", util.force_string("aaa")) self.assertEquals("12", util.force_string(12)) self.assertEquals(u'\u30e6\u30cb\u30b3\u30fc\u30c9', util.force_string(u'\u30e6\u30cb\u30b3\u30fc\u30c9'))