diff --git a/CFM001-T-IT-I en-it.dotx b/CFM001-T-IT-I en-it.dotx
new file mode 100644
index 0000000..68b8d78
Binary files /dev/null and b/CFM001-T-IT-I en-it.dotx differ
diff --git a/markdownconverter/core/core.py b/markdownconverter/core/core.py
index 8bef167..bc45bfd 100644
--- a/markdownconverter/core/core.py
+++ b/markdownconverter/core/core.py
@@ -1,31 +1,113 @@
import os
+import re
import markdown
import pdfkit
import pypandoc
+# Path to the wkhtmltopdf executable
WKHTMLTOPDF_PATH = r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"
config = pdfkit.configuration(wkhtmltopdf=WKHTMLTOPDF_PATH)
-TEMPLATE_DOCX_PATH = os.path.join(os.path.dirname(__file__), "..", "templates", "default_template.docx")
+# Default path for the DOCX template
+TEMPLATE_DOCX_PATH = os.path.join(
+ os.path.dirname(__file__), "..", "templates", "default_template.docx"
+)
-def convert_markdown(input_file, output_format, font=None, template_path=None):
- output_file = os.path.splitext(input_file)[0] + (".pdf" if output_format == "PDF" else ".docx")
+
+def _get_document_title(markdown_text):
+ """Extracts the first header (any level) from markdown text to use as a title."""
+ for line in markdown_text.splitlines():
+ if re.match(r'^#+\s', line.strip()):
+ return re.sub(r'^#+\s*', '', line.strip())
+ return "Document"
+
+
+def _extract_title_and_separate_content(markdown_text):
+ """
+ Extracts the first header (any level) and returns it with the rest of the content.
+ """
+ lines = markdown_text.splitlines()
+ title = "Document"
+ content_lines = []
+ title_found = False
+
+ for line in lines:
+ if not title_found and re.match(r'^#+\s', line.strip()):
+ title = re.sub(r'^#+\s*', '', line.strip())
+ title_found = True
+ else:
+ content_lines.append(line)
+
+ content_without_title = "\n".join(content_lines)
+ return title, content_without_title
+
+
+def convert_markdown(input_file, output_format, add_toc=False, font=None, template_path=None):
+ """
+ Converts a Markdown file to the specified output format (PDF or DOCX).
+ """
+ if not os.path.exists(input_file):
+ raise FileNotFoundError(f"Input file not found: {input_file}")
+
+ output_file = os.path.splitext(input_file)[0] + (
+ ".pdf" if output_format == "PDF" else ".docx"
+ )
if output_format == "PDF":
with open(input_file, 'r', encoding='utf-8') as f:
- html = markdown.markdown(f.read())
- style = f"" if font else ""
- pdfkit.from_string(style + html, output_file, configuration=config)
+ markdown_text = f.read()
+
+ # PDF logic requires manual assembly, so it remains the same
+ extensions = ['toc'] if add_toc else []
+ md = markdown.Markdown(extensions=extensions)
+
+ body_markdown = markdown_text
+ title = _get_document_title(markdown_text)
+
+ if add_toc:
+ title, body_markdown = _extract_title_and_separate_content(markdown_text)
+
+ html_body = md.convert(body_markdown)
+ style = f"" if font else ""
+
+ toc_html = ""
+ if add_toc and hasattr(md, 'toc'):
+ toc_html = f"""
+
{title}
+ Table of Contents
+ {md.toc}
+
+ """
+
+ full_html = f"""
+ {style}{title}{toc_html}{html_body}
+ """
+ options = {'encoding': "UTF-8"}
+ pdfkit.from_string(full_html, output_file, configuration=config, options=options)
elif output_format == "DOCX":
- args = []
+ # --- THE CORRECT AND SIMPLIFIED LOGIC ---
+ args = ["--standalone"]
+
+ if add_toc:
+ # Let pandoc handle title detection and TOC generation automatically.
+ args.append("--toc")
+
if template_path and os.path.exists(template_path):
args.extend(["--reference-doc", template_path])
elif os.path.exists(TEMPLATE_DOCX_PATH):
args.extend(["--reference-doc", TEMPLATE_DOCX_PATH])
- pypandoc.convert_file(input_file, 'docx', outputfile=output_file, extra_args=args)
+
+ # We use convert_file with the original, unmodified input file.
+ pypandoc.convert_file(
+ input_file,
+ 'docx',
+ outputfile=output_file,
+ extra_args=args,
+ encoding='utf-8'
+ )
else:
- raise ValueError("Formato non supportato")
+ raise ValueError("Unsupported format")
- return output_file
+ return output_file
\ No newline at end of file
diff --git a/markdownconverter/gui/gui.py b/markdownconverter/gui/gui.py
index 7df9dcd..dafaa1b 100644
--- a/markdownconverter/gui/gui.py
+++ b/markdownconverter/gui/gui.py
@@ -3,16 +3,18 @@ import subprocess
import json
import ttkbootstrap as tb
from ttkbootstrap.constants import *
-from tkinter import filedialog, messagebox, StringVar
+from tkinter import filedialog, messagebox, StringVar, BooleanVar
from ..core.core import convert_markdown
CONFIG_FILE = os.path.join(os.path.expanduser("~"), ".markdown_converter_config.json")
def save_config(font_name):
+ # This function seems duplicated with config.py, consider centralizing
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump({"font": font_name}, f)
def load_config():
+ # This function seems duplicated with config.py, consider centralizing
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
@@ -20,73 +22,121 @@ def load_config():
return ""
def open_with_default_app(filepath):
+ """Opens a file with the default registered application."""
+ if not filepath:
+ messagebox.showwarning("Warning", "No output file has been generated yet.")
+ return
try:
- os.startfile(filepath) # solo su Windows
+ os.startfile(filepath) # Windows-specific
+ except FileNotFoundError:
+ messagebox.showerror("Error", f"File not found:\n{filepath}")
except Exception as e:
- messagebox.showerror("Errore", f"Impossibile aprire il file:\n{str(e)}")
+ messagebox.showerror("Error", f"Could not open the file:\n{str(e)}")
def open_output_folder(filepath):
+ """Opens the folder containing the specified file."""
+ if not filepath:
+ messagebox.showwarning("Warning", "No output file has been generated yet.")
+ return
try:
folder = os.path.dirname(filepath)
os.startfile(folder)
except Exception as e:
- messagebox.showerror("Errore", f"Impossibile aprire la cartella:\n{str(e)}")
+ messagebox.showerror("Error", f"Could not open the folder:\n{str(e)}")
def run_app():
+ """Initializes and runs the main application window."""
app = tb.Window(themename="sandstone")
app.title("Markdown Converter")
- app.geometry("680x240")
+ app.geometry("680x280") # Increased height for the new widget
app.resizable(False, False)
+ # --- Variables ---
selected_file = StringVar()
selected_font = StringVar(value=load_config())
selected_template = StringVar()
output_path = StringVar()
+ add_toc_var = BooleanVar(value=True) # Variable for the TOC checkbox
+ # --- Functions ---
def browse_markdown():
- path = filedialog.askopenfilename(filetypes=[("Markdown files", "*.md")])
+ """Opens a file dialog to select a Markdown file."""
+ path = filedialog.askopenfilename(
+ title="Select a Markdown file",
+ filetypes=[("Markdown files", "*.md"), ("All files", "*.*")]
+ )
if path:
selected_file.set(path)
def browse_template():
- path = filedialog.askopenfilename(filetypes=[("DOCX files", "*.docx")])
+ """Opens a file dialog to select a DOCX or DOTX template file."""
+ path = filedialog.askopenfilename(
+ title="Select a template file",
+ filetypes=[
+ ("Word Documents", "*.docx"),
+ ("Word Templates", "*.dotx"),
+ ("All files", "*.*")
+ ]
+ )
if path:
selected_template.set(path)
def convert(fmt):
+ """Handles the conversion process when a button is clicked."""
file_path = selected_file.get()
font = selected_font.get()
template = selected_template.get()
+ add_toc = add_toc_var.get()
if not file_path:
- messagebox.showerror("Errore", "Seleziona un file Markdown.")
+ messagebox.showerror("Error", "Please select a Markdown file.")
return
try:
- output = convert_markdown(file_path, fmt, font, template)
+ output = convert_markdown(file_path, fmt, add_toc, font, template)
output_path.set(output)
- save_config(font)
- messagebox.showinfo("Successo", f"File convertito:\n{output}")
+ save_config(font) # Save font on successful conversion
+ messagebox.showinfo("Success", f"File converted successfully:\n{output}")
except Exception as e:
- messagebox.showerror("Errore", str(e))
+ messagebox.showerror("Error", f"An error occurred during conversion:\n{str(e)}")
- # Layout
- tb.Label(app, text="File Markdown:").grid(row=0, column=0, padx=10, pady=10, sticky="w")
- tb.Entry(app, textvariable=selected_file, width=60).grid(row=0, column=1, padx=5, sticky="w")
- tb.Button(app, text="Sfoglia", command=browse_markdown, bootstyle=PRIMARY).grid(row=0, column=2, padx=5)
+ # --- Layout ---
+ main_frame = tb.Frame(app, padding=10)
+ main_frame.pack(fill=BOTH, expand=True)
- tb.Label(app, text="Font:").grid(row=1, column=0, padx=10, pady=10, sticky="w")
+ # Row 0: Input file
+ tb.Label(main_frame, text="Markdown File:").grid(row=0, column=0, padx=(0, 10), pady=5, sticky="w")
+ tb.Entry(main_frame, textvariable=selected_file, width=60).grid(row=0, column=1, padx=5, sticky="ew")
+ tb.Button(main_frame, text="Browse...", command=browse_markdown, bootstyle=PRIMARY).grid(row=0, column=2, padx=5)
+
+ # Row 1: Font (for PDF)
+ tb.Label(main_frame, text="Font (PDF):").grid(row=1, column=0, padx=(0, 10), pady=5, sticky="w")
fonts = ["Arial", "Times New Roman", "Verdana", "Calibri", "Courier New"]
- tb.Combobox(app, values=fonts, textvariable=selected_font, width=30, bootstyle=INFO).grid(row=1, column=1, sticky="w", padx=5)
+ tb.Combobox(main_frame, values=fonts, textvariable=selected_font, width=30, bootstyle=INFO).grid(row=1, column=1, sticky="w", padx=5)
- tb.Label(app, text="Template DOCX (opzionale):").grid(row=2, column=0, padx=10, pady=10, sticky="w")
- tb.Entry(app, textvariable=selected_template, width=60).grid(row=2, column=1, padx=5)
- tb.Button(app, text="Sfoglia", command=browse_template, bootstyle=SECONDARY).grid(row=2, column=2, padx=5)
+ # Row 2: Template (for DOCX)
+ tb.Label(main_frame, text="Template (DOCX):").grid(row=2, column=0, padx=(0, 10), pady=5, sticky="w")
+ tb.Entry(main_frame, textvariable=selected_template, width=60).grid(row=2, column=1, padx=5, sticky="ew")
+ tb.Button(main_frame, text="Browse...", command=browse_template, bootstyle=SECONDARY).grid(row=2, column=2, padx=5)
+
+ # Row 3: Options (TOC)
+ tb.Checkbutton(
+ main_frame,
+ text="Add Table of Contents at the beginning",
+ variable=add_toc_var,
+ bootstyle="primary-round-toggle"
+ ).grid(row=3, column=1, pady=10, sticky="w")
- tb.Button(app, text="Converti in PDF", command=lambda: convert("PDF"), bootstyle=SUCCESS).grid(row=3, column=0, pady=20)
- tb.Button(app, text="Converti in DOCX", command=lambda: convert("DOCX"), bootstyle=SUCCESS).grid(row=3, column=1, sticky="w", pady=20)
+ # Row 4: Action Buttons
+ action_frame = tb.Frame(main_frame)
+ action_frame.grid(row=4, column=0, columnspan=3, pady=10)
+
+ tb.Button(action_frame, text="Convert to PDF", command=lambda: convert("PDF"), bootstyle=SUCCESS).pack(side=LEFT, padx=5)
+ tb.Button(action_frame, text="Convert to DOCX", command=lambda: convert("DOCX"), bootstyle=SUCCESS).pack(side=LEFT, padx=5)
+
+ tb.Button(action_frame, text="Open File", command=lambda: open_with_default_app(output_path.get()), bootstyle=WARNING).pack(side=LEFT, padx=(20, 5))
+ tb.Button(action_frame, text="Open Folder", command=lambda: open_output_folder(output_path.get()), bootstyle=WARNING).pack(side=LEFT, padx=5)
+
+ main_frame.columnconfigure(1, weight=1)
- tb.Button(app, text="Apri file", command=lambda: open_with_default_app(output_path.get()), bootstyle=WARNING).grid(row=4, column=0, padx=10, pady=10)
- tb.Button(app, text="Apri cartella", command=lambda: open_output_folder(output_path.get()), bootstyle=WARNING).grid(row=4, column=1, sticky="w", padx=5)
-
- app.mainloop()
+ app.mainloop()
\ No newline at end of file