Разбор аннотаций из pdf - PullRequest
26 голосов
/ 09 июля 2009

Мне нужна функция Python, которая принимает PDF-файл и возвращает список текста примечаний к примечанию в документе. Я посмотрел на python-poppler (https://code.launchpad.net/~poppler-python/poppler-python/trunk), но не могу понять, как заставить его получить что-нибудь полезное.

Я нашел метод get_annot_mapping и изменил демонстрационную программу, предоставляемую для его вызова через self.current_page.get_annot_mapping(), но я не знаю, что делать с объектом AnnotMapping. Кажется, он не полностью реализован, предоставляя только метод копирования.

Если есть другие библиотеки, которые предоставляют эту функцию, это тоже хорошо.

Ответы [ 5 ]

18 голосов
/ 20 сентября 2012

На всякий случай, если кто-то ищет какой-нибудь рабочий код. Вот скрипт, который я использую.

import poppler
import sys
import urllib
import os

def main():
  input_filename = sys.argv[1]
    # http://blog.hartwork.org/?p=612
  document = poppler.document_new_from_file('file://%s' % \
    urllib.pathname2url(os.path.abspath(input_filename)), None)
  n_pages = document.get_n_pages()
  all_annots = 0

  for i in range(n_pages):
        page = document.get_page(i)
        annot_mappings = page.get_annot_mapping ()
        num_annots = len(annot_mappings)
        if num_annots > 0:
            for annot_mapping in annot_mappings:
                if  annot_mapping.annot.get_annot_type().value_name != 'POPPLER_ANNOT_LINK':
                    all_annots += 1
                    print 'page: {0:3}, {1:10}, type: {2:10}, content: {3}'.format(i+1, annot_mapping.annot.get_modified(), annot_mapping.annot.get_annot_type().value_nick, annot_mapping.annot.get_contents())

  if all_annots > 0:
    print str(all_annots) + " annotation(s) found"
  else:
    print "no annotations found"

if __name__ == "__main__":
    main()
3 голосов
/ 13 июля 2009

Оказывается, привязки были неполными. Это сейчас исправлено. https://bugs.launchpad.net/poppler-python/+bug/397850

1 голос
/ 22 февраля 2019

Вы должны ОПРЕДЕЛЕННО взглянуть на PyPDF2. Эта удивительная библиотека обладает невероятным потенциалом, вы можете извлечь что угодно из PDF, включая изображения или комментарии. Попробуйте начать с изучения того, что Acrobat Reader DC (Reader) может дать вам в комментариях PDF. Возьмите простой PDF, аннотируйте его (добавьте несколько комментариев) с помощью Reader и на вкладке комментариев в правом верхнем углу щелкните три горизонтальные точки и нажмите Export All To Data File... и выберите формат с расширением xfdf. Это создает замечательный XML-файл, который вы можете анализировать. Формат очень прозрачный и самоочевидный.

Если, однако, вы не можете полагаться на то, что пользователь щелкает по этой кнопке, и вместо этого вам нужно программно извлекать те же данные из PDF-файла с использованием python, не отчаивайтесь, есть решение. (Вдохновленный Извлечение изображений из PDF без передискретизации, в python? )

Необходимые условия:

PyPDF2 (pip install PyPDF2)

То, что Reader дает вам в вышеупомянутом файле xfdf, выглядит следующим образом:

<?xml version="1.0" ?>
<xfdf xml:space="preserve" xmlns="http://ns.adobe.com/xfdf/">
    <annots>
        <caret IT="Replace" color="#0000FF" creationdate="D:20190221151519+01'00'" date="D:20190221151526+01'00'" flags="print" fringe="1.069520,1.069520,1.069520,1.069520" name="72f8d1b7-d878-4281-bd33-3a6fb4578673" page="0" rect="636.942000,476.891000,652.693000,489.725000" subject="Inserted Text" title="Admin">
            <contents-richtext>
                <body xfa:APIVersion="Acrobat:19.10.0" xfa:spec="2.0.2" xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
                    <p dir="ltr">
                        <span dir="ltr" style="font-size:10.5pt;text-align:left;color:#000000;font-weight:normal;font-style:normal"> comment1</span>
                    </p>
                </body>
            </contents-richtext>
            <popup flags="print,nozoom,norotate" open="no" page="0" rect="737.008000,374.656000,941.008000,488.656000"/>
        </caret>
        <highlight color="#FFD100" coords="183.867000,402.332000,220.968000,402.332000,183.867000,387.587000,220.968000,387.587000" creationdate="D:20190221151441+01'00'" date="D:20190221151448+01'00'" flags="print" name="a18c7fb0-0af3-435e-8c32-1af2af3c46ea" opacity="0.399994" page="0" rect="179.930000,387.126000,224.904000,402.793000" subject="Highlight" title="Admin">
            <contents-richtext>
                <body xfa:APIVersion="Acrobat:19.10.0" xfa:spec="2.0.2" xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
                    <p dir="ltr">
                        <span dir="ltr" style="font-size:10.5pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">comment2</span>
                    </p>
                </body>
            </contents-richtext>
            <popup flags="print,nozoom,norotate" open="no" page="0" rect="737.008000,288.332000,941.008000,402.332000"/>
        </highlight>
        <caret color="#0000FF" creationdate="D:20190221151452+01'00'" date="D:20190221151452+01'00'" flags="print" fringe="0.828156,0.828156,0.828156,0.828156" name="6bf0226e-a3fb-49bf-bc89-05bb671e1627" page="0" rect="285.877000,372.978000,298.073000,382.916000" subject="Inserted Text" title="Admin">
            <popup flags="print,nozoom,norotate" open="no" page="0" rect="737.008000,268.088000,941.008000,382.088000"/>
        </caret>
        <strikeout IT="StrikeOutTextEdit" color="#0000FF" coords="588.088000,497.406000,644.818000,497.406000,588.088000,477.960000,644.818000,477.960000" creationdate="D:20190221151519+01'00'" date="D:20190221151519+01'00'" flags="print" inreplyto="72f8d1b7-d878-4281-bd33-3a6fb4578673" name="6686b852-3924-4252-af21-c1b10390841f" page="0" rect="582.290000,476.745000,650.616000,498.621000" replyType="group" subject="Cross-Out" title="Admin">
            <popup flags="print,nozoom,norotate" open="no" page="0" rect="737.008000,383.406000,941.008000,497.406000"/>
        </strikeout>
    </annots>
    <f href="p1.pdf"/>
    <ids modified="ABB10FA107DAAA47822FB5D311112349" original="474F087D87E7E544F6DEB9E0A93ADFB2"/>
</xfdf>

Различные типы комментариев представлены здесь в виде тегов в блоке <annots>. Python может дать вам почти те же данные. Чтобы получить его, взгляните на то, что дает вывод следующего скрипта:

import sys
import PyPDF2, traceback

try :
    src = sys.argv[1]
except :
    src = r'/path/to/my/file.pdf'


input1 = PyPDF2.PdfFileReader(open(src, "rb"))
nPages = input1.getNumPages()

for i in range(nPages) :
    page0 = input1.getPage(i)
    try :
        for annot in page0['/Annots'] :
            print annot.getObject()       # (1)
            print ''
    except : 
        # there are no annotations on this page
        pass

Вывод для того же файла, что и в файле xfdf выше, будет выглядеть следующим образом:

{'/Popup': IndirectObject(192, 0), '/M': u"D:20190221151448+01'00'", '/CreationDate': u"D:20190221151441+01'00'", '/NM': u'a18c7fb0-0af3-435e-8c32-1af2af3c46ea', '/F': 4, '/C': [1, 0.81961, 0], '/Rect': [179.93, 387.126, 224.904, 402.793], '/Type': '/Annot', '/T': u'Admin', '/RC': u'<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:19.10.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:10.5pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">comment2</span></p></body>', '/P': IndirectObject(5, 0), '/Contents': u'otrasneho', '/QuadPoints': [183.867, 402.332, 220.968, 402.332, 183.867, 387.587, 220.968, 387.587], '/Subj': u'Highlight', '/CA': 0.39999, '/AP': {'/N': IndirectObject(202, 0)}, '/Subtype': '/Highlight'}

{'/Parent': IndirectObject(191, 0), '/Rect': [737.008, 288.332, 941.008, 402.332], '/Type': '/Annot', '/F': 28, '/Open': <PyPDF2.generic.BooleanObject object at 0x02A425D0>, '/Subtype': '/Popup'}

{'/Popup': IndirectObject(194, 0), '/M': u"D:20190221151452+01'00'", '/CreationDate': u"D:20190221151452+01'00'", '/NM': u'6bf0226e-a3fb-49bf-bc89-05bb671e1627', '/F': 4, '/C': [0, 0, 1], '/Subj': u'Inserted Text', '/Rect': [285.877, 372.978, 298.073, 382.916], '/Type': '/Annot', '/P': IndirectObject(5, 0), '/AP': {'/N': IndirectObject(201, 0)}, '/RD': [0.82816, 0.82816, 0.82816, 0.82816], '/T': u'Admin', '/Subtype': '/Caret'}

{'/Parent': IndirectObject(193, 0), '/Rect': [737.008, 268.088, 941.008, 382.088], '/Type': '/Annot', '/F': 28, '/Open': <PyPDF2.generic.BooleanObject object at 0x02A42830>, '/Subtype': '/Popup'}

{'/Popup': IndirectObject(196, 0), '/M': u"D:20190221151519+01'00'", '/CreationDate': u"D:20190221151519+01'00'", '/NM': u'6686b852-3924-4252-af21-c1b10390841f', '/F': 4, '/IRT': IndirectObject(197, 0), '/C': [0, 0, 1], '/Rect': [582.29, 476.745, 650.616, 498.621], '/Type': '/Annot', '/T': u'Admin', '/P': IndirectObject(5, 0), '/QuadPoints': [588.088, 497.406, 644.818, 497.406, 588.088, 477.96, 644.818, 477.96], '/Subj': u'Cross-Out', '/IT': '/StrikeOutTextEdit', '/AP': {'/N': IndirectObject(200, 0)}, '/RT': '/Group', '/Subtype': '/StrikeOut'}

{'/Parent': IndirectObject(195, 0), '/Rect': [737.008, 383.406, 941.008, 497.406], '/Type': '/Annot', '/F': 28, '/Open': <PyPDF2.generic.BooleanObject object at 0x02A42AF0>, '/Subtype': '/Popup'}

{'/Popup': IndirectObject(198, 0), '/M': u"D:20190221151526+01'00'", '/CreationDate': u"D:20190221151519+01'00'", '/NM': u'72f8d1b7-d878-4281-bd33-3a6fb4578673', '/F': 4, '/C': [0, 0, 1], '/Rect': [636.942, 476.891, 652.693, 489.725], '/Type': '/Annot', '/RD': [1.06952, 1.06952, 1.06952, 1.06952], '/T': u'Admin', '/RC': u'<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:19.10.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:10.5pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">comment1</span></p></body>', '/P': IndirectObject(5, 0), '/Contents': u' pica', '/Subj': u'Inserted Text', '/IT': '/Replace', '/AP': {'/N': IndirectObject(212, 0)}, '/Subtype': '/Caret'}

{'/Parent': IndirectObject(197, 0), '/Rect': [737.008, 374.656, 941.008, 488.656], '/Type': '/Annot', '/F': 28, '/Open': <PyPDF2.generic.BooleanObject object at 0x02A42AB0>, '/Subtype': '/Popup'}

Если вы изучите выход, вы поймете, что все выходы более или менее одинаковы. Каждый комментарий в файле xfdf имеет два аналога в выводе PyPDF2 в python. Атрибут /C - это цвет подсветки в RGB, масштабированный до значений с плавающей точкой в ​​диапазоне <0, 1>. /Rect определяет ограничивающую рамку комментария на странице / развороте в точках (1/72 дюйма) относительно нижнего левого угла страницы, увеличивая значения вправо и вверх. /M и /CreationDate изменяются и время создания, /QuadPoints - это массив [x1, y1, x2, y2, ..., xn, yn] координат строки вокруг комментария, /Subject, /Type, /SubType, /IT идентифицируют тип комментарий, /T, вероятно, является создателем, /RC - это xhtml-представление текста комментария, если таковой имеется. Если есть нарисованный чернилами комментарий, он будет представлен здесь как имеющий атрибут /InkList с данными в форме [[L1x1, L1y1, L1x2, L1y2, ..., L1xn, L1yn], [L2x1, L2y1, ..., L2xn, L2yn], ..., [Lmx1, Lmy1, ..., Lmxn, Lmyn]] для строки 1, строки 2, ..., строки m.

Для более подробного объяснения различных полей, которые вы получаете из getObject() в данном коде Python, помеченном как строка (1), пожалуйста, обратитесь к https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf и особенно к разделу 12.5 Аннотации, начиная со страниц 381–413 .

1 голос
/ 15 января 2018

Кто-то задал похожий вопрос . Я попробовал пример кода там, и он не работал для меня, пока я не сделал несколько функциональных и косметических изменений.

#!/usr/bin/ruby

require 'pdf-reader'

ARGV.each do |filename|
  PDF::Reader.open(filename) do |reader|
    puts "file: #{filename}"
    puts "page\tcomment"
    reader.pages.each do |page|
      annots_ref = page.attributes[:Annots]
      if annots_ref
        actual_annots = annots_ref.map { |a| reader.objects[a] }
        actual_annots.each do |actual_annot|
          unless actual_annot[:Contents].nil?
            puts "#{page.number}\t#{actual_annot[:Contents]}"
          end
        end
      end
    end       
  end
end

Если сохранено как pdfannot.rb, chmod +x 'ed и помещено в ваш любимый каталог PATH, использование:

./pdfannot.rb <path>

Первый раз пишу / редактирую / ремикширую код Ruby, поэтому очень открыт для предложений НТН.

Кстати, обнаружение этого вопроса раньше могло бы спасти меня от двойной работы. Надеюсь, что в будущем этому вопросу будет уделено больше внимания, так что его будет легче найти.

1 голос
/ 10 июля 2009

Я никогда не использовал это, и я не хотел такого рода функций, но я нашел PDFMiner - эта ссылка содержит информацию об основном использовании, может быть, это то, что вы ищете?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...