2009-12-16 3 views
5

Ich möchte pyPdf verwenden, um eine PDF-Datei basierend auf der Gliederung zu teilen, wobei sich jedes Ziel in der Gliederung auf eine andere Seite innerhalb der PDF bezieht.split a pdf basierend auf Gliederung

Beispiel Umriss:

 
main  --> points to page 1 
    sect1 --> points to page 1 
    sect2 --> points to page 15 
    sect3 --> points to page 22 

es ist einfach innerhalb pyPdf über jeder Seite des Dokuments oder jedes Ziel im Dokument des Umrisses zu iterieren; Allerdings kann ich nicht herausfinden, wie man die Seitennummer erhält, auf die das Ziel zeigt.

weiß jemand, wie man die referenzierende Seitenzahl für jedes Ziel in der Gliederung findet?

Antwort

6

i es herausgefunden:

 
    class Darrell(pyPdf.PdfFileReader): 

     def getDestinationPageNumbers(self): 
      def _setup_outline_page_ids(outline, _result=None): 
       if _result is None: 
        _result = {} 
       for obj in outline: 
        if isinstance(obj, pyPdf.pdf.Destination): 
         _result[(id(obj), obj.title)] = obj.page.idnum 
        elif isinstance(obj, list): 
         _setup_outline_page_ids(obj, _result) 
       return _result 

      def _setup_page_id_to_num(pages=None, _result=None, _num_pages=None): 
       if _result is None: 
        _result = {} 
       if pages is None: 
        _num_pages = [] 
        pages = self.trailer["/Root"].getObject()["/Pages"].getObject() 
       t = pages["/Type"] 
       if t == "/Pages": 
        for page in pages["/Kids"]: 
         _result[page.idnum] = len(_num_pages) 
         _setup_page_id_to_num(page.getObject(), _result, _num_pages) 
       elif t == "/Page": 
        _num_pages.append(1) 
       return _result 

      outline_page_ids = _setup_outline_page_ids(self.getOutlines()) 
      page_id_to_page_numbers = _setup_page_id_to_num() 

      result = {} 
      for (_, title), page_idnum in outline_page_ids.iteritems(): 
       result[title] = page_id_to_page_numbers.get(page_idnum, '???') 
      return result 

    pdf = Darrell(open(PATH-TO-PDF, 'rb')) 
    template = '%-5s %s' 
    print template % ('page', 'title') 
    for p,t in sorted([(v,k) for k,v in pdf.getDestinationPageNumbers().iteritems()]): 
     print template % (p+1,t) 
1

Darrells Klasse leicht modifiziert, ein Multi-Level-Inhaltsverzeichnis für eine pdf produzieren kann

(in der Art von pdftoc im pdftk Toolkit.) Meine Änderung fügt _setup_page_id_to_num einen weiteren Parameter hinzu, eine Ganzzahl "level", die standardmäßig den Wert 1 hat. Jeder Aufruf inkrementiert den Level. Anstatt nur die Seitennummer im Ergebnis zu speichern, speichern wir das Paar Seitenzahl und Ebene. Bei Verwendung des zurückgegebenen Ergebnisses sollten entsprechende Änderungen vorgenommen werden.

Ich benutze dies, um die "PDF Hacks" Browser-basierte Seite-in-Zeit-Dokument-Viewer mit einem Sidebar-Inhaltsverzeichnis zu implementieren, die LaTeX Abschnitt, Unterabschnitt usw. Lesezeichen enthält. Ich arbeite an einem geteilten System, wo pdftk nicht installiert werden kann, aber wo python verfügbar ist.

0

Dies ist genau das, was ich gesucht habe. Darrells Ergänzungen zu PdfFileReader sollten Teil von PyPDF2 sein.

Ich schrieb ein kleines Rezept, das PyPDF2 und sejda-Konsole verwendet, um eine PDF-Datei nach Lesezeichen zu teilen. In meinem Fall gibt es mehrere Level 1 Abschnitte, die ich zusammenhalten möchte. Dieses Skript erlaubt mir dies zu tun und den resultierenden Dateien sinnvolle Namen zu geben.

import operator 
import os 
import subprocess 
import sys 
import time 

import PyPDF2 as pyPdf 

# need to have sejda-console installed 
# change this to point to your installation 
sejda = 'C:\\sejda-console-1.0.0.M2\\bin\\sejda-console.bat' 

class Darrell(pyPdf.PdfFileReader): 
    ... 

if __name__ == '__main__': 
    t0= time.time() 

    # get the name of the file to split as a command line arg 
    pdfname = sys.argv[1] 

    # open up the pdf 
    pdf = Darrell(open(pdfname, 'rb')) 

    # build list of (pagenumbers, newFileNames) 
    splitlist = [(1,'FrontMatter')] # Customize name of first section 

    template = '%-5s %s' 
    print template % ('Page', 'Title') 
    print '-'*72 
    for t,p in sorted(pdf.getDestinationPageNumbers().iteritems(), 
         key=operator.itemgetter(1)): 

     # Customize this to get it to split where you want 
     if t.startswith('Chapter') or \ 
      t.startswith('Preface') or \ 
      t.startswith('References'): 

      print template % (p+1, t) 

      # this customizes how files are renamed 
      new = t.replace('Chapter ', 'Chapter')\ 
        .replace(': ', '-')\ 
        .replace(': ', '-')\ 
        .replace(' ', '_') 
      splitlist.append((p+1, new)) 

    # call sejda tools and split document 
    call = sejda 
    call += ' splitbypages' 
    call += ' -f "%s"'%pdfname 
    call += ' -o ./' 
    call += ' -n ' 
    call += ' '.join([str(p) for p,t in splitlist[1:]]) 
    print '\n', call 
    subprocess.call(call) 
    print '\nsejda-console has completed.\n\n' 

    # rename the split files 
    for p,t in splitlist: 
     old ='./%i_'%p + pdfname 
     new = './' + t + '.pdf' 
     print 'renaming "%s"\n  to "%s"...'%(old, new), 

     try: 
      os.remove(new) 
     except OSError: 
      pass 

     try: 
      os.rename(old, new) 
      print' succeeded.\n' 
     except: 
      print' failed.\n' 

    print '\ndone. Spliting took %.2f seconds'%(time.time() - t0) 
0

Kleines Update zu @darrell Klasse zu können UTF-8 Umrisse analysieren, die ich als Antwort posten, weil Kommentar wäre schwer zu lesen.

Problem ist in pyPdf.pdf.Destination.title, die in zwei Geschmacksrichtungen zurückgeführt werden kann:

  • pyPdf.generic.TextStringObject
  • pyPdf.generic.ByteStringObject

so dass die Ausgabe von _setup_outline_page_ids() Funktion gibt auch zwei unterschiedliche Typen für title Objekt, das ausfällt mit UnicodeDecodeError, wenn der Gliederungstitel nichts als ASCII enthält.

Ich habe diesen Code, um das Problem zu lösen:

if isinstance(title, pyPdf.generic.TextStringObject): 
    title = title.encode('utf-8') 

der ganzen Klasse:

class PdfOutline(pyPdf.PdfFileReader): 

    def getDestinationPageNumbers(self): 

     def _setup_outline_page_ids(outline, _result=None): 
      if _result is None: 
       _result = {} 
      for obj in outline: 
       if isinstance(obj, pyPdf.pdf.Destination): 
        _result[(id(obj), obj.title)] = obj.page.idnum 
       elif isinstance(obj, list): 
        _setup_outline_page_ids(obj, _result) 
      return _result 

     def _setup_page_id_to_num(pages=None, _result=None, _num_pages=None): 
      if _result is None: 
       _result = {} 
      if pages is None: 
       _num_pages = [] 
       pages = self.trailer["/Root"].getObject()["/Pages"].getObject() 
      t = pages["/Type"] 
      if t == "/Pages": 
       for page in pages["/Kids"]: 
        _result[page.idnum] = len(_num_pages) 
        _setup_page_id_to_num(page.getObject(), _result, _num_pages) 
      elif t == "/Page": 
       _num_pages.append(1) 
      return _result 

     outline_page_ids = _setup_outline_page_ids(self.getOutlines()) 
     page_id_to_page_numbers = _setup_page_id_to_num() 

     result = {} 
     for (_, title), page_idnum in outline_page_ids.iteritems(): 
      if isinstance(title, pyPdf.generic.TextStringObject): 
       title = title.encode('utf-8') 
      result[title] = page_id_to_page_numbers.get(page_idnum, '???') 
     return result