簡體   English   中英

通過標題將 docx 拆分為 Python 中的單獨文件

[英]Splitting a docx by headings into separate files in Python

我想編寫一個程序來獲取我的 docx 文件,遍歷它們並根據標題將每個文件拆分為多個單獨的文件。 在每個 docx 里面都有幾篇文章,每篇文章都有一個“標題 1”和下面的文字。

因此,如果我的原始 file1.docx 有 4 篇文章,我希望將其拆分為 4 個單獨的文件,每個文件都有其標題和文本。

我到達了它遍歷我保存 .docx 文件的路徑中的所有文件的部分,我可以分別閱讀標題和文本,但我似乎無法弄清楚如何合並所有文件並將其拆分為單獨的文件,每個文件都有標題和文本。 我正在使用 python-docx 庫。

import glob
from docx import Document

headings = []
texts = []

def iter_headings(paragraphs):
    for paragraph in paragraphs:
        if paragraph.style.name.startswith('Heading'):
            yield paragraph

def iter_text(paragraphs):
    for paragraph in paragraphs:
        if paragraph.style.name.startswith('Normal'):
            yield paragraph

for name in glob.glob('/*.docx'):
    document = Document(name)
    for heading in iter_headings(document.paragraphs):
        headings.append(heading.text)
        for paragraph in iter_text(document.paragraphs):
            texts.append(paragraph.text)
    print(texts)

如何提取每篇文章的文本和標題?

這是 python-docx 給我的 XML 閱讀。 紅色大括號標記了我想從每個文件中提取的內容。

https://user-images.githubusercontent.com/17858776/51575980-4dcd0200-1eac-11e9-95a8-f643f87b1f40.png

我願意就如何用不同的方法實現我想要的目標的任何替代建議,或者是否有更簡單的方法來使用 PDF 文件。

我認為使用迭代器的方法是合理的,但我傾向於以不同的方式對它們進行打包。 在頂層,您可以擁有:

for paragraphs in iterate_document_sections(document.paragraphs):
    create_document_from_paragraphs(paragraphs)

然后iterate_document_sections()看起來像:

def iterate_document_sections(document):
    """Generate a sequence of paragraphs for each headed section in document.

    Each generated sequence has a heading paragraph in its first position, 
    followed by one or more body paragraphs.
    """
    paragraphs = [document.paragraphs[0]]
    for paragraph in document.paragraphs[1:]:
        if is_heading(paragraph):
             yield paragraphs
             paragraphs = [paragraph]
             continue
        paragraphs.append(paragraph)
    yield paragraphs

像這樣的東西與你的其他代碼的一部分相結合應該會給你一些可行的開始。 您需要實現is_heading()create_document_from_paragraphs()

請注意,此處的術語“節”在常用出版用語中用於指(節)標題及其下級段落,而不是指 Word 文檔節對象(如document.sections )。

事實上,僅當文檔沒有除段落(例如表格)之外的任何其他元素時,提供的解決方案才有效。

另一種可能的解決方案是不僅迭代段落,而且迭代所有文檔正文的子 xml 元素。 一旦找到“子文檔”的開始和結束元素(示例中帶有標題的段落),您應該刪除與此部分無關的其他 xml 元素(一種切斷所有其他文檔內容的方法)。 通過這種方式,您可以保留所有樣式、文本、表格和其他文檔元素和格式。 這不是一個優雅的解決方案,意味着您必須在內存中保留完整源文檔的臨時副本。

這是我的代碼:

import tempfile
from typing import Generator, Tuple, Union

from docx import Document
from docx.document import Document as DocType
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from docx.oxml.xmlchemy import BaseOxmlElement
from docx.text.paragraph import Paragraph


def iterparts(doc_path:str, skip_first=True, bias:int=0) -> Generator[Tuple[int,DocType],None,None]:
    """Iterate over sub-documents by splitting source document into parts
    Split into parts by copying source document and cutting off unrelevant
    data.

    Args:
        doc_path (str):                 path to source *docx* file
        skip_first (bool, optional):    skip first split point and wait for 
                                        second occurrence. Defaults to True.
        bias (int, optional):           split point bias. Defaults to 0.

    Yields:
        Generator[Tuple[int,DocType],None,None]:    first element of each tuple 
                                                    indicates the number of a 
                                                    sub-document, if number is 0 
                                                    then there are no sub-documents
    """
    doc = Document(doc_path)
    counter = 0
    while doc:
        split_elem_idx = -1
        doc_body = doc.element.body
        cutted = [doc, None]
        for idx, elem in enumerate(doc_body.iterchildren()):
            if is_split_point(elem):
                if split_elem_idx == -1 and skip_first:
                    split_elem_idx = idx
                else:
                    cutted = split(doc, idx+bias) # idx-1 to keep previous paragraph
                    counter += 1
                    break
        yield (counter, cutted[0])
        doc = cutted[1]

def is_split_point(element:BaseOxmlElement) -> bool:
    """Split criteria

    Args:
        element (BaseOxmlElement): oxml element

    Returns:
        bool: whether the *element* is the beginning of a new sub-document
    """
    if isinstance(element, CT_P):
        p = Paragraph(element, element.getparent())
        return p.text.startswith("Some text")
    return False

def split(doc:DocType, cut_idx:int) -> Tuple[DocType,DocType]:
    """Splitting into parts by copying source document and cutting of
    unrelevant data.

    Args:
        doc (DocType): [description]
        cut_idx (int): [description]

    Returns:
        Tuple[DocType,DocType]: [description]
    """
    tmpdocfile = write_tmp_doc(doc)
    second_part = doc
    second_elems = list(second_part.element.body.iterchildren())
    for i in range(0, cut_idx):
        remove_element(second_elems[i])
    first_part = Document(tmpdocfile)
    first_elems = list(first_part.element.body.iterchildren())
    for i in range(cut_idx, len(first_elems)):
        remove_element(first_elems[i])
    tmpdocfile.close()
    return (first_part, second_part)

def remove_element(elem: Union[CT_P,CT_Tbl]):
    elem.getparent().remove(elem)

def write_tmp_doc(doc:DocType):
    tmp = tempfile.TemporaryFile()
    doc.save(tmp)
    return tmp

請注意,您應該根據您的拆分條件定義is_split_point方法

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM