From 1d3d6e802969654345bfb3744c82cd91f8290a39 Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Tue, 10 Mar 2026 14:36:50 +0400 Subject: [PATCH 1/8] Fix(parser): change return data like described in #30 --- parser/src/lib.rs | 6 ++- parser/src/match_parsers.rs | 17 ++++---- parser/src/parsers/docx.rs | 82 ++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/parser/src/lib.rs b/parser/src/lib.rs index fbf2d74..9340605 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -9,11 +9,15 @@ use pyo3::{PyResult, types::PyModule}; /// Модуль для реализации функций модуля `docs_parser` mod parser { + use std::collections::HashMap; + use pyo3::prelude::*; + type ImgNumber = u32; + type ImagesInfo = HashMap<(u32, ImgNumber), Vec>; /// Парсинг текста `from` файла по `path` #[pyo3::pyfunction] - pub fn get_text(from_path: &str) -> PyResult { + pub fn get_text(from_path: &str) -> PyResult<(String, ImagesInfo)> { Ok(crate::match_parsers::get_text(from_path)?) } diff --git a/parser/src/match_parsers.rs b/parser/src/match_parsers.rs index 085cfa9..964a8d8 100644 --- a/parser/src/match_parsers.rs +++ b/parser/src/match_parsers.rs @@ -1,6 +1,6 @@ //! Функции определения MIME и выбора парсера в зависимости от MIME -use std::{str::from_utf8, sync::LazyLock}; +use std::{collections::HashMap, str::from_utf8, sync::LazyLock}; use infer::Infer; use mime::{IMAGE, Mime, TEXT, TEXT_PLAIN}; @@ -15,6 +15,8 @@ use crate::{ }; type Result = std::result::Result; +type ImgNumber = u32; +type ImagesInfo = HashMap<(u32, ImgNumber), Vec>; static INFER: LazyLock = LazyLock::new(Infer::new); @@ -30,25 +32,24 @@ static INFER: LazyLock = LazyLock::new(Infer::new); /// # Errors /// - [`ParserError::InvalidFormat`] - тип файла не поддерживается/не определен /// - Остальные варианты [`ParserError`], если ошибка во время парсинга файла -pub fn get_text(file_name: &str) -> Result { +pub fn get_text(file_name: &str) -> Result<(String, ImagesInfo)> { let file_data = read_data_from_file(file_name)?; match define_mime_type(&file_data) { Some(mime) if mime == APPLICATION_DOCX || (mime == APPLICATION_DOCX_ZIP && file_name.ends_with(".docx")) => { - let mut docx_parser = docx::DocxParser::new(); + let docx_parser = docx::DocxParser::new(); docx_parser.get_from_docx(&file_data) } Some(mime) if mime == APPLICATION_XLSX => todo!(), Some(mime) if mime == APPLICATION_PPTX => { let pptx_parser = pptx::PptxParser::new(); - let (res, _) = pptx_parser.get_from_pptx(&file_data)?; - Ok(res) + pptx_parser.get_from_pptx(&file_data) } - Some(mime) if mime == APPLICATION_PDF => get_from_pdf(&file_data), - Some(mime) if mime.type_() == TEXT => get_from_text(&file_data), - Some(mime) if mime.type_() == IMAGE => get_from_image(&file_data), + Some(mime) if mime == APPLICATION_PDF => Ok((get_from_pdf(&file_data)?, HashMap::new())), + Some(mime) if mime.type_() == TEXT => Ok((get_from_text(&file_data)?, HashMap::new())), + Some(mime) if mime.type_() == IMAGE => Ok((get_from_image(&file_data)?, HashMap::new())), Some(mime) if is_converted_mime_type(&mime) => Err(ParserError::InvalidFormat(format!( "Не поддерживается данный тип файла {mime}, но его вы можете конвертировать \ в поддерживаемый формат через отдельный метод конвертации" diff --git a/parser/src/parsers/docx.rs b/parser/src/parsers/docx.rs index 5101f7b..9e1127f 100644 --- a/parser/src/parsers/docx.rs +++ b/parser/src/parsers/docx.rs @@ -19,10 +19,15 @@ use zip::ZipArchive; type Result = std::result::Result; type Id = String; type Target = String; +type ImgNumber = u32; +type ImagesInfo = HashMap<(u32, ImgNumber), Vec>; pub(crate) struct DocxParser { /// HashMap, где хранятся id картинок и текст извлеченный из них pub images: HashMap, + pub img_info: ImagesInfo, + temp_img_info: HashMap>, + cur_img_ind: ImgNumber, } // FIX: переделать под выдачу и текста и картинки с метакми в тексте (типа этот текст из картинки @@ -32,6 +37,9 @@ impl DocxParser { pub(crate) fn new() -> Self { Self { images: HashMap::new(), + img_info: HashMap::new(), + temp_img_info: HashMap::new(), + cur_img_ind: 0, } } @@ -50,33 +58,35 @@ impl DocxParser { /// - [`ParserError::ZipError`] - ошибка во время парсинга docx как zip /// - [`ParserError::XmlError`] - ошибка во время парсинга конфигурационного файла docx /// - Остальные [`ParserError`] связанные с Tesseract ошибки во время парсинга картинки - pub(crate) fn get_from_docx(&mut self, data: &[u8]) -> Result { + pub(crate) fn get_from_docx(mut self, data: &[u8]) -> Result<(String, ImagesInfo)> { let dox = read_docx(data)?; // Вытаскиваем все картинки let images_bytes = self.extract_images_from_docx(data)?; // Парсим текст из картинок self.extract_text_from_images(images_bytes)?; - Ok(dox - .document - .children - .iter() - .filter_map(|from| match from { - docx_rs::DocumentChild::Paragraph(paragraph) => Some({ - let mut paragraph_text = self.paragraph_unwrap(paragraph); - paragraph_text.push('\n'); - paragraph_text - }), - docx_rs::DocumentChild::Table(table) => Some({ - let mut table_text = self.table_unwrap(table); - table_text.push('\n'); - table_text - }), - _ => None, - }) - .collect::>() - .join("\n") - .to_string()) + Ok(( + dox.document + .children + .iter() + .filter_map(|from| match from { + docx_rs::DocumentChild::Paragraph(paragraph) => Some({ + let mut paragraph_text = self.paragraph_unwrap(paragraph); + paragraph_text.push('\n'); + paragraph_text + }), + docx_rs::DocumentChild::Table(table) => Some({ + let mut table_text = self.table_unwrap(table); + table_text.push('\n'); + table_text + }), + _ => None, + }) + .collect::>() + .join("\n") + .to_string(), + self.img_info, + )) } /// Проходится по всем парам @@ -92,6 +102,7 @@ impl DocxParser { /// - [`ParserError::ImageError`] - ошибка во время парсинга картинки /// - Остальные [`ParserError`] связанные с Tesseract ошибки во время парсинга картинки fn extract_text_from_images(&mut self, images: HashMap>) -> Result<()> { + self.temp_img_info = images.clone(); self.images = images .into_par_iter() .map(|(id, data)| Ok((id, get_from_image(&data)?))) @@ -186,7 +197,7 @@ impl DocxParser { // ************************************************************************* /// Проходится по всем детям [`docx_rs::Paragraph`] и извлекает из них текст - fn paragraph_unwrap(&self, paragraph: &docx_rs::Paragraph) -> String { + fn paragraph_unwrap(&mut self, paragraph: &docx_rs::Paragraph) -> String { paragraph .children .iter() @@ -198,7 +209,7 @@ impl DocxParser { } /// Проходится по всем детям [`docx_rs::Run`] и извлекает из них текст - fn run_unwrap(&self, run: &docx_rs::Run) -> String { + fn run_unwrap(&mut self, run: &docx_rs::Run) -> String { run.children .iter() .filter_map(|from| match from { @@ -210,7 +221,7 @@ impl DocxParser { } /// Извлекает текст из [`docx_rs::Drawing`], если он есть - fn drawing_unwrap(&self, drawing: &docx_rs::Drawing) -> Result> { + fn drawing_unwrap(&mut self, drawing: &docx_rs::Drawing) -> Result> { Ok(match &drawing.data { Some(docx_rs::DrawingData::Pic(pic)) => Some(self.pic_unwrap(pic)?), Some(docx_rs::DrawingData::TextBox(text_box)) => Some(self.text_box_unwrap(text_box)), @@ -219,15 +230,22 @@ impl DocxParser { } /// Подставляет текст с нужной картинки вместо [`docx_rs::Pic`] - fn pic_unwrap(&self, pic: &docx_rs::Pic) -> Result { + fn pic_unwrap(&mut self, pic: &docx_rs::Pic) -> Result { match self.images.get(&pic.id) { - Some(text) => Ok(text.clone()), + Some(text) => { + let data = self + .temp_img_info + .remove(&pic.id) + .expect("Байты картинки обязаны существовать в момент работы с картинкой"); + self.img_info.insert((0, self.cur_img_ind), data); + Ok(text.clone()) + } None => Ok(String::new()), } } /// Извлекает текст из [`docx_rs::TextBox`] - fn text_box_unwrap(&self, text_box: &docx_rs::TextBox) -> String { + fn text_box_unwrap(&mut self, text_box: &docx_rs::TextBox) -> String { text_box .children .iter() @@ -241,7 +259,7 @@ impl DocxParser { } /// Проходится по всем детям [`docx_rs::Table`] и извлекает из них текст - fn table_unwrap(&self, table: &docx_rs::Table) -> String { + fn table_unwrap(&mut self, table: &docx_rs::Table) -> String { table .rows .iter() @@ -252,7 +270,7 @@ impl DocxParser { } /// Извлекает текст из [`docx_rs::TableRow`] - fn table_row_unwrap(&self, table_row: &docx_rs::TableRow) -> String { + fn table_row_unwrap(&mut self, table_row: &docx_rs::TableRow) -> String { table_row .cells .iter() @@ -267,7 +285,7 @@ impl DocxParser { } /// Извлекает текст из [`docx_rs::TableCell`] - fn table_cell_unwrap(&self, cell: &docx_rs::TableCell) -> String { + fn table_cell_unwrap(&mut self, cell: &docx_rs::TableCell) -> String { cell.children .iter() .filter_map(|from_cell_content| match from_cell_content { @@ -339,8 +357,8 @@ mod tests { fn extract_text_from_docx(extract_file: &str, check_file: &str) -> Result<()> { let data = read_data_from_file(extract_file)?; - let mut pars = DocxParser::new(); - let res = pars.get_from_docx(&data)?; + let pars = DocxParser::new(); + let (res, _) = pars.get_from_docx(&data)?; assert_eq!( res.trim(), From 7db15d656b60cd9fceafd5964ed871ac478dd13b Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Tue, 10 Mar 2026 14:37:19 +0400 Subject: [PATCH 2/8] Chore(docs_parser): update lsp info --- parser/docs_parser.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/docs_parser.pyi b/parser/docs_parser.pyi index 48cd6a4..588db5a 100644 --- a/parser/docs_parser.pyi +++ b/parser/docs_parser.pyi @@ -1,2 +1,2 @@ -def get_text(from_path: str) -> str: ... +def get_text(from_path: str) -> tuple[str, dict[tuple[int, int], bytes]]: ... def convert_to_new_format(old_file_path: str, new_path: str): ... From e84ae20ee6e665d0241ec04a1bb3440ba885c63f Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Tue, 10 Mar 2026 14:37:25 +0400 Subject: [PATCH 3/8] Chore(app): update main.py examples --- app/main.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/main.py b/app/main.py index 413f3e0..b093b21 100644 --- a/app/main.py +++ b/app/main.py @@ -1,14 +1,16 @@ import docs_parser # NOTE: все эти точно работают и работают хорошо -# print(docs_parser.get_text("parser/assets/text_and_tables.docx")) -# print(docs_parser.get_text("parser/assets/some_text.docx")) -# print(docs_parser.get_text("parser/assets/text_tables_png.docx")) -# print(docs_parser.get_text("parser/assets/text_from_img.png")) -# print(docs_parser.get_text("parser/assets/main.typ")) -# print(docs_parser.get_text("parser/assets/main.pdf")) -# print(docs_parser.get_text("parser/assets/too_many_png.docx")) -# print(docs_parser.get_text("parser/assets/Presentation.pptx")) -docs_parser.convert_to_new_format("parser/assets/old_docs.doc", "parser/assets/tests_results") -docs_parser.convert_to_new_format("parser/assets/old_pres.ppt", "parser/assets/tests_results") -docs_parser.convert_to_new_format("parser/assets/old_exel.xls", "parser/assets/tests_results") +# (doc_p, _) = docs_parser.get_text("parser/assets/text_and_tables.docx") +# (doc_p, _) = docs_parser.get_text("parser/assets/text_and_tables.docx") +# (doc_p, _) = docs_parser.get_text("parser/assets/some_text.docx") +# (doc_p, _) = docs_parser.get_text("parser/assets/text_tables_png.docx") +# (doc_p, _) = docs_parser.get_text("parser/assets/text_from_img.png") +# (doc_p, _) = docs_parser.get_text("parser/assets/main.typ") +# (doc_p, _) = docs_parser.get_text("parser/assets/main.pdf") +# (doc_p, _) = docs_parser.get_text("parser/assets/too_many_png.docx") +# (doc_p, _) = docs_parser.get_text("parser/assets/Presentation.pptx") +# print(doc_p) +# docs_parser.convert_to_new_format("parser/assets/old_docs.doc", "parser/assets/tests_results") +# docs_parser.convert_to_new_format("parser/assets/old_pres.ppt", "parser/assets/tests_results") +# docs_parser.convert_to_new_format("parser/assets/old_exel.xls", "parser/assets/tests_results") From 01043781f2eb9e7c3ff59a97790328ec9f86cd73 Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Tue, 10 Mar 2026 14:40:21 +0400 Subject: [PATCH 4/8] Chore(docx): remove fix comment --- parser/src/parsers/docx.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/parser/src/parsers/docx.rs b/parser/src/parsers/docx.rs index 9e1127f..6510960 100644 --- a/parser/src/parsers/docx.rs +++ b/parser/src/parsers/docx.rs @@ -30,8 +30,6 @@ pub(crate) struct DocxParser { cur_img_ind: ImgNumber, } -// FIX: переделать под выдачу и текста и картинки с метакми в тексте (типа этот текст из картинки -// такой-то) impl DocxParser { /// Создает новый [`DocxParser`]. pub(crate) fn new() -> Self { From 936f6c888fea9387871e205f77500e276d6ab8a6 Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Thu, 12 Mar 2026 11:16:26 +0400 Subject: [PATCH 5/8] Fix(docx): add formatting text from img in docx --- parser/src/parsers/docx.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/parser/src/parsers/docx.rs b/parser/src/parsers/docx.rs index 6510960..f28357c 100644 --- a/parser/src/parsers/docx.rs +++ b/parser/src/parsers/docx.rs @@ -235,8 +235,15 @@ impl DocxParser { .temp_img_info .remove(&pic.id) .expect("Байты картинки обязаны существовать в момент работы с картинкой"); - self.img_info.insert((0, self.cur_img_ind), data); - Ok(text.clone()) + let num = self.cur_img_ind; + + self.img_info.insert((0, num), data); + + Ok(format!( + "\n/************Image = {num}************/\n \ + {text} \ + \n/*************************************/\n", + )) } None => Ok(String::new()), } From 268b190cc020cf1802d5de7967f16283b0b06ddb Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Thu, 12 Mar 2026 11:16:55 +0400 Subject: [PATCH 6/8] Fix(docx): add inc cur_img_ind --- parser/src/parsers/docx.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/parser/src/parsers/docx.rs b/parser/src/parsers/docx.rs index f28357c..34fc72d 100644 --- a/parser/src/parsers/docx.rs +++ b/parser/src/parsers/docx.rs @@ -238,6 +238,7 @@ impl DocxParser { let num = self.cur_img_ind; self.img_info.insert((0, num), data); + self.cur_img_ind += 1; Ok(format!( "\n/************Image = {num}************/\n \ From 81dbed7d9c1feeb5f2082b66eae4e99f663fcc00 Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Thu, 12 Mar 2026 11:19:27 +0400 Subject: [PATCH 7/8] Chore(pptx): update text from img wrapping --- parser/src/parsers/pptx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/src/parsers/pptx.rs b/parser/src/parsers/pptx.rs index c7b7821..7745dc7 100644 --- a/parser/src/parsers/pptx.rs +++ b/parser/src/parsers/pptx.rs @@ -67,7 +67,7 @@ impl PptxParser { } self.slides_text.push(format!( - "\n/*****************slide = {} ***************/\n {}\n", + "\n/********************slide = {}********************/\n {}\n", slide.index, slide .text_elements @@ -110,7 +110,7 @@ impl PptxParser { res_slide_text.push_str(&get_from_image(data)?); res_slide_text - .push_str("\n/*****************************************************/"); + .push_str("\n/**************************************************/\n"); } Ok(res_slide_text) }) From 1aec39e3f29024c521eaa589f8f93190e347602f Mon Sep 17 00:00:00 2001 From: aragami3070 Date: Thu, 12 Mar 2026 11:28:51 +0400 Subject: [PATCH 8/8] Chore(assets-for-tests): update for fixes --- .../tests_results/extract_text_from_docx_with_png.txt | 6 +++++- .../tests_results/extract_text_from_pptx_with_png.txt | 6 +++--- .../tests_results/extract_text_from_pptx_without_png.txt | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/parser/assets/tests_results/extract_text_from_docx_with_png.txt b/parser/assets/tests_results/extract_text_from_docx_with_png.txt index fbc4e5e..c51ec44 100644 --- a/parser/assets/tests_results/extract_text_from_docx_with_png.txt +++ b/parser/assets/tests_results/extract_text_from_docx_with_png.txt @@ -4,4 +4,8 @@ A11 (в ней же таблица)A11_11 A11_12 A11_21 A11_22 a12 a13 a21 a23 -МЯУ=191919 + +/************Image = 0************/ + МЯУ=191919 +/*************************************/ + diff --git a/parser/assets/tests_results/extract_text_from_pptx_with_png.txt b/parser/assets/tests_results/extract_text_from_pptx_with_png.txt index 233fd74..676ad6d 100644 --- a/parser/assets/tests_results/extract_text_from_pptx_with_png.txt +++ b/parser/assets/tests_results/extract_text_from_pptx_with_png.txt @@ -1,15 +1,15 @@ -/*****************slide = 1 ***************/ +/********************slide = 1********************/ Тема презентации Подзаголовок презентации -/*****************slide = 2 ***************/ +/********************slide = 2********************/ Текст заголовка со слайда Абиба /********slide = 2; img_num = 0********/ МЯУ=191919 -/*****************************************************/ \ No newline at end of file +/**************************************************/ diff --git a/parser/assets/tests_results/extract_text_from_pptx_without_png.txt b/parser/assets/tests_results/extract_text_from_pptx_without_png.txt index 397c389..87694c5 100644 --- a/parser/assets/tests_results/extract_text_from_pptx_without_png.txt +++ b/parser/assets/tests_results/extract_text_from_pptx_without_png.txt @@ -1,11 +1,11 @@ -/*****************slide = 1 ***************/ +/********************slide = 1********************/ Тема презентации Подзаголовок презентации -/*****************slide = 2 ***************/ +/********************slide = 2********************/ Текст заголовка со слайда Абиба