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)
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
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