def add_mention_in_rels(loader: Loader, rels_filepath: str, new_filepath: str) -> str: xml = loader.get_file_xml(rels_filepath) last_index = 0 for rel in xml.xpath('r:Relationship', namespaces=pptx_xml_ns): rel_id = rel.get('Id') result = _REL_INDEX_REGEXP.match(rel_id) index = int(result.group(1)) last_index = max(last_index, index) relation_id = f'rId{last_index+1}' is_root_rels = rels_filepath.startswith(ROOT_RELS_PATH_PREFIX) if is_root_rels: relative_new_filepath = relativize_filepath_relatively_to_root( new_filepath) else: relative_new_filepath = relativize_filepath_relatively_to_content_dirs( new_filepath) item_xml = etree.Element('{%s}Relationship' % pptx_xml_ns['r']) item_xml.set('Id', relation_id) item_xml.set('Type', detect_relation_type_by_filepath(new_filepath)) item_xml.set('Target', relative_new_filepath) xml.append(item_xml) loader.save_file_xml(rels_filepath, xml) return relation_id
def delete_mention_in_content_type(loader: Loader, filepath: str) -> None: xml = loader.get_file_xml(CONTENT_TYPES_PATH) filepath_with_slash = f'/{filepath}' item_xml = xml.xpath(f'c:Override[@PartName="{filepath_with_slash}"][1]', namespaces=pptx_xml_ns)[0] item_xml.getparent().remove(item_xml) loader.save_file_xml(CONTENT_TYPES_PATH, xml)
def delete_mention_in_rels(loader: Loader, rels_filepath: str, relation_id: str) -> None: xml = loader.get_file_xml(rels_filepath) item_xml = xml.xpath(f'r:Relationship[@Id="{relation_id}"][1]', namespaces=pptx_xml_ns)[0] item_xml.getparent().remove(item_xml) loader.save_file_xml(rels_filepath, xml)
def delete_shapes_with_relation(loader: Loader, slide_filepath: str, r_id_to_delete: str): xml = loader.get_file_xml(slide_filepath) items_xml = xml.xpath( f'.//p:sp//*[@r_for_ids:id="{r_id_to_delete}"]/ancestor-or-self::p:sp', namespaces=pptx_xml_ns) for it in items_xml: it.getparent().remove(it) loader.save_file_xml(slide_filepath, xml)
def add_mention_in_content_type(loader: Loader, filepath: str) -> None: content_type = detect_content_type_by_filepath(filepath) if content_type is None: return xml = loader.get_file_xml(CONTENT_TYPES_PATH) filepath_with_slash = f'/{filepath}' item_xml = etree.Element('{%s}Override' % pptx_xml_ns['c']) item_xml.set('PartName', filepath_with_slash) item_xml.set('ContentType', content_type) xml.append(item_xml) loader.save_file_xml(CONTENT_TYPES_PATH, xml)
def __init__(self, file: Union[BinaryIO, BytesIO] = None, cache: Dict[str, Any] = None, do_log_stats: bool = False): loader = Loader() if file is not None: loader.load(file) cacher = Cacher() if cache is not None: cacher.load_persisting_cache(cache) self._storage = PresentationStorage(loader, cacher, do_log_stats=do_log_stats) self._root_cache_key = CacheKey('')
def _copy_relations_recursively_repeated_case(src_loader: Loader, src_rels_filepath: str, dest_loader: Loader, dest_rels_filepath: str, state: _CopyRelationState) -> None: src_xml = src_loader.get_file_xml(src_rels_filepath) dest_xml = dest_loader.get_file_xml(dest_rels_filepath) for src_rel in src_xml.xpath('r:Relationship', namespaces=pptx_xml_ns): # make dest target filepath relative_src_target = src_rel.get('Target') abs_src_target = absolutize_filepath_relatively_to_content_dirs(relative_src_target) # track in rels dest_item_xml = etree.Element('{%s}Relationship' % pptx_xml_ns['r']) dest_item_xml.set('Id', src_rel.get('Id')) dest_item_xml.set('Type', src_rel.get('Type')) dest_item_xml.set('Target', state.src_to_dest(abs_src_target)) dest_xml.append(dest_item_xml) dest_loader.save_file_xml(dest_rels_filepath, dest_xml)
def delete_unused_media(loader: Loader) -> None: media_filepaths = [ it for it in loader.get_filelist() if it.startswith(MEDIA_PATH_PREFIX) and not it.endswith('.rels') ] rels_filepaths = [ it for it in loader.get_filelist() if it.endswith('.rels') ] media_used_filepaths = set() for rels_filepath in rels_filepaths: relation_paths = get_all_relation_paths_in_rels( loader=loader, rels_filepath=rels_filepath) for it in relation_paths: media_used_filepaths.add(it) for media_filepath in media_filepaths: if media_filepath not in media_used_filepaths: loader.delete_file(media_filepath)
def delete_slide(loader: Loader, slide_index: int, do_garbage_collection: bool = True): slide_filepath = make_slide_path(slide_index) slide_rels_filepath = make_rels_path(slide_filepath) presentation_rels_filepath = make_rels_path(PRESENTATION_PATH) # delete slide xml try: loader.delete_file(slide_filepath) except KeyError: return loader.delete_file(slide_rels_filepath) # delete relations if do_garbage_collection: delete_unused_media(loader=loader) last_pptx_slide_index = find_last_index_of_content(loader=loader, content_name='slide') for i in range(1, last_pptx_slide_index + 1): delete_mention_in_slide(loader=loader, slide_id=i, slide_id_to_delete=slide_index) # update presentation xml slide_relation_id = find_relation_id_in_rels( loader=loader, rels_filepath=presentation_rels_filepath, filepath=slide_filepath) assert slide_relation_id is not None delete_mention_in_rels(loader=loader, rels_filepath=presentation_rels_filepath, relation_id=slide_relation_id) delete_slide_mention_in_presentation(loader=loader, relation_id=slide_relation_id) # update content type xml delete_mention_in_content_type(loader=loader, filepath=slide_filepath)
def _iter_all_relation_paths_in_rels(loader: Loader, rels_filepath: str) -> Iterator[str]: xml = loader.get_file_xml(rels_filepath) is_root_rels = rels_filepath.startswith(ROOT_RELS_PATH_PREFIX) for relation in xml.xpath(f'r:Relationship', namespaces=pptx_xml_ns): relative_filepath = relation.get('Target') if is_root_rels: abs_filepath = absolutize_filepath_relatively_to_root( relative_filepath) else: abs_filepath = absolutize_filepath_relatively_to_content_dirs( relative_filepath) yield abs_filepath
def find_relation_id_in_rels(loader: Loader, rels_filepath: str, filepath: str) -> Optional[str]: xml = loader.get_file_xml(rels_filepath) is_root_rels = rels_filepath.startswith(ROOT_RELS_PATH_PREFIX) if is_root_rels: relative_filepath = relativize_filepath_relatively_to_root(filepath) else: relative_filepath = relativize_filepath_relatively_to_content_dirs( filepath) ids = xml.xpath(f'r:Relationship[@Target="{relative_filepath}"][1]/@Id', namespaces=pptx_xml_ns) if len(ids) == 0: return None return ids[0]
def copy_relations_recursively(src_loader: Loader, src_rels_filepath: str, dest_loader: Loader, dest_rels_filepath: str, state: Optional[_CopyRelationState] = None) -> Iterable[str]: """ :return: abs destination paths of what was copied """ if state is None: state = _CopyRelationState() src_xml = src_loader.get_file_xml(src_rels_filepath) dest_xml = dest_loader.get_file_xml(dest_rels_filepath) target_filepaths_queue = list() repeated_target_filepaths_queue = list() for src_rel in src_xml.xpath('r:Relationship', namespaces=pptx_xml_ns): # make dest target filepath relative_src_target = src_rel.get('Target') match_result = _PATH_DIR_CONTENT_NAME_EXT_REGEX.match(relative_src_target) dir_name = match_result.group(1) content_name = match_result.group(2) ext = match_result.group(3) last_target_index = find_last_index_of_content(loader=dest_loader, content_name=content_name, dir_name=dir_name) dest_target_index = last_target_index + 1 relative_dest_target = f'../{dir_name}/{content_name}{dest_target_index}.{ext}' # copy file abs_src_target = absolutize_filepath_relatively_to_content_dirs(relative_src_target) abs_dest_target = absolutize_filepath_relatively_to_content_dirs(relative_dest_target) contents = src_loader.get_file(abs_src_target) dest_loader.save_file(abs_dest_target, contents) # add this file's rels to copy queue src_rel_filepath = make_rels_path(abs_src_target) dest_rel_filepath = make_rels_path(abs_dest_target) if src_loader.does_file_exist(src_rel_filepath): if state.has_src(abs_src_target): repeated_target_filepaths_queue.append((src_rel_filepath, dest_rel_filepath)) else: target_filepaths_queue.append((src_rel_filepath, dest_rel_filepath)) # track state.track(abs_src_target, abs_dest_target) # track in rels dest_item_xml = etree.Element('{%s}Relationship' % pptx_xml_ns['r']) dest_item_xml.set('Id', src_rel.get('Id')) dest_item_xml.set('Type', src_rel.get('Type')) dest_item_xml.set('Target', relative_dest_target) dest_xml.append(dest_item_xml) dest_loader.save_file_xml(dest_rels_filepath, dest_xml) for src_rel_filepath, dest_rel_filepath in target_filepaths_queue: create_blank_rels(loader=dest_loader, filepath=dest_rel_filepath) copy_relations_recursively(src_loader=src_loader, src_rels_filepath=src_rel_filepath, dest_loader=dest_loader, dest_rels_filepath=dest_rel_filepath, state=state) for src_rel_filepath, dest_rel_filepath in repeated_target_filepaths_queue: create_blank_rels(loader=dest_loader, filepath=dest_rel_filepath) _copy_relations_recursively_repeated_case(src_loader=src_loader, src_rels_filepath=src_rel_filepath, dest_loader=dest_loader, dest_rels_filepath=dest_rel_filepath, state=state) return state.dest_filepaths
def create_blank_rels(loader: Loader, filepath: str) -> None: contents = """ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships> """.strip() loader.save_file_str(filepath, contents)