Detectar y alterar cadenas en archivos PDF.

Quiero poder detectar un patrón en un PDF y marcarlo de alguna manera.

Por ejemplo, en este PDF , está la cadena *2 . Quiero poder analizar el PDF, detectar todas las instancias de *[integer] y hacer algo para llamar la atención sobre las coincidencias (como resaltarlas en amarillo o agregar un símbolo en el margen).

Preferiría hacer esto en Python, pero estoy abierto a otros idiomas. Hasta ahora, he podido usar pyPdf para leer el texto del PDF. Puedo usar una expresión regular para detectar el patrón. Pero no he podido averiguar cómo marcar la coincidencia y volver a guardar el PDF.

O las personas no están interesadas, o Python no es capaz, así que aquí hay una solución en Perl :-). En serio, como se señaló anteriormente, no es necesario “alterar cadenas”. Las anotaciones en PDF son la solución para usted. Tuve un pequeño proyecto con anotaciones no hace mucho, algunos códigos son de allí. Pero, mi analizador de contenido no era universal, y usted no necesita un análisis completo, lo que significa poder modificar el contenido y escribirlo. Por eso recurrí a herramienta externa. La biblioteca de PDF que uso es algo de bajo nivel, pero no me importa. También significa que se espera que uno tenga un conocimiento adecuado de los aspectos internos de PDF para comprender lo que está sucediendo. De lo contrario, basta con utilizar la herramienta.

Aquí hay una foto de marcar, por ejemplo, todos los gerundios en el archivo de OP con un comando

perl pdf_hl.pl -f westlaw.pdf -p '\S*ing'

introduzca la descripción de la imagen aquí

El código (comentario dentro vale la pena leer, también):

 use strict; use warnings; use XML::Simple; use CAM::PDF; use Getopt::Long; use Regexp::Assemble; ##################################################################### # # This is PDF highlight mark-up tool. # Though fully functional, it's still a prototype proof-of-concept. # Please don't feed it with non-pdf files or patterns like '\d*' # (because you probably want '\d+', don't you?). # # Requires muPDF-tools installed and in the PATH, plus some CPAN modules. # # ToDo: # - error handling is primitive if any. # - cropped files (CropBox) are processed incorrectly. Fix it. # - of course there can be other useful parameters. # - allow loading them from file. # - allow searching across lines (eg for multi-word patterns) # and certainly across "spans" within a line (see mudraw output). # - multi-color mark-up, not just yellow. # - control over output file name. # - compress output (use cleanoutput method instead of output, # plus more robust (think compressed object streams) compressors # may be useful). # - file list processing. # - annotations are not just colorful marks on the page, their # dictionaries can contain all sorts of useful information, which may # be extracted automatically further up the food chain ie by # whoever consumes these files (date, time, author, comments, actual # text below, etc., etc., plus think of customized appearence streams, # placing them on layers, etc.. # - ??? # # Most complexity in the code comes from adding appearance # dictionary (AP). You can safely delete it, because most viewers don't # need AP for standard annotations. Ironically, muPDF-viewer wants it # (otherwise highlight placement is not 100% correct), and since I relied # on muPDF-tools, I thought it be proper to create PDFs consumble by # their viewer... Firefox wants AP too, btw. # ##################################################################### my ($file, $csv); my ($c_flag, $w_flag) = (0, 1); GetOptions('-f=s' => \$file, '-p=s' => \$csv, '-c!' => \$c_flag, '-w!' => \$w_flag) and defined($file) and defined($csv) or die "\nUsage: perl $0 -f FILE -p LIST -c -w\n\n", "\tf\t\tFILE\t PDF file to annotate\n", "\tp\t\tLIST\t comma-separated patterns\n", "\tc or -noc\t\t be case sensitive (default = no)\n", "\tw or -now\t\t whole words only (default = yes)\n"; my $re = Regexp::Assemble->new ->add(split(',', $csv)) ->anchor_word($w_flag) ->flags($c_flag ? '' : 'i') ->re; my $xml = qx/mudraw -ttt $file/; my $tree = XMLin($xml, ForceArray => [qw/page block line span char/]); my $pdf = CAM::PDF->new($file); sub __num_nodes_list { my $precision = shift; [ map {CAM::PDF::Node->new('number', sprintf("%.${precision}f", $_))} @_ ] } sub add_highlight { my ($idx, $x1, $y1, $x2, $y2) = @_; my $p = $pdf->getPage($idx); # mirror vertically to get to normal cartesian plane my ($X1, $Y1, $X2, $Y2) = $pdf->getPageDimensions($idx); ($x1, $y1, $x2, $y2) = ($X1 + $x1, $Y2 - $y2, $X1 + $x2, $Y2 - $y1); # corner radius my $r = 2; # AP appearance stream my $s = "/GS0 gs 1 1 0 rg 1 1 0 RG\n"; $s .= "1 j @{[sprintf '%.0f', $r * 2]} w\n"; $s .= "0 0 @{[sprintf '%.1f', $x2 - $x1]} "; $s .= "@{[sprintf '%.1f',$y2 - $y1]} re B\n"; my $highlight = CAM::PDF::Node->new('dictionary', { Subtype => CAM::PDF::Node->new('label', 'Highlight'), Rect => CAM::PDF::Node->new('array', __num_nodes_list(1, $x1 - $r, $y1 - $r, $x2 + $r * 2, $y2 + $r * 2)), QuadPoints => CAM::PDF::Node->new('array', __num_nodes_list(1, $x1, $y2, $x2, $y2, $x1, $y1, $x2, $y1)), BS => CAM::PDF::Node->new('dictionary', { S => CAM::PDF::Node->new('label', 'S'), W => CAM::PDF::Node->new('number', 0), }), Border => CAM::PDF::Node->new('array', __num_nodes_list(0, 0, 0, 0)), C => CAM::PDF::Node->new('array', __num_nodes_list(0, 1, 1, 0)), AP => CAM::PDF::Node->new('dictionary', { N => CAM::PDF::Node->new('reference', $pdf->appendObject(undef, CAM::PDF::Node->new('object', CAM::PDF::Node->new('dictionary', { Subtype => CAM::PDF::Node->new('label', 'Form'), BBox => CAM::PDF::Node->new('array', __num_nodes_list(1, -$r, -$r, $x2 - $x1 + $r * 2, $y2 - $y1 + $r * 2)), Resources => CAM::PDF::Node->new('dictionary', { ExtGState => CAM::PDF::Node->new('dictionary', { GS0 => CAM::PDF::Node->new('dictionary', { BM => CAM::PDF::Node->new('label', 'Multiply'), }), }), }), StreamData => CAM::PDF::Node->new('stream', $s), Length => CAM::PDF::Node->new('number', length $s), }), ), ,0), ), }), }); $p->{Annots} ||= CAM::PDF::Node->new('array', []); push @{$pdf->getValue($p->{Annots})}, $highlight; $pdf->{changes}->{$p->{Type}->{objnum}} = 1 } my $page_index = 1; for my $page (@{$tree->{page}}) { for my $block (@{$page->{block}}) { for my $line (@{$block->{line}}) { for my $span (@{$line->{span}}) { my $string = join '', map {$_->{c}} @{$span->{char}}; while ($string =~ /$re/g) { my ($x1, $y1) = split ' ', $span->{char}->[$-[0]]->{bbox}; my (undef, undef, $x2, $y2) = split ' ', $span->{char}->[$+[0] - 1]->{bbox}; add_highlight($page_index, $x1, $y1, $x2, $y2) } } } } $page_index ++ } $pdf->output($file =~ s/(.{4}$)/++$1/r); __END__ 

Salmo etiqueté la pregunta con ‘Perl’, para tener quizás algún comentario (correcciones de código, etc.) de la comunidad.

Esto no es trivial. El problema es que los archivos PDF no deben ser “actualizados” en nada menos que una página. Básicamente, debe analizar la página, ajustar la representación de PostScript y luego volver a escribirla. No creo que PyPDF tenga el soporte para hacer lo que quieres.

Si “todo” lo que quiere hacer es agregar resaltado, probablemente pueda usar el diccionario de anotaciones. Ver la especificación de PDF para más información.

Es posible que pueda hacer esto usando pyPDF2 pero no lo he investigado detenidamente.