[Python] how can i extract numbered lists,bullets and alignment of content if docx file using...

Discussão em 'Python' iniciado por Stack, Fevereiro 19, 2025.

  1. Stack

    Stack Membro Participativo

    I can extract the contents of a DOCX file using Python's python-docx, and I am generating an HTML file from the extracted content. However, I'm struggling to ensure that the contents are properly aligned, and I am also having difficulty extracting numbered lists, bullets, and styles correctly. My current code is functioning well, and I will paste it below. Please assist me in figuring out how to properly extract numbered lists, bullets, and apply styles while creating the HTML. Although I can extract the styles, they are not applied correctly in the generated HTML. My main issues lie with the handling of numbered lists and bullets and alignment of text

    Code :


    from docx.oxml.ns import qn
    import json
    import re
    from docx import Document
    from docx.oxml.table import CT_Tbl
    from docx.oxml.text.paragraph import CT_P


    def extract_elements(docx_file):
    """Extract all paragraphs and tables in the order they appear."""
    document = Document(docx_file)
    elements = []

    for child in document.element.body:
    if isinstance(child, CT_P): # If it's a paragraph
    paragraph_data = _get_paragraph(child, document)
    if paragraph_data:
    elements.append(paragraph_data)

    elif isinstance(child, CT_Tbl): # If it's a table
    table_data = _get_table(child, document)
    if table_data:
    elements.append({"type": "table", "data": table_data})

    return elements



    def _get_paragraph(element, document):
    """Extract paragraph text, bullet/numbering style, and format."""
    try:
    # Find the matching paragraph in the document by element reference
    for paragraph in document.paragraphs:
    if paragraph._element == element:
    text = paragraph.text.strip()
    alignment = _get_alignment(paragraph) # Get paragraph alignment
    numbered_list = _get_numbered_list(paragraph) # Detect numbered list

    return {
    "type": "paragraph",
    "text": text,
    "style": paragraph.style.name if paragraph.style else "",
    "alignment": alignment,
    "numberedlist": numbered_list,
    }

    except Exception as e:
    print(f"Error in _get_paragraph: {e}")
    return None


    def _get_alignment(paragraph):
    """Get alignment of the paragraph."""
    alignment_map = {0: "left", 1: "center", 2: "right", 3: "justify"}
    return alignment_map.get(paragraph.alignment, "left") # Default to left alignment


    def _get_numbered_list(paragraph):
    """Detect whether the paragraph has a bullet/numbered style and retrieve its details."""
    try:
    # Access the XML element directly
    p = paragraph._element

    # Print the entire XML of the paragraph for debugging
    # print(p.xml)
    xml_content = p.xml

    # Append the XML content to the file
    with open("D://newxml.xml", "a", encoding="utf-8") as xml_file:
    # Optional: Add a separator between paragraphs for clarity
    xml_file.write(f"\n<!-- New Paragraph -->\n{xml_content}\n")

    # Accessing the paragraph properties
    pPr = p.find(qn("w:pPr"))

    if pPr is not None:
    # Try to find numPr in the paragraph properties
    numPr = pPr.find(qn("w:numPr"))
    print(f"numPr found: {numPr is not None}") # Debugging output

    if numPr is not None:
    numId = numPr.find(qn("w:numId"))
    ilvl = numPr.find(qn("w:ilvl"))

    if numId is not None and ilvl is not None:
    numId_val = numId.get(qn("w:val"))
    ilvl_val = ilvl.get(qn("w:val"))
    return _get_number_format(paragraph.part, numId_val, ilvl_val)
    else:
    print("numPr is None, check paragraph properties.")
    else:
    print("pPr is None, check paragraph element.")

    return None
    except Exception as e:
    print(f"Error in _get_bullet_style: {e}")
    return None


    def _get_number_format(doc_part, numId, ilvl):
    """Retrieve the numbering format from the DOCX numbering part."""
    try:
    numbering_part = doc_part.numbering_part # Access numbering definitions
    xpath = f".//w:num[@w:numId='{numId}']//w:abstractNumId"
    abstract_num = numbering_part.element.xpath(xpath)

    if abstract_num:
    abstract_num_id = abstract_num[0].get(qn("w:val"))
    level_xpath = f".//w:abstractNum[@w:abstractNumId='{abstract_num_id}']//w:lvl[@w:ilvl='{ilvl}']//w:numFmt"
    num_fmt = numbering_part.element.xpath(level_xpath)

    if num_fmt:
    return {
    "numId": numId,
    "level": int(ilvl),
    "format": num_fmt[0].get(qn("w:val")),
    }
    return None
    except Exception as e:
    print(f"Error in _get_number_format: {e}")


    def _get_table(element, document):
    """Extract table data ensuring correct structure."""
    for table in document.tables:
    if table._element == element:
    return _extract_table_content(table)
    return None


    def _extract_table_content(table):
    """Ensure correct table row and column consistency."""
    rows = []
    max_cols = max(len(row.cells) for row in table.rows) # Get the max columns
    for row in table.rows:
    row_data = []
    for cell in row.cells:
    cell_text = cell.text.strip()

    # Avoid appending duplicate content in the same row
    if cell_text not in row_data:
    row_data.append(cell_text)

    # Ensure row has consistent number of columns
    while len(row_data) < max_cols:
    row_data.append("") # Pad missing cells with empty strings

    rows.append(row_data)

    return rows


    def save_elements_to_json(elements, file_path):
    """Save extracted elements to a JSON file."""
    with open(file_path, "w", encoding="utf-8") as f:
    json.dump(elements, f, ensure_ascii=False, indent=4)


    def save_elements_to_html(elements, file_path):
    """Convert extracted elements to properly aligned HTML."""
    html_content = "<html><body>"
    in_list = False # Track whether we are in a list
    in_toc_table = False # Track if we're inside a TOC table
    list_type = "" # Keep track of the list type (decimal, lowerLetter, upperLetter)

    for element in elements:
    if element["type"] == "paragraph":
    numbered_list = element.get("numberedlist") # Safely get 'numbered_list'

    # Handle TOC entries
    if element["style"] == "toc 1":
    if not in_toc_table:
    # Start a new TOC table
    html_content += '<table border="1" style="border-collapse: collapse; width: 100%;">'
    html_content += (
    "<tr><th>Clause</th><th>Heading</th><th>Page</th></tr>"
    )
    in_toc_table = True # Mark that we are inside the TOC table

    # Split the text by tabs and add as a row in the table
    row_data = element["text"].split("\t")
    html_content += "<tr>"
    for cell in row_data:
    html_content += (
    f'<td style="padding: 8px; border: 1px solid #ddd;">{cell}</td>'
    )
    html_content += "</tr>"
    else:
    # Close the TOC table if we encounter non-TOC content
    if in_toc_table:
    html_content += "</table>"
    in_toc_table = False

    # Handle numbered/bullet list
    if numbered_list: # Check if the paragraph belongs to a list
    if not in_list:
    # Set the list type based on the numbering format
    list_format = numbered_list["format"]
    if list_format == "decimal":
    list_type = "1" # Decimal list
    elif list_format == "lowerLetter":
    list_type = "a" # Lowercase letters
    elif list_format == "upperLetter":
    list_type = "A" # Uppercase letters
    else:
    list_type = "" # Default case

    html_content += f'<ol style="list-style-type: {list_type};">' # Start a new ordered list
    in_list = True

    # Indent the list item based on its level
    indent = numbered_list["level"] * 20
    html_content += (
    f'<li style="margin-left: {indent}px;">'
    f'{element["text"]}</li>'
    )
    else: # Handle non-list paragraphs
    if in_list:
    html_content += "</ol>" # Close the list if we were in one
    in_list = False

    # Render the paragraph with alignment
    alignment = element.get(
    "alignment", "left"
    ) # Default to 'left' if missing
    style = element.get(
    "style", "Normal"
    ) # Default style if not available
    html_content += (
    f'<p style="text-align: {alignment}; font-family: {style};">'
    f'{element["text"]}</p>'
    )

    elif element["type"] == "table":
    # Close any open list before starting a new table
    if in_list:
    html_content += "</ol>"
    in_list = False

    # Render table content
    html_content += (
    '<table border="1" style="border-collapse: collapse; width: 100%;">'
    )
    for row in element["data"]:
    html_content += "<tr>"
    for cell in row:
    html_content += (
    f'<td style="padding: 8px; border: 1px solid #ddd;">{cell}</td>'
    )
    html_content += "</tr>"
    html_content += "</table>"

    # Close any remaining open list or table
    if in_list:
    html_content += "</ol>"
    if in_toc_table:
    html_content += "</table>"

    html_content += "</body></html>"

    with open(file_path, "w", encoding="utf-8") as f:
    f.write(html_content)





    if __name__ == "__main__":


    docx_file = "/Services Agreement.docx"
    elements = extract_elements(docx_file)
    save_elements_to_json(elements, "D://extracted_content.json")
    save_elements_to_html(elements, "D://extracted_content.html")



    I am expecting to extract the numberedlists,bullets and styles properly

    Continue reading...

Compartilhe esta Página