review gui, add convert docx to pdf
This commit is contained in:
parent
c32d857f02
commit
c6ad73d449
1
.~lock.TemplateSumSample.docx#
Normal file
1
.~lock.TemplateSumSample.docx#
Normal file
@ -0,0 +1 @@
|
|||||||
|
Luca Vallongo,Win11_Dev/admin,Win11_Dev,18.06.2025 08:03,file:///C:/Users/admin/AppData/Roaming/LibreOffice/4;
|
||||||
Binary file not shown.
@ -8,10 +8,23 @@ import docx
|
|||||||
import pypandoc
|
import pypandoc
|
||||||
import pdfkit
|
import pdfkit
|
||||||
import markdown
|
import markdown
|
||||||
|
import subprocess
|
||||||
|
from docx.enum.text import WD_BREAK
|
||||||
|
from docx2pdf import convert as convert_word # Import per la conversione via Word
|
||||||
from ..utils.logger import get_logger
|
from ..utils.logger import get_logger
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
# --- Custom Exceptions ---
|
||||||
|
|
||||||
|
class TemplatePlaceholderError(ValueError):
|
||||||
|
"""Custom exception raised when a required placeholder is missing in the DOCX template."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConverterNotFoundError(Exception):
|
||||||
|
"""Custom exception raised when no suitable DOCX to PDF converter is found."""
|
||||||
|
pass
|
||||||
|
|
||||||
# --- PDFKit Configuration ---
|
# --- PDFKit Configuration ---
|
||||||
try:
|
try:
|
||||||
config = pdfkit.configuration()
|
config = pdfkit.configuration()
|
||||||
@ -23,25 +36,38 @@ except OSError:
|
|||||||
log.info(f"pdfkit configured using fallback path: {WKHTMLTOPDF_PATH}")
|
log.info(f"pdfkit configured using fallback path: {WKHTMLTOPDF_PATH}")
|
||||||
else:
|
else:
|
||||||
config = None
|
config = None
|
||||||
log.warning("wkhtmltopdf not found. PDF conversion may fail.")
|
log.warning(
|
||||||
|
"wkhtmltopdf not found in PATH or fallback location. PDF conversion will fail."
|
||||||
|
)
|
||||||
|
|
||||||
# --- Helper Functions ---
|
# --- Helper Functions (nessuna modifica qui) ---
|
||||||
|
|
||||||
def _get_document_title(markdown_text):
|
def _get_document_title(markdown_text: str) -> str:
|
||||||
match = re.search(r"^\s*#\s+(.+)", markdown_text, re.MULTILINE)
|
"""Extracts the first heading (any level) from the markdown text to use as the title."""
|
||||||
return match.group(1).strip() if match else "Untitled Document"
|
match = re.search(r"^\s*#+\s+(.+)", markdown_text, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
return "Untitled Document"
|
||||||
|
|
||||||
def _split_markdown_by_revision_history(markdown_text, separator_heading="## Revision Record"):
|
def _split_markdown_by_revision_history(markdown_text: str, separator_heading="## Revision Record") -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Splits markdown text into revision history and main content.
|
||||||
|
The revision history section ends at the next heading.
|
||||||
|
"""
|
||||||
pattern = re.compile(f"({re.escape(separator_heading)}.*?)(?=\n#+)", re.DOTALL | re.S)
|
pattern = re.compile(f"({re.escape(separator_heading)}.*?)(?=\n#+)", re.DOTALL | re.S)
|
||||||
match = pattern.search(markdown_text)
|
match = pattern.search(markdown_text)
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
log.warning(f"'{separator_heading}' section not found. No revision history will be added.")
|
log.warning(f"'{separator_heading}' section not found. No revision history will be added.")
|
||||||
return "", markdown_text
|
return "", markdown_text
|
||||||
|
|
||||||
rev_history_md = match.group(0).strip()
|
rev_history_md = match.group(0).strip()
|
||||||
main_content_md = markdown_text.replace(rev_history_md, "").strip()
|
main_content_md = markdown_text.replace(rev_history_md, "", 1).strip()
|
||||||
|
|
||||||
return rev_history_md, main_content_md
|
return rev_history_md, main_content_md
|
||||||
|
|
||||||
def _replace_text_in_paragraph(paragraph, placeholders):
|
def _replace_text_in_paragraph(paragraph, placeholders: dict[str, str]):
|
||||||
|
"""Replaces placeholder text within a single paragraph, preserving formatting."""
|
||||||
full_text = "".join(run.text for run in paragraph.runs)
|
full_text = "".join(run.text for run in paragraph.runs)
|
||||||
if not any(key in full_text for key in placeholders):
|
if not any(key in full_text for key in placeholders):
|
||||||
return
|
return
|
||||||
@ -69,7 +95,8 @@ def _replace_text_in_paragraph(paragraph, placeholders):
|
|||||||
if font.color and font.color.rgb:
|
if font.color and font.color.rgb:
|
||||||
new_run.font.color.rgb = font.color.rgb
|
new_run.font.color.rgb = font.color.rgb
|
||||||
|
|
||||||
def _replace_text_in_element(element, placeholders):
|
def _replace_text_in_element(element, placeholders: dict[str, str]):
|
||||||
|
"""Recursively replaces placeholders in paragraphs and tables within an element."""
|
||||||
for p in element.paragraphs:
|
for p in element.paragraphs:
|
||||||
_replace_text_in_paragraph(p, placeholders)
|
_replace_text_in_paragraph(p, placeholders)
|
||||||
for table in element.tables:
|
for table in element.tables:
|
||||||
@ -77,30 +104,292 @@ def _replace_text_in_element(element, placeholders):
|
|||||||
for cell in row.cells:
|
for cell in row.cells:
|
||||||
_replace_text_in_element(cell, placeholders)
|
_replace_text_in_element(cell, placeholders)
|
||||||
|
|
||||||
def _replace_text_placeholders(doc, placeholders):
|
def _replace_metadata_placeholders(doc: docx.Document, placeholders: dict[str, str]):
|
||||||
log.info(f"Replacing text placeholders: {list(placeholders.keys())}")
|
"""Replaces all metadata placeholders throughout the document's body, header, and footer."""
|
||||||
|
log.info(f"Replacing metadata placeholders: {list(placeholders.keys())}")
|
||||||
_replace_text_in_element(doc, placeholders)
|
_replace_text_in_element(doc, placeholders)
|
||||||
for section in doc.sections:
|
for section in doc.sections:
|
||||||
_replace_text_in_element(section.header, placeholders)
|
_replace_text_in_element(section.header, placeholders)
|
||||||
_replace_text_in_element(section.footer, placeholders)
|
_replace_text_in_element(section.footer, placeholders)
|
||||||
|
|
||||||
def _find_placeholder_paragraph(doc, placeholder):
|
def _find_placeholder_paragraph(doc: docx.Document, placeholder: str):
|
||||||
|
"""Finds the first paragraph containing a given placeholder text."""
|
||||||
for p in doc.paragraphs:
|
for p in doc.paragraphs:
|
||||||
if placeholder in "".join(run.text for run in p.runs):
|
if placeholder in "".join(run.text for run in p.runs):
|
||||||
return p
|
return p
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _insert_docx_at_paragraph(paragraph, source_docx_path):
|
def _insert_docx_at_paragraph(paragraph, source_docx_path: str):
|
||||||
|
"""Inserts content from a source DOCX file at a specific paragraph's location."""
|
||||||
parent = paragraph._p.getparent()
|
parent = paragraph._p.getparent()
|
||||||
index = parent.index(paragraph._p)
|
index = parent.index(paragraph._p)
|
||||||
|
|
||||||
source_doc = docx.Document(source_docx_path)
|
source_doc = docx.Document(source_docx_path)
|
||||||
|
|
||||||
for element in source_doc.element.body:
|
for element in source_doc.element.body:
|
||||||
parent.insert(index, element)
|
parent.insert(index, element)
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
parent.remove(paragraph._p)
|
parent.remove(paragraph._p)
|
||||||
|
|
||||||
# --- Main Conversion Function ---
|
def _remove_paragraph(paragraph):
|
||||||
def convert_markdown(input_file, output_format, add_toc=False, template_path=None, metadata=None):
|
"""Removes a paragraph from its parent element."""
|
||||||
|
if paragraph is None:
|
||||||
|
return
|
||||||
|
parent = paragraph._p.getparent()
|
||||||
|
parent.remove(paragraph._p)
|
||||||
|
|
||||||
|
def _add_revision_table(doc: docx.Document, rev_history_md: str):
|
||||||
|
"""Parses a markdown table from the revision history text and adds it to the document."""
|
||||||
|
placeholder_p = _find_placeholder_paragraph(doc, "%%REVISION_RECORD%%")
|
||||||
|
if not placeholder_p:
|
||||||
|
log.warning("Revision record placeholder not found in template. Skipping.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not rev_history_md:
|
||||||
|
log.info("No revision history content found. Removing placeholder.")
|
||||||
|
_remove_paragraph(placeholder_p)
|
||||||
|
return
|
||||||
|
|
||||||
|
lines = [line.strip() for line in rev_history_md.strip().split('\n')]
|
||||||
|
|
||||||
|
table_lines = [line for line in lines if line.startswith('|') and not line.startswith('|:--')]
|
||||||
|
|
||||||
|
if not table_lines:
|
||||||
|
log.warning("Could not parse a markdown table from the revision history section.")
|
||||||
|
_remove_paragraph(placeholder_p)
|
||||||
|
return
|
||||||
|
|
||||||
|
table_data = []
|
||||||
|
for line in table_lines:
|
||||||
|
cells = [cell.strip() for cell in line.split('|')][1:-1]
|
||||||
|
table_data.append(cells)
|
||||||
|
|
||||||
|
if not table_data or len(table_data) < 1:
|
||||||
|
log.warning("Revision history table is empty.")
|
||||||
|
_remove_paragraph(placeholder_p)
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info(f"Adding revision history table with {len(table_data)} rows.")
|
||||||
|
|
||||||
|
num_cols = len(table_data[0])
|
||||||
|
table = doc.add_table(rows=1, cols=num_cols)
|
||||||
|
table.style = 'Table Grid'
|
||||||
|
|
||||||
|
hdr_cells = table.rows[0].cells
|
||||||
|
for i, header_text in enumerate(table_data[0]):
|
||||||
|
hdr_cells[i].text = header_text
|
||||||
|
|
||||||
|
for row_data in table_data[1:]:
|
||||||
|
row_cells = table.add_row().cells
|
||||||
|
for i, cell_text in enumerate(row_data):
|
||||||
|
row_cells[i].text = cell_text
|
||||||
|
|
||||||
|
parent = placeholder_p._p.getparent()
|
||||||
|
parent.insert(parent.index(placeholder_p._p), table._tbl)
|
||||||
|
_remove_paragraph(placeholder_p)
|
||||||
|
|
||||||
|
# --- Format-Specific Conversion Functions ---
|
||||||
|
|
||||||
|
def _convert_to_pdf(markdown_text: str, output_file: str, add_toc: bool):
|
||||||
|
"""Converts markdown text to a PDF file."""
|
||||||
|
log.info("Starting PDF conversion using pdfkit.")
|
||||||
|
if config is None:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"wkhtmltopdf executable not found. Cannot create PDF."
|
||||||
|
)
|
||||||
|
|
||||||
|
title = _get_document_title(markdown_text)
|
||||||
|
|
||||||
|
content_without_title = markdown_text
|
||||||
|
match = re.search(r"^\s*#+\s+(.+)\n?", markdown_text, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
content_without_title = markdown_text[match.end():]
|
||||||
|
|
||||||
|
md_converter = markdown.Markdown(extensions=['toc', 'fenced_code', 'tables'])
|
||||||
|
html_body = md_converter.convert(content_without_title)
|
||||||
|
|
||||||
|
toc_html = ""
|
||||||
|
if add_toc and hasattr(md_converter, 'toc') and md_converter.toc:
|
||||||
|
log.info("Generating Table of Contents for PDF.")
|
||||||
|
toc_html = f"<h2>Table of Contents</h2>{md_converter.toc}<div style='page-break-after: always;'></div>"
|
||||||
|
|
||||||
|
full_html = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{title}</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: sans-serif; }}
|
||||||
|
h1, h2 {{ border-bottom: 1px solid #eaecef; padding-bottom: .3em; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
{toc_html}
|
||||||
|
{html_body}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
pdf_options = {'encoding': "UTF-8", 'enable-local-file-access': None}
|
||||||
|
pdfkit.from_string(full_html, output_file, configuration=config, options=pdf_options)
|
||||||
|
log.info(f"PDF successfully generated: {output_file}")
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_to_docx(markdown_text: str, output_file: str, template_path: str, metadata: dict, add_toc: bool):
|
||||||
|
"""Converts markdown text to a DOCX file using a template."""
|
||||||
|
log.info("Starting DOCX conversion.")
|
||||||
|
if not template_path or not os.path.exists(template_path):
|
||||||
|
raise FileNotFoundError("A valid DOCX template file is required for this conversion.")
|
||||||
|
|
||||||
|
doc = docx.Document(template_path)
|
||||||
|
|
||||||
|
# --- Step 1: Validate Template Placeholders ---
|
||||||
|
rev_placeholder_p = _find_placeholder_paragraph(doc, "%%REVISION_RECORD%%")
|
||||||
|
toc_placeholder_p = _find_placeholder_paragraph(doc, "%%DOC_TOC%%")
|
||||||
|
content_placeholder_p = _find_placeholder_paragraph(doc, "%%DOC_CONTENT%%")
|
||||||
|
|
||||||
|
missing_placeholders = []
|
||||||
|
if not rev_placeholder_p:
|
||||||
|
missing_placeholders.append("%%REVISION_RECORD%%")
|
||||||
|
if not toc_placeholder_p:
|
||||||
|
missing_placeholders.append("%%DOC_TOC%%")
|
||||||
|
if not content_placeholder_p:
|
||||||
|
missing_placeholders.append("%%DOC_CONTENT%%")
|
||||||
|
|
||||||
|
if missing_placeholders:
|
||||||
|
raise TemplatePlaceholderError(
|
||||||
|
f"Template is missing required placeholders: {', '.join(missing_placeholders)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Step 2: Replace Metadata ---
|
||||||
|
metadata['DOC_PROJECT'] = metadata.get('DOC_PROJECT') or _get_document_title(markdown_text)
|
||||||
|
placeholders = {f"%%{key}%%": value for key, value in metadata.items() if value}
|
||||||
|
placeholders["%%DOC_DATE%%"] = date.today().strftime("%d/%m/%Y")
|
||||||
|
_replace_metadata_placeholders(doc, placeholders)
|
||||||
|
|
||||||
|
# --- Step 3: Split Markdown and Prepare for Insertion ---
|
||||||
|
rev_history_md, main_content_md = _split_markdown_by_revision_history(markdown_text)
|
||||||
|
|
||||||
|
# --- Step 4: Add Revision History Table Natively ---
|
||||||
|
_add_revision_table(doc, rev_history_md)
|
||||||
|
|
||||||
|
temp_files = []
|
||||||
|
try:
|
||||||
|
# --- Step 5: Insert Main Content and TOC with Page Breaks ---
|
||||||
|
if main_content_md:
|
||||||
|
content_for_pandoc = main_content_md
|
||||||
|
match = re.search(r"^\s*#\s+(.+)\n?", content_for_pandoc, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
log.info("Removing main title from content to exclude it from DOCX TOC.")
|
||||||
|
content_for_pandoc = content_for_pandoc[match.end():]
|
||||||
|
|
||||||
|
log.info("Stripping manual numbering from headings.")
|
||||||
|
content_for_pandoc = re.sub(
|
||||||
|
r"^(\s*#+)\s+[0-9\.]+\s+",
|
||||||
|
r"\1 ",
|
||||||
|
content_for_pandoc,
|
||||||
|
flags=re.MULTILINE
|
||||||
|
)
|
||||||
|
|
||||||
|
pandoc_args = ["--shift-heading-level-by=-1"]
|
||||||
|
if add_toc:
|
||||||
|
pandoc_args.append("--toc")
|
||||||
|
log.info("Adding page break before Table of Contents.")
|
||||||
|
toc_placeholder_p.insert_paragraph_before().add_run().add_break(WD_BREAK.PAGE)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file:
|
||||||
|
pypandoc.convert_text(content_for_pandoc, 'docx', format='md', extra_args=pandoc_args, outputfile=temp_file.name)
|
||||||
|
temp_files.append(temp_file.name)
|
||||||
|
_insert_docx_at_paragraph(toc_placeholder_p, temp_file.name)
|
||||||
|
|
||||||
|
_remove_paragraph(content_placeholder_p)
|
||||||
|
else:
|
||||||
|
log.info("Adding page break before main content.")
|
||||||
|
content_placeholder_p.insert_paragraph_before().add_run().add_break(WD_BREAK.PAGE)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file:
|
||||||
|
pypandoc.convert_text(content_for_pandoc, 'docx', format='md', extra_args=pandoc_args, outputfile=temp_file.name)
|
||||||
|
temp_files.append(temp_file.name)
|
||||||
|
_insert_docx_at_paragraph(content_placeholder_p, temp_file.name)
|
||||||
|
|
||||||
|
_remove_paragraph(toc_placeholder_p)
|
||||||
|
else:
|
||||||
|
_remove_paragraph(toc_placeholder_p)
|
||||||
|
_remove_paragraph(content_placeholder_p)
|
||||||
|
|
||||||
|
doc.save(output_file)
|
||||||
|
log.info(f"Document successfully created at {output_file}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# --- Final Step: Cleanup ---
|
||||||
|
for temp_file in temp_files:
|
||||||
|
if os.path.exists(temp_file):
|
||||||
|
os.remove(temp_file)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_docx_to_pdf(input_docx_path: str, output_pdf_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Converts a DOCX file to a PDF, trying MS Word first and falling back to LibreOffice.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(input_docx_path):
|
||||||
|
raise FileNotFoundError(f"Input DOCX file not found: {input_docx_path}")
|
||||||
|
|
||||||
|
# --- Strategy 1: Try to use Microsoft Word via COM ---
|
||||||
|
try:
|
||||||
|
log.info("Attempting DOCX to PDF conversion using MS Word.")
|
||||||
|
convert_word(input_docx_path, output_pdf_path)
|
||||||
|
log.info(f"Successfully converted using MS Word: {output_pdf_path}")
|
||||||
|
return output_pdf_path
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"MS Word conversion failed. It might not be installed. Error: {e}")
|
||||||
|
log.info("Falling back to LibreOffice conversion.")
|
||||||
|
|
||||||
|
# --- Strategy 2: Fallback to LibreOffice ---
|
||||||
|
libreoffice_path = r"C:\Program Files\LibreOffice\program\soffice.exe"
|
||||||
|
if not os.path.exists(libreoffice_path):
|
||||||
|
log.error("LibreOffice executable not found. Cannot convert DOCX to PDF.")
|
||||||
|
raise ConverterNotFoundError(
|
||||||
|
"Neither MS Word nor LibreOffice could be used for conversion. "
|
||||||
|
"Please install one of them to use this feature."
|
||||||
|
)
|
||||||
|
|
||||||
|
# LibreOffice usa --outdir per la cartella, quindi dobbiamo estrarla.
|
||||||
|
output_dir = os.path.dirname(output_pdf_path)
|
||||||
|
log.info(f"Attempting conversion using LibreOffice at: {libreoffice_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Per LibreOffice, dobbiamo convertire il file e poi rinominarlo se necessario,
|
||||||
|
# poiché non supporta la specifica di un nome file di output diretto.
|
||||||
|
expected_lo_output = os.path.join(output_dir, os.path.splitext(os.path.basename(input_docx_path))[0] + ".pdf")
|
||||||
|
|
||||||
|
command = [
|
||||||
|
libreoffice_path, "--headless", "--convert-to", "pdf",
|
||||||
|
"--outdir", output_dir, input_docx_path,
|
||||||
|
]
|
||||||
|
subprocess.run(
|
||||||
|
command, check=True, capture_output=True, text=True,
|
||||||
|
encoding='utf-8', errors='ignore'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Se il nome file di output desiderato è diverso da quello generato da LibreOffice, rinominiamo.
|
||||||
|
if expected_lo_output != output_pdf_path and os.path.exists(expected_lo_output):
|
||||||
|
os.rename(expected_lo_output, output_pdf_path)
|
||||||
|
|
||||||
|
log.info(f"Successfully converted using LibreOffice: {output_pdf_path}")
|
||||||
|
return output_pdf_path
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
||||||
|
log.error(f"LibreOffice conversion failed. Error: {e}", exc_info=True)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# --- Main Conversion Dispatcher Function ---
|
||||||
|
def convert_markdown(input_file: str, output_path: str, output_format: str, add_toc: bool = False, template_path: str = None, metadata: dict = None):
|
||||||
|
"""
|
||||||
|
Converts a Markdown file to the specified output format (PDF or DOCX).
|
||||||
|
Writes directly to the specified output_path.
|
||||||
|
"""
|
||||||
if not os.path.exists(input_file):
|
if not os.path.exists(input_file):
|
||||||
raise FileNotFoundError(f"Input file not found: {input_file}")
|
raise FileNotFoundError(f"Input file not found: {input_file}")
|
||||||
|
|
||||||
@ -108,90 +397,16 @@ def convert_markdown(input_file, output_format, add_toc=False, template_path=Non
|
|||||||
|
|
||||||
with open(input_file, 'r', encoding='utf-8') as f:
|
with open(input_file, 'r', encoding='utf-8') as f:
|
||||||
markdown_text = f.read()
|
markdown_text = f.read()
|
||||||
|
|
||||||
# --- CORREZIONE LOGICA PDF ---
|
|
||||||
if output_format == "PDF":
|
if output_format == "PDF":
|
||||||
output_file = os.path.splitext(input_file)[0] + ".pdf"
|
_convert_to_pdf(markdown_text, output_path, add_toc)
|
||||||
log.info("Starting PDF conversion using pdfkit.")
|
|
||||||
if config is None:
|
|
||||||
raise FileNotFoundError("wkhtmltopdf not found.")
|
|
||||||
|
|
||||||
md_converter = markdown.Markdown(extensions=['toc', 'fenced_code', 'tables'])
|
|
||||||
|
|
||||||
# Estrai il titolo dal testo markdown
|
|
||||||
title = _get_document_title(markdown_text)
|
|
||||||
|
|
||||||
# Converti il corpo del testo
|
|
||||||
html_body = md_converter.convert(markdown_text)
|
|
||||||
|
|
||||||
toc_html = ""
|
|
||||||
# Genera il TOC se richiesto
|
|
||||||
if add_toc and hasattr(md_converter, 'toc') and md_converter.toc:
|
|
||||||
log.info("Generating Table of Contents for PDF.")
|
|
||||||
# Mettiamo il TOC dopo il titolo principale, con un page-break
|
|
||||||
toc_html = f"<div style='page-break-after: always;'><h2>Table of Contents</h2>{md_converter.toc}</div>"
|
|
||||||
|
|
||||||
# Costruisci l'HTML finale, usando il titolo estratto sia nel <title> che come <h1>
|
|
||||||
full_html = f"""
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>{title}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>{title}</h1>
|
|
||||||
{toc_html}
|
|
||||||
{html_body}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
pdfkit.from_string(full_html, output_file, configuration=config, options={'encoding': "UTF-8"})
|
|
||||||
log.info(f"PDF successfully generated: {output_file}")
|
|
||||||
|
|
||||||
elif output_format == "DOCX":
|
elif output_format == "DOCX":
|
||||||
output_file = os.path.splitext(input_file)[0] + ".docx"
|
if metadata is None:
|
||||||
if not template_path:
|
metadata = {}
|
||||||
raise FileNotFoundError("A DOCX template file is required.")
|
_convert_to_docx(markdown_text, output_path, template_path, metadata, add_toc)
|
||||||
|
|
||||||
doc = docx.Document(template_path)
|
|
||||||
|
|
||||||
if metadata:
|
|
||||||
metadata['DOC_PROJECT'] = metadata.get('DOC_PROJECT') or _get_document_title(markdown_text)
|
|
||||||
placeholders = {f"%%{key}%%": value for key, value in metadata.items() if value}
|
|
||||||
placeholders["%%DOC_DATE%%"] = date.today().strftime("%d/%m/%Y")
|
|
||||||
_replace_text_placeholders(doc, placeholders)
|
|
||||||
|
|
||||||
rev_history_md, main_content_md = _split_markdown_by_revision_history(markdown_text)
|
|
||||||
|
|
||||||
temp_files = []
|
|
||||||
try:
|
|
||||||
rev_placeholder_p = _find_placeholder_paragraph(doc, "%%REVISION_RECORD%%")
|
|
||||||
if rev_history_md and rev_placeholder_p:
|
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file:
|
|
||||||
pypandoc.convert_text(rev_history_md, 'docx', format='md', outputfile=temp_file.name)
|
|
||||||
temp_files.append(temp_file.name)
|
|
||||||
_insert_docx_at_paragraph(rev_placeholder_p, temp_file.name)
|
|
||||||
|
|
||||||
content_placeholder_p = _find_placeholder_paragraph(doc, "%%DOC_CONTENT%%")
|
|
||||||
if main_content_md and content_placeholder_p:
|
|
||||||
pandoc_args = ["--toc"] if add_toc else []
|
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as temp_file:
|
|
||||||
pypandoc.convert_text(main_content_md, 'docx', format='md', extra_args=pandoc_args, outputfile=temp_file.name)
|
|
||||||
temp_files.append(temp_file.name)
|
|
||||||
|
|
||||||
content_placeholder_p.insert_paragraph_before().add_run().add_break(docx.enum.text.WD_BREAK.PAGE)
|
|
||||||
_insert_docx_at_paragraph(content_placeholder_p, temp_file.name)
|
|
||||||
|
|
||||||
doc.save(output_file)
|
|
||||||
log.info(f"Document successfully created at {output_file}")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
for temp_file in temp_files:
|
|
||||||
if os.path.exists(temp_file):
|
|
||||||
os.remove(temp_file)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported output format: {output_format}")
|
raise ValueError(f"Unsupported output format: {output_format}")
|
||||||
|
|
||||||
return output_file
|
return output_path
|
||||||
@ -5,12 +5,13 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from datetime import date
|
||||||
import ttkbootstrap as tb
|
import ttkbootstrap as tb
|
||||||
from tkinter.scrolledtext import ScrolledText
|
from tkinter.scrolledtext import ScrolledText
|
||||||
from ttkbootstrap.constants import *
|
from ttkbootstrap.constants import *
|
||||||
from tkinter import filedialog, messagebox, StringVar, BooleanVar
|
from tkinter import filedialog, messagebox, StringVar, BooleanVar
|
||||||
|
|
||||||
from ..core.core import convert_markdown
|
from ..core.core import convert_markdown, convert_docx_to_pdf, ConverterNotFoundError
|
||||||
from ..utils.config import save_configuration, load_configuration
|
from ..utils.config import save_configuration, load_configuration
|
||||||
from ..utils.logger import (
|
from ..utils.logger import (
|
||||||
setup_basic_logging,
|
setup_basic_logging,
|
||||||
@ -23,8 +24,8 @@ from .editor import EditorWindow
|
|||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
def open_with_default_app(filepath):
|
def open_with_default_app(filepath):
|
||||||
if not filepath:
|
if not filepath or not os.path.exists(filepath):
|
||||||
log.warning("Open file/folder requested, but no path was provided.")
|
log.warning("Open file/folder requested, but path is invalid or not provided.")
|
||||||
messagebox.showwarning("Warning", "No output file or folder to open.")
|
messagebox.showwarning("Warning", "No output file or folder to open.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@ -51,7 +52,7 @@ def open_output_folder(filepath):
|
|||||||
def run_app():
|
def run_app():
|
||||||
app = tb.Window(themename="sandstone")
|
app = tb.Window(themename="sandstone")
|
||||||
app.title("Markdown Converter")
|
app.title("Markdown Converter")
|
||||||
app.geometry("800x750")
|
app.geometry("900x850")
|
||||||
app.resizable(True, True)
|
app.resizable(True, True)
|
||||||
|
|
||||||
log_config = {
|
log_config = {
|
||||||
@ -70,17 +71,56 @@ def run_app():
|
|||||||
config = load_configuration()
|
config = load_configuration()
|
||||||
metadata_config = config.get("metadata", {})
|
metadata_config = config.get("metadata", {})
|
||||||
|
|
||||||
|
# --- Variabili di stato della GUI ---
|
||||||
selected_file = StringVar(value=config.get("last_markdown_file", ""))
|
selected_file = StringVar(value=config.get("last_markdown_file", ""))
|
||||||
selected_template = StringVar(value=config.get("last_template_file", ""))
|
selected_template = StringVar(value=config.get("last_template_file", ""))
|
||||||
add_toc_var = BooleanVar(value=config.get("add_toc", True))
|
add_toc_var = BooleanVar(value=config.get("add_toc", True))
|
||||||
output_path = StringVar()
|
|
||||||
|
|
||||||
doc_security_var = StringVar(value=metadata_config.get("DOC_SECURITY", ""))
|
doc_security_var = StringVar(value=metadata_config.get("DOC_SECURITY", ""))
|
||||||
doc_number_var = StringVar(value=metadata_config.get("DOC_NUMBER", ""))
|
doc_number_var = StringVar(value=metadata_config.get("DOC_NUMBER", ""))
|
||||||
doc_rev_var = StringVar(value=metadata_config.get("DOC_REV", ""))
|
doc_rev_var = StringVar(value=metadata_config.get("DOC_REV", ""))
|
||||||
doc_project_var = StringVar(value=metadata_config.get("DOC_PROJECT", ""))
|
doc_project_var = StringVar(value=metadata_config.get("DOC_PROJECT", ""))
|
||||||
customer_var = StringVar(value=metadata_config.get("CUSTOMER", ""))
|
customer_var = StringVar(value=metadata_config.get("DOC_CUSTOMER", ""))
|
||||||
|
|
||||||
|
docx_output_path = StringVar()
|
||||||
|
pdf_direct_output_path = StringVar()
|
||||||
|
pdf_from_docx_output_path = StringVar()
|
||||||
|
|
||||||
|
# --- Funzioni di supporto ---
|
||||||
|
def _confirm_overwrite(filepath: str) -> bool:
|
||||||
|
"""Checks if a file exists and asks the user for overwrite confirmation."""
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
return messagebox.askyesno(
|
||||||
|
title="Confirm Overwrite",
|
||||||
|
message=f"The file already exists:\n\n{filepath}\n\nDo you want to overwrite it?"
|
||||||
|
)
|
||||||
|
return True # File doesn't exist, proceed.
|
||||||
|
|
||||||
|
def _update_output_paths(*args):
|
||||||
|
source_path = selected_file.get()
|
||||||
|
if not source_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
output_dir = os.path.dirname(source_path)
|
||||||
|
|
||||||
|
project = doc_project_var.get() or "NoProject"
|
||||||
|
doc_num = doc_number_var.get() or "NoNum"
|
||||||
|
rev = doc_rev_var.get() or "NoRev"
|
||||||
|
today = date.today().strftime("%Y%m%d")
|
||||||
|
|
||||||
|
docx_filename = f"{project}_SUM_{doc_num}_{rev}_{today}.docx"
|
||||||
|
docx_output_path.set(os.path.join(output_dir, docx_filename).replace("\\", "/"))
|
||||||
|
|
||||||
|
pdf_from_docx_filename = f"{project}_SUM_{doc_num}_{rev}_{today}.pdf"
|
||||||
|
pdf_from_docx_output_path.set(os.path.join(output_dir, pdf_from_docx_filename).replace("\\", "/"))
|
||||||
|
|
||||||
|
pdf_direct_filename = f"{project}_SUM_{today}.pdf"
|
||||||
|
pdf_direct_output_path.set(os.path.join(output_dir, pdf_direct_filename).replace("\\", "/"))
|
||||||
|
|
||||||
|
for var in [selected_file, doc_project_var, doc_number_var, doc_rev_var]:
|
||||||
|
var.trace_add("write", _update_output_paths)
|
||||||
|
|
||||||
|
# --- Funzioni associate agli eventi della GUI ---
|
||||||
def open_editor():
|
def open_editor():
|
||||||
file_path = selected_file.get()
|
file_path = selected_file.get()
|
||||||
if not file_path or not os.path.exists(file_path):
|
if not file_path or not os.path.exists(file_path):
|
||||||
@ -94,17 +134,48 @@ def run_app():
|
|||||||
selected_file.set(path)
|
selected_file.set(path)
|
||||||
|
|
||||||
def browse_template():
|
def browse_template():
|
||||||
# --- CORREZIONE: Cerca prima i file .docx ---
|
|
||||||
path = filedialog.askopenfilename(
|
path = filedialog.askopenfilename(
|
||||||
title="Select a Template Document",
|
title="Select a Template Document",
|
||||||
filetypes=[("Word Documents", "*.docx"), ("All files", "*.*")]
|
filetypes=[("Word Documents", "*.docx"), ("All files", "*.*")]
|
||||||
)
|
)
|
||||||
if path:
|
if path:
|
||||||
selected_template.set(path)
|
selected_template.set(path)
|
||||||
|
edit_template_btn.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
docx_to_pdf_btn = None
|
||||||
|
|
||||||
|
def convert_from_docx_to_pdf():
|
||||||
|
input_path = docx_output_path.get()
|
||||||
|
output_path = pdf_from_docx_output_path.get()
|
||||||
|
|
||||||
|
if not input_path or not os.path.exists(input_path):
|
||||||
|
messagebox.showerror("Error", "The source DOCX file does not exist. Generate it first.")
|
||||||
|
return
|
||||||
|
if not output_path:
|
||||||
|
messagebox.showerror("Error", "Please specify a valid output path for the PDF.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _confirm_overwrite(output_path):
|
||||||
|
return # User cancelled overwrite
|
||||||
|
|
||||||
|
try:
|
||||||
|
pdf_output = convert_docx_to_pdf(input_path, output_path)
|
||||||
|
messagebox.showinfo("Success", f"DOCX successfully converted to PDF:\n{pdf_output}")
|
||||||
|
except ConverterNotFoundError as e:
|
||||||
|
messagebox.showerror("Converter Not Found", str(e))
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror(
|
||||||
|
"DOCX to PDF Conversion Error",
|
||||||
|
f"An unexpected error occurred.\nError: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
def convert(fmt):
|
def convert(fmt):
|
||||||
file_path = selected_file.get()
|
nonlocal docx_to_pdf_btn
|
||||||
if not file_path:
|
if docx_to_pdf_btn:
|
||||||
|
docx_to_pdf_btn.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
input_path = selected_file.get()
|
||||||
|
if not input_path:
|
||||||
messagebox.showerror("Error", "Please select a Markdown file.")
|
messagebox.showerror("Error", "Please select a Markdown file.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -113,6 +184,19 @@ def run_app():
|
|||||||
messagebox.showwarning("Warning", "A DOCX template is required.")
|
messagebox.showwarning("Warning", "A DOCX template is required.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
output_path_for_conversion = ""
|
||||||
|
if fmt == "DOCX":
|
||||||
|
output_path_for_conversion = docx_output_path.get()
|
||||||
|
elif fmt == "PDF":
|
||||||
|
output_path_for_conversion = pdf_direct_output_path.get()
|
||||||
|
|
||||||
|
if not output_path_for_conversion:
|
||||||
|
messagebox.showerror("Error", "Please specify a valid output path.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _confirm_overwrite(output_path_for_conversion):
|
||||||
|
return # User cancelled overwrite
|
||||||
|
|
||||||
metadata_to_pass = {
|
metadata_to_pass = {
|
||||||
'DOC_SECURITY': doc_security_var.get(),
|
'DOC_SECURITY': doc_security_var.get(),
|
||||||
'DOC_NUMBER': doc_number_var.get(),
|
'DOC_NUMBER': doc_number_var.get(),
|
||||||
@ -123,71 +207,99 @@ def run_app():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
output = convert_markdown(
|
output = convert_markdown(
|
||||||
input_file=file_path, output_format=fmt, add_toc=add_toc_var.get(),
|
input_file=input_path,
|
||||||
template_path=template, metadata=metadata_to_pass
|
output_path=output_path_for_conversion, # Pass the final path to the core
|
||||||
|
output_format=fmt,
|
||||||
|
add_toc=add_toc_var.get(),
|
||||||
|
template_path=template,
|
||||||
|
metadata=metadata_to_pass,
|
||||||
)
|
)
|
||||||
output_path.set(output)
|
|
||||||
messagebox.showinfo("Success", f"File converted successfully:\n{output}")
|
messagebox.showinfo("Success", f"File converted successfully:\n{output}")
|
||||||
|
|
||||||
save_configuration(
|
save_configuration(
|
||||||
last_markdown=file_path, last_template=template,
|
last_markdown=input_path, last_template=template,
|
||||||
add_toc=add_toc_var.get(), metadata=metadata_to_pass
|
add_toc=add_toc_var.get(), metadata=metadata_to_pass
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if fmt == "DOCX" and docx_to_pdf_btn:
|
||||||
|
docx_to_pdf_btn.config(state=tk.NORMAL)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.critical(f"An error occurred during conversion: {e}", exc_info=True)
|
log.critical(f"An error occurred during conversion: {e}", exc_info=True)
|
||||||
messagebox.showerror("Error", f"An error occurred during conversion:\n{str(e)}")
|
messagebox.showerror("Error", f"An error occurred during conversion:\n{str(e)}")
|
||||||
|
|
||||||
|
# --- Costruzione della GUI ---
|
||||||
container = tb.Frame(app, padding=10)
|
container = tb.Frame(app, padding=10)
|
||||||
container.pack(fill=tk.BOTH, expand=True)
|
container.pack(fill=tk.BOTH, expand=True)
|
||||||
top_frame = tb.Frame(container)
|
|
||||||
top_frame.pack(fill=tk.X, side=tk.TOP, pady=(0, 10))
|
|
||||||
|
|
||||||
tb.Label(top_frame, text="Markdown File:").grid(row=0, column=0, padx=(0, 10), pady=5, sticky="w")
|
input_frame = tb.Labelframe(container, text="Input Files", padding=10)
|
||||||
tb.Entry(top_frame, textvariable=selected_file, width=70).grid(row=0, column=1, padx=5, sticky="ew")
|
input_frame.pack(fill=tk.X, side=tk.TOP, pady=(0, 10))
|
||||||
file_button_frame = tb.Frame(top_frame)
|
input_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
tb.Label(input_frame, text="Markdown File:").grid(row=0, column=0, padx=(0, 10), pady=5, sticky="w")
|
||||||
|
tb.Entry(input_frame, textvariable=selected_file).grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
file_button_frame = tb.Frame(input_frame)
|
||||||
file_button_frame.grid(row=0, column=2, sticky="w")
|
file_button_frame.grid(row=0, column=2, sticky="w")
|
||||||
tb.Button(file_button_frame, text="Browse...", command=browse_markdown, bootstyle=PRIMARY).pack(side=tk.LEFT, padx=(0, 5))
|
tb.Button(file_button_frame, text="Browse...", command=browse_markdown, bootstyle=PRIMARY).pack(side=tk.LEFT, padx=(0, 5))
|
||||||
tb.Button(file_button_frame, text="Edit...", command=open_editor, bootstyle=INFO).pack(side=tk.LEFT)
|
tb.Button(file_button_frame, text="Edit...", command=open_editor, bootstyle=INFO).pack(side=tk.LEFT)
|
||||||
|
|
||||||
tb.Label(top_frame, text="Template File:").grid(row=1, column=0, padx=(0, 10), pady=5, sticky="w")
|
tb.Label(input_frame, text="Template File:").grid(row=1, column=0, padx=(0, 10), pady=5, sticky="w")
|
||||||
tb.Entry(top_frame, textvariable=selected_template, width=70).grid(row=1, column=1, padx=5, sticky="ew")
|
tb.Entry(input_frame, textvariable=selected_template).grid(row=1, column=1, padx=5, pady=5, sticky="ew")
|
||||||
tb.Button(top_frame, text="Browse...", command=browse_template, bootstyle=SECONDARY).grid(row=1, column=2, padx=5)
|
template_button_frame = tb.Frame(input_frame)
|
||||||
|
template_button_frame.grid(row=1, column=2, sticky="w")
|
||||||
|
tb.Button(template_button_frame, text="Browse...", command=browse_template, bootstyle=SECONDARY).pack(side=tk.LEFT, padx=(0, 5))
|
||||||
|
edit_template_btn = tb.Button(template_button_frame, text="Edit...", command=lambda: open_with_default_app(selected_template.get()), bootstyle="info", state=tk.DISABLED)
|
||||||
|
edit_template_btn.pack(side=tk.LEFT)
|
||||||
|
|
||||||
metadata_frame = tb.Labelframe(top_frame, text="Template Placeholders", padding=10)
|
metadata_frame = tb.Labelframe(container, text="Template Placeholders", padding=10)
|
||||||
metadata_frame.grid(row=2, column=0, columnspan=3, padx=0, pady=10, sticky="ew")
|
metadata_frame.pack(fill=tk.X, side=tk.TOP, pady=(0, 10))
|
||||||
|
|
||||||
tb.Label(metadata_frame, text="Document Number:").grid(row=0, column=0, padx=5, pady=3, sticky="w")
|
|
||||||
tb.Entry(metadata_frame, textvariable=doc_number_var).grid(row=0, column=1, padx=5, pady=3, sticky="ew")
|
|
||||||
tb.Label(metadata_frame, text="Revision:").grid(row=0, column=2, padx=(10, 5), pady=3, sticky="w")
|
|
||||||
tb.Entry(metadata_frame, textvariable=doc_rev_var).grid(row=0, column=3, padx=5, pady=3, sticky="ew")
|
|
||||||
tb.Label(metadata_frame, text="Project Name:").grid(row=1, column=0, padx=5, pady=3, sticky="w")
|
|
||||||
tb.Entry(metadata_frame, textvariable=doc_project_var).grid(row=1, column=1, padx=5, pady=3, sticky="ew")
|
|
||||||
tb.Label(metadata_frame, text="Customer:").grid(row=1, column=2, padx=(10, 5), pady=3, sticky="w")
|
|
||||||
tb.Entry(metadata_frame, textvariable=customer_var).grid(row=1, column=3, padx=5, pady=3, sticky="ew")
|
|
||||||
tb.Label(metadata_frame, text="Security Class:").grid(row=2, column=0, padx=5, pady=3, sticky="w")
|
|
||||||
tb.Entry(metadata_frame, textvariable=doc_security_var).grid(row=2, column=1, padx=5, pady=3, sticky="ew")
|
|
||||||
|
|
||||||
options_frame = tb.Frame(top_frame)
|
|
||||||
options_frame.grid(row=3, column=0, columnspan=3, pady=10, sticky="ew")
|
|
||||||
tb.Checkbutton(options_frame, text="Add Table of Contents", variable=add_toc_var, bootstyle="primary-round-toggle").pack(side=tk.LEFT, padx=(0, 20))
|
|
||||||
action_frame = tb.Frame(options_frame)
|
|
||||||
action_frame.pack(side=tk.LEFT)
|
|
||||||
tb.Button(action_frame, text="Convert to DOCX", command=lambda: convert("DOCX"), bootstyle=SUCCESS).pack(side=tk.LEFT, padx=5)
|
|
||||||
tb.Button(action_frame, text="Convert to PDF", command=lambda: convert("PDF"), bootstyle=SUCCESS).pack(side=tk.LEFT, padx=5)
|
|
||||||
tb.Button(action_frame, text="Open File", command=lambda: open_with_default_app(output_path.get()), bootstyle=WARNING).pack(side=tk.LEFT, padx=(20, 5))
|
|
||||||
tb.Button(action_frame, text="Open Folder", command=lambda: open_output_folder(output_path.get()), bootstyle=WARNING).pack(side=tk.LEFT, padx=5)
|
|
||||||
|
|
||||||
top_frame.columnconfigure(1, weight=1)
|
|
||||||
metadata_frame.columnconfigure(1, weight=1)
|
metadata_frame.columnconfigure(1, weight=1)
|
||||||
metadata_frame.columnconfigure(3, weight=1)
|
metadata_frame.columnconfigure(3, weight=1)
|
||||||
|
|
||||||
|
tb.Label(metadata_frame, text="Project Name:").grid(row=0, column=0, padx=5, pady=3, sticky="w")
|
||||||
|
tb.Entry(metadata_frame, textvariable=doc_project_var).grid(row=0, column=1, padx=5, pady=3, sticky="ew")
|
||||||
|
tb.Label(metadata_frame, text="Customer:").grid(row=0, column=2, padx=(10, 5), pady=3, sticky="w")
|
||||||
|
tb.Entry(metadata_frame, textvariable=customer_var).grid(row=0, column=3, padx=5, pady=3, sticky="ew")
|
||||||
|
tb.Label(metadata_frame, text="Document Number:").grid(row=1, column=0, padx=5, pady=3, sticky="w")
|
||||||
|
tb.Entry(metadata_frame, textvariable=doc_number_var).grid(row=1, column=1, padx=5, pady=3, sticky="ew")
|
||||||
|
tb.Label(metadata_frame, text="Revision:").grid(row=1, column=2, padx=(10, 5), pady=3, sticky="w")
|
||||||
|
tb.Entry(metadata_frame, textvariable=doc_rev_var).grid(row=1, column=3, padx=5, pady=3, sticky="ew")
|
||||||
|
tb.Label(metadata_frame, text="Security Class:").grid(row=2, column=0, padx=5, pady=3, sticky="w")
|
||||||
|
tb.Entry(metadata_frame, textvariable=doc_security_var).grid(row=2, column=1, padx=5, pady=3, sticky="ew")
|
||||||
|
|
||||||
|
options_frame = tb.Frame(container)
|
||||||
|
options_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
|
tb.Checkbutton(options_frame, text="Add Table of Contents", variable=add_toc_var, bootstyle="primary-round-toggle").pack(side=tk.LEFT, padx=(0, 20))
|
||||||
|
tb.Button(options_frame, text="Open Source Folder", command=lambda: open_output_folder(selected_file.get()), bootstyle=WARNING).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
action_frame = tb.Labelframe(container, text="Conversion Actions", padding=10)
|
||||||
|
action_frame.pack(fill=tk.X, pady=10)
|
||||||
|
action_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
tb.Button(action_frame, text="MD -> DOCX", command=lambda: convert("DOCX"), bootstyle=SUCCESS, width=12).grid(row=0, column=0, padx=5, pady=5)
|
||||||
|
tb.Entry(action_frame, textvariable=docx_output_path).grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(docx_output_path.get()), bootstyle="secondary").grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
tb.Button(action_frame, text="MD -> PDF", command=lambda: convert("PDF"), bootstyle=SUCCESS, width=12).grid(row=1, column=0, padx=5, pady=5)
|
||||||
|
tb.Entry(action_frame, textvariable=pdf_direct_output_path).grid(row=1, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(pdf_direct_output_path.get()), bootstyle="secondary").grid(row=1, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
docx_to_pdf_btn = tb.Button(action_frame, text="DOCX -> PDF", command=convert_from_docx_to_pdf, bootstyle="info", width=12, state=tk.DISABLED)
|
||||||
|
docx_to_pdf_btn.grid(row=2, column=0, padx=5, pady=5)
|
||||||
|
tb.Entry(action_frame, textvariable=pdf_from_docx_output_path).grid(row=2, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(pdf_from_docx_output_path.get()), bootstyle="secondary").grid(row=2, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
log_frame = tb.Labelframe(container, text="Log Viewer", padding=10)
|
log_frame = tb.Labelframe(container, text="Log Viewer", padding=10)
|
||||||
log_frame.pack(fill=tk.BOTH, expand=True, side=tk.BOTTOM)
|
log_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
log_text_widget = ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED, height=10)
|
log_text_widget = ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED, height=10)
|
||||||
log_text_widget.pack(fill=tk.BOTH, expand=True)
|
log_text_widget.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
add_tkinter_handler(gui_log_widget=log_text_widget, root_tk_instance_for_gui_handler=app, logging_config_dict=log_config)
|
add_tkinter_handler(gui_log_widget=log_text_widget, root_tk_instance_for_gui_handler=app, logging_config_dict=log_config)
|
||||||
|
|
||||||
|
_update_output_paths()
|
||||||
|
if selected_template.get():
|
||||||
|
edit_template_btn.config(state=tk.NORMAL)
|
||||||
|
|
||||||
def on_closing():
|
def on_closing():
|
||||||
shutdown_logging_system()
|
shutdown_logging_system()
|
||||||
app.destroy()
|
app.destroy()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user