예제 #1
0
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
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
    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('')
예제 #7
0
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)
예제 #8
0
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)
예제 #9
0
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)
예제 #10
0
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
예제 #11
0
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]
예제 #12
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
예제 #13
0
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)