Текущее время: Сб, фев 22 2020, 19:24

Часовой пояс: UTC + 4 часа


Правила форума


ВНИМАНИЕ!

Вопросы по SAP Query и Quick View - сюда



Начать новую тему Ответить на тему  [ Сообщений: 18 ]  На страницу 1, 2  След.
Автор Сообщение
 Заголовок сообщения: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Сб, июл 18 2009, 18:42 
Специалист
Специалист
Аватара пользователя

Зарегистрирован:
Вт, июн 02 2009, 23:28
Сообщения: 228
Откуда: MOW
Пол: Мужской
Понадобилось автоматически загружать в SAP информацию из счета-фактуры. Но поставщик присылает счета только в формате PDF.
Поэтому пришлось написать небольшой парсер PDF.
Естественно, можно использовать только для файлов, содержащих текст (иногда в PDF бывает просто картинка, тогда без вариантов).

Прилагаю исходный код парсера в виде отдельного инклуда (оформлен в виде класса).
Также прилагаю небольшой пример для демонстрации возможностей (к сожалению, без файла PDF).

Принцип парсера заключается в том, что он на каждой странице файла PDF считывает все текстовые элементы: их текст, их координаты на странице, а также шрифт. Все это записывается во внутреннюю таблицу элементов ( с помощью внутреннего метода parse ).
Для удобства работы с данной таблицей предоставляется ряд функций: поиск текста, поиск текста по координатам, поиск текста под другим текстом и т.д.

Код самого парсера:
Code:
*&---------------------------------------------------------------------*
*&  Include           ZMM_PDF_PARSER
*&---------------------------------------------------------------------*

* Текстовый элемент, загруженный из PDF
TYPES: BEGIN OF t_text_element,
         num_element   TYPE I,
         num_page      TYPE I,
         X             TYPE I,
         Y             TYPE I,
         font(20)      TYPE C,
         text          TYPE STRING,
         textUcase     TYPE STRING,
       END OF t_text_element.

TYPES: tt_text_element TYPE t_text_element OCCURS 0.

* Класс для парсинга PDF (разделение и структурирование его текстовых элементов) и
* удобной работы с данными элементами
CLASS lcl_pdf_parser DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: try_read_number IMPORTING
                                     pi_number TYPE ANY
                                   CHANGING
                                     pc_number TYPE ANY
                                   EXCEPTIONS
                                     INVALID_FORMAT.
    METHODS: load_pdf_from_server IMPORTING
                                    pi_filename TYPE C
                                  EXCEPTIONS
                                    ERROR_IN_LOADING
                                    ERROR_IN_PARSING,
             load_pdf_from_gui    IMPORTING
                                    pi_filename TYPE C
                                  EXCEPTIONS
                                    ERROR_IN_LOADING
                                    ERROR_IN_PARSING,
             load_pdf_from_itab   IMPORTING
                                    pi_it_filedata   TYPE STANDARD TABLE
                                  EXCEPTIONS
                                    ERROR_IN_PARSING,
             is_loaded EXPORTING
                         pe_loaded TYPE C,
             get_num_pages EXPORTING
                             pe_num_pages TYPE I,
             get_all_elements EXPORTING
                                pe_elements TYPE tt_text_element,
             find_text IMPORTING
                         pi_num_page   TYPE I DEFAULT 1
                         pi_text       TYPE C
                         pi_match_case TYPE C DEFAULT SPACE
                       EXPORTING
                         pe_elements   TYPE tt_text_element,
             find_text_below IMPORTING
                               pi_element   TYPE t_text_element
                               pi_accuracy  TYPE I
                             EXPORTING
                               pe_elements  TYPE tt_text_element,
             find_text_right IMPORTING
                               pi_element   TYPE t_text_element
                               pi_accuracy  TYPE I
                             EXPORTING
                               pe_elements  TYPE tt_text_element,
             find_text_in_box IMPORTING
                                pi_num_page TYPE I
                                pi_left     TYPE I
                                pi_top      TYPE I
                                pi_right    TYPE I
                                pi_bottom   TYPE I
                              EXPORTING
                                pe_elements TYPE tt_text_element.

  PRIVATE SECTION.
    DATA: mt_filelines      TYPE TABLE OF STRING,
          mt_elements       TYPE TABLE OF t_text_element,
          m_num_pages       TYPE I.

    METHODS: parse.
ENDCLASS.

CLASS lcl_pdf_parser IMPLEMENTATION.

  " Загружает файл PDF из папки на сервере
  METHOD load_pdf_from_server.
    DATA: BEGIN OF lwa_xline,
            XLINE(3000) TYPE X,
          END OF lwa_xline.
    DATA: lh_all_file TYPE STRING,
          lh_len   TYPE I,
          lh_len1  TYPE I,
          lh_line(3000) TYPE C.
    DATA: c_conv TYPE REF TO CL_ABAP_CONV_IN_CE.

    " Загружаем

    OPEN DATASET pi_filename FOR INPUT IN BINARY MODE.
    IF sy-subrc <> 0.
      "PERFORM log_msg USING '' '' 'ZCAT' c_error c_important '402' pi_filename '' '' ''.
      RAISE ERROR_IN_LOADING.
    ENDIF.

    DO.
      CLEAR lh_len.
      READ DATASET pi_filename INTO lwa_xline-xline ACTUAL LENGTH lh_len.
      IF lh_len > 0.
        c_conv = cl_abap_conv_in_ce=>create( input       = lwa_xline-xline
                                             replacement = space
                                             encoding    = '1504' ).
        c_conv->read( EXPORTING n = lh_len IMPORTING data = lh_line len = lh_len1 ).
        CONCATENATE lh_all_file lh_line INTO lh_all_file RESPECTING BLANKS.
      ENDIF.
      IF sy-subrc <> 0.
        EXIT.
      ENDIF.
    ENDDO.

    FREE c_conv.

    REFRESH mt_filelines.
    SPLIT lh_all_file AT CL_ABAP_CHAR_UTILITIES=>CR_LF+1(1) INTO TABLE mt_filelines.
    CLOSE DATASET pi_filename.

    " Парсим
    CALL METHOD parse( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.     "    METHOD load_pdf_from_server.

  " Загружает файл PDF в GUI
  METHOD load_pdf_from_gui.

    " Загружаем

    REFRESH mt_filelines[].

    CALL FUNCTION 'GUI_UPLOAD'
      EXPORTING
        filename                = pi_filename
      TABLES
        data_tab                = mt_filelines[]
      EXCEPTIONS
        file_open_error         = 1
        file_read_error         = 2
        no_batch                = 3
        gui_refuse_filetransfer = 4
        invalid_type            = 5
        no_authority            = 6
        unknown_error           = 7
        bad_data_format         = 8
        header_not_allowed      = 9
        separator_not_allowed   = 10
        header_too_long         = 11
        unknown_dp_error        = 12
        access_denied           = 13
        dp_out_of_memory        = 14
        disk_full               = 15
        dp_timeout              = 16
        OTHERS                  = 17.

    IF sy-subrc <> 0.
      RAISE ERROR_IN_LOADING.
    ENDIF.

    " Парсим
    CALL METHOD parse( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.     "    METHOD load_pdf_from_gui.

  " Загружает файл PDF, предварительно загруженный во внутреннюю таблицу
  METHOD load_pdf_from_itab.
    REFRESH mt_filelines[].

    mt_filelines[] = pi_it_filedata[].

    IF mt_filelines[] IS INITIAL.
      RAISE ERROR_IN_PARSING.
    ENDIF.

    " Парсим
    CALL METHOD parse( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.   "  load_pdf_from_itab

  " Выполняет парсинг загруженного PDF
  METHOD parse.

    DATA: lit_str            TYPE TABLE OF STRING,
          lh_str             TYPE STRING,
          lwa_element        TYPE t_text_element,
          lh_line            TYPE STRING,
          lh_in_obj(1)       TYPE C,
          lh_in_stream(1)    TYPE C,
          lh_in_text(1)      TYPE C,
          lh_font            TYPE STRING,
          lh_X               TYPE I,
          lh_Y               TYPE I,
          lh_len             TYPE I,
          lh_num             TYPE I.

    CHECK mt_filelines[] IS NOT INITIAL.

    REFRESH mt_elements[].

    " Проверяем, что это действительно PDF - это должно быть написано в начале файла
    READ TABLE mt_filelines INDEX 1 INTO lh_line.
    IF lh_line(4) <> '%PDF'.
      RETURN.
    ENDIF.

    m_num_pages = 1.

    LOOP AT mt_filelines INTO lh_line.
      CONDENSE lh_line.
      SPLIT lh_line AT SPACE INTO TABLE lit_str.

      " Если мы не внутри блока текста, отслеживаем его начало
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text <> 'X'
        AND lh_line = 'BT'.

        lh_in_text = 'X'.
        CLEAR: lh_font, lh_X, lh_Y.
      ENDIF.

      " Если мы не внутри stream, отслеживаем его начало
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X' AND lh_line = 'stream'.
        lh_in_stream = 'X'.
      ENDIF.

      " Если мы не внутри объекта, отслеживаем начало объекта
      IF lh_in_obj <> 'X'.
        IF LINES( lit_str ) >= 3.
          READ TABLE lit_str INDEX 3 INTO lh_str.
          IF lh_str = 'obj'.
            lh_in_obj = 'X'.
          ENDIF.
        ENDIF.
      ENDIF.

      " Отслеживаем начало новой страницы
      IF lh_in_obj = 'X' AND lh_line = '<< /Type /Page'.
        m_num_pages = m_num_pages + 1.
        CLEAR: lh_X, lh_Y.
      ENDIF.

      " Отслеживаем тип строки текста по последним 2 символам и считываем
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text = 'X'.
        lh_str = lh_line.
        lh_len = strlen( lh_str ).
        IF lh_len >= 2.
          IF lh_len > 2.
            lh_len = lh_len - 2.
            SHIFT lh_str BY lh_len PLACES LEFT.
          ENDIF.

          CASE lh_str.
            WHEN 'Tf'.   " Шрифт        [ /F410130 8 Tf
              READ TABLE lit_str INDEX 1 INTO lh_font.
            WHEN 'Td'.   " Позиционирование        [ -499 63 Td

              " X
              READ TABLE lit_str INDEX 1 INTO lh_str.
              CALL METHOD try_read_number EXPORTING
                                            pi_number = lh_str
                                          CHANGING
                                            pc_number = lh_num
                                          EXCEPTIONS
                                            INVALID_FORMAT = 1.
              IF sy-subrc = 0.
                lh_X = lh_X + lh_num.
              ENDIF.

              " Y
              READ TABLE lit_str INDEX 2 INTO lh_str.
              CALL METHOD try_read_number EXPORTING
                                            pi_number = lh_str
                                          CHANGING
                                            pc_number = lh_num
                                          EXCEPTIONS
                                            INVALID_FORMAT = 1.
              IF sy-subrc = 0.
                lh_Y = lh_Y + lh_num.
              ENDIF.

            WHEN 'Tj'.   " Текст        [ (AGCO Parts Division) Tj

              lh_str = lh_line.
              SHIFT lh_str BY 1 PLACES LEFT.
              lh_len = strlen( lh_str ).
              lh_len = lh_len - 4.
              lh_str = lh_str(lh_len).
              "SHIFT lh_str RIGHT DELETING TRAILING ') Tj'.
              "SHIFT lh_str BY 4 PLACES LEFT.

              " Добавляем текстовый элемент
              lwa_element-num_page  = m_num_pages.
              lwa_element-X         = lh_X.
              lwa_element-Y         = lh_Y.
              lwa_element-font      = lh_font.
              lwa_element-text      = lh_str.
              lwa_element-textUcase = lwa_element-text.
              TRANSLATE lwa_element-textUcase TO UPPER CASE.
              APPEND lwa_element TO mt_elements.
          ENDCASE.
        ENDIF.
      ENDIF.

      " Если мы внутри блока текста, то отслеживаем его окончание
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text = 'X'
        AND lh_line = 'ET'.

        CLEAR lh_in_text.
      ENDIF.

      " Если мы внутри stream, отслеживаем ее окончание
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text <> 'X'
        AND lh_line = 'endstream'.

        CLEAR lh_in_stream.
      ENDIF.

      " Если мы внутри объекта, отслеживаем окончание объекта
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X' AND lh_in_text <> 'X'
        AND lh_line = 'endobj'.

        CLEAR lh_in_obj.
      ENDIF.
    ENDLOOP.

    " Сортируем, чтобы все текстовые надписи шли сверху вниз и слева направо
    SORT mt_elements BY num_page y DESCENDING x.

    LOOP AT mt_elements INTO lwa_element.
      lwa_element-num_element = sy-tabix.
      MODIFY mt_elements FROM lwa_element.
    ENDLOOP.

    m_num_pages = m_num_pages - 1.

    IF mt_elements[] IS INITIAL.
      m_num_pages = 0.
    ENDIF.
  ENDMETHOD.    "   METHOD parse

  " Читает число из строки, если неверный формат, то генерит exception
  METHOD try_read_number.
    DATA: lh_number  TYPE STRING.

    lh_number = pi_number.
    REPLACE ',' IN lh_number WITH '.'.

    CATCH SYSTEM-EXCEPTIONS
      CONVERSION_ERRORS = 1.

      pc_number = lh_number.
    ENDCATCH.
    IF sy-subrc <> 0.
      RAISE INVALID_FORMAT.
    ENDIF.
  ENDMETHOD.       "    try_read_number

  " Возвращает X если PDF загружен и пусто в противном случае
  METHOD is_loaded.
    IF mt_elements[] IS NOT INITIAL.
      pe_loaded = 'X'.
    ELSE.
      CLEAR pe_loaded.
    ENDIF.
  ENDMETHOD.       "    is_loaded

  " Возвращает количество страниц в документе
  METHOD get_num_pages.
    pe_num_pages = m_num_pages.
  ENDMETHOD.     "    get_num_pages

  " Возвращает список всех элементов, отсортированный по страницам, сверху вниз и
  " слева направо
  METHOD get_all_elements.
    pe_elements = mt_elements.
  ENDMETHOD.        "   get_all_elements

  " Возвращает элементы, содержащие определенный текст
  "  pi_num_page   - номер страницы, если 0, то искать на всех страницах;
  "  pi_text       - искомый текст;
  "  pi_match_case - учитывать ли большие-маленькие буквы или искать невзирая на них;
  "  pe_elements   - возвращает список найденных текстовых элементов или пустую таблицу,
  "                  если такой текст не найден.
  METHOD find_text.
    DATA: lr_page            TYPE RANGE OF t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_text            TYPE RANGE OF t_text_element-text,
          lwa_r_text         LIKE LINE OF lr_text,
          lr_textUcase       TYPE RANGE OF t_text_element-textUcase,
          lwa_r_textUcase    LIKE LINE OF lr_textUcase,
          lwa_element        TYPE t_text_element,
          lh_text            TYPE STRING.

    REFRESH pe_elements[].

    IF pi_num_page IS NOT INITIAL.
      lwa_r_page-option = 'EQ'.
      lwa_r_page-sign   = 'I'.
      lwa_r_page-low    = pi_num_page.
      APPEND lwa_r_page TO lr_page.
    ENDIF.

    IF pi_text IS NOT INITIAL.
      IF pi_match_case = 'X'.
        lwa_r_text-option = 'EQ'.
        lwa_r_text-sign   = 'I'.
        lwa_r_text-low    = pi_text.
        APPEND lwa_r_text TO lr_text.
      ELSE.
        lh_text = pi_text.
        TRANSLATE lh_text TO UPPER CASE.

        lwa_r_textUcase-option = 'EQ'.
        lwa_r_textUcase-sign   = 'I'.
        lwa_r_textUcase-low    = lh_text.
        APPEND lwa_r_textUcase TO lr_textUcase.
      ENDIF.
    ENDIF.

    LOOP AT mt_elements INTO lwa_element
      WHERE num_page   IN lr_page
        AND text       IN lr_text
        AND textUcase  IN lr_textUcase.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.      "    find_text

  " Возвращает элементы, находящиеся под данным элементом, сравнивается левый верхний угол
  " элемента с левыми верхними углами других элементов на той же странице.
  "  pi_element  - текстовый элемент, под которым нужно искать;
  "  pi_accuracy - диапазон поиска влево и вправо от координаты X левого верхнего угла
  "                элемента при сравнении с Х-координатой других элементов;
  "  pe_elements - найденные элементы.
  METHOD find_text_below.
    DATA: lr_page            TYPE RANGE OF t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE t_text_element.

    REFRESH pe_elements[].

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_element-num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'BT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = pi_element-X - pi_accuracy.
    lwa_r_X-high   = pi_element-X + pi_accuracy.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'LT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = pi_element-Y.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      FROM pi_element-num_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.      "    find_text_below

  " Возвращает элементы, находящиеся справа от данного элемента,
  " сравнивается левый верхний угол элемента с левыми верхними углами других
  " элементов на той же странице.
  "  pi_element  - текстовый элемент, справа от которого нужно искать;
  "  pi_accuracy - диапазон поиска вниз и вверх от координаты Y левого верхнего угла
  "                элемента при сравнении с Y-координатой других элементов;
  "  pe_elements - найденные элементы.
  METHOD find_text_right.
    DATA: lr_page            TYPE RANGE OF t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE t_text_element.

    REFRESH pe_elements[].

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_element-num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'GT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = pi_element-X.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'BT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = pi_element-Y - pi_accuracy.
    lwa_r_Y-high   = pi_element-Y + pi_accuracy.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.        "     find_text_right

  " Возвращает элементы, находящиеся внутри прямоугольника на данной странице
  METHOD find_text_in_box.
    DATA: lr_page            TYPE RANGE OF t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE t_text_element,
          lh_left            TYPE I,
          lh_right           TYPE I,
          lh_top             TYPE I,
          lh_bottom          TYPE I,
          lh_num             TYPE I.

    REFRESH pe_elements[].

    lh_left   = pi_left.
    lh_top    = pi_top.
    lh_right  = pi_right.
    lh_bottom = pi_bottom.

    IF lh_left > lh_right.
      lh_num = lh_left.
      lh_left = lh_right.
      lh_right = lh_num.
    ENDIF.

    IF lh_top > lh_bottom.
      lh_num = lh_bottom.
      lh_bottom = lh_top.
      lh_top = lh_num.
    ENDIF.

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'BT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = lh_left.
    lwa_r_X-high   = lh_right.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'BT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = lh_top.
    lwa_r_Y-high   = lh_bottom.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.       "    find_text_in_box

ENDCLASS.


Теперь привожу пример чтения PDF
Предположим, у нас счет-фактура. Нужно прочитать номер этого счета-фактуры и его дату.
Номер располагается на первой странице под надписью "No de. Facture", дата располагается также на первой странице под надписью "Date emission".

Код примера:
Code:
PROGRAM ZZP_PDF_PARSER_DEMO.

PERFORM pdf_parser_demo.

INCLUDE ZMM_PDF_PARSER.   " В данном инклуде сам парсер, код которого приведен выше

FORM pdf_parser_demo.
  DATA: lc_pdf_parser TYPE REF TO lcl_pdf_parser,
        lt_elements   TYPE TABLE OF t_text_element WITH HEADER LINE,
        lt_elements2  TYPE TABLE OF t_text_element WITH HEADER LINE,
        l_left        TYPE I,
        l_top         TYPE I,
        l_right       TYPE I,
        l_bottom      TYPE I,
        l_invnumb     TYPE STRING,
        l_invdate     TYPE STRING,
        l_text        TYPE STRING.

  CREATE OBJECT lc_pdf_parser.
*  lc_pdf_parser->load_pdf_from_server( EXPORTING
*                                         pi_filename = 'D:\USR\SAP\PUT\CAT\AG\INBOX\20090619\XA0189872.pdf'
*                                       EXCEPTIONS
*                                         ERROR_IN_LOADING = 1
*                                     ).

* Загрузка файла PDF с локального компьютера, есть также возможность с сервера или из внутренней таблицы
  lc_pdf_parser->load_pdf_from_gui( EXPORTING
                                      pi_filename = 'C:\_toarchive\444\sf1.pdf'
                                    EXCEPTIONS
                                      ERROR_IN_LOADING = 1 ).

* Поиск текста на странице. Поиск по маске пока не предусмотрен, но теоретически это несложно доработать,
* но у меня такой необходимости не было
  lc_pdf_parser->find_text( EXPORTING
                              pi_num_page   = 1
                              pi_text       = 'No. de facture'
                              pi_match_case = SPACE
                            IMPORTING
                              pe_elements   = lt_elements[] ).

  IF lt_elements[] IS INITIAL.
    MESSAGE 'Файл не является счетом-фактурой от поставщика' TYPE 'I'.
    FREE lc_pdf_parser.
    RETURN.
  ENDIF.

  READ TABLE lt_elements INDEX 1.

* Идем текст непосредственно под надписью No. de facture, координаты которой нашли раньше
* pi_accuracy - это диапазон поиска по координате X, например если текст на 2 единицы вправо, то
*               визуально он все равно находится под надписью
  lc_pdf_parser->find_text_below( EXPORTING
                                    pi_element  = lt_elements
                                    pi_accuracy = 10
                                  IMPORTING
                                    pe_elements = lt_elements2[] ).

  IF lt_elements2[] IS INITIAL.
    MESSAGE 'Неверный формат файла, номер счета-фактуры не найден' TYPE 'I'.
    FREE lc_pdf_parser.
    RETURN.
  ENDIF.

  READ TABLE lt_elements2 INDEX 1.
  l_invnumb = lt_elements2-text.

* Аналогично ищем другую надпись
  lc_pdf_parser->find_text( EXPORTING
                              pi_num_page   = 1
                              pi_text       = 'Date Emission'
                              pi_match_case = SPACE
                            IMPORTING
                              pe_elements   = lt_elements[] ).

  IF lt_elements[] IS INITIAL.
    MESSAGE 'Файл не является счетом-фактурой от поставщика' TYPE 'I'.
    FREE lc_pdf_parser.
    RETURN.
  ENDIF.

* Демонстрация другой возможности - поиска всех текстов в прямоугольнике
* Координаты: слева направо идет возрастание X, а снизу вверх возрастание Y (а не наоборот),
*             как в школе по математике. Точка (0,0) располагается в левом нижнем углу
  READ TABLE lt_elements INDEX 1.

  l_left   = lt_elements-X - 10.
  l_top    = lt_elements-Y - 3.
  l_right  = lt_elements-X + 10.
  l_bottom = lt_elements-Y - 30.

  lc_pdf_parser->find_text_in_box( EXPORTING
                                    pi_num_page = 1
                                    pi_left     = l_left
                                    pi_top      = l_top
                                    pi_right    = l_right
                                    pi_bottom   = l_bottom
                                  IMPORTING
                                    pe_elements = lt_elements2[] ).

  IF lt_elements2[] IS INITIAL.
    MESSAGE 'Неверный формат файла, дата счета-фактуры не найдена' TYPE 'I'.
    FREE lc_pdf_parser.
    RETURN.
  ENDIF.

  READ TABLE lt_elements2 INDEX 1.
  l_invdate = lt_elements2-text.

  CONCATENATE 'Файл PDF успешно прочитан, № СФ =' l_invnumb ', дата СФ =' l_invdate INTO l_text SEPARATED BY SPACE.
  MESSAGE l_text TYPE 'I'.

  FREE lc_pdf_parser.
ENDFORM.          "     pdf_parser_demo


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Ср, июл 22 2009, 16:39 
Почетный гуру
Почетный гуру
Аватара пользователя

Зарегистрирован:
Вт, май 30 2006, 09:34
Сообщения: 1898
Ого!
Спасибо.
Пока не требуется, но сохраняю себе на память.

_________________
С уважением.


Принять этот ответ
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, июн 02 2011, 14:10 
Начинающий
Начинающий

Зарегистрирован:
Чт, мар 17 2011, 18:05
Сообщения: 17
Не могли бы вы подсказать как получить физический размер страницы? Так же получает Adobe Reader в свойствах файла.


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, июн 02 2011, 17:56 
Специалист
Специалист

Зарегистрирован:
Вт, ноя 28 2006, 17:02
Сообщения: 114
Спасибо!
Надеюсь в следующий раз будете получать данные для загрузки в XML.


Принять этот ответ
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, июн 16 2011, 18:06 
Специалист
Специалист
Аватара пользователя

Зарегистрирован:
Вт, июн 02 2009, 23:28
Сообщения: 228
Откуда: MOW
Пол: Мужской
v2k написал(а):
Не могли бы вы подсказать как получить физический размер страницы? Так же получает Adobe Reader в свойствах файла.

К сожалению не разбирался с этим, так как нужно было только выдрать текст. Более того, реальная проблема была когда начал читать сканированный текст - там буквы часто произвольно соединялись в слова (не соответствовали реальным словам), и только по расположению и размеру шрифта можно было понять как они расположены реально. Все равно особо не разобрался с этим т.к. надо было читать какой-то зашифрованный кусок со шрифтом, решил задачу и так по ключевым словам, значкам, по расположению строк и т.д. То есть по косвенным признакам - и все работало 100%.

Вообще по формату PDF есть подробный reference

PS Для следующей разработки понадобилось доработать класс. Но руки пока не дошли его выложить. Так что если кто-то захочет воспользоваться и не заработает выложенный вариант, пишите, пришлю новую версию в том виде как есть, может кому-то поможет.


Последний раз редактировалось raaleksandr Пт, июл 01 2011, 21:38, всего редактировалось 1 раз.

Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Пт, июл 01 2011, 17:47 
Начинающий
Начинающий

Зарегистрирован:
Чт, мар 17 2011, 18:05
Сообщения: 17
Со своим вопросом я разобрался. PDF в принципе тэговый формат, есть тэг MediaBox, там и хранится физический размер страницы.


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, июл 12 2018, 14:16 
Специалист
Специалист

Зарегистрирован:
Вт, авг 17 2004, 09:47
Сообщения: 219
Пол: Мужской
Я вот так парсю. Получаю XML из PDF и ее уже через DOM или трансформацию получаю значения:
Code:
      data lr_fp type ref to if_fp.
      lr_fp = cl_fp=>get_reference( ).

      data lr_pdf type ref to if_fp_pdf_object.
      lr_pdf =
        lr_fp->create_pdf_object(
          connection = cl_fp=>get_ads_connection( ) ).

      lr_pdf->set_document(
        pdfdata = i_pdf ). " Бинарные данные PDF файла

      lr_pdf->set_task_extractdata( ).

      lr_pdf->execute( ).

      data l_xml type xstring.
      lr_pdf->get_data(
        importing
          formdata = l_xml ).

      e_xml = zalrcl_text_static=>convert_xstr2str( l_xml ).

_________________
Абап в телеграмме
https://t.me/sapabap


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Пн, июл 16 2018, 16:23 
Почетный гуру
Почетный гуру
Аватара пользователя

Зарегистрирован:
Чт, дек 20 2007, 19:21
Сообщения: 1438
Кроме картинок в PDF есть еще формат "обфускации". При этом текст в документе кликабелен, но в исходнике хранится как-бы шифрованный.

_________________
я твой сап эфай внедрял
BAdI-позитив


Принять этот ответ
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 15:28 
Почетный гуру
Почетный гуру

Зарегистрирован:
Пт, дек 04 2009, 13:52
Сообщения: 200
Добрый день, подниму тему, т.к. изучаю способы вытащить текст из PDF-файла посредством ABAP.
SAPer написал:
Я вот так парсю. Получаю XML из PDF и ее уже через DOM или трансформацию получаю значения:
это выглядит довольно многообещающе... но не работает на моем примере (как и все примеры FP_PDF_TEST_* из пакета SAFP). На методе execute выдается ошибка:
Code:
SYSTEM ERROR: ADS: com.adobe.ProcessingException: com.adobe.ProcessingException: PDF is not an interactive form.  Data cannot be exported from it.(200 201).
Похоже, что это работает только для редактируемых (интерактивных) форм, с заранее известной структурой, увы...

raaleksandr написал:
Прилагаю исходный код парсера в виде отдельного инклуда (оформлен в виде класса).
Также прилагаю небольшой пример для демонстрации возможностей (к сожалению, без файла PDF).
Спасибо, однако, чтобы применить к моему примеру, требуется доработка. Как минимум, в части разбора потоков. В моем примере stream сжат, вот небольшой фрагмент
Code:
27 0 obj
<</Filter/FlateDecode/Length 321>>stream
xњ]’KkГ0 Ђпю>nмђШQ—‚.ѓц`нFЇ‰­”Ав'=фЯПЏ:…тЃ>GF–\<п^vfXxсй&µ§…чѓСЋжймсЋNѓaBr=ЁеEЄ±µ¬рЙыЛјРё3эДљ†_~s^Ь…ЯЗ‡тћN“МЙђЯ?ЮмПЦюТHfб%CдљzФ[kЯЫ‘xoтp±ДeЊEЄ@MљfЫ*r­9kJї°yх эo[Ф)«лoїWёR–” \Y‰¤j\YAR-®¬к d‚DeR=f‚ШD%f‚xLJb&€mRМС%Uc&ќФ3APRO˜   ўO*Ц   йЋf‚”IiМYEµ˜   2ЭqЫб•R„к}“s7CїГSXЗ§ООщЙЖчз&7Zџ”ќlИвюcVЯ¬З
endstream
endobj
Здесь /Filter/FlateDecode/ означает gzip-сжатие. Пытаюсь использовать cl_abap_gzip=>decompress_binary для декомпрессии содержимого между stream и endstream, но выдается непонятная ошибка (код возврата 30). Реализовывал ли кто-нибудь такое? Приведите фрагмент кода, пожалуйста...
И вообще, может быть существуют стандартные решения, чтобы не изобретать такие велосипеды?


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 15:45 
Старший специалист
Старший специалист

Зарегистрирован:
Чт, мар 29 2007, 12:51
Сообщения: 297
Откуда: Yugorsk.RU
Пол: Мужской
с декодированием текстового слоя с двухслойных пдф-ок (text under image) не всё просто. Хорошо если отправитель генерит в классическом PDF формате (не помню номер стандарта).
Но бывают некие вендорские расширения PDF-стандарта (ну например с систем поточного распознавания типа Captiva, Kofax такое может выходить), там текстовый слой кодируется както хитро, чуть ли не шифруется, с ключом, который Adobe продаёт вендорам.
Т.е. в бинарике пдфки тэг находишь, а расшифровать контент не получается, flatdecode не помогает. Хотя сертифицированные читалки и машины полнотекстового поиска через родную адобовскую dll его видят както.

С табличным содержимым тоже непросто. Не всё что генерит пдф, впиливает в текстовый слой именно "таблицу". Тотже Kofax почемуто расставляет отдельные значения по ячейкам, но таблицей это в чистом виде не является после извлечения, - скорее в 1 столбик значения поколоночно (а если ячейки таблицы с объединением, то ваще капец получается).


Принять этот ответ
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 15:54 
Старший специалист
Старший специалист

Зарегистрирован:
Чт, мар 29 2007, 12:51
Сообщения: 297
Откуда: Yugorsk.RU
Пол: Мужской
поэтому полудиалоговый макрос в экселе:
1. открыть пдф в читалке
2. эмулировать Ctrl+A, Ctrl+C
3. на чистый лист экселя Ctrl+V
4. разное :D


Принять этот ответ
Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 17:23 
Почетный гуру
Почетный гуру

Зарегистрирован:
Пт, дек 04 2009, 13:52
Сообщения: 200
pberezin написал:
с декодированием текстового слоя с двухслойных пдф-ок (text under image) не всё просто. Хорошо если отправитель генерит в классическом PDF формате (не помню номер стандарта).
к счастью, это не сканы, а выгруженные из клиент-банка файлы
pberezin написал:
Но бывают некие вендорские расширения PDF-стандарта
да, я уже понял, что универсального парсера сделать не получится, но задачи такой и не стоит, т.к. структура платежек более-менее одинакова и довольно проста (табличного содержимого в них нет)
pberezin написал:
поэтому полудиалоговый макрос в экселе
это интересный вариант, спасибо, но пока оставлю его как запасной.


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 20:14 
Специалист
Специалист
Аватара пользователя

Зарегистрирован:
Вт, июн 02 2009, 23:28
Сообщения: 228
Откуда: MOW
Пол: Мужской
Я решал задачу распаковки текста из stream-ов, запакованных с помощью FlateDecode. Выкладываю код.
Если не заработает, присылайте пример в pdf, попробую посмотреть

Code:


CLASS lcl_pdf_parser DEFINITION DEFERRED.

CLASS lcl_pdf_concat_line DEFINITION.
  PUBLIC SECTION.

    " Текстовый элемент, загруженный из PDF
    TYPES: BEGIN OF t_text_element,
             num_element    TYPE I,
             num_page       TYPE I,
             X              TYPE I,
             Y              TYPE I,
             font(20)       TYPE C,
             font_size      TYPE I,
             text           TYPE STRING,
             textUcase      TYPE STRING,
             src_file_line  TYPE I,
           END OF t_text_element.

    TYPES: tt_text_element TYPE TABLE OF t_text_element.

    METHODS: constructor IMPORTING
                           pi_elements   TYPE tt_text_element
                           pi_line_num   TYPE I.

    METHODS: get_text EXPORTING
                        pe_text     TYPE STRING,
             get_text_condensed EXPORTING
                                  pe_text    TYPE STRING,
             get_line_num EXPORTING
                            pe_line_num TYPE I,
             get_Y EXPORTING
                     pe_Y TYPE I.

  PRIVATE SECTION.

    TYPES: BEGIN OF t_concat_element,
             num_page               TYPE I,
             start_pos              TYPE I,
             end_pos                TYPE I,
             start_pos_condensed    TYPE I,
             end_pos_condensed      TYPE I,
             element                TYPE t_text_element,
           END OF t_concat_element.

    DATA: m_concat_text             TYPE STRING,
          m_concat_text_condensed   TYPE STRING,
          m_concat_elements         TYPE TABLE OF t_concat_element,
          m_line_num                TYPE I,
          m_Y                       TYPE I.

ENDCLASS.

CLASS lcl_pdf_concat_line IMPLEMENTATION.

  METHOD constructor.

    DATA: lit_elements          TYPE TABLE OF t_text_element,
          lwa_element           TYPE t_text_element,
          lwa_concat_element    TYPE t_concat_element,
          lh_text_condensed     TYPE STRING,
          lh_len                TYPE I,
          lh_len_c              TYPE I,
          lh_len_all            TYPE I,
          lh_len_c_all          TYPE I.

    m_line_num = pi_line_num.

    CHECK pi_elements[] IS NOT INITIAL.

    " Сортируем элементы так, чтобы элементы с меньшим X шли раньше, но в остальном порядок
    " не меняя
    m_Y = 99999.

    lit_elements[] = pi_elements[].
    SORT lit_elements BY X num_element.

    "LOOP AT pi_elements INTO lwa_element.
    LOOP AT lit_elements INTO lwa_element.
      lh_text_condensed = lwa_element-text.
      CONDENSE lh_text_condensed NO-GAPS.

      lwa_concat_element-num_page            = lwa_element-num_page.
      lwa_concat_element-start_pos           = lh_len_all.
      lwa_concat_element-end_pos             = lwa_concat_element-start_pos + strlen( lwa_element-text ) - 1.
      lwa_concat_element-start_pos_condensed = lh_len_c_all.
      lwa_concat_element-end_pos_condensed   = lh_len_c_all + strlen( lh_text_condensed ) - 1.
      lwa_concat_element-element             = lwa_element.
      APPEND lwa_concat_element TO m_concat_elements.

      CONCATENATE m_concat_text lwa_element-text INTO m_concat_text.
      CONCATENATE m_concat_text_condensed lh_text_condensed INTO m_concat_text_condensed.

      IF lwa_element-Y < m_Y.
        m_Y = lwa_element-Y.
      ENDIF.

      lh_len_all   = lh_len_all + strlen( lwa_element-text ).
      lh_len_c_all = lh_len_c_all + strlen( lh_text_condensed ).
    ENDLOOP.



  ENDMETHOD.   "   constructor

  METHOD get_text.
    pe_text = m_concat_text.
  ENDMETHOD.

  METHOD get_text_condensed.
    pe_text = m_concat_text_condensed.
  ENDMETHOD.

  METHOD get_line_num.
    pe_line_num = m_line_num.
  ENDMETHOD.

  " Возвращает Y-координату объекта
  METHOD get_Y.
    pe_Y = m_Y.
  ENDMETHOD.    "    get_Y

ENDCLASS.

* Класс для парсинга PDF (разделение и структурирование его текстовых элементов) и
* удобной работы с данными элементами
CLASS lcl_pdf_parser DEFINITION.
  PUBLIC SECTION.

    TYPES: lcl_pdf_concat_line_tab  TYPE TABLE OF REF TO lcl_pdf_concat_line.

    CLASS-METHODS: try_read_number IMPORTING
                                     pi_number TYPE ANY
                                   CHANGING
                                     pc_number TYPE ANY
                                   EXCEPTIONS
                                     INVALID_FORMAT.

    METHODS: load_pdf_from_server IMPORTING
                                    pi_filename TYPE C
                                  EXCEPTIONS
                                    ERROR_IN_LOADING
                                    ERROR_IN_PARSING,
             load_pdf_from_gui    IMPORTING
                                    pi_filename TYPE C
                                  EXCEPTIONS
                                    ERROR_IN_LOADING
                                    ERROR_IN_PARSING,
             load_pdf_from_itab   IMPORTING
                                    pi_it_filedata   TYPE STANDARD TABLE
                                  EXCEPTIONS
                                    ERROR_IN_PARSING,
             is_loaded EXPORTING
                         pe_loaded TYPE C,
             get_num_pages EXPORTING
                             pe_num_pages TYPE I,
             get_all_elements EXPORTING
                                pe_elements TYPE lcl_pdf_concat_line=>tt_text_element,
             " Методы для текстовых элементов как есть
             find_text IMPORTING
                         pi_num_page   TYPE I DEFAULT 1
                         pi_text       TYPE C
                         pi_match_case TYPE C DEFAULT SPACE
                       EXPORTING
                         pe_elements   TYPE lcl_pdf_concat_line=>tt_text_element,
             find_text_by_mask IMPORTING
                                 pi_num_page   TYPE I DEFAULT 1
                                 pi_text       TYPE C
                                 pi_match_case TYPE C DEFAULT SPACE
                               EXPORTING
                                 pe_elements  TYPE lcl_pdf_concat_line=>tt_text_element,
             find_text_below IMPORTING
                               pi_element   TYPE lcl_pdf_concat_line=>t_text_element
                               pi_accuracy  TYPE I
                             EXPORTING
                               pe_elements  TYPE lcl_pdf_concat_line=>tt_text_element,
             find_text_right IMPORTING
                               pi_element   TYPE lcl_pdf_concat_line=>t_text_element
                               pi_accuracy  TYPE I
                             EXPORTING
                               pe_elements  TYPE lcl_pdf_concat_line=>tt_text_element,
             find_text_in_box IMPORTING
                                pi_num_page TYPE I
                                pi_left     TYPE I
                                pi_top      TYPE I
                                pi_right    TYPE I
                                pi_bottom   TYPE I
                              EXPORTING
                                pe_elements TYPE lcl_pdf_concat_line=>tt_text_element,
             " Методы для конкатенированного текста
             find_text_concat IMPORTING
                                pi_num_page    TYPE I
                                pi_text        TYPE C
                                pi_match_case  TYPE C DEFAULT SPACE
                                pi_condensed   TYPE C DEFAULT SPACE
                              EXPORTING
                                pe_concat_lines   TYPE lcl_pdf_concat_line_tab,
             find_lines_in_range IMPORTING
                                   pi_num_page  TYPE I
                                   pi_top       TYPE I
                                   pi_bottom    TYPE I
                                 EXPORTING
                                   pe_concat_lines   TYPE lcl_pdf_concat_line_tab,
             find_next_concat_line IMPORTING
                                     pi_concat_line  TYPE REF TO lcl_pdf_concat_line
                                     pi_ignore_page  TYPE C DEFAULT SPACE
                                   EXPORTING
                                     pe_concat_line_next TYPE REF TO lcl_pdf_concat_line.

  PRIVATE SECTION.
    CONSTANTS: mc_endstreamx(9)   TYPE X    VALUE '656E6473747265616D'.    " Слово endstream в 16-ричном виде для удобного поиска в xstring

    TYPES: BEGIN OF t_concat_text,
             line_num      TYPE I,
             num_page      TYPE I,
             Y             TYPE I,
             text          TYPE STRING,
             text_cond     TYPE STRING,
             text_u        TYPE STRING,
             text_cond_u   TYPE STRING,
           END OF t_concat_text.

    DATA: mt_filelines           TYPE TABLE OF STRING,
          m_contentx             TYPE XSTRING,
          mt_elements            TYPE TABLE OF lcl_pdf_concat_line=>t_text_element,
          mt_line_ends           TYPE match_result_tab,
          m_num_pages            TYPE I,
          m_concat_lines         TYPE TABLE OF REF TO lcl_pdf_concat_line,
          m_concat_texts         TYPE TABLE OF t_concat_text.

    METHODS: load_from_content,
             parse,
             get_lines_as_xstring IMPORTING
                                    pi_start_line  TYPE I
                                    pi_end_line    TYPE I
                                  EXPORTING
                                    pe_xlines      TYPE XSTRING,
             build_concat_lines.

ENDCLASS.

CLASS lcl_pdf_parser IMPLEMENTATION.

  " Загружает файл PDF из папки на сервере
  METHOD load_pdf_from_server.

    CLEAR m_contentx.

    " Загружаем

    OPEN DATASET pi_filename FOR INPUT IN BINARY MODE.
    IF sy-subrc <> 0.
      "PERFORM log_msg USING '' '' 'ZCAT' c_error c_important '402' pi_filename '' '' ''.
      RAISE ERROR_IN_LOADING.
    ENDIF.

    READ DATASET pi_filename INTO m_contentx.
    CLOSE DATASET pi_filename.

    " Загружаем из m_xcontent во внутреннюю таблицу, распаковывая FlateDecode-куски
    CALL METHOD load_from_content( ).

    IF mt_filelines[] IS INITIAL.
      CLEAR m_contentx.
      RAISE ERROR_IN_PARSING.
    ENDIF.

    " Парсим
    CALL METHOD parse( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.     "    METHOD load_pdf_from_server.

  " Загружает файл PDF в GUI
  METHOD load_pdf_from_gui.

    TYPES: BEGIN OF lt_xline,
             xline(3000)    TYPE X,
           END OF lt_xline.

    DATA: lit_lines      TYPE TABLE OF lt_xline,
          lwa_line       TYPE lt_xline,
          lh_filename    TYPE STRING.

    " Загружаем
    REFRESH mt_filelines[].
    CLEAR m_contentx.

    lh_filename = pi_filename.
    CALL FUNCTION 'GUI_UPLOAD'
      EXPORTING
        filename                = lh_filename
        filetype                = 'BIN'
      TABLES
        "data_tab                = mt_filelines[]
        data_tab                = lit_lines[]
      EXCEPTIONS
        file_open_error         = 1
        file_read_error         = 2
        no_batch                = 3
        gui_refuse_filetransfer = 4
        invalid_type            = 5
        no_authority            = 6
        unknown_error           = 7
        bad_data_format         = 8
        header_not_allowed      = 9
        separator_not_allowed   = 10
        header_too_long         = 11
        unknown_dp_error        = 12
        access_denied           = 13
        dp_out_of_memory        = 14
        disk_full               = 15
        dp_timeout              = 16
        OTHERS                  = 17.

    IF sy-subrc <> 0.
      RAISE ERROR_IN_LOADING.
    ENDIF.

    LOOP AT lit_lines INTO lwa_line.
      CONCATENATE m_contentx lwa_line-xline INTO m_contentx IN BYTE MODE.
    ENDLOOP.

    " Загружаем из m_xcontent во внутреннюю таблицу, распаковывая FlateDecode-куски
    CALL METHOD load_from_content( ).

    IF mt_filelines[] IS INITIAL.
      CLEAR m_contentx.
      RAISE ERROR_IN_PARSING.
    ENDIF.

    " Парсим
    CALL METHOD parse( ).

    " Test
    "CALL METHOD build_concat_lines( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.     "    METHOD load_pdf_from_gui.

  " Загружает файл PDF, предварительно загруженный во внутреннюю таблицу
  METHOD load_pdf_from_itab.
    REFRESH mt_filelines[].

    mt_filelines[] = pi_it_filedata[].

    IF mt_filelines[] IS INITIAL.
      RAISE ERROR_IN_PARSING.
    ENDIF.

    " Парсим
    CALL METHOD parse( ).

    IF mt_elements[] IS INITIAL.
      REFRESH mt_filelines[].
      RAISE ERROR_IN_PARSING.
    ENDIF.
  ENDMETHOD.   "  load_pdf_from_itab

  " После того, как файл в бинарном виде загружен в переменную m_contentx, данный метод
  " разбирает его на строки и если нужно распаговывает куски, запакованные с помощью FlateDecode
  " Все строки записываются во внутреннюю таблицу, где все запакованные куски уже распакованы, то есть уже как текст
  METHOD load_from_content.

    TYPES: BEGIN OF lt_flateDecode_stream,
             start_line         TYPE I,
             end_line           TYPE I,
             end_offset         TYPE I,     " Количество символов до конца строки, где заканчивается stream
           END OF lt_flateDecode_stream.

    DATA: lit_str                  TYPE TABLE OF STRING,
          lwa_result               TYPE match_result,
          lwa_result_last          TYPE match_result,
          lit_flDecode             TYPE TABLE OF lt_flateDecode_stream,
          lwa_flDecode             TYPE lt_flateDecode_stream,
          lit_filelines_tmp        TYPE TABLE OF STRING,
          lit_lines                TYPE TABLE OF STRING,
          lit_line_ends            TYPE match_result_tab,
          lh_str                   TYPE STRING,
          lh_line                  TYPE STRING,
          lh_xstring               TYPE XSTRING,
          lh_xstring_unp           TYPE XSTRING,
          lh_in_obj(1)             TYPE C,
          lh_in_stream(1)          TYPE C,
          lh_in_gg(1)              TYPE C,
          lh_flateDecode(1)        TYPE C,
          lh_last_end              TYPE I,
          lh_len                   TYPE I,
          lh_len1                  TYPE I,
          lh_line_num              TYPE I,
          lh_stream_start_line     TYPE I,
          lh_last_end_line         TYPE I.

    DATA: c_conv TYPE REF TO cl_abap_conv_in_ce,
          c_zip  TYPE REF TO cl_abap_gzip.

    REFRESH mt_filelines[].

    " Разделяем нас троки, разделители могут быть любые, причем разные в пределах одного
    " файла
    "FIND ALL OCCURRENCES OF m_sepx IN m_contentx IN BYTE MODE RESULTS mt_line_ends.
    lh_xstring = '0D0A'.
    FIND ALL OCCURRENCES OF lh_xstring IN m_contentx IN BYTE MODE RESULTS mt_line_ends.

    lh_xstring = '0D'.
    FIND ALL OCCURRENCES OF lh_xstring IN m_contentx IN BYTE MODE RESULTS lit_line_ends.
    APPEND LINES OF lit_line_ends TO mt_line_ends.

    lh_xstring = '0A'.
    FIND ALL OCCURRENCES OF lh_xstring IN m_contentx IN BYTE MODE RESULTS lit_line_ends.
    APPEND LINES OF lit_line_ends TO mt_line_ends.

    SORT mt_line_ends BY offset length DESCENDING.

    " Сначала убираем лишние точки - например, если разделитель ODOA, то мы также найдем
    " отдельно OD и отдельно OA, но эти точки лишние и их нужно убрать
    LOOP AT mt_line_ends INTO lwa_result.
      IF lwa_result-length = 2.
        lh_last_end = lwa_result-offset.
      ELSEIF lwa_result-length = 1.
        lh_len = lwa_result-offset - lh_last_end.
        IF lh_len <= 1.
          DELETE mt_line_ends.
        ENDIF.
      ENDIF.
    ENDLOOP.

    CHECK mt_line_ends[] IS NOT INITIAL.

    " Конвертируем в текст, разделенный по строкам
    READ TABLE mt_line_ends INDEX 1 INTO lwa_result.
    lh_last_end = 0 - lwa_result-length.
    lwa_result_last = lwa_result.

    LOOP AT mt_line_ends INTO lwa_result.
      "lh_last_end = lwa_result_last-offset + lwa_result_last-length.
      lh_last_end = lh_last_end + lwa_result_last-length.

      lh_len = lwa_result-offset - lh_last_end. " - lwa_result-length.
      "lh_last_end = lh_last_end + lwa_result-length.

      lh_xstring = m_contentx+lh_last_end(lh_len).
      c_conv = cl_abap_conv_in_ce=>create( input       = lh_xstring
                                           replacement = space
                                           encoding    = '1504' ).
      c_conv->read( EXPORTING
                      n    = lh_len
                    IMPORTING
                      data = lh_line
                      len  = lh_len1 ).

      APPEND lh_line TO mt_filelines[].

      lh_last_end = lwa_result-offset.
      lwa_result_last = lwa_result.
    ENDLOOP.

    " Проверяем, что это действительно PDF - это должно быть написано в начале файла
    READ TABLE mt_filelines INDEX 1 INTO lh_line.
    IF lh_line(4) <> '%PDF'.
      REFRESH mt_filelines[].
      RETURN.
    ENDIF.

    " Ищем куски, запакованные с помощью FlateDecode (по сути выполняем легкий парсинг, настоящий парсинг будет в процедуре parse)
    LOOP AT mt_filelines INTO lh_line.
      lh_line_num = sy-tabix.

      CONDENSE lh_line.
      SPLIT lh_line AT SPACE INTO TABLE lit_str.

      " Если мы не внутри объекта, отслеживаем начало объекта
      IF lh_in_obj <> 'X'.
        IF LINES( lit_str ) >= 3.
          READ TABLE lit_str INDEX 3 INTO lh_str.
          IF lh_str = 'obj'.
            lh_in_obj = 'X'.
          ENDIF.
        ENDIF.
      ENDIF.

      " Отслеживаем начало блока с параметрами <<..... >>
      IF strlen( lh_line ) >= 2
        AND lh_in_obj = 'X'
        AND lh_in_gg IS INITIAL.

        IF lh_line(2) = '<<'.
          lh_in_gg = 'X'.
        ENDIF.
      ENDIF.

      " Если нашли в параметрах FlateDecode, значит следующий stream будет закодирован
      IF lh_in_obj = 'X' AND lh_in_gg = 'X' AND lh_line CP '*/FlateDecode*'.
        lh_flateDecode = 'X'.
      ENDIF.

      " Отслеживаем конец блока с параметрами <<...... >>
      IF lh_in_obj = 'X' AND lh_in_gg = 'X' AND lh_line CP '*>>'.
        CLEAR lh_in_gg.
      ENDIF.

      IF strlen( lh_line ) >= 2.
        IF lh_line(2) = '<<' AND lh_line CP '*/FlateDecode*'.
          lh_flateDecode = 'X'.
        ENDIF.
      ENDIF.

      " Если мы не внутри stream, отслеживаем его начало
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X' AND lh_line CP '*stream'.
        lh_in_stream = 'X'.
        lh_stream_start_line = lh_line_num.
      ENDIF.

      " Если мы внутри stream, отслеживаем ее окончание
      IF lh_in_obj = 'X' AND lh_in_stream = 'X'.

        CLEAR lh_xstring.
        CLEAR lwa_result.

        " Если в строке надпись endstream - тогда точно
        IF lh_line CP '*endstream'.
          CLEAR lh_in_stream.
        ELSE.

          " В бинарной строке может так не найтись, пытаемся в шестнадцатеричном виде
          CALL METHOD get_lines_as_xstring( EXPORTING
                                              pi_start_line = lh_line_num
                                              pi_end_line   = lh_line_num
                                            IMPORTING
                                              pe_xlines     = lh_xstring ).

          FIND FIRST OCCURRENCE OF mc_endstreamx IN lh_xstring IN BYTE MODE RESULTS lwa_result.
          IF sy-subrc = 0.
            CLEAR lh_in_stream.
          ENDIF.
        ENDIF.

        " Если кончился stream и это был FlateDecode - запоминаем все байты stream-а
        IF lh_in_stream IS INITIAL AND lh_flateDecode = 'X'.

          IF lh_xstring IS INITIAL.
            CALL METHOD get_lines_as_xstring( EXPORTING
                                                pi_start_line = lh_line_num
                                                pi_end_line   = lh_line_num
                                              IMPORTING
                                                pe_xlines     = lh_xstring ).

          ENDIF.

          IF lwa_result IS INITIAL.
            FIND FIRST OCCURRENCE OF mc_endstreamx IN lh_xstring IN BYTE MODE RESULTS lwa_result.
          ENDIF.

          CLEAR lwa_flDecode.
          lwa_flDecode-start_line = lh_stream_start_line + 1.    " Stream начинается со следующей строки после слова stream

          IF lwa_result-offset = 0.
            lwa_flDecode-end_line    = lh_line_num - 1.
            lwa_flDecode-end_offset  = 0.   " До конца строки
          ELSE.
            lwa_flDecode-end_line    = lh_line_num.
            lwa_flDecode-end_offset  = xstrlen( lh_xstring ) - lwa_result-offset. " + 1.
          ENDIF.

          APPEND lwa_flDecode TO lit_flDecode.

          CLEAR lh_flateDecode.
        ENDIF.
      ENDIF.

      " Если мы внутри объекта, отслеживаем окончание объекта
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X'
        AND lh_line CP '*endobj'.

        CLEAR lh_in_obj.
        CLEAR lh_flateDecode.
      ENDIF.

    ENDLOOP.

    " Если не нашли stream-ы, запакованные flateDecode, больше ничего не делаем
    CHECK lit_flDecode[] IS NOT INITIAL.

    CREATE OBJECT c_zip.

    lh_last_end_line = 1.
    LOOP AT lit_flDecode INTO lwa_flDecode.

      " Все строки от предыдущего куска flateDecode до текущего просто копируем
      lh_line_num = lwa_flDecode-start_line - 1.
      IF lh_line_num < 1.
        lh_line_num = 1.
      ENDIF.

      LOOP AT mt_filelines INTO lh_line
        FROM lh_last_end_line TO lh_line_num.

        APPEND lh_line TO lit_filelines_tmp.
      ENDLOOP.

      " Извлекаем кусок файла в виде xstream, являющийся запакованной областью stream-а, причем первые 2 байта не берем, они лишние, реально
      " zip начинается с 3-го байта
      CALL METHOD get_lines_as_xstring( EXPORTING
                                          pi_start_line = lwa_flDecode-start_line
                                          pi_end_line   = lwa_flDecode-end_line
                                        IMPORTING
                                          pe_xlines     = lh_xstring ).

      lh_len = xstrlen( lh_xstring ) - lwa_flDecode-end_offset - 2.
      IF lh_len > 0.
        lh_xstring = lh_xstring+2(lh_len).

        " Разархивируем
        CLEAR lh_xstring_unp.
        TRY.
*          c_zip->decompress_text( EXPORTING
*                                    gzip_in      = lh_xstring
*                                  IMPORTING
*                                    text_out     = lh_str
*                                    text_out_len = lh_len1 ).
          c_zip->decompress_binary( EXPORTING
                                      gzip_in      = lh_xstring
                                    IMPORTING
                                      raw_out      = lh_xstring_unp
                                      raw_out_len  = lh_len1 ).
        CATCH CX_SY_COMPRESSION_ERROR.
          CLEAR lh_xstring_unp.
        CATCH CX_SY_CONVERSION_CODEPAGE.
          CLEAR lh_xstring_unp.
        ENDTRY.

        IF lh_xstring_unp IS NOT INITIAL.

          lh_len = lh_len1.
          c_conv = cl_abap_conv_in_ce=>create( input       = lh_xstring_unp
                                               replacement = space
                                               encoding    = '1504' ).

          c_conv->read( EXPORTING
                          n    = lh_len
                        IMPORTING
                          data = lh_str
                          len  = lh_len1 ).

          " Разделяем строку на отдельные строки, учитывая что разделители могут быть разные
          lh_line = '#~$'.
          DO.
            FIND FIRST OCCURRENCE OF lh_line IN lh_str.
            IF sy-subrc = 0.
              CONCATENATE lh_line lh_line INTO lh_line.
            ELSE.
              EXIT.
            ENDIF.
          ENDDO.

          REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf IN lh_str WITH lh_line.
          REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf(1) IN lh_str WITH lh_line.
          REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf+1(1) IN lh_str WITH lh_line.

          SPLIT lh_str AT lh_line INTO TABLE lit_lines.
*          REFRESH lit_lines2[].
*          LOOP AT lit_lines INTO lh_line.
*            SPLIT
*          ENDLOOP.

          " Записываем распакованные строки на месте запакованных
          LOOP AT lit_lines INTO lh_line.
            APPEND lh_line TO lit_filelines_tmp.
          ENDLOOP.

          " Добавляем строку со словом endstream, чтобы сохранить корректную структуру файла
          IF lwa_flDecode-end_offset IS NOT INITIAL.
            lh_line = 'endstream'.
            APPEND lh_line TO lit_filelines_tmp.
          ENDIF.

        ELSE.

          " Если произошла ошибка при распаковывании, просто записываем все строки как есть
          LOOP AT mt_filelines INTO lh_line
            FROM lwa_flDecode-start_line TO lwa_flDecode-end_line.

            APPEND lh_line TO lit_filelines_tmp.
          ENDLOOP.
        ENDIF.

      ENDIF.

      lh_last_end_line = lwa_flDecode-end_line + 1.
    ENDLOOP.

    " Записываем все строки после последнего элемента
    lh_line_num = LINES( mt_filelines ).
    LOOP AT mt_filelines INTO lh_line
      FROM lh_last_end_line TO lh_line_num.

      APPEND lh_line TO lit_filelines_tmp.
    ENDLOOP.

    mt_filelines[] = lit_filelines_tmp[].
  ENDMETHOD.         "    load_from_content

  " Выполняет парсинг загруженного PDF
  METHOD parse.

    " Подгонка - выписал примерные размеры шрифта в зависимости от размера
    TYPES: BEGIN OF t_fonthsize,
             font_size         TYPE I,
             glyth_width(15)   TYPE P DECIMALS 3,
           END OF t_fonthsize.

    DATA: lit_str            TYPE TABLE OF STRING,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element,
          lit_result         TYPE match_result_tab,
          lwa_result         TYPE match_result,
          lwa_result_next    TYPE match_result,
          lit_fonthsize      TYPE TABLE OF t_fonthsize,
          lwa_fonthsize      TYPE t_fonthsize,
          lh_str             TYPE STRING,
          lh_line            TYPE STRING,
          lh_in_obj(1)       TYPE C,
          lh_in_stream(1)    TYPE C,
          lh_in_text(1)      TYPE C,
          lh_font            TYPE STRING,
          lh_font_size       TYPE I,
          lh_X               TYPE I,
          lh_Y               TYPE I,
          lh_len             TYPE I,
          lh_lines           TYPE I,
          lh_num             TYPE I,
          lh_tabix           LIKE sy-tabix,
          lh_space_str       TYPE STRING,
          lh_text_flag(1)    TYPE C,
          lh_line_num        TYPE I,
          lh_min_page        TYPE I,
          lh_max_page        TYPE I,
          lh_correct(1)      TYPE C.

    CHECK mt_filelines[] IS NOT INITIAL.

    REFRESH mt_elements[].

    " Проверяем, что это действительно PDF - это должно быть написано в начале файла
    READ TABLE mt_filelines INDEX 1 INTO lh_line.
    IF lh_line(4) <> '%PDF'.
      RETURN.
    ENDIF.

    " Готовим таблицу зависимостей ширины символа от шрифта
    lwa_fonthsize-font_size   = 10.
    lwa_fonthsize-glyth_width = '5.45'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    lwa_fonthsize-font_size   = 9.
    lwa_fonthsize-glyth_width = '4.8'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    lwa_fonthsize-font_size   = 8.
    lwa_fonthsize-glyth_width = '3.8'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    lwa_fonthsize-font_size   = 7.
    lwa_fonthsize-glyth_width = '3.43'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    lwa_fonthsize-font_size   = 6.
    lwa_fonthsize-glyth_width = '3.2'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    lwa_fonthsize-font_size   = 5.
    lwa_fonthsize-glyth_width = '2.67'.
    APPEND lwa_fonthsize TO lit_fonthsize.

    m_num_pages = 1.

    LOOP AT mt_filelines INTO lh_line.
      lh_line_num = sy-tabix.
      CONDENSE lh_line.
      SPLIT lh_line AT SPACE INTO TABLE lit_str.

      " Если мы не внутри блока текста, отслеживаем его начало
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text <> 'X'
        AND lh_line CP '*BT'.

        lh_in_text = 'X'.
        CLEAR: lh_font, lh_X, lh_Y.
      ENDIF.

      " Если мы не внутри stream, отслеживаем его начало
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X' AND lh_line CP '*stream'.
        lh_in_stream = 'X'.
      ENDIF.

      " Если мы не внутри объекта, отслеживаем начало объекта
      IF lh_in_obj <> 'X'.
        IF LINES( lit_str ) >= 3.
          READ TABLE lit_str INDEX 3 INTO lh_str.
          IF lh_str = 'obj'.
            lh_in_obj = 'X'.
          ENDIF.
        ENDIF.
      ENDIF.

      " Отслеживаем начало новой страницы
      IF strlen( lh_line ) >= 2.
        IF lh_in_obj = 'X'
          AND lh_line CP '*/Page*'
          AND lh_line CP '*/Type*'
          AND lh_line NP '*/Pages*'.

          m_num_pages = m_num_pages + 1.
          CLEAR: lh_X, lh_Y.
        ENDIF.
      ENDIF.

      " Отслеживаем тип строки текста по последним 2 символам и считываем
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text = 'X'.
        lh_str = lh_line.
        lh_len = strlen( lh_str ).
        IF lh_len >= 2.
          IF lh_len > 2.
            lh_len = lh_len - 2.
            SHIFT lh_str BY lh_len PLACES LEFT.
          ENDIF.

          CASE lh_str.
            WHEN 'Tf'.   " Шрифт        [ /F410130 8 Tf
              READ TABLE lit_str INDEX 1 INTO lh_font.

              IF LINES( lit_str ) >= 2.
                READ TABLE lit_str INDEX 2 INTO lh_str.
                CALL METHOD try_read_number EXPORTING
                                              pi_number = lh_str
                                            CHANGING
                                              pc_number = lh_num
                                            EXCEPTIONS
                                              INVALID_FORMAT = 1.
                IF sy-subrc = 0.
                  lh_font_size = lh_num.
                ENDIF.
              ENDIF.

            WHEN 'Tm'.   " Абсолютное позиционирование          0.99941 0 0 1 85.0795 774.561 Tm    (в конце идут X и Y)

              lh_lines = LINES( lit_str ).

              " Y
              lh_lines = lh_lines - 1.
              IF lh_lines > 0.
                READ TABLE lit_str INDEX lh_lines INTO lh_str.
                CALL METHOD try_read_number EXPORTING
                                              pi_number = lh_str
                                            CHANGING
                                              pc_number = lh_num
                                            EXCEPTIONS
                                              INVALID_FORMAT = 1.
                IF sy-subrc = 0.
                  lh_Y = lh_num.
                ENDIF.
              ENDIF.

              " X
              lh_lines = lh_lines - 1.
              IF lh_lines > 0.
                READ TABLE lit_str INDEX lh_lines INTO lh_str.
                CALL METHOD try_read_number EXPORTING
                                              pi_number = lh_str
                                            CHANGING
                                              pc_number = lh_num
                                            EXCEPTIONS
                                              INVALID_FORMAT = 1.
                IF sy-subrc = 0.
                  lh_X = lh_num.
                ENDIF.
              ENDIF.

            WHEN 'Td'.   " Позиционирование относительно текущих координат        [ -499 63 Td

              " X
              READ TABLE lit_str INDEX 1 INTO lh_str.
              CALL METHOD try_read_number EXPORTING
                                            pi_number = lh_str
                                          CHANGING
                                            pc_number = lh_num
                                          EXCEPTIONS
                                            INVALID_FORMAT = 1.
              IF sy-subrc = 0.
                lh_X = lh_X + lh_num.
              ENDIF.

              " Y
              READ TABLE lit_str INDEX 2 INTO lh_str.
              CALL METHOD try_read_number EXPORTING
                                            pi_number = lh_str
                                          CHANGING
                                            pc_number = lh_num
                                          EXCEPTIONS
                                            INVALID_FORMAT = 1.
              IF sy-subrc = 0.
                lh_Y = lh_Y + lh_num.
              ENDIF.

            WHEN 'Tj'.   " Текст        [ (AGCO Parts Division) Tj

              lh_str = lh_line.
              SHIFT lh_str BY 1 PLACES LEFT.
              lh_len = strlen( lh_str ).
              lh_len = lh_len - 4.
              lh_str = lh_str(lh_len).

              " Добавляем текстовый элемент
              lwa_element-num_page      = m_num_pages.
              lwa_element-src_file_line = lh_line_num.
              lwa_element-X             = lh_X.
              lwa_element-Y             = lh_Y.
              lwa_element-font          = lh_font.
              lwa_element-font_size     = lh_font_size.
              lwa_element-text          = lh_str.
              lwa_element-textUcase     = lwa_element-text.
              TRANSLATE lwa_element-textUcase TO UPPER CASE.
              APPEND lwa_element TO mt_elements.

            WHEN 'TJ'.    " Другой вид текста      [(t)-2.16558(e)3.74(s)-1.22997(t)-2.1653( )]TJ   " Это слово test
              CLEAR lwa_element-text.

              lwa_element-num_page      = m_num_pages.
              lwa_element-src_file_line = lh_line_num.
              lwa_element-X             = lh_X.
              lwa_element-Y             = lh_Y.
              lwa_element-font          = lh_font.
              lwa_element-font_size     = lh_font_size.

              lh_str = lh_line.
              SHIFT lh_str BY 1 PLACES LEFT.
              lh_len = strlen( lh_str ).
              lh_len = lh_len - 2.
              lh_str = lh_str(lh_len).
              SHIFT lh_str RIGHT DELETING TRAILING SPACE.
              SHIFT lh_str RIGHT DELETING TRAILING ']'.

              FIND ALL OCCURRENCES OF '(' IN lh_str RESULTS lit_result.
              LOOP AT lit_result INTO lwa_result.

                lh_tabix = sy-tabix.
                lh_num = lwa_result-offset + 1.

                IF lh_tabix < LINES( lit_result ).
                  lh_tabix = lh_tabix + 1.
                  READ TABLE lit_result INDEX lh_tabix INTO lwa_result_next.
                  lh_tabix = lh_tabix - 1.
                ELSE.
                  CLEAR lwa_result_next.
                ENDIF.

                " Читаем весь текст в скобках, там может быть одна реже несколько букв
                lh_text_flag = 'X'.
                CLEAR lh_space_str.
                DO.
                  IF lh_num >= strlen( lh_str ).
                    EXIT.
                  ENDIF.

                  IF lwa_result_next-offset IS NOT INITIAL.
                    IF lh_num >= lwa_result_next-offset.
                      EXIT.
                    ENDIF.
                  ENDIF.

                  IF lh_str+lh_num(1) = ')'.
                    CLEAR lh_text_flag.
                    lh_num = lh_num + 1.
                    CONTINUE.
                  ENDIF.

                  IF lh_text_flag = 'X'.
                    CONCATENATE lwa_element-text lh_str+lh_num(1) INTO lwa_element-text RESPECTING BLANKS.
                  ELSE.
                    CONCATENATE lh_space_str lh_str+lh_num(1) INTO lh_space_str.
                  ENDIF.

                  lh_num = lh_num + 1.
                ENDDO.

                " Смотрим, удалось ли нам прочитать смещение
                IF lh_space_str IS NOT INITIAL.
                  CLEAR lh_num.
                  CALL METHOD try_read_number EXPORTING
                                                pi_number = lh_space_str
                                              CHANGING
                                                pc_number = lh_num
                                              EXCEPTIONS
                                                INVALID_FORMAT = 1.
                ENDIF.

                IF lh_num IS NOT INITIAL.
                  lh_num = 0 - lh_num / 52.    " Движение вправо соответствует отрицательному числу

                  " Если считаем, что отступ довольно большой, отделяем текстовый элемент
                  IF lh_num > 7.
                    lwa_element-textUcase     = lwa_element-text.
                    TRANSLATE lwa_element-textUcase TO UPPER CASE.
                    APPEND lwa_element TO mt_elements.

                    CLEAR lwa_element-text.

                    READ TABLE lit_fonthsize INTO lwa_fonthsize WITH KEY font_size = lh_font_size.
                    IF sy-subrc <> 0.
                      lwa_fonthsize-glyth_width = 4.
                    ENDIF.

                    " Отступаем на количество уже имеющихся символов в прошлом элементе + отступ
                    lwa_element-X = lwa_element-X + strlen( lwa_element-text ) * lwa_fonthsize-glyth_width + lh_num.
                    lwa_element-num_page      = m_num_pages.
                    lwa_element-src_file_line = lh_line_num.
                    lwa_element-Y             = lh_Y.
                    lwa_element-font          = lh_font.
                    lwa_element-font_size     = lh_font_size.
                  ENDIF.
                ENDIF.
              ENDLOOP.

              lwa_element-textUcase     = lwa_element-text.
              TRANSLATE lwa_element-textUcase TO UPPER CASE.
              APPEND lwa_element TO mt_elements.

          ENDCASE.
        ENDIF.
      ENDIF.

      " Если мы внутри блока текста, то отслеживаем его окончание
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text = 'X'
        AND lh_line = 'ET'.

        CLEAR lh_in_text.
      ENDIF.

      " Если мы внутри stream, отслеживаем ее окончание
      IF lh_in_obj = 'X' AND lh_in_stream = 'X' AND lh_in_text <> 'X'
        AND lh_line CP '*endstream'.

        CLEAR lh_in_stream.
      ENDIF.

      " Если мы внутри объекта, отслеживаем окончание объекта
      IF lh_in_obj = 'X' AND lh_in_stream <> 'X' AND lh_in_text <> 'X'
        AND lh_line CP '*endobj'.

        CLEAR lh_in_obj.
      ENDIF.
    ENDLOOP.

    LOOP AT mt_elements INTO lwa_element.
      lwa_element-num_element = sy-tabix.
      MODIFY mt_elements FROM lwa_element.
    ENDLOOP.

    " Сортируем, чтобы все текстовые надписи шли сверху вниз и слева направо
    SORT mt_elements BY num_page y DESCENDING x num_element.

    READ TABLE mt_elements INDEX 1 INTO lwa_element.
    lh_min_page = lwa_element-num_page.

    lh_lines = LINES( mt_elements ).
    READ TABLE mt_elements INDEX lh_lines INTO lwa_element.
    lh_max_page = lwa_element-num_page.

    m_num_pages = m_num_pages - 1.

    " Корректировка номеров страниц, иногда номера сдвигаются на 1 вверх
    IF lh_max_page > m_num_pages AND lh_min_page > 1.
      lh_num = 1.
    ELSE.
      lh_num = 0.
    ENDIF.

    LOOP AT mt_elements INTO lwa_element.
      lwa_element-num_element = sy-tabix.
      lwa_element-num_page = lwa_element-num_page - lh_num.
      MODIFY mt_elements FROM lwa_element.
    ENDLOOP.

    IF mt_elements[] IS INITIAL.
      m_num_pages = 0.
    ENDIF.
  ENDMETHOD.    "   METHOD parse

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

    DATA: lwa_result     TYPE match_result,
          lh_offset      TYPE I,
          lh_length      TYPE I,
          lh_index       TYPE I.

    CLEAR pe_xlines.

    " На всякий случай проверка, что параметры адекватные
    IF pi_start_line <= 0 OR pi_end_line > LINES( mt_filelines ) OR pi_start_line > pi_end_line.
      RETURN.
    ENDIF.

    IF pi_start_line > 1.
      lh_index = pi_start_line - 1.
      READ TABLE mt_line_ends INDEX lh_index INTO lwa_result.
      lh_offset = lwa_result-offset + lwa_result-length.
    ELSE.
      lh_offset = 0.
    ENDIF.

    IF pi_end_line < LINES( mt_filelines ).
      READ TABLE mt_line_ends INDEX pi_end_line INTO lwa_result.
      lh_length = lwa_result-offset - lh_offset.
    ELSE.
      lh_length = xstrlen( m_contentx ) - lh_offset + 1.
    ENDIF.

    pe_xlines = m_contentx+lh_offset(lh_length).
  ENDMETHOD.

  " Читает число из строки, если неверный формат, то генерит exception
  METHOD try_read_number.
    DATA: lh_number  TYPE STRING.

    lh_number = pi_number.
    REPLACE ',' IN lh_number WITH '.'.

    CATCH SYSTEM-EXCEPTIONS
      CONVERSION_ERRORS = 1.

      pc_number = lh_number.
    ENDCATCH.
    IF sy-subrc <> 0.
      RAISE INVALID_FORMAT.
    ENDIF.
  ENDMETHOD.       "    try_read_number

  " Возвращает X если PDF загружен и пусто в противном случае
  METHOD is_loaded.
    IF mt_elements[] IS NOT INITIAL.
      pe_loaded = 'X'.
    ELSE.
      CLEAR pe_loaded.
    ENDIF.
  ENDMETHOD.       "    is_loaded

  " Возвращает количество страниц в документе
  METHOD get_num_pages.
    pe_num_pages = m_num_pages.
  ENDMETHOD.     "    get_num_pages

  " Возвращает список всех элементов, отсортированный по страницам, сверху вниз и
  " слева направо
  METHOD get_all_elements.
    pe_elements = mt_elements.
  ENDMETHOD.        "   get_all_elements

  " Возвращает элементы, содержащие определенный текст
  "  pi_num_page   - номер страницы, если 0, то искать на всех страницах;
  "  pi_text       - искомый текст;
  "  pi_match_case - учитывать ли большие-маленькие буквы или искать невзирая на них;
  "  pe_elements   - возвращает список найденных текстовых элементов или пустую таблицу,
  "                  если такой текст не найден.
  METHOD find_text.
    DATA: lr_page            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_text            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-text,
          lwa_r_text         LIKE LINE OF lr_text,
          lr_textUcase       TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-textUcase,
          lwa_r_textUcase    LIKE LINE OF lr_textUcase,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element,
          lh_text            TYPE STRING,
          lh_index           TYPE I.

    " Для того, чтобы перебирать только элементы нужной страницы, используется
    " отсортированность таблицы mt_elements по номеру страницы

    REFRESH pe_elements[].

    IF pi_num_page IS NOT INITIAL.
      lwa_r_page-option = 'EQ'.
      lwa_r_page-sign   = 'I'.
      lwa_r_page-low    = pi_num_page.
      APPEND lwa_r_page TO lr_page.

      READ TABLE mt_elements WITH KEY num_page = pi_num_page TRANSPORTING NO FIELDS.
      lh_index = sy-tabix.
    ELSE.
      lh_index = 1.
    ENDIF.

    IF pi_text IS NOT INITIAL.
      IF pi_match_case = 'X'.
        lwa_r_text-option = 'EQ'.
        lwa_r_text-sign   = 'I'.
        lwa_r_text-low    = pi_text.
        APPEND lwa_r_text TO lr_text.
      ELSE.
        lh_text = pi_text.
        TRANSLATE lh_text TO UPPER CASE.

        lwa_r_textUcase-option = 'EQ'.
        lwa_r_textUcase-sign   = 'I'.
        lwa_r_textUcase-low    = lh_text.
        APPEND lwa_r_textUcase TO lr_textUcase.
      ENDIF.
    ENDIF.

    LOOP AT mt_elements INTO lwa_element
      FROM lh_index.

      "WHERE "num_page   IN lr_page
      "  text           IN lr_text
      "  AND textUcase  IN lr_textUcase.

      CHECK lwa_element-text      IN lr_text
        AND lwa_element-textUcase IN lr_textUcase.

      IF lwa_element-num_page IN lr_page.
        APPEND lwa_element TO pe_elements.
      ELSE.
        EXIT.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.      "    find_text

  " Возвращает элементы, содержащие определенный текст, который ищется по маске
  "  pi_num_page   - номер страницы, если 0, то искать на всех страницах;
  "  pi_text       - искомый текст или маска для команды CP;
  "  pi_match_case - учитывать ли большие-маленькие буквы или искать невзирая на них;
  "  pe_elements   - возвращает список найденных текстовых элементов или пустую таблицу,
  "                  если такой текст не найден.
  METHOD find_text_by_mask.

    DATA: lr_page            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element,
          lh_text            TYPE STRING,
          lh_index           TYPE I.

    lh_text = pi_text.
    IF pi_match_case IS NOT INITIAL.
      TRANSLATE lh_text TO UPPER CASE.
    ENDIF.

    " Для того, чтобы перебирать только элементы нужной страницы, используется
    " отсортированность таблицы mt_elements по номеру страницы

    REFRESH pe_elements[].

    IF pi_num_page IS NOT INITIAL.
      lwa_r_page-option = 'EQ'.
      lwa_r_page-sign   = 'I'.
      lwa_r_page-low    = pi_num_page.
      APPEND lwa_r_page TO lr_page.

      READ TABLE mt_elements WITH KEY num_page = pi_num_page TRANSPORTING NO FIELDS.
      lh_index = sy-tabix.
    ELSE.
      lh_index = 1.
    ENDIF.

    LOOP AT mt_elements INTO lwa_element
      FROM lh_index.

      IF lwa_element-num_page NOT IN lr_page.
        EXIT.
      ENDIF.

      IF pi_match_case IS NOT INITIAL
        AND lwa_element-textUcase CP lh_text.

        APPEND lwa_element TO pe_elements.
      ELSEIF pi_match_case IS INITIAL
        AND lwa_element-text CP lh_text.

        APPEND lwa_element TO pe_elements.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.       "     find_text_by_mask

  " Возвращает элементы, находящиеся под данным элементом, сравнивается левый верхний угол
  " элемента с левыми верхними углами других элементов на той же странице.
  "  pi_element  - текстовый элемент, под которым нужно искать;
  "  pi_accuracy - диапазон поиска влево и вправо от координаты X левого верхнего угла
  "                элемента при сравнении с Х-координатой других элементов;
  "  pe_elements - найденные элементы.
  METHOD find_text_below.
    DATA: lr_page            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element.

    REFRESH pe_elements[].

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_element-num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'BT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = pi_element-X - pi_accuracy.
    lwa_r_X-high   = pi_element-X + pi_accuracy.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'LT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = pi_element-Y.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      FROM pi_element-num_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.      "    find_text_below

  " Возвращает элементы, находящиеся справа от данного элемента,
  " сравнивается левый верхний угол элемента с левыми верхними углами других
  " элементов на той же странице.
  "  pi_element  - текстовый элемент, справа от которого нужно искать;
  "  pi_accuracy - диапазон поиска вниз и вверх от координаты Y левого верхнего угла
  "                элемента при сравнении с Y-координатой других элементов;
  "  pe_elements - найденные элементы.
  METHOD find_text_right.
    DATA: lr_page            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element.

    REFRESH pe_elements[].

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_element-num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'GT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = pi_element-X.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'BT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = pi_element-Y - pi_accuracy.
    lwa_r_Y-high   = pi_element-Y + pi_accuracy.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.        "     find_text_right

  " Возвращает элементы, находящиеся внутри прямоугольника на данной странице
  METHOD find_text_in_box.
    DATA: lr_page            TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-num_page,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_X               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-X,
          lwa_r_X            LIKE LINE OF lr_X,
          lr_Y               TYPE RANGE OF lcl_pdf_concat_line=>t_text_element-Y,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lwa_element        TYPE lcl_pdf_concat_line=>t_text_element,
          lh_left            TYPE I,
          lh_right           TYPE I,
          lh_top             TYPE I,
          lh_bottom          TYPE I,
          lh_num             TYPE I.

    REFRESH pe_elements[].

    lh_left   = pi_left.
    lh_top    = pi_top.
    lh_right  = pi_right.
    lh_bottom = pi_bottom.

    IF lh_left > lh_right.
      lh_num = lh_left.
      lh_left = lh_right.
      lh_right = lh_num.
    ENDIF.

    IF lh_top > lh_bottom.
      lh_num = lh_bottom.
      lh_bottom = lh_top.
      lh_top = lh_num.
    ENDIF.

    lwa_r_page-option = 'EQ'.
    lwa_r_page-sign   = 'I'.
    lwa_r_page-low    = pi_num_page.
    APPEND lwa_r_page TO lr_page.

    lwa_r_X-option = 'BT'.
    lwa_r_X-sign   = 'I'.
    lwa_r_X-low    = lh_left.
    lwa_r_X-high   = lh_right.
    APPEND lwa_r_X TO lr_X.

    lwa_r_Y-option = 'BT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = lh_top.
    lwa_r_Y-high   = lh_bottom.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT mt_elements INTO lwa_element
      WHERE num_page   IN lr_page
        AND X          IN lr_X
        AND Y          IN lr_Y.

      APPEND lwa_element TO pe_elements.
    ENDLOOP.
  ENDMETHOD.       "    find_text_in_box

  " Формирует структуры для поиска в целых строках PDF - полезны для поиска
  " в файлах неожиданной структуры, например из FineReader
  METHOD build_concat_lines.

    DATA: lit_elements          TYPE TABLE OF lcl_pdf_concat_line=>t_text_element,
          lwa_element           TYPE lcl_pdf_concat_line=>t_text_element,
          lwa_element_last      TYPE lcl_pdf_concat_line=>t_text_element,
          lwa_concat_text       TYPE t_concat_text,
          lc_concat_line        TYPE REF TO lcl_pdf_concat_line,
          lh_num                TYPE I,
          lh_concat_line_num    TYPE I.

    REFRESH: m_concat_lines[], m_concat_texts[].

    LOOP AT mt_elements INTO lwa_element.

      lh_num = abs( lwa_element-Y - lwa_element_last-Y ).

      " Если элементы на одной странице примерно на одной высоте,
      IF lwa_element-num_page = lwa_element_last-num_page
        AND lh_num <= 3.

        APPEND lwa_element TO lit_elements.
      ELSE.

        IF lit_elements[] IS NOT INITIAL.
          lh_concat_line_num = lh_concat_line_num + 1.

          CREATE OBJECT lc_concat_line EXPORTING
                                         pi_elements = lit_elements
                                         pi_line_num = lh_concat_line_num.

          lwa_concat_text-line_num = lh_concat_line_num.
          lwa_concat_text-num_page = lwa_element_last-num_page.
          lwa_concat_text-Y        = lwa_element_last-Y.

          lc_concat_line->get_text( IMPORTING pe_text = lwa_concat_text-text ).

          lwa_concat_text-text_u = lwa_concat_text-text.
          TRANSLATE lwa_concat_text-text_u TO UPPER CASE.

          lc_concat_line->get_text_condensed( IMPORTING pe_text = lwa_concat_text-text_cond ).

          lwa_concat_text-text_cond_u = lwa_concat_text-text_cond.
          TRANSLATE lwa_concat_text-text_cond_u TO UPPER CASE.

          APPEND lc_concat_line TO m_concat_lines.
          APPEND lwa_concat_text TO m_concat_texts.

          REFRESH lit_elements[].
        ENDIF.

        APPEND lwa_element TO lit_elements[].

      ENDIF.

      lwa_element_last = lwa_element.
    ENDLOOP.
  ENDMETHOD.         "     build_concat_lines

  " Находит текст по маске в конкатенированной строке PDF (все текстовые элементы,
  " примерно совпадающие по высоте, считаются одной строкой и объединяются в текст)
  "
  " Удобно для файло с заранее неизвестной и меняющейся структурой, например для
  " PDF из Fine Reader-а
  "
  " Параметры:
  "  pi_num_page     - номер страницы
  "  pi_text         - искомый текст, может содержать маску (ищется по CP)
  "  pi_match_case   - учитывать ли регистр
  "  pi_condensed    - искать ли в тексте без пробелов или с пробелами
  "  pe_concat_lines - возвращает найденные строки PDF.
  METHOD find_text_concat.

    DATA: lr_page            TYPE RANGE OF I,
          lwa_r_page         LIKE LINE OF lr_page,
          lwa_concat_text    TYPE t_concat_text,
          lc_concat_line     TYPE REF TO lcl_pdf_concat_line,
          lh_text            TYPE STRING,
          lh_text2           TYPE STRING,
          lh_index           TYPE I,
          lh_line_num        TYPE I.

    REFRESH pe_concat_lines[].

    IF m_concat_texts[] IS INITIAL
      OR m_concat_lines[] IS INITIAL.

      CALL METHOD build_concat_lines( ).
    ENDIF.

    lh_text = pi_text.
    IF pi_match_case IS INITIAL.
      TRANSLATE lh_text TO UPPER CASE.
    ENDIF.

    IF pi_condensed IS NOT INITIAL.
      CONDENSE lh_text NO-GAPS.
    ENDIF.

    IF pi_num_page IS NOT INITIAL.
      lwa_r_page-option = 'EQ'.
      lwa_r_page-sign   = 'I'.
      lwa_r_page-low    = pi_num_page.
      APPEND lwa_r_page TO lr_page.

      READ TABLE m_concat_texts WITH KEY num_page = pi_num_page TRANSPORTING NO FIELDS BINARY SEARCH.
      lh_index = sy-tabix.
    ELSE.
      lh_index = 1.
    ENDIF.

    LOOP AT m_concat_texts INTO lwa_concat_text
      FROM lh_index.

      lh_line_num = sy-tabix.

      IF lwa_concat_text-num_page NOT IN lr_page.
        EXIT.
      ENDIF.

      IF pi_match_case IS NOT INITIAL.
        IF pi_condensed IS NOT INITIAL.
          lh_text2 = lwa_concat_text-text_cond.
        ELSE.
          lh_text2 = lwa_concat_text-text.
        ENDIF.
      ELSE.
        IF pi_condensed IS NOT INITIAL.
          lh_text2 = lwa_concat_text-text_cond_u.
        ELSE.
          lh_text2 = lwa_concat_text-text_u.
        ENDIF.
      ENDIF.

      IF lh_text2 CP lh_text.
        READ TABLE m_concat_lines INDEX lh_line_num INTO lc_concat_line.
        APPEND lc_concat_line TO pe_concat_lines.
      ENDIF.

    ENDLOOP.
  ENDMETHOD.   "    find_text_concat

  " Возвращает все конкатенированные строки PDF на определенной странице между координатами
  " top и bottom
  METHOD find_lines_in_range.

    DATA: lr_page            TYPE RANGE OF I,
          lwa_r_page         LIKE LINE OF lr_page,
          lr_Y               TYPE RANGE OF I,
          lwa_r_Y            LIKE LINE OF lr_Y,
          lc_concat_line     TYPE REF TO lcl_pdf_concat_line,
          lwa_concat_text    TYPE t_concat_text,
          lh_index           TYPE I,
          lh_line_num        TYPE I,
          lh_top             TYPE I,
          lh_bottom          TYPE I,
          lh_num             TYPE I.

    REFRESH pe_concat_lines[].

    IF m_concat_texts[] IS INITIAL
      OR m_concat_lines[] IS INITIAL.

      CALL METHOD build_concat_lines( ).
    ENDIF.

    lh_top    = pi_top.
    lh_bottom = pi_bottom.

    IF lh_bottom > lh_top.
      lh_num    = lh_bottom.
      lh_bottom = lh_top.
      lh_top    = lh_num.
    ENDIF.

    IF pi_num_page IS NOT INITIAL.
      lwa_r_page-option = 'EQ'.
      lwa_r_page-sign   = 'I'.
      lwa_r_page-low    = pi_num_page.
      APPEND lwa_r_page TO lr_page.

      READ TABLE m_concat_texts WITH KEY num_page = pi_num_page TRANSPORTING NO FIELDS BINARY SEARCH.
      lh_index = sy-tabix.
    ELSE.
      lh_index = 1.
    ENDIF.

    lwa_r_Y-option = 'BT'.
    lwa_r_Y-sign   = 'I'.
    lwa_r_Y-low    = lh_bottom.
    lwa_r_Y-high   = lh_top.
    APPEND lwa_r_Y TO lr_Y.

    LOOP AT m_concat_texts INTO lwa_concat_text
      FROM lh_index
      WHERE Y        IN lr_Y
        AND num_page IN lr_page.

      lh_line_num = sy-tabix.

      READ TABLE m_concat_lines INDEX lh_line_num INTO lc_concat_line.
      APPEND lc_concat_line TO pe_concat_lines.
    ENDLOOP.
  ENDMETHOD.    "     find_lines_in_range

  " Возвращает следующую конкатенированную строку, если следующей строки нет, то
  " неприсвоенный объект
  " pi_ignore_page - искать ли только на той же странице или можно переходить и на
  "                  следующую
  METHOD find_next_concat_line.

    DATA: lc_concat_line          TYPE REF TO lcl_pdf_concat_line,
          lwa_concat_text         TYPE t_concat_text,
          lwa_concat_text_next    TYPE t_concat_text,
          lh_line_num             TYPE I,
          lh_line_num_next        TYPE I.

    IF pe_concat_line_next IS BOUND.
      FREE pe_concat_line_next.
    ENDIF.

    IF pi_concat_line IS NOT BOUND.
      RETURN.
    ENDIF.

    CALL METHOD pi_concat_line->get_line_num( IMPORTING pe_line_num = lh_line_num ).

    lh_line_num_next = lh_line_num + 1.
    IF lh_line_num_next > LINES( m_concat_lines ).
      RETURN.
    ENDIF.

    READ TABLE m_concat_lines INDEX lh_line_num_next INTO lc_concat_line.

    IF pi_ignore_page IS INITIAL.
      READ TABLE m_concat_texts INDEX lh_line_num INTO lwa_concat_text.
      READ TABLE m_concat_texts INDEX lh_line_num_next INTO lwa_concat_text_next.

      IF lwa_concat_text_next-num_page <> lwa_concat_text-num_page.
        RETURN.
      ENDIF.
    ENDIF.

    pe_concat_line_next = lc_concat_line.
  ENDMETHOD.         "        find_next_concat_line

ENDCLASS.



Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Чт, сен 12 2019, 20:15 
Специалист
Специалист
Аватара пользователя

Зарегистрирован:
Вт, июн 02 2009, 23:28
Сообщения: 228
Откуда: MOW
Пол: Мужской
Выложил как был на проекте, могут быть внешние ссылки. Если что присылайте ошибки, постараюсь всё посмотреть


Принять этот ответ
Вернуться к началу
 Профиль Отправить email  
 
 Заголовок сообщения: Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую)
СообщениеДобавлено: Пт, сен 13 2019, 12:41 
Младший специалист
Младший специалист

Зарегистрирован:
Вт, авг 05 2008, 20:46
Сообщения: 73
Откуда: С Урала
День добрый, давно интересовался темой, но с кодированием не получилось, тему забросил. Сейчас быстро глянул - внешних ссылок нет, но в моем случае дампы на CONVT_CODEPAGE - CX_SY_CONVERSION_CODEPAGE не может преобразовать некоторые символы в строке. c_conv->read Вот на этом н-р
LH_LEN 75
LH_LINE xnjn=SCHTTCH bZH xIO'>IIdRKK# # #U)TS}MI9(TSCHhKui1v#,?X"%{GHZ ir0ts ts#tsz
LH_LEN1 75
и в другом файле
LH_LEN 124
LH_LINE xnjkjVKoblF#dzlW#| #Hl}q#>#(zi #GJ^|Kbl#E] nU?IE##A/ bl# cGqshDZHkI #J/saZ##D# uLINcG #j9blnM73YAm## K#ie yx USCHTTCHsch/GHG
LH_LEN1 124
Акт. код. стр. сервера приложений 1500
Код. страница фронтэнда 1504
помогло
Code:
try.
          c_conv = cl_abap_conv_in_ce=>create( input       = lh_xstring
                                               replacement = space
                                               encoding    = '1504' ).
          c_conv->read( exporting
                          n    = lh_len
                        importing
                          data = lh_line
                          len  = lh_len1 ).
        catch cx_root.
      endtry.


Принять этот ответ
Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 18 ]  На страницу 1, 2  След.

Часовой пояс: UTC + 4 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: Ahrefs [Bot], Yandex [Bot]


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Русская поддержка phpBB