Текущее время: Чт, мар 28 2024, 22:52

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


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


ВНИМАНИЕ!

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



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

Зарегистрирован:
Вт, июн 02 2009, 22: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, 15:39 
Почетный гуру
Почетный гуру
Аватара пользователя

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

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


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

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


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

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


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

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

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

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

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


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

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

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


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

Зарегистрирован:
Вт, авг 17 2004, 08:47
Сообщения: 222
Пол: Мужской
Я вот так парсю. Получаю 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, 15:23 
Почетный гуру
Почетный гуру
Аватара пользователя

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

_________________
я твой сап эфай внедрял
BAdI-позитив
Взять немножечко абопу, сунь туда кошачью *опу, RFC лапки, БТ старой бабки, на медленном базиснике переносить, тестовое окружение материть, снимать SAT пенку, биться головой о стенку, охапка тайм-шитов, отчет готов!


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

Зарегистрирован:
Пт, дек 04 2009, 12:52
Сообщения: 219
Добрый день, подниму тему, т.к. изучаю способы вытащить текст из 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, 14:45 
Старший специалист
Старший специалист

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

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


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

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


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

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


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

Зарегистрирован:
Вт, июн 02 2009, 22: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, 19:15 
Специалист
Специалист
Аватара пользователя

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


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

Зарегистрирован:
Вт, авг 05 2008, 19:46
Сообщения: 96
Откуда: С Урала
День добрый, давно интересовался темой, но с кодированием не получилось, тему забросил. Сейчас быстро глянул - внешних ссылок нет, но в моем случае дампы на 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.


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

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


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

Сейчас этот форум просматривают: нет зарегистрированных пользователей


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

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