view paraspace/structpath.py @ 35:2f9e7f03dbf7

Support predicates in structpath.py
author Thinker K.F. Li <thinker@codemud.net>
date Wed, 15 Jun 2011 12:03:23 +0800
parents fe1ebf0c3d40
children 0b9ac7cef6e5
line wrap: on
line source

##
# Implement a xpath liked query language.
#
# \section predicate Predicate
# Structpath uses syntax of Python for predicate.  That means you must use
# '==' instead of '='.
#
class context(object):
    all_classes = None
    class_instances = None
    root = None
    objs = None

    def __init__(self, objs, ctx=None):
        if ctx:
            self.all_classes = ctx.all_classes
            self.class_instances = ctx.class_instances
            self.root = ctx.root
            pass
        self.objs = objs
        pass

    def get_parent(self, obj):
        raise NotImplementedError, 'get_parent() is not implemented'
    pass


def _path_split(path):
    parts = [p.strip()
             for p in path.split('/')]
    return parts


def _is_abs(path_parts):
    if len(path_parts) == 0:
        return False
    return path_parts[0] == ''


def _rel_of_abs(path_parts):
    return path_parts[1:]


def _is_class(part):
    return part.startswith('.')
        

def _class_name(part):
    return part[1:]


def _is_parent_name(part):
    return part == '..'


def _split_attr_pred(part):
    try:
        lq_idx = part.index('[')
    except:
        return part, ''
    return part[:lq_idx].strip(), part[lq_idx:].strip()


def _eval_obj_sub_pred(ctx, obj, sub_pred):
    ns_global = {}
    for attr in dir(obj):
        if attr.startswith('_'):
            continue
        v = _obj_attr(obj, attr)
        ns_global[attr] = v
        pass

    truth_v = eval(sub_pred, ns_global)
    return truth_v


def _split_pred_into_subs(pred):
    striped_pred = pred.strip(' []\n\r')
    subs = striped_pred.split('][')
    return subs


def _eval_obj_pred(ctx, obj, pred):
    subs = _split_pred_into_subs(pred)
    
    for sub in subs:
        if not sub:
            continue
        
        if not _eval_obj_sub_pred(ctx, obj, sub):
            return False
        pass
    return True


def _obj_attr(obj, attrname):
    if isinstance(obj, list):
        idx = int(attrname)
        return obj[idx]
    elif isinstance(obj, dict):
        key = eval(attrname)
        return obj[key]
    return getattr(obj, attrname)


def _handle_path_part_obj(ctx, part, obj):
    attr, pred = _split_attr_pred(part)
    
    if _is_parent_name(attr):
        new_objs = [ctx.get_parent(obj)]
    elif _is_class(attr):
        class_name = _class_name(attr)
        new_objs = ctx.class_instances[class_name]
    else:
        try:
            new_objs = [_obj_attr(obj, attr)]
        except AttributeError:
            return []
        pass

    new_objs = filter((lambda x: _eval_obj_pred(ctx, x, pred)), new_objs)
                   
    return new_objs


def _handle_path_part(ctx, part):
    from itertools import chain
    
    if not ctx.objs:
        ctx = ctx.__class__([ctx.root], ctx)
        pass

    objss = [_handle_path_part_obj(ctx, part, obj)
             for obj in ctx.objs]
    objs = [o for o in chain(*objss)]
    new_ctx = ctx.__class__(objs, ctx)
    return new_ctx


def _handle_path_parts(ctx, path_parts):
    if _is_abs(path_parts):
        ctx = ctx.__class__([ctx.root], ctx)
        path_parts = _rel_of_abs(path_parts)
        pass

    if len(path_parts) == 1 and path_parts[0] == '':
        return ctx
    
    for path_part in path_parts:
        ctx = _handle_path_part(ctx, path_part)
        pass
    
    return ctx


def find_objs_path(ctx, path):
    path_parts = _path_split(path)
    rctx = _handle_path_parts(ctx, path_parts)
    return rctx.objs