Exemple #1
0
def apply_commands(rule: Dict[str, dict], shapes, data: dict):
    '''
    Apply commands in rule to change shapes using data.

    :arg dict rule: a dict of shape names, and commands to apply on each.
        e.g. ``{"Oval 1": {"fill": "red"}, "Oval 2": {"text": "OK"}}``
    :arg Shapes shapes: a slide.shapes or group.shapes object on which the rule should be applied
    :arg dict data: data context for the commands in the rule
    '''
    # Apply every rule to every pattern -- as long as the rule key matches the shape name
    for pattern, spec in rule.items():
        if pattern in rule_cmdlist:
            continue
        shape_matched = False
        for shape in shapes:
            if not fnmatchcase(shape.name, pattern):
                continue
            shape_matched = True
            # Clone all slides into the `clones` list BEFORE applying any command. Ensures that
            # commands applied to the shape don't propagate into its clones
            clones = []
            clone_seq = iterate_on(spec.get('clone-shape', [None]), data)
            parent_clone = data.get('clone', None)
            for i, (clone_key, clone_val) in enumerate(clone_seq):
                if i > 0:
                    # This copies only a shape, group or image. Not table, chart, media, equation,
                    # or zoom. But we don't see a need for these yet.
                    el = copy.deepcopy(shape.element)
                    shape.element.addnext(el)
                    shape = pptx.shapes.autoshape.Shape(el, None)
                clones.append(AttrDict(pos=i, key=clone_key, val=clone_val, shape=shape,
                                       parent=parent_clone))
            # Run commands in the spec on all cloned shapes
            is_group = shape.element.tag.endswith('}grpSp')
            for i, clone in enumerate(clones):
                # Include shape-level `data:`. Add shape, clone as variables
                shape_data = load_data(
                    spec.get('data', {}), _default_key='function', shape=shape, clone=clone,
                    **{k: v for k, v in data.items() if k not in {'shape', 'clone'}})
                for cmd in spec:
                    if cmd in commands.cmdlist:
                        commands.cmdlist[cmd](clone.shape, spec[cmd], shape_data)
                    # Warn on unknown commands. But don't warn on groups -- they have sub-shapes
                    elif cmd not in special_cmdlist and not is_group:
                        app_log.warn('pptgen2: Unknown command: %s on shape: %s', cmd, pattern)
                # If the shape is a group, apply spec to each sub-shape
                if is_group:
                    apply_commands(spec, SlideShapes(clone.shape.element, shapes), shape_data)
        # Warn if the pattern is neither a shape nor a command
        if (not shape_matched and pattern not in special_cmdlist and
                pattern not in commands.cmdlist):
            app_log.warn('pptgen2: No shape matches pattern: %s', pattern)
Exemple #2
0
def eltree(data, root=None):
    '''Convert dict to etree.Element(s)'''
    attr_prefix = '@'
    text_key = '$'
    tpl_key = '_$'
    result = [] if root is None else root
    if isinstance(data, dict):
        for key, value in data.items():
            if root is not None:
                # attribute prefixes
                if key.startswith(attr_prefix):
                    key = key.lstrip(attr_prefix)
                    result.set(key, unicode(value))
                    continue
                # text content
                if key == text_key:
                    result.text = unicode(value)
                    continue
                # template hooks
                if key == tpl_key:
                    for tpl in value if isinstance(value, list) else [value]:
                        template = '{}.html'.format(tpl['tpl'])
                        raw_node = load_component(template,
                                                  values=tpl.get(
                                                      'values', tpl))
                        result.append(fromstring(raw_node))
                    continue
            # add other keys as children
            values = value if isinstance(value, list) else [value]
            for value in values:
                elem = Element(key)
                result.append(elem)
                # scalars to text
                if not isinstance(value, (dict, list)):
                    value = {text_key: value}
                eltree(value, root=elem)
    else:
        result.append(Element(unicode(data)))
    return result
Exemple #3
0
def load_data(_conf, _default_key: str = None, **kwargs) -> dict:
    '''
    Loads datasets based on configuration and returns a dict of those datasets.

    :arg dataset _conf: The dataset configuration
    :arg str _default_key: Can be ``function``, ``url`` or ``None`` (default).
        If specified, it converts string data configurations into ``{_default_key: _conf}``.
    :return: A dict of datasets loaded based on the configuration.

    ``_conf`` is processed as follows:

    - String ``'data.xlsx'`` is loaded via :py:func:`gramex.cache.open` into ``{data: ...}`` if
        ``_default_key == 'url'``
    - String ``'data[0]'`` is evaluated via :py:func:`gramex.transforms.build_transform` into
        ``{data: ...}``` if ``_default_key == 'function'``
    - String ``anything``` raises an Exception if ``_default_key`` is None
    - Dict ``{url: ...}`` is loaded with :py:func:`gramex.data.filter` into ``{data: ...}``
    - Dict ``{function: ...}`` is evaluated via :py:func:`gramex.transforms.build_transform`
        into ``{data: ...}``
    - Dict ``{x: ..., y: ...}`` loads the respective datasets into ``x`` and ``y`` instead of
        ``data``. Each dataset is processed using the above rules.
    - Any other datatype passed is returned as is in ``{data: ...}``

    Any keyword arguments passed are also added to the resulting dataset, but overwritten only if
    ``_conf`` loaded a dataset that's not ``None``.
    '''

    def str2conf(data, key):
        '''Convert string configurations to {url: str} or {function:str} based on _default_key'''
        # If data is not a string, return data as-is
        if not isinstance(data, str):
            return data
        # If data is a string, return {_default_key: data} (or raise a TypeError)
        if _default_key is not None:
            return {_default_key: data}
        raise TypeError('%s: must be a dict, not %r' % (key, data))

    data = str2conf(_conf, 'data')
    if not isinstance(data, dict) or 'url' in data or 'function' in data:
        data = {'data': data}
    data = {key: str2conf(conf, key) for key, conf in data.items()}
    for key, conf in data.items():
        if isinstance(conf, dict):
            conf = copy.copy(conf)
            if 'url' in conf:
                if 'transform' in conf:
                    conf['transform'] = build_transform(
                        {'function': conf['transform']},
                        vars={'data': None, 'handler': None},
                        filename='PPTXHandler:data.%s' % key, iter=False)
                data[key] = gramex.data.filter(**conf)
            if 'function' in conf:
                # Let functions use previously defined data variables, including current one
                _kwargs = {**kwargs, **data}
                _vars = {key: None for key in _kwargs}
                data[key] = build_transform(conf, vars=_vars, iter=False)(**_kwargs)
    # If the dataset returns a None, don't overwrite the default kwargs.
    # This allow defaults to pass through if a dataset is specified as None.
    for key, val in data.items():
        if (key not in kwargs) or (val is not None):
            kwargs[key] = val
    return kwargs