Я решал задачу распаковки текста из 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.