Esempio n. 1
0
class PresentationMerger(object):

    def __init__(self, presentations):
        self.presentations = presentations

    def merge(self):
        self.dirs = [unzip_pres(pres) for pres in self.presentations]
        try:
            self.main_dir = self.dirs[0]

            self.content_types = XMLModifier(path.join(self.main_dir, '[Content_Types].xml'))
            self.default_content_types = {
                e.get('Extension') for e in self.content_types.tree.getroot().iter(tag='{*}Default')
            }
            self.pres_rels = XMLModifier(path.join(self.main_dir, 'ppt', '_rels', 'presentation.xml.rels'))
            self.pres = XMLModifier(path.join(self.main_dir, 'ppt', 'presentation.xml'))
            self.last_slide_id = int(self.pres.xpath('//p:sldId')[0].get('id'))

            for i, d in enumerate(self.dirs[1:], 2):
                self.merge_presentation_dir(d, i)

            self.content_types.write()
            self.pres_rels.write()
            self.pres.write()

            zip_pres_dir(self.main_dir, OUTFILE)
        finally:
            for d in self.dirs:
                shutil.rmtree(d)

    def add_content_type(self, path, content_type):
        etree.SubElement(
            self.content_types.tree.getroot(),
            'Override',
            PartName=path,
            ContentType=content_type,
        )

    def add_default_content_type(self, extension, content_type):
        if extension in self.default_content_types:
            return

        self.default_content_types.add(extension)
        etree.SubElement(
            self.content_types.tree.getroot(),
            'Default',
            Extension=extension,
            ContentType=content_type,
        )

    def merge_presentation_dir(self, d, n):
        slide_number = 1

        self.add_content_type(
            path.join('/', 'ppt', 'slides', '%sslide%s.xml' % (n, slide_number)),
            "application/vnd.openxmlformats-officedocument.presentationml.slide+xml",
        )
        self.last_slide_id += 1
        sldId = etree.SubElement(
            self.pres.xpath('//p:sldIdLst')[0],
            '{%s}sldId' % self.pres.NS['p'],
            id=str(self.last_slide_id)
        )
        sldId.attrib['{%s}id' % self.pres.NS['r']] = "rId%s" % (n*100)
        etree.SubElement(
            self.pres_rels.tree.getroot(),
            'Relationship',
            Id="rId%s" % (n*100),
            Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide",
            Target=path.join('slides', '%sslide%s.xml' % (n, slide_number)),
        )

        content_types = etree.parse(path.join(d, '[Content_Types].xml'))
        for e in content_types.getroot().iter(tag='{*}Default'):
            self.add_default_content_type(e.get('Extension'), e.get('ContentType'))
        self.merge_related(
            path.join('ppt', 'slides', 'slide%s.xml' % slide_number),
            d,
            n,
            {},
            content_types,
        )

    def merge_related(self, path_, d, n, merged, content_types):
        path_ = path.abspath(path.join('/', path_))
        if path_ in merged:
            return

        head, tail = path.split(path_)
        new_path = path.join(head, '%s%s' % (n, tail))

        merged[path_] = new_path


        try:
            os.makedirs(path.dirname(path.join(self.main_dir, path.relpath(new_path, '/'))))
        except OSError:
            pass
        shutil.copy(
            path.join(d, path.relpath(path_, '/')),
            path.join(self.main_dir, path.relpath(new_path, '/')),
        )

        rels_path = path.relpath(path.join(head, '_rels', '%s.rels' % tail), '/')
        if path.exists(path.join(d, rels_path)):
            new_rels_path = path.relpath(path.join(head, '_rels', '%s%s.rels' % (n, tail)), '/')
            non_follow_types = [
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster',
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster',
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout',
                'http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject',
            ]
            rels = etree.parse(path.join(d, rels_path))
            for e in rels.getroot():
                if e.get('Type') in non_follow_types:
                    continue
                self.merge_related(path.join(path_, '..', e.get('Target')), d, n, merged, content_types)

            for e in rels.getroot():
                if e.get('Type') in non_follow_types:
                    continue
                new_abs_path = path.join('/', merged[path.normpath(path.join(path_, '..', e.get('Target')))])
                ext = e.get('Target').rsplit('.', 1)[-1]
                if ext == 'xml' or ext not in self.default_content_types:
                    for c in content_types.getroot():
                        if c.get('PartName') == path.abspath(path.join('/', path_, '..', e.get('Target'))):
                            if not any(c.get('PartName') == new_abs_path for c in self.content_types.tree.getroot()):
                                c = copy.deepcopy(c)
                                c.set('PartName', new_abs_path)
                                self.content_types.tree.getroot().append(c)
                            break
                    else:
                        raise Exception
                e.set(
                    'Target',
                    new_abs_path,
                )
            try:
                os.makedirs(path.dirname(path.join(self.main_dir, new_rels_path)))
            except OSError:
                pass
            rels.write(path.join(self.main_dir, new_rels_path))
            path.join(self.main_dir, new_rels_path)