def test_without_numbering(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "without_numbering.docx") try: parser.parse(path) result = parser.get_lines_with_meta() except AttributeError: result = None self.assertTrue(result is not None)
def __init__(self, path2docs: str): self.path2docs = path2docs self.docx_reader = DOCXParser() self.color_step = 16 self.first_color = 15 self.base_color = 0 self.base_color_step = 1 self.many_colors_file_name = 'many_colors_doc' self.two_colors_file_name = 'two_colors_doc'
def test_list_1(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "lists_1.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Header 1', result[0]["text"]) self.assertEqual('1)\tbla', result[1]["text"]) self.assertEqual('1.\ttest', result[2]["text"]) self.assertEqual('2.\ttest', result[3]["text"]) self.assertEqual('2)\tbla', result[4]["text"]) self.assertEqual('3)\tbla', result[5]["text"]) self.assertEqual('1.\tbla', result[6]["text"]) self.assertEqual('2.\tbla bla', result[7]["text"]) self.assertEqual('2.1.\tbla bla bla', result[8]["text"]) self.assertEqual('2.2.\tbla bla', result[9]["text"]) self.assertEqual('2.2.1.\ttest test', result[10]["text"]) self.assertEqual('2.2.2.\ttest text', result[11]["text"]) self.assertEqual('2.2.2.1.\togo', result[12]["text"]) self.assertEqual('2.2.2.2.\tmdaaa', result[13]["text"]) self.assertEqual('2.2.2.3.\ttest go deeper', result[14]["text"]) self.assertEqual('2.2.2.3.1.\tare you seriously?', result[15]["text"]) self.assertEqual('2.2.2.3.1.1.\tno, SERIOUSLY??', result[16]["text"]) self.assertEqual('2.2.2.3.1.1.1.\tthis is baaad list', result[17]["text"]) self.assertEqual('2.2.2.3.2.\tnew item?', result[18]["text"]) self.assertEqual('2.2.2.3.3.\tyou are very crazy', result[19]["text"]) self.assertEqual('2.2.2.3.4.\tyou are very crazy', result[20]["text"]) self.assertEqual('2.2.2.3.5.\tyou are very crazy', result[21]["text"]) self.assertEqual('a)\tno', result[22]["text"]) self.assertEqual('b)\tnono', result[23]["text"]) self.assertEqual('c)\tnonono', result[24]["text"]) self.assertEqual('2.2.2.4.\tnoooo', result[25]["text"]) self.assertEqual('2.2.2.5.\treally?', result[26]["text"]) self.assertEqual('2.2.3.\tha-ha', result[27]["text"]) self.assertEqual('Text text', result[28]["text"]) self.assertEqual('а. Пункт а', result[29]["text"]) self.assertEqual('б. Пункт б', result[30]["text"]) self.assertEqual('в. Пункт в', result[31]["text"]) self.assertEqual('г. Пункт г', result[32]["text"]) self.assertEqual('Test', result[33]["text"]) self.assertEqual('Lorem ipsum', result[34]["text"]) self.assertEqual('a)\ttest a', result[35]["text"]) self.assertEqual('b)\ttest b', result[36]["text"]) self.assertEqual('c)\ttest c', result[37]["text"]) self.assertEqual('TEXT TEXT TEXT', result[38]["text"]) self.assertEqual('2.2.4.\ttest', result[39]["text"]) self.assertEqual('2.3.\ttest test', result[40]["text"]) self.assertEqual('3.\tbla bla bla', result[41]["text"]) self.assertEqual('4.\tand some test text', result[42]["text"]) self.assertEqual('5.\tlorem ipsum', result[43]["text"]) self.assertEqual('6.\tsome text', result[44]["text"]) self.assertEqual('4)\ttest test', result[45]["text"]) self.assertEqual('5)\ttest test test', result[46]["text"])
def doc2tree(doc_name: str, comparator_type: str = "pair", data_path: str = None) -> dict: doc_name = os.path.abspath(doc_name) if comparator_type == "test": assert (data_path is not None) comparator = TestComparator(data_path) else: comparator = PairClassifier.load_pickled(config={}) docx_parser = DOCXParser() docx_parser.parse(doc_name) lines = docx_parser.get_lines_with_meta() tree_constructor = DocumentTreeConstructor(comparator=comparator) doc_tree = tree_constructor.construct_tree(lines) return doc_tree
def test_caps(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "caps_1.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('ШИЖМАШ МОГАЙ ЛИЕШ ГЫН?\t', result[0]["text"]) self.assertEqual('АНАСТАСИЯ АЙГУЗИНА', result[1]["text"]) path = os.path.join(TEST_DIR, "caps_2.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('И. Одар "Таргылтыш"', result[0]["text"]) self.assertEqual('I глава', result[1]["text"])
class AbstractDocxImagesCreator(ABC): def __init__(self, path2docs: str): self.path2docs = path2docs self.docx_reader = DOCXParser() self.color_step = 16 self.first_color = 15 self.base_color = 0 self.base_color_step = 1 self.many_colors_file_name = 'many_colors_doc' self.two_colors_file_name = 'two_colors_doc' def create_images(self, path: str) -> Iterator[Optional[Image.Image]]: """ The algorithm if as follows: 1 create two pdf: the first with bboxes around paragraph (each of different color) and the second with bboxes of two colors. 2 We read images from both pdf (one page by one) and subtracting first image from the second (we get image with nonzero pixels on bboxes only) 3 we clear bboxes from first image 4 and create one image per bbox and save in tmp dir 5 and finally we return image with bboxes in the proper order @param path: path to docx document @return: sequence of Images """ # here we get half processing docx document (with raw xml) self.docx_reader.parse(os.path.join(self.path2docs, path)) with zipfile.ZipFile(os.path.join(self.path2docs, path)) as d: with tempfile.TemporaryDirectory() as tmp_dir: pdfs = self.__create_pair_pdfs(docx_archive=d, tmp_dir=tmp_dir) # create image with bbox yield from self._create_images_from_pdf(pdfs=pdfs, tmp_dir=tmp_dir) @abstractmethod def _create_images_from_pdf(self, pdfs: PairedPdf, tmp_dir: str) -> Iterable[Image.Image]: """ we take two paired pdfs with bboxes and create images from them. Then we return images according to page order @param pdfs: tuple with path to 2 pdfs and info about used colors @param tmp_dir: path where we save intermediate images @return: """ pass def __create_pair_pdfs(self, docx_archive: zipfile.ZipFile, tmp_dir: str) -> PairedPdf: """ here we create two paired pdfs, we modify docx xml (drow bbox around paragraph) and create pdf, based on this modified docx. We create pdf with multi colors and with two colors @param docx_archive: opened docx document (docx is a zip archive) @param tmp_dir: directory where we save intermediate results @return: """ docx_archive.extractall(tmp_dir) namelist = docx_archive.namelist() document_bs = self.docx_reader.get_document_bs paragraph_list = [par for par in self.docx_reader.get_paragraph_xml_list] # create docx file with bboxes of different colors used_many_colors = self.__draw_bboxes(paragraph_list=paragraph_list, many_colors=True) # document_bs was changed implicitly text = re.sub("w:pbdr", "w:pBdr", str(document_bs)) text = re.sub("w:ppr", "w:pPr", text) many_colors_pdf = self.__create_pdf_from_docx(tmp_dir, self.many_colors_file_name, namelist, text) # clear document_bs from border tags border_tags = document_bs.find_all('w:pbdr') for tag in border_tags: tag.decompose() # create docx file with bboxes of two interleaving colors used_two_colors = self.__draw_bboxes(paragraph_list=paragraph_list, many_colors=False) # document_bs was changed implicitly text = re.sub("pbdr", "pBdr", str(document_bs)) text = re.sub("w:ppr", "w:pPr", text) two_colors_pdf = self.__create_pdf_from_docx(tmp_dir, self.two_colors_file_name, namelist, text) return PairedPdf(many_colors_pdf, two_colors_pdf, used_many_colors, used_two_colors) def __draw_bboxes(self, paragraph_list: List[BeautifulSoup], many_colors: bool) -> Dict[str, int]: """ draw bbox in docx document around each paragraph @param paragraph_list: @param many_colors: @return: """ if many_colors: decimal_color = self.first_color else: decimal_color = self.base_color used_colors = {} # draw bboxes using different colors # if many_colors == False we draw bboxes using # only two interleaving colors for correct drawing bboxes in docx lines = self.docx_reader.get_lines_with_meta() for paragraph, line in zip(paragraph_list, lines): color = self._color_from_decimal(decimal_color) used_colors[line["uid"]] = decimal_color self.__insert_border(paragraph, color) if many_colors: decimal_color += self.color_step else: if decimal_color == self.base_color: decimal_color += self.base_color_step else: decimal_color -= self.base_color_step return used_colors @staticmethod def _color_from_decimal(decimal_color: int) -> str: color = hex(decimal_color)[2:] if len(color) < 6: color = '0' * (6 - len(color)) + color return color @staticmethod def __create_pdf_from_docx(tmp_dir: str, doc_name: str, namelist: List[str], doc_text: str) -> str: with open('{}/word/document.xml'.format(tmp_dir), 'w') as f: f.write(doc_text) docx_path = "{}/{}.docx".format(tmp_dir, doc_name) with zipfile.ZipFile(docx_path, mode='w') as new_d: for filename in namelist: new_d.write('{}/{}'.format(tmp_dir, filename), arcname=filename) # create pdf file with bbox pdf_name = AbstractDocxImagesCreator.__docx2pdf(tmp_dir, docx_path) os.remove(docx_path) return pdf_name @staticmethod def __await_for_conversion(filename: str) -> None: timeout = 10 period_checking = 0.05 t = 0 while (not os.path.isfile(filename)) and (t < timeout): time.sleep(period_checking) t += period_checking if t >= timeout: raise Exception("fail with {filename}".format(filename=filename)) @staticmethod def __docx2pdf(out_dir: str, path: str) -> str: os.system("/Applications/LibreOffice.app/Contents/MacOS/soffice --headless" " --convert-to pdf {} --outdir {}".format(path, out_dir)) out_file = '{}/{}pdf'.format(out_dir, os.path.split(path)[-1][:-4]) AbstractDocxImagesCreator.__await_for_conversion(out_file) return out_file @staticmethod def __insert_border(bs_tree: Optional[BeautifulSoup], color: str) -> None: if bs_tree is None: return border_str = '<w:pBdr><w:top w:val="single" ' \ 'w:color="{color}" w:sz="8" w:space="0" ' \ 'w:shadow="0" w:frame="0"/><w:left w:val="single" ' \ 'w:color="{color}" w:sz="8" w:space="0" ' \ 'w:shadow="0" w:frame="0"/><w:bottom w:val="single" ' \ 'w:color="{color}" w:sz="8" w:space="0" w:shadow="0" ' \ 'w:frame="0"/><w:right w:val="single" w:color="{color}" ' \ 'w:sz="8" w:space="0" w:shadow="0" w:frame="0"/></w:pBdr>'.format(color=color) border_bs = BeautifulSoup(border_str, 'lxml').body.contents[0] if bs_tree.pPr: bs_tree.pPr.insert(1, border_bs) else: border_bs = BeautifulSoup('<w:pPr>' + border_str + '</w:pPr>', 'lxml').body.contents[0] bs_tree.insert(0, border_bs) @staticmethod def _split_pdf2image(path: str) -> Iterator[np.ndarray]: page_num = 1 images = None while images is None or len(images) > 0: images = convert_from_path(path, first_page=page_num, last_page=page_num) page_num += 1 if len(images) > 0: yield np.array(images[0]) @staticmethod def get_concat_v(images: List[Image.Image]) -> Image: if len(images) == 1: return images[0] width = max((image.width for image in images)) height = sum((image.height for image in images)) dst = Image.new('RGB', (width, height)) height = 0 for image in images: dst.paste(image, (0, height)) height += image.height return dst
def test_annotations_libreoffice(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "annotation_libreoffice_1.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Example document', result[0]['text']) self.assertIn(('alignment', 0, 16, 'center'), result[0]['annotations']) self.assertIn(('bold', 0, 16, 'True'), result[0]['annotations']) self.assertIn(('size', 0, 16, '28.0'), result[0]['annotations']) self.assertIn(('style', 0, 16, 'title'), result[0]['annotations']) self.assertEqual('Chapter 1', result[1]['text']) self.assertIn(('alignment', 0, 9, 'left'), result[1]['annotations']) self.assertIn(('bold', 0, 9, 'True'), result[1]['annotations']) self.assertIn(('size', 0, 9, '18.0'), result[1]['annotations']) self.assertIn(('style', 0, 9, 'heading 1'), result[1]['annotations']) self.assertEqual('Here we check simple text', result[2]["text"]) self.assertIn(('size', 0, 25, '12.0'), result[2]['annotations']) self.assertIn(('style', 0, 25, 'body text'), result[2]['annotations']) self.assertEqual('Chapter 1.1', result[3]['text']) self.assertIn(('bold', 0, 11, 'True'), result[3]['annotations']) self.assertIn(('size', 0, 11, '16.0'), result[3]['annotations']) self.assertIn(('style', 0, 11, 'heading 2'), result[3]['annotations']) self.assertEqual('1.\tHere', result[4]['text']) self.assertIn(('size', 0, 7, '12.0'), result[4]['annotations']) self.assertIn(('style', 0, 7, 'body text'), result[4]['annotations']) self.assertEqual('Chapter 1.1.1', result[8]['text']) self.assertIn(('bold', 0, 13, 'True'), result[8]['annotations']) self.assertIn(('size', 0, 13, '14.0'), result[8]['annotations']) self.assertIn(('style', 0, 13, 'heading 3'), result[8]['annotations']) self.assertEqual('HERE WE CHECK CAPS LETTERS', result[9]['text']) self.assertEqual('Here we check custom styles', result[11]['text']) self.assertIn(('alignment', 0, 27, 'center'), result[11]['annotations']) self.assertIn(('italic', 0, 27, 'True'), result[11]['annotations']) self.assertIn(('size', 0, 27, '13.0'), result[11]['annotations']) self.assertIn(('style', 0, 27, 'custom style'), result[11]['annotations']) self.assertEqual('−\tHere we check custom ', result[12]['text']) self.assertIn(('italic', 0, 23, 'True'), result[12]['annotations']) self.assertIn(('style', 0, 23, 'custom style with numbering'), result[12]['annotations']) self.assertEqual( 'Here we check bold, italic, underlined, large font and small font.', result[15]['text']) self.assertIn(('bold', 14, 18, 'True'), result[15]['annotations']) self.assertIn(('italic', 20, 26, 'True'), result[15]['annotations']) self.assertIn(('underlined', 28, 38, 'True'), result[15]['annotations']) self.assertIn(('size', 0, 40, '12.0'), result[15]['annotations']) self.assertIn(('size', 40, 50, '18.0'), result[15]['annotations']) self.assertIn(('size', 50, 55, '12.0'), result[15]['annotations']) self.assertIn(('size', 55, 65, '8.0'), result[15]['annotations']) self.assertIn(('size', 65, 66, '12.0'), result[15]['annotations']) self.assertEqual('Center justification', result[16]['text']) self.assertIn(('alignment', 0, 20, 'center'), result[16]['annotations']) self.assertEqual('Both justification', result[17]['text']) self.assertIn(('alignment', 0, 18, 'both'), result[17]['annotations']) self.assertEqual('Right justification', result[18]['text']) self.assertIn(('alignment', 0, 19, 'right'), result[18]['annotations']) self.assertEqual('⎝\tHere', result[20]['text']) path = os.path.join(TEST_DIR, "annotation_libreoffice_2.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual( 'Техническое задание\nна оказание услуг по созданию системы защиты персональных данных ', result[0]['text']) self.assertIn(('alignment', 0, 85, 'center'), result[0]['annotations']) self.assertIn(('bold', 0, 85, 'True'), result[0]['annotations']) self.assertIn(('size', 0, 85, '12.0'), result[0]['annotations']) self.assertIn(('style', 0, 85, 'normal'), result[0]['annotations']) self.assertEqual('1.\tНаименование оказываемых услуг.', result[3]['text']) self.assertIn(('alignment', 0, 34, 'center'), result[3]['annotations']) self.assertIn(('bold', 0, 34, 'True'), result[3]['annotations']) self.assertIn(('style', 0, 34, 'list paragraph'), result[3]['annotations']) self.assertEqual( 'Услуги по созданию системы защиты персональных данных и аттестации автоматизированных ' 'рабочих мест в администрации Пушкинского муниципального района Московской области ' '(далее – Заказчик).', result[4]['text']) self.assertIn(('alignment', 0, 187, 'both'), result[4]['annotations']) self.assertNotIn(('bold', 0, 187, 'True'), result[4]['annotations']) self.assertEqual( '7. Общие требования к оказанию услуг, требования к их качеству, в том числе к ' 'техническим характеристикам поставляемых средств защиты информации.', result[6]['text']) self.assertIn(('bold', 0, 145, 'True'), result[6]['annotations']) self.assertEqual( '7.1.\tУслуги по обновлению системы защиты информации должны быть оказаны в соответствии с ' 'требованиями и рекомендациями следующих нормативных документов:', result[7]['text']) self.assertIn(('style', 0, 152, 'list paragraph'), result[7]['annotations']) self.assertEqual( '⎯\tФедеральный закон от 27 июля 2006 г. № 149-ФЗ "Об информации, ' 'информационных технологиях и о защите информации".', result[8]['text']) self.assertIn(('style', 0, 114, 'list paragraph'), result[8]['annotations']) self.assertEqual( '7.1.1.\tДоработка проектов организационно-распорядительной документации.', result[9]['text']) self.assertEqual('-\tАнализ процесса обработки персональных данных.', result[11]['text']) self.assertEqual( '• управление заявками пользователей (заявки на обслуживание и техподдержку);', result[13]['text']) self.assertEqual( '7.1.2.\tТребования к качественным и техническим характеристикам программного обеспечения, ' 'реализующего функции средства анализа защищенности:', result[14]['text']) self.assertEqual( '⎯\tАнализ и классификацию уязвимостей на 32 узлах защищаемой сети.', result[16]['text'])
def test_annotations_word(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "annotation_word_1.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Это заголовок первого уровня', result[0]['text']) self.assertIn(('size', 0, 28, '16.0'), result[0]['annotations']) self.assertIn(('style', 0, 28, 'heading 1'), result[0]['annotations']) self.assertEqual('Это заголовок четвёртого уровня', result[3]['text']) self.assertIn(('italic', 0, 31, 'True'), result[3]['annotations']) self.assertIn(('size', 0, 31, '14.0'), result[3]['annotations']) self.assertIn(('style', 0, 31, 'heading 4'), result[3]['annotations']) self.assertEqual('Это заголовок седьмого уровня', result[6]['text']) self.assertIn(('italic', 0, 29, 'True'), result[6]['annotations']) self.assertIn(('style', 0, 29, 'heading 7'), result[6]['annotations']) self.assertEqual('Это заголовок девятого уровня', result[8]['text']) self.assertIn(('italic', 0, 29, 'True'), result[8]['annotations']) self.assertIn(('size', 0, 29, '10.5'), result[8]['annotations']) self.assertIn(('style', 0, 29, 'heading 9'), result[8]['annotations']) self.assertEqual('Это обычный текст', result[9]['text']) self.assertIn(('size', 0, 17, '14.0'), result[9]['annotations']) self.assertEqual('•\tПервый элемент маркированного списка', result[10]['text']) self.assertIn(('indentation', 0, 38, '720'), result[10]['annotations']) self.assertIn(('size', 0, 38, '14.0'), result[10]['annotations']) self.assertIn(('style', 0, 38, 'list paragraph'), result[10]['annotations']) self.assertEqual('1.\tПервый элемент нумерованного списка', result[13]['text']) self.assertEqual('1.\tПервый элемент сложного нумерованного списка', result[16]['text']) self.assertEqual('1.1.\tПервый элемент первого элемента списка', result[17]['text']) self.assertEqual( '3.2.1.\tПервый элемент второго элемента третьего элемента списка', result[23]['text']) self.assertEqual('•\tПервый элемент второго маркированного списка', result[26]['text']) self.assertEqual('a)\tПервый элемент буквенного нумерованного списка', result[28]['text']) self.assertEqual( 'Обычный параграф, внутри которого присутствует вcякая дичь в виде курсива, жирного шрифта, ' 'подчёркнутого шрифта, а также выделенный текст другим цветом, шрифт большего размера, шрифт ' 'меньшего размера, и, конечно же, шрифт с другим font-name’ом.', result[30]['text']) self.assertIn(('italic', 66, 73, 'True'), result[30]['annotations']) self.assertIn(('bold', 75, 89, 'True'), result[30]['annotations']) self.assertIn(('underlined', 91, 111, 'True'), result[30]['annotations']) self.assertIn(('size', 0, 153, '14.0'), result[30]['annotations']) self.assertIn(('size', 153, 175, '20.0'), result[30]['annotations']) self.assertIn(('size', 175, 183, '14.0'), result[30]['annotations']) self.assertIn(('size', 183, 199, '11.0'), result[30]['annotations']) self.assertIn(('size', 199, 244, '14.0'), result[30]['annotations']) self.assertEqual( '5.\tВнезапное продолжение того сложного списка, пункт 5', result[31]['text']) self.assertEqual('•\tМаркированный список из одного элемента', result[33]['text']) self.assertEqual('7.\tЕщё один пункт сложного списка', result[34]['text']) self.assertEqual('Обычный текст с выравниванием по левому краю', result[35]['text']) self.assertIn(('alignment', 0, 44, 'left'), result[35]['annotations']) self.assertEqual('Обычный текст с выравниванием по правому краю', result[36]['text']) self.assertIn(('alignment', 0, 45, 'right'), result[36]['annotations']) self.assertEqual('Обычный текст с выравниванием по центру', result[37]['text']) self.assertIn(('alignment', 0, 39, 'center'), result[37]['annotations']) self.assertEqual( 'Обычный текст, который мы решили растянуть по всей ширине страницы, чтобы проверить, как ' 'оно будет выглядеть, а для этого приходится писать этот длинный текст.', result[38]['text']) self.assertIn(('alignment', 0, 159, 'both'), result[38]['annotations']) self.assertEqual('Этот параграф весь жирный', result[39]['text']) self.assertIn(('bold', 0, 25, 'True'), result[39]['annotations']) self.assertEqual('Этот параграф весь курсивный', result[40]['text']) self.assertIn(('italic', 0, 28, 'True'), result[40]['annotations']) self.assertEqual('Этот параграф весь подчёркнутый', result[41]['text']) self.assertIn(('underlined', 0, 31, 'True'), result[41]['annotations']) self.assertEqual('Этот параграф и жирный и курсивный', result[42]['text']) self.assertIn(('bold', 0, 34, 'True'), result[42]['annotations']) self.assertIn(('italic', 0, 34, 'True'), result[42]['annotations']) self.assertEqual('Этот параграф и курсивный, и жирный и подчёркнутый', result[43]['text']) self.assertIn(('bold', 0, 50, 'True'), result[43]['annotations']) self.assertIn(('italic', 0, 50, 'True'), result[43]['annotations']) self.assertIn(('underlined', 0, 50, 'True'), result[43]['annotations']) path = os.path.join(TEST_DIR, "annotation_word_2.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Header 1', result[0]['text']) self.assertIn(('size', 0, 8, '16.0'), result[0]['annotations']) self.assertIn(('style', 0, 8, 'heading 1'), result[0]['annotations']) self.assertEqual('Header 3', result[1]['text']) self.assertIn(('size', 0, 8, '12.0'), result[1]['annotations']) self.assertIn(('style', 0, 8, 'heading 3'), result[1]['annotations']) self.assertEqual('Simple text', result[6]['text']) self.assertIn(('alignment', 0, 11, 'center'), result[6]['annotations']) self.assertEqual('1.\tBullet list point 1', result[7]['text']) self.assertIn(('style', 0, 22, 'list paragraph'), result[7]['annotations']) self.assertEqual('Some simple text again', result[11]['text']) self.assertIn(('alignment', 0, 22, 'right'), result[11]['annotations']) self.assertEqual('Start of a little table', result[20]['text']) self.assertIn(('size', 0, 23, '18.0'), result[20]['annotations']) self.assertIn(('style', 0, 23, 'heading 2'), result[20]['annotations']) self.assertEqual('•\tBullet list 2 point 1', result[23]['text']) self.assertIn(('indentation', 0, 23, '720'), result[23]['annotations']) self.assertEqual('Test hard lists:', result[27]['text']) self.assertIn(('size', 0, 5, '14.0'), result[27]['annotations']) self.assertIn(('size', 5, 9, '18.0'), result[27]['annotations']) self.assertIn(('size', 9, 16, '14.0'), result[27]['annotations']) self.assertEqual('1. First item', result[28]['text']) self.assertEqual( 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ' 'ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco ' 'laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in ' 'voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat ' 'cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', result[40]['text']) self.assertIn(('alignment', 0, 446, 'both'), result[40]['annotations']) self.assertIn(('size', 0, 197, '14.0'), result[40]['annotations']) self.assertIn(('size', 197, 221, '18.0'), result[40]['annotations']) self.assertIn(('bold', 243, 325, 'True'), result[40]['annotations']) self.assertIn(('size', 221, 446, '14.0'), result[40]['annotations']) self.assertEqual('Test test test', result[41]['text']) self.assertIn(('size', 0, 14, '16.0'), result[41]['annotations']) self.assertIn(('style', 0, 14, 'heading 1'), result[41]['annotations']) self.assertEqual('Blab la bla', result[42]['text']) self.assertIn(('size', 0, 11, '14.0'), result[42]['annotations']) path = os.path.join(TEST_DIR, "annotation_word_3.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Договор № ____', result[0]['text']) self.assertIn(('alignment', 0, 14, 'center'), result[0]['annotations']) self.assertIn(('bold', 0, 14, 'True'), result[0]['annotations']) self.assertEqual( 'Общество с ограниченной ответственностью «Объединенная дирекция по управлению активами и ' 'сервисами Центра разработки и коммерциализации новых технологий (инновационного центра ' '«Сколково»)» (ООО «ОДАС Сколково»), именуемое в дальнейшем «Заказчик», в лице Директора ' 'Дирекции по эксплуатации объектов недвижимости Троценко Дениса Сергеевича, действующего ' 'на основании Доверенности № 37 от 10.07.2015 г., с одной стороны, и', result[3]['text']) self.assertIn(('alignment', 0, 420, 'both'), result[3]['annotations']) self.assertIn(('bold', 0, 210, 'True'), result[3]['annotations']) self.assertIn(('bold', 236, 244, 'True'), result[3]['annotations']) self.assertEqual( ' ______________________________________(__)_, именуемое в дальнейшем «Исполнитель», ' 'в лице __________________________________, действующего на основании _______________, ' 'с другой стороны, в дальнейшем совместно именуемые «Стороны», а по отдельности «Сторона», ' 'заключили настоящий договор (далее – «Договор») о нижеследующем.', result[4]['text']) self.assertIn(('bold', 1, 46, 'True'), result[4]['annotations']) self.assertIn(('bold', 70, 81, 'True'), result[4]['annotations']) self.assertEqual('Статья 1. Термины и определения', result[5]['text']) self.assertIn(('bold', 0, 31, 'True'), result[5]['annotations']) self.assertEqual( '1.\t«АВР» – аварийно-восстановительные работы, связанные с оперативным реагированием ' 'Исполнителя и устранением последствий нештатных (аварийных) ситуаций при эксплуатации ' 'Инженерных систем.', result[7]['text']) self.assertIn(('bold', 0, 9, 'True'), result[7]['annotations']) self.assertEqual('Статья 2. Предмет Договора', result[8]['text']) self.assertIn(('bold', 0, 26, 'True'), result[8]['annotations']) self.assertEqual( '1.\tВ соответствии с настоящим Договором, Заказчик поручает, а Исполнитель принимает на ' 'себя обязательства своевременно и в полном объеме выполнять комплекс работ и услуг по ' 'содержанию объектов (зданий и строений), сервисному и техническому обслуживанию комплекса ' 'объектов инженерной инфраструктуры (далее – «Работы»), в соответствии с условиями настоящего ' 'Договора и Приложений к нему, включая:', result[9]['text']) self.assertIn(('bold', 147, 298, 'True'), result[9]['annotations']) self.assertEqual('- Техническую эксплуатацию Инженерных систем;', result[10]['text'])
def test_annotations_pages(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "annotation_pages_1.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Plain text', result[0]['text']) self.assertIn(('size', 0, 10, '11.0'), result[0]['annotations']) self.assertEqual('Italic text', result[1]['text']) self.assertIn(('italic', 0, 11, 'True'), result[1]['annotations']) self.assertIn(('size', 0, 11, '12.0'), result[1]['annotations']) self.assertEqual('Bold text', result[2]['text']) self.assertIn(('bold', 0, 9, 'True'), result[2]['annotations']) self.assertEqual('Underlined text', result[3]['text']) self.assertIn(('underlined', 0, 15, 'True'), result[3]['annotations']) self.assertEqual('Italic nonitalic', result[4]['text']) self.assertIn(('italic', 0, 6, 'True'), result[4]['annotations']) self.assertIn(('size', 0, 16, '13.0'), result[4]['annotations']) self.assertEqual('Nonbold bold', result[5]['text']) self.assertIn(('bold', 8, 12, 'True'), result[5]['annotations']) self.assertIn(('size', 0, 12, '14.0'), result[5]['annotations']) self.assertEqual('Bold boldUnderlined', result[6]['text']) self.assertIn(('bold', 0, 19, 'True'), result[6]['annotations']) self.assertIn(('underlined', 5, 19, 'True'), result[6]['annotations']) self.assertIn(('size', 0, 19, '11.0'), result[6]['annotations']) self.assertEqual('Left text', result[7]['text']) self.assertIn(('alignment', 0, 9, 'left'), result[7]['annotations']) self.assertEqual('Centered text', result[8]['text']) self.assertIn(('alignment', 0, 13, 'center'), result[8]['annotations']) self.assertEqual('Right text', result[9]['text']) self.assertIn(('alignment', 0, 10, 'right'), result[9]['annotations']) self.assertEqual('Text aligned to both borders', result[10]['text']) self.assertIn(('alignment', 0, 28, 'both'), result[10]['annotations']) self.assertEqual('Zero indent', result[11]['text']) self.assertIn(('indentation', 0, 11, '0'), result[11]['annotations']) self.assertEqual('One indent', result[12]['text']) self.assertIn(('indentation', 0, 10, '720'), result[12]['annotations']) self.assertEqual('Two indents', result[13]['text']) self.assertIn(('indentation', 0, 11, '1440'), result[13]['annotations']) path = os.path.join(TEST_DIR, "annotation_pages_2.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('The first line', result[0]['text']) self.assertIn(('size', 0, 14, '15.0'), result[0]['annotations']) self.assertEqual('The second line', result[1]['text']) self.assertIn(('size', 0, 15, '12.0'), result[1]['annotations']) self.assertEqual('The third line', result[2]['text']) self.assertIn(('size', 0, 14, '13.0'), result[2]['annotations']) self.assertEqual('11 line', result[8]['text']) self.assertIn(('bold', 0, 7, 'True'), result[8]['annotations']) self.assertEqual('12 line', result[9]['text']) self.assertIn(('italic', 0, 7, 'True'), result[9]['annotations']) self.assertEqual('13 line', result[10]['text']) self.assertIn(('italic', 0, 7, 'True'), result[10]['annotations']) self.assertIn(('underlined', 0, 7, 'True'), result[10]['annotations']) self.assertEqual('15 line', result[12]['text']) self.assertIn(('alignment', 0, 7, 'center'), result[12]['annotations']) self.assertEqual('16 line', result[13]['text']) self.assertIn(('alignment', 0, 7, 'right'), result[13]['annotations']) self.assertEqual('18 line', result[15]['text']) self.assertIn(('indentation', 0, 7, '720'), result[15]['annotations']) self.assertEqual('19 line', result[16]['text']) self.assertIn(('indentation', 0, 7, '1440'), result[16]['annotations']) self.assertEqual('•\t21 line', result[18]['text']) self.assertIn(('italic', 5, 9, 'True'), result[18]['annotations']) self.assertEqual('•\t22 line', result[19]['text']) self.assertIn(('indentation', 0, 9, '245'), result[19]['annotations']) self.assertEqual('1.\t23 line', result[20]['text']) self.assertIn(('indentation', 0, 10, '360'), result[20]['annotations']) self.assertEqual('2.\t24 line', result[21]['text']) self.assertIn(('indentation', 0, 10, '643'), result[21]['annotations']) self.assertIn(('bold', 6, 10, 'True'), result[21]['annotations']) self.assertEqual('3)\t25 строка', result[22]['text']) self.assertEqual('4)\t26 строка', result[23]['text'])
from bs4 import BeautifulSoup from docx_parser.document_parser import DOCXParser def get_xml(doc_name: str, xml_name: str = 'word/document.xml') -> str: document = zipfile.ZipFile(doc_name) try: bs = BeautifulSoup(document.read(xml_name), 'xml') except KeyError: return "" return bs.prettify() if __name__ == "__main__": doc_name = "2.docx" # word/styles.xml # word/numbering.xml with open("document.xml", "w") as f: print(get_xml(doc_name), file=f) with open("numbering.xml", "w") as f: print(get_xml(doc_name, "word/numbering.xml"), file=f) doc_name = os.path.abspath(doc_name) docx_parser = DOCXParser() docx_parser.parse(doc_name) lines = docx_parser.get_lines_with_meta() for line in lines: print(line["text"])
def test_list_2(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "lists_2.docx") parser.parse(path) result = parser.get_lines_with_meta() self.assertEqual('Header ', result[0]["text"]) self.assertEqual( '1.\tНАИМЕНОВАНИЕ, ШИФР ОКР, ОСНОВАНИЕ, ИСПОЛНИТЕЛЬ И СРОКИ ВЫПОЛНЕНИЯ ОКР', result[1]["text"]) self.assertEqual( '2.\tЦЕЛЬ ВЫПОЛНЕНИЯ ОКР, НАИМЕНОВАНИЕ И ИНДЕКС ИЗДЕЛИЯ', result[2]["text"]) self.assertEqual('3.\tТактико-технические требования к изделию', result[3]["text"]) self.assertEqual('3.1.\tСостав изделия', result[4]["text"]) self.assertEqual('3.2.\tТребования назначения', result[5]["text"]) self.assertEqual( '3.2.1.\tТребования к возможностям и характеристикам Пользовательской подсистемы', result[6]["text"]) self.assertEqual( '3.2.2.\tТребования к разделам и сервисам Сайта МО РФ', result[7]["text"]) self.assertEqual( '3.2.3.\tТребования к функциям мобильного приложения «Министерство обороны» ', result[8]["text"]) self.assertEqual('3.2.3.1.\tОбщие требования к приложению', result[9]["text"]) self.assertEqual('3.2.3.2.\tТребования к совместимости', result[10]["text"]) self.assertEqual( '3.2.3.3.\tТребования к реакции на внешние прерывания', result[11]["text"]) self.assertEqual( '3.2.3.4.\tТребования к обеспечению постоянной обратной связи с пользователем', result[12]["text"]) self.assertEqual( '3.2.3.5.\tТребования к размещению в магазинах приложений', result[13]["text"]) self.assertEqual( '3.2.3.6.\tТребования к дизайну и интерфейсу мобильного приложения «Министерство обороны»', result[14]["text"]) self.assertEqual('3.2.3.7.\tТребования к экрану загрузки ', result[15]["text"]) self.assertEqual('3.2.3.8.\tТребования к главному экрану', result[16]["text"]) self.assertEqual('3.2.3.9.\tТребования к разделу «Минобороны Инфо»', result[17]["text"]) self.assertEqual( '3.2.3.10.\tТребования к функциям кольца сайтов образовательных учреждений МО РФ', result[18]["text"]) self.assertEqual( '3.2.3.10.1.\tТребования к возможностям типового сайта кольца образовательных учреждений ' 'МО РФ', result[19]["text"]) self.assertEqual( '3.2.3.10.2.\tТребования к дизайну типового сайта кольца образовательных учреждений МО РФ', result[20]["text"]) self.assertEqual( '3.2.3.10.3.\tТребования к структуре типового сайта кольца образовательных учреждений МО РФ', result[21]["text"]) self.assertEqual('1.\tО ВУЗе:', result[22]["text"]) self.assertEqual('2.\tНовости', result[23]["text"]) self.assertEqual('3.\tОбразование', result[24]["text"]) self.assertEqual( '3.1.\tОбразовательные программы (образовательные стандарты)', result[25]["text"]) self.assertEqual('3.2.\tДополнительное образование', result[26]["text"]) self.assertEqual('4.\tНаука', result[27]["text"]) self.assertEqual('5.\tУчебно-материальная база', result[28]["text"]) self.assertEqual('6.\tПоступающим', result[29]["text"]) self.assertEqual('7.\tОбучающимся', result[30]["text"]) self.assertEqual('8.\tКонтакты', result[31]["text"]) self.assertEqual('9.\tВыдающиеся выпускники', result[32]["text"]) self.assertEqual( 'Шаблон сайта учебного заведения среднего образования должен содержать типовые разделы:', result[33]["text"]) self.assertEqual('1.\tСведения об образовательной организации\xa0', result[34]["text"]) self.assertEqual('2.\tНовости', result[35]["text"]) self.assertEqual('3.\tОбразование\xa0', result[36]["text"]) self.assertEqual('3.1.\tОбразовательные стандарты\xa0', result[37]["text"]) self.assertEqual( '3.2.\tМатериально-техническое обеспечение и оснащенность образовательного процесса', result[38]["text"]) self.assertEqual('3.3.\tПлатные образовательные услуги\xa0', result[39]["text"]) self.assertEqual('4.\tПоступающим', result[40]["text"]) self.assertEqual('4.1.\tПриём в училище (школу, пансион, корпус)', result[41]["text"]) self.assertEqual('4.2.\tВакантные места для приема (перевода)\xa0', result[42]["text"]) self.assertEqual('5.\tУченикам', result[43]["text"]) self.assertEqual( '5.1.\tСтипендии и иные виды материальной поддержки\xa0', result[44]["text"]) self.assertEqual('5.2.\tСекции, кружки, клубы\xa0', result[45]["text"]) self.assertEqual('5.3.\tПравила внутреннего распорядка', result[46]["text"]) self.assertEqual('6.\tКонтакты', result[47]["text"]) self.assertEqual( '3.2.3.11.\tТребования к функциям кольца сайтов подразделений и организаций МО РФ', result[48]["text"]) self.assertEqual( '3.2.3.12.\tТребования к функциям сайта «Жилье военнослужащим»', result[49]["text"]) self.assertEqual( '3.2.3.12.1.\tТребования к возможностям сайта «Жилье военнослужащим»', result[50]["text"]) self.assertEqual( 'В Систему должен входить сайт «Жилье военнослужащим». Сайт должен располагаться на поддомене ' 'dom.mil.ru. ', result[51]["text"]) self.assertEqual( '3.2.3.12.2.\tТребования к структуре сайта «Жилье военнослужащим»', result[52]["text"]) self.assertEqual( 'Сайт «Жилье военнослужащим» должен иметь следующую структуру: ', result[53]["text"]) self.assertEqual('1.\tИнформация', result[54]["text"]) self.assertEqual('2.\tОбеспечение жильем', result[55]["text"]) self.assertEqual('2.1.\tПостоянное жилье', result[56]["text"]) self.assertEqual('2.2.\tСпециализированный жилищный фонд', result[57]["text"]) self.assertEqual('3.\tНакопительно-ипотечная система', result[58]["text"]) self.assertEqual('4.\tВопросы и ответы', result[59]["text"]) self.assertEqual('5.\tЕдиный реестр', result[60]["text"]) self.assertEqual('6.\tДействующие нормативные правовые документы', result[61]["text"]) self.assertEqual( '3.2.3.12.3.\tСервис «Единый реестр жилья военнослужащих»', result[62]["text"]) self.assertEqual('3.2.3.12.4.\tСервис «Калькулятор жилищных субсидий»', result[63]["text"]) self.assertEqual( '3.2.4.\tТребования к возможностям и характеристикам Служебной подсистемы', result[64]["text"]) self.assertEqual('3.3.\tТребования радиоэлектронной защиты', result[65]["text"]) self.assertEqual('Требования не предъявляются. ', result[66]["text"]) self.assertEqual( '3.4.\tТребования живучести и стойкости к внешним воздействиям', result[67]["text"]) self.assertEqual('3.5.\tТребования надежности', result[68]["text"]) self.assertEqual( '3.6.\tТребования эргономики, обитаемости и технической эстетики', result[69]["text"]) self.assertEqual( '3.7.\tТребования к эксплуатации, хранению, удобству технического обслуживания и ремонта', result[70]["text"]) self.assertEqual('3.8.\tТребования транспортабельности', result[71]["text"]) self.assertEqual( 'Изделие должно допускать транспортирование в тарной упаковке автомобильным, железнодорожным, ' 'водным и авиационным видами транспорта в средних условиях по ГОСТ В 9.001 в соответствии с ' 'правилами, действующими на соответствующем виде транспорта.', result[72]["text"]) self.assertEqual( 'В транспортных средствах, где перевозится изделие, не должно быть паров кислот, щелочей или ' 'других химически активных веществ, пары и газы которых могут вызвать коррозию.', result[73]["text"]) self.assertEqual('3.9.\tТребования безопасности', result[74]["text"]) self.assertEqual('3.10.\tТребования к обеспечению режима секретности', result[75]["text"]) self.assertEqual('3.11.\tТребования защиты от ИТР', result[76]["text"]) self.assertEqual( '3.12.\tТребования стандартизации, унификации и каталогизации', result[77]["text"]) self.assertEqual('3.13.\tТребования технологичности', result[78]["text"]) self.assertEqual('3.14.\tКонструктивные требования', result[79]["text"]) self.assertEqual( '3.15.\tТребования к наполнению и обеспечению доступности компонент Системы', result[80]["text"]) self.assertEqual('4.\tТехнико-экономические требования', result[81]["text"]) self.assertEqual('5.\tТребования к видам обеспечения', result[82]["text"]) self.assertEqual( '6.\tТребования к сырью, материалам и комплектующим изделиям', result[83]["text"]) self.assertEqual( 'Сырье, материалы, комплектующие изделия должны соответствовать «Перечню электрорадиоизделий ' '(ЭРИ), разрешенных для применения при разработке и модернизации военной аппаратуры, приборов ' 'и другой военной техники.', result[84]["text"]) self.assertEqual('7.\tТребования к консервации, упаковке и маркировке', result[85]["text"]) self.assertEqual('8.\tТребования к учебно-тренировочным средствам', result[86]["text"]) self.assertEqual('9.\tСпециальные требования', result[87]["text"]) self.assertEqual( '10.\tТребования к защите государственной тайны при выполнении ОКР', result[88]["text"]) self.assertEqual( '11.\tТребования к порядку разработки конструкторской документации на военное время', result[89]["text"])
def test_style_pages(self): parser = DOCXParser() path = os.path.join(TEST_DIR, "with_style_pages.docx") parser.parse(path) result = parser.get_lines_with_meta() self.__test_content(result)