def create_comment(collection, document, id, comment=None): directory = collection undo_resp = {} real_dir = real_directory(directory) document = path_join(real_dir, document) projectconf = ProjectConfiguration(real_dir) txt_file_path = document + '.' + TEXT_FILE_SUFFIX # XXX what is this doing here? # path_split(document)[0] with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) mods = ModificationTracker() _set_special_comments(ann_obj, id, comment, mods, undo_resp=undo_resp) mods_json = mods.json_response() if undo_resp: mods_json['undo'] = json_dumps(undo_resp) mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def create_arc(collection, document, origin, target, type, attributes=None, old_type=None, old_target=None, comment=None): directory = collection undo_resp = {} real_dir = real_directory(directory) mods = ModificationTracker() projectconf = ProjectConfiguration(real_dir) document = path_join(real_dir, document) with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only # TODO: make consistent across the different editing # functions, integrate ann_obj initialization and checks if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) origin = ann_obj.get_ann_by_id(origin) target = ann_obj.get_ann_by_id(target) # if there is a previous annotation and the arcs aren't in # the same category (e.g. relation vs. event arg), process # as delete + create instead of update. if old_type is not None and ( projectconf.is_relation_type(old_type) != projectconf.is_relation_type(type) or projectconf.is_equiv_type(old_type) != projectconf.is_equiv_type(type)): _delete_arc_with_ann(origin.id, old_target, old_type, mods, ann_obj, projectconf) old_target, old_type = None, None if projectconf.is_equiv_type(type): ann = _create_equiv(ann_obj, projectconf, mods, origin, target, type, attributes, old_type, old_target) elif projectconf.is_relation_type(type): ann = _create_relation(ann_obj, projectconf, mods, origin, target, type, attributes, old_type, old_target) else: ann = _create_argument(ann_obj, projectconf, mods, origin, target, type, attributes, old_type, old_target) # process comments if ann is not None: _set_comments(ann_obj, ann, comment, mods, undo_resp=undo_resp) elif comment is not None: Messager.warning( 'create_arc: non-empty comment for None annotation (unsupported type for comment?)') mods_json = mods.json_response() mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def delete_arc(collection, document, origin, target, type): directory = collection real_dir = real_directory(directory) mods = ModificationTracker() projectconf = ProjectConfiguration(real_dir) document = path_join(real_dir, document) with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) _delete_arc_with_ann(origin, target, type, mods, ann_obj, projectconf) mods_json = mods.json_response() mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def delete_span(collection, document, id): directory = collection real_dir = real_directory(directory) document = path_join(real_dir, document) txt_file_path = document + '.' + TEXT_FILE_SUFFIX with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) mods = ModificationTracker() #TODO: Handle a failure to find it #XXX: Slow, O(2N) ann = ann_obj.get_ann_by_id(id) try: # Note: need to pass the tracker to del_annotation to track # recursive deletes. TODO: make usage consistent. ann_obj.del_annotation(ann, mods) try: trig = ann_obj.get_ann_by_id(ann.trigger) try: ann_obj.del_annotation(trig, mods) except DependingAnnotationDeleteError: # Someone else depended on that trigger pass except AttributeError: pass except DependingAnnotationDeleteError, e: Messager.error(e.html_error_str()) return { 'exception': True, } mods_json = mods.json_response() mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def reverse_arc(collection, document, origin, target, type, attributes=None): directory = collection # undo_resp = {} # TODO real_dir = real_directory(directory) # mods = ModificationTracker() # TODO projectconf = ProjectConfiguration(real_dir) document = urllib.parse.unquote(document) document = path_join(real_dir, document) with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) if projectconf.is_equiv_type(type): Messager.warning('Cannot reverse Equiv arc') elif not projectconf.is_relation_type(type): Messager.warning('Can only reverse configured binary relations') else: # OK to reverse found = None # TODO: more sensible lookup for ann in ann_obj.get_relations(): if (ann.arg1 == origin and ann.arg2 == target and ann.type == type): found = ann break if found is None: Messager.error( 'reverse_arc: failed to identify target relation (from %s to %s, type %s) (deleted?)' % (str(origin), str(target), str(type))) else: # found it; just adjust this found.arg1, found.arg2 = found.arg2, found.arg1 # TODO: modification tracker json_response = {} json_response['annotations'] = _json_from_ann(ann_obj) return json_response
def _create_span(collection, document, offsets, _type, attributes=None, normalizations=None, _id=None, comment=None): if _offset_overlaps(offsets): raise SpanOffsetOverlapError(offsets) directory = collection undo_resp = {} _attributes = _parse_attributes(attributes) _normalizations = _parse_span_normalizations(normalizations) #log_info('ATTR: %s' %(_attributes, )) real_dir = real_directory(directory) document = path_join(real_dir, document) projectconf = ProjectConfiguration(real_dir) txt_file_path = document + '.' + TEXT_FILE_SUFFIX path_split(document)[0] with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) mods = ModificationTracker() if _id is not None: # We are to edit an existing annotation tb_ann, e_ann = _edit_span(ann_obj, mods, _id, offsets, projectconf, _attributes, _type, undo_resp=undo_resp) else: # We are to create a new annotation tb_ann, e_ann = __create_span( ann_obj, mods, _type, offsets, txt_file_path, projectconf, _attributes) undo_resp['action'] = 'add_tb' if e_ann is not None: undo_resp['id'] = e_ann.id else: undo_resp['id'] = tb_ann.id # Determine which annotation attributes, normalizations, # comments etc. should be attached to. If there's an event, # attach to that; otherwise attach to the textbound. if e_ann is not None: # Assign to the event, not the trigger target_ann = e_ann else: target_ann = tb_ann # Set attributes _set_attributes(ann_obj, target_ann, _attributes, mods, undo_resp=undo_resp) # Set normalizations _set_normalizations(ann_obj, target_ann, _normalizations, mods, undo_resp=undo_resp) # Set comments if tb_ann is not None: _set_comments(ann_obj, target_ann, comment, mods, undo_resp=undo_resp) if tb_ann is not None: mods_json = mods.json_response() else: # Hack, probably we had a new-line in the span mods_json = {} Messager.error( 'Text span contained new-line, rejected', duration=3) if undo_resp: mods_json['undo'] = json_dumps(undo_resp) mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def split_span(collection, document, args, id): directory = collection real_dir = real_directory(directory) document = path_join(real_dir, document) # TODO don't know how to pass an array directly, so doing extra catenate # and split tosplit_args = json_loads(args) with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) mods = ModificationTracker() ann = ann_obj.get_ann_by_id(id) # currently only allowing splits for events if not isinstance(ann, EventAnnotation): raise AnnotationSplitError( "Cannot split an annotation of type %s" % ann.type) # group event arguments into ones that will be split on and # ones that will not, placing the former into a dict keyed by # the argument without trailing numbers (e.g. "Theme1" -> # "Theme") and the latter in a straight list. split_args = {} nonsplit_args = [] import re for arg, aid in ann.args: m = re.match(r'^(.*?)\d*$', arg) if m: arg = m.group(1) if arg in tosplit_args: if arg not in split_args: split_args[arg] = [] split_args[arg].append(aid) else: nonsplit_args.append((arg, aid)) # verify that split is possible for a in tosplit_args: acount = len(split_args.get(a, [])) if acount < 2: raise AnnotationSplitError( "Cannot split %s on %s: only %d %s arguments (need two or more)" % (ann.id, a, acount, a)) # create all combinations of the args on which to split argument_combos = [[]] for a in tosplit_args: new_combos = [] for aid in split_args[a]: for c in argument_combos: new_combos.append(c + [(a, aid)]) argument_combos = new_combos # create the new events (first combo will use the existing event) from copy import deepcopy new_events = [] for i, arg_combo in enumerate(argument_combos): # tweak args if i == 0: ann.args = nonsplit_args[:] + arg_combo else: newann = deepcopy(ann) # TODO: avoid hard-coding ID prefix newann.id = ann_obj.get_new_id("E") newann.args = nonsplit_args[:] + arg_combo ann_obj.add_annotation(newann) new_events.append(newann) mods.addition(newann) # then, go through all the annotations referencing the original # event, and create appropriate copies for a in ann_obj: soft_deps, hard_deps = a.get_deps() refs = soft_deps | hard_deps if ann.id in refs: # Referenced; make duplicates appropriately if isinstance(a, EventAnnotation): # go through args and make copies for referencing new_args = [] for arg, aid in a.args: if aid == ann.id: for newe in new_events: new_args.append((arg, newe.id)) a.args.extend(new_args) elif isinstance(a, AttributeAnnotation): for newe in new_events: newmod = deepcopy(a) newmod.target = newe.id # TODO: avoid hard-coding ID prefix newmod.id = ann_obj.get_new_id("A") ann_obj.add_annotation(newmod) mods.addition(newmod) elif isinstance(a, BinaryRelationAnnotation): # TODO raise AnnotationSplitError( "Cannot adjust annotation referencing split: not implemented for relations! (WARNING: annotations may be in inconsistent state, please reload!) (Please complain to the developers to fix this!)") elif isinstance(a, OnelineCommentAnnotation): for newe in new_events: newcomm = deepcopy(a) newcomm.target = newe.id # TODO: avoid hard-coding ID prefix newcomm.id = ann_obj.get_new_id("#") ann_obj.add_annotation(newcomm) mods.addition(newcomm) elif isinstance(a, NormalizationAnnotation): for newe in new_events: newnorm = deepcopy(a) newnorm.target = newe.id # TODO: avoid hard-coding ID prefix newnorm.id = ann_obj.get_new_id("N") ann_obj.add_annotation(newnorm) mods.addition(newnorm) else: raise AnnotationSplitError( "Cannot adjust annotation referencing split: not implemented for %s! (Please complain to the lazy developers to fix this!)" % a.__class__) mods_json = mods.json_response() mods_json['annotations'] = _json_from_ann(ann_obj) return mods_json
def delete_arc(collection, document, origin, target, type): directory = collection real_dir = real_directory(directory) document = path_join(real_dir, document) txt_file_path = document + '.' + TEXT_FILE_SUFFIX with TextAnnotations(document) as ann_obj: # bail as quick as possible if read-only if ann_obj._read_only: raise AnnotationsIsReadOnlyError(ann_obj.get_document()) mods = ModificationTracker() # This can be an event or an equiv #TODO: Check for None! try: event_ann = ann_obj.get_ann_by_id(origin) # Try if it is an event arg_tup = (type, unicode(target)) if arg_tup in event_ann.args: before = unicode(event_ann) event_ann.args.remove(arg_tup) mods.change(before, event_ann) ''' if not event_ann.args: # It was the last argument tuple, remove it all try: ann_obj.del_annotation(event_ann) mods.deletion(event_ann) except DependingAnnotationDeleteError, e: #XXX: Old message api print 'Content-Type: application/json\n' print dumps(e.json_error_response()) return ''' else: # What we were to remove did not even exist in the first place pass except AttributeError: projectconf = ProjectConfiguration(real_dir) if projectconf.is_equiv_type(type): # It is an equiv then? #XXX: Slow hack! Should have a better accessor! O(eq_ann) for eq_ann in ann_obj.get_equivs(): # We don't assume that the ids only occur in one Equiv, we # keep on going since the data "could" be corrupted if (unicode(origin) in eq_ann.entities and unicode(target) in eq_ann.entities): before = unicode(eq_ann) eq_ann.entities.remove(unicode(origin)) eq_ann.entities.remove(unicode(target)) mods.change(before, eq_ann) if len(eq_ann.entities) < 2: # We need to delete this one try: ann_obj.del_annotation(eq_ann) mods.deletion(eq_ann) except DependingAnnotationDeleteError, e: #TODO: This should never happen, dep on equiv #print 'Content-Type: application/json\n' # TODO: Proper exception here! Messager.error(e.json_error_response()) return {} elif type in projectconf.get_relation_types(): for ann in ann_obj.get_relations(): if ann.type == type and ann.arg1 == origin and ann.arg2 == target: ann_obj.del_annotation(ann) mods.deletion(ann) break else: