2009-12-16 28 views
5

me gustaría utilizar pyPdf para dividir un archivo pdf basado en el esquema donde cada destino en el esquema se refiere a una página diferente dentro del pdf.dividir un pdf basado en el esquema

ejemplo de esquema:

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

es fácil dentro de pyPdf para iterar sobre cada página del documento o cada destino en el contorno del documento; sin embargo, no puedo entender cómo obtener el número de página donde apunta el destino.

¿Alguien sabe cómo encontrar el número de página de referencia para cada destino en el esquema?

Respuesta

6

lo he descubierto:

 
    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

clase de Darrell puede modificarse ligeramente para producir una tabla de niveles múltiples de contenido de un PDF

(a la manera de pdftoc en el kit de herramientas pdftk). Mi modificación agrega un parámetro más a _setup_page_id_to_num, un "nivel" entero que por defecto es 1. Cada invocación incrementa el nivel. En lugar de almacenar solo el número de página en el resultado, almacenamos el par de número de página y nivel. Se deben aplicar modificaciones apropiadas cuando se usa el resultado devuelto.

Estoy usando esto para implementar el visor de documentos página a página "PDF Hacks" basado en navegador con una tabla de contenido que refleja la sección LaTeX, subsección, etc. marcadores. Estoy trabajando en un sistema compartido donde pdftk no se puede instalar, pero donde python está disponible.

0

Esto es justo lo que estaba buscando. Las adiciones de Darrell a PdfFileReader deberían ser parte de PyPDF2.

Escribí una pequeña receta que usa PyPDF2 y sejda-console para dividir un PDF por marcadores. En mi caso, hay varias secciones de Nivel 1 que quiero mantener juntas. Este script me permite hacer eso y dar nombres significativos a los archivos resultantes.

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

Pequeña actualización a @darrell clase sea capaz de analizar UTF-8 contornos, que he puesto como respuesta porque comentario sería difícil de leer.

problema está en pyPdf.pdf.Destination.title que pueden ser devueltos en dos sabores:

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

modo que la salida de _setup_outline_page_ids() función devuelve también dos tipos diferentes de title objeto, que falla con UnicodeDecodeError si el título del esquema contiene algo más que ASCII.

que añade este código para resolver el problema:

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

de toda la clase:

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 
Cuestiones relacionadas