SAPфорум.RU https://sapboard.ru/forum/ |
|
Выкладываю парсер PDF (класс для чтения файла PDF напрямую) https://sapboard.ru/forum/viewtopic.php?f=13&t=51283 |
Страница 1 из 2 |
Автор: | raaleksandr [ Сб, июл 18 2009, 17:42 ] |
Заголовок сообщения: | Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Понадобилось автоматически загружать в 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 |
Автор: | calm [ Ср, июл 22 2009, 15:39 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Ого! Спасибо. Пока не требуется, но сохраняю себе на память. |
Автор: | v2k [ Чт, июн 02 2011, 13:10 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Не могли бы вы подсказать как получить физический размер страницы? Так же получает Adobe Reader в свойствах файла. |
Автор: | Fugitive [ Чт, июн 02 2011, 16:56 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Спасибо! Надеюсь в следующий раз будете получать данные для загрузки в XML. |
Автор: | raaleksandr [ Чт, июн 16 2011, 17:06 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
v2k написал(а): Не могли бы вы подсказать как получить физический размер страницы? Так же получает Adobe Reader в свойствах файла. К сожалению не разбирался с этим, так как нужно было только выдрать текст. Более того, реальная проблема была когда начал читать сканированный текст - там буквы часто произвольно соединялись в слова (не соответствовали реальным словам), и только по расположению и размеру шрифта можно было понять как они расположены реально. Все равно особо не разобрался с этим т.к. надо было читать какой-то зашифрованный кусок со шрифтом, решил задачу и так по ключевым словам, значкам, по расположению строк и т.д. То есть по косвенным признакам - и все работало 100%. Вообще по формату PDF есть подробный reference PS Для следующей разработки понадобилось доработать класс. Но руки пока не дошли его выложить. Так что если кто-то захочет воспользоваться и не заработает выложенный вариант, пишите, пришлю новую версию в том виде как есть, может кому-то поможет. |
Автор: | v2k [ Пт, июл 01 2011, 16:47 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Со своим вопросом я разобрался. PDF в принципе тэговый формат, есть тэг MediaBox, там и хранится физический размер страницы. |
Автор: | SAPer [ Чт, июл 12 2018, 13:16 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Я вот так парсю. Получаю 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 ). |
Автор: | Kengur [ Пн, июл 16 2018, 15:23 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Кроме картинок в PDF есть еще формат "обфускации". При этом текст в документе кликабелен, но в исходнике хранится как-бы шифрованный. |
Автор: | Бородин Игорь [ Чт, сен 12 2019, 14:28 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Добрый день, подниму тему, т.к. изучаю способы вытащить текст из 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 написал: Прилагаю исходный код парсера в виде отдельного инклуда (оформлен в виде класса). Спасибо, однако, чтобы применить к моему примеру, требуется доработка. Как минимум, в части разбора потоков. В моем примере stream сжат, вот небольшой фрагментТакже прилагаю небольшой пример для демонстрации возможностей (к сожалению, без файла PDF). Code: 27 0 obj Здесь /Filter/FlateDecode/ означает gzip-сжатие. Пытаюсь использовать cl_abap_gzip=>decompress_binary для декомпрессии содержимого между stream и endstream, но выдается непонятная ошибка (код возврата 30). Реализовывал ли кто-нибудь такое? Приведите фрагмент кода, пожалуйста...<</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Ы*r9kJї°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 И вообще, может быть существуют стандартные решения, чтобы не изобретать такие велосипеды? |
Автор: | pberezin [ Чт, сен 12 2019, 14:45 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
с декодированием текстового слоя с двухслойных пдф-ок (text under image) не всё просто. Хорошо если отправитель генерит в классическом PDF формате (не помню номер стандарта). Но бывают некие вендорские расширения PDF-стандарта (ну например с систем поточного распознавания типа Captiva, Kofax такое может выходить), там текстовый слой кодируется както хитро, чуть ли не шифруется, с ключом, который Adobe продаёт вендорам. Т.е. в бинарике пдфки тэг находишь, а расшифровать контент не получается, flatdecode не помогает. Хотя сертифицированные читалки и машины полнотекстового поиска через родную адобовскую dll его видят както. С табличным содержимым тоже непросто. Не всё что генерит пдф, впиливает в текстовый слой именно "таблицу". Тотже Kofax почемуто расставляет отдельные значения по ячейкам, но таблицей это в чистом виде не является после извлечения, - скорее в 1 столбик значения поколоночно (а если ячейки таблицы с объединением, то ваще капец получается). |
Автор: | pberezin [ Чт, сен 12 2019, 14:54 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
поэтому полудиалоговый макрос в экселе: 1. открыть пдф в читалке 2. эмулировать Ctrl+A, Ctrl+C 3. на чистый лист экселя Ctrl+V 4. разное |
Автор: | Бородин Игорь [ Чт, сен 12 2019, 16:23 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
pberezin написал: с декодированием текстового слоя с двухслойных пдф-ок (text under image) не всё просто. Хорошо если отправитель генерит в классическом PDF формате (не помню номер стандарта). к счастью, это не сканы, а выгруженные из клиент-банка файлыpberezin написал: Но бывают некие вендорские расширения PDF-стандарта да, я уже понял, что универсального парсера сделать не получится, но задачи такой и не стоит, т.к. структура платежек более-менее одинакова и довольно проста (табличного содержимого в них нет)pberezin написал: поэтому полудиалоговый макрос в экселе это интересный вариант, спасибо, но пока оставлю его как запасной.
|
Автор: | raaleksandr [ Чт, сен 12 2019, 19:14 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Я решал задачу распаковки текста из 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. |
Автор: | raaleksandr [ Чт, сен 12 2019, 19:15 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
Выложил как был на проекте, могут быть внешние ссылки. Если что присылайте ошибки, постараюсь всё посмотреть |
Автор: | NickS [ Пт, сен 13 2019, 11:41 ] |
Заголовок сообщения: | Re: Выкладываю парсер PDF (класс для чтения файла PDF напрямую) |
День добрый, давно интересовался темой, но с кодированием не получилось, тему забросил. Сейчас быстро глянул - внешних ссылок нет, но в моем случае дампы на 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. |
Страница 1 из 2 | Часовой пояс: UTC + 3 часа |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |