Spaces:
Runtime error
Runtime error
File size: 9,574 Bytes
402122a d5e836f 402122a de0b6bd 402122a 3c0d939 57e5629 402122a 57e5629 d5e836f 402122a 57e5629 402122a d734bf7 402122a 88a3921 7b50d28 88a3921 402122a d734bf7 402122a 09421ea 402122a 09421ea 402122a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | # 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 |