planify / app /DocumentGeneration /word_generator.py
devZenaight's picture
Word Doc
09421ea
Raw
History Blame Contribute Delete
9.57 kB
# Word Generator Module
# Contains create_word_document() and all Word-related functions
import re
import os
import tempfile
import time
import io
import sys
from typing import List, Dict, Any
# Add the parent directory to sys.path to import from models.py
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from models import ExportRequest
# Import from the same directory (Document Generation)
from document_helpers import (
filter_empty_sections,
extract_business_name_from_content
)
# from table_of_content import generate_table_of_contents # Not used in word generator
from chart_generator import parse_embedded_chart_data, create_chart_from_data
from markdown_renderer import (
render_markdown_to_docx_grouped,
process_content_with_inline_charts,
clean_text_for_pdf
)
# Add Currency Mapping path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'CurrencyMapping'))
from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso
# Import sort_sections_by_order from utils module
from utils import sort_sections_by_order
def create_word_document(request: ExportRequest):
"""Create a Word document from the business plan data with front page, TOC, and proper page breaks"""
try:
from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_TAB_ALIGNMENT
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
except ImportError:
raise Exception("python-docx is not installed. Please install it with: pip install python-docx")
# Filter out empty sections to prevent blank pages
try:
filtered_sections = filter_empty_sections(request.sections)
except Exception as e:
raise Exception(f"Failed to filter sections: {str(e)}")
if not filtered_sections:
raise Exception("No valid sections found after filtering. All sections appear to be empty.")
doc = Document()
# Add confidentiality footer to every page
section = doc.sections[0]
footer = section.footer
footer_para = footer.paragraphs[0]
footer_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
footer_run = footer_para.add_run("CONFIDENTIAL - This document contains proprietary information and may not be shared without consent")
footer_run.font.size = Pt(8)
footer_run.font.color.rgb = RGBColor(100, 100, 100) # Gray text
# ===== FRONT PAGE =====
# Add company logo at the top
try:
# Use the MBD logo PNG file - now in app folder for HF deployment
app_root = os.path.dirname(os.path.dirname(__file__))
logo_path = os.path.join(app_root, 'assets', 'logos', 'MBD logo.png')
if os.path.exists(logo_path):
# Add logo to Word document (centered, top of page)
logo_para = doc.add_paragraph()
logo_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
logo_run = logo_para.add_run()
logo_run.add_picture(logo_path, width=Inches(3.0)) # Even larger size to show full logo
print(f"Logo added successfully from: {logo_path}")
else:
print(f"Logo file not found at: {logo_path}")
except Exception as e:
# Log any errors with logo
print(f"Logo error: {e}")
pass
# Add top spacing
doc.add_paragraph()
doc.add_paragraph()
# Extract business name from content, fallback to businessIdea
extracted_business_name = extract_business_name_from_content(filtered_sections)
business_name = extracted_business_name or request.businessIdea or "Business Plan"
# Business name as main title with enhanced styling - PURPLE THEME
title = doc.add_heading(business_name, 0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Style the title with larger font and PURPLE color
for run in title.runs:
run.font.size = Pt(36)
run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE
# Removed decorative line under title
# Add subtitle with enhanced styling - PURPLE
doc.add_paragraph()
subtitle = doc.add_paragraph("Professional Business Plan Document")
subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
subtitle_run = subtitle.runs[0]
subtitle_run.font.size = Pt(18)
subtitle_run.font.color.rgb = RGBColor(124, 58, 237) # DARKER PURPLE
subtitle_run.italic = True
# Add more spacing
doc.add_paragraph()
doc.add_paragraph()
doc.add_paragraph()
# Add document info in a styled box - PURPLE BORDER
info_para = doc.add_paragraph()
info_para.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Add background color and border styling
info_run = info_para.add_run("Prepared by: Business Plan Generator")
info_run.font.size = Pt(14)
info_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE
doc.add_paragraph()
date_para = doc.add_paragraph()
date_para.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
date_run = date_para.add_run(f"Date: {time.strftime('%B %Y')}")
date_run.font.size = Pt(14)
date_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE
# Confidentiality statement removed - now in footer of every page
# Add more spacing before footer
doc.add_paragraph()
doc.add_paragraph()
doc.add_paragraph()
doc.add_paragraph()
# Add footer with enhanced styling - PURPLE
footer = doc.add_paragraph("Confidential Business Information")
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
footer_run = footer.runs[0]
footer_run.font.size = Pt(12)
footer_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE
footer_run.bold = True
# Removed decorative line above footer
# ===== TABLE OF CONTENTS =====
# Add page break before TOC
doc.add_page_break()
# Add TOC title
toc_title = doc.add_heading("Table of Contents", level=1)
toc_title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Removed decorative line under TOC title
# Add spacing before TOC entries
doc.add_paragraph()
# Sort sections according to predefined order (do this before TOC generation)
try:
sorted_sections = sort_sections_by_order(filtered_sections)
except Exception as e:
# Fallback: use filtered order if sorting fails
sorted_sections = filtered_sections
# Generate TOC entries with actual page numbers
# Since each section starts on a new page, we can calculate page numbers
toc_items = []
current_page = 3 # Start after cover (1) and TOC (2)
for section in sorted_sections:
if section.content and section.content.strip():
# Add main section
toc_items.append({
"title": section.title,
"level": 1,
"page": current_page
})
# Skip subheadings and chart titles - only include main section headings
current_page += 1 # Each section starts on a new page
# Add the actual TOC entries
for item in toc_items:
# Create TOC entry paragraph
toc_para = doc.add_paragraph()
toc_para.paragraph_format.space_after = Pt(6)
# Add section title
title_run = toc_para.add_run(item["title"])
title_run.font.size = Pt(11)
# Add dots and page number
if item["level"] == 1:
# Main section - bold
title_run.bold = True
# Add dots
dots_run = toc_para.add_run(" " + "." * (60 - len(item["title"])) + " ")
dots_run.font.size = Pt(11)
dots_run.font.color.rgb = RGBColor(128, 128, 128) # Gray
# Add page number
page_run = toc_para.add_run(str(item["page"]))
page_run.font.size = Pt(11)
page_run.bold = True
else:
# Subsection - indented
toc_para.paragraph_format.left_indent = Inches(0.3)
# Add dots
dots_run = toc_para.add_run(" " + "." * (50 - len(item["title"])) + " ")
dots_run.font.size = Pt(11)
dots_run.font.color.rgb = RGBColor(128, 128, 128) # Gray
# Add page number
page_run = toc_para.add_run(str(item["page"]))
page_run.font.size = Pt(11)
# logging.info(f"Generated {len(toc_items)} TOC items with actual page numbers")
# Executive Summary is now handled as a regular section above
# ===== MAIN SECTIONS =====
for i, section in enumerate(sorted_sections):
if section.content.strip():
# Add page break before each section (including Executive Summary)
doc.add_page_break()
# Don't add section heading here - it's already in the TOC
# The section title is already displayed in the Table of Contents
# Process content with inline charts - use ORIGINAL content to preserve chart markers
process_content_with_inline_charts(doc, section.content, request.businessIdea or "Business")
doc.add_paragraph()
# Charts are now handled inline with the sections above
# Save to bytes
buffer = io.BytesIO()
doc.save(buffer)
buffer.seek(0)
doc_bytes = buffer.getvalue()
buffer.close()
filename = f"business_plan_{request.businessIdea or 'export'}.docx"
return doc_bytes, filename