{{ entry.notes }}
diff --git a/tests/Export to html and email.py b/tests/Export to html and email.py
new file mode 100644
index 00000000..3f82170e
--- /dev/null
+++ b/tests/Export to html and email.py
@@ -0,0 +1,357 @@
+# region: Docstring
+'''
+Author: Wim Gielis (wim.gielis@gmail.com)
+Timing: January 2025
+'''
+# endregion
+
+# region: Imports
+import os
+import re
+import uuid
+from datetime import datetime
+from pykeepass import PyKeePass
+from jinja2 import Template
+
+import smtplib
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.mime.base import MIMEBase
+from email import encoders
+# endregion
+
+# region: Constant values
+KEEPASS_DB_PATH = "D:/Paswoorden.kdbx"
+KEEPASS_DB_PASSWORD = "test"
+OUTPUT_HTML_FILE = "D:/pw.html"
+HTML_TEMPLATE_FILE = "D:/template_for_export.html"
+
+DATE_FORMAT = '%d-%m-%Y'
+
+# For sending the HTML file by email
+SEND_EMAIL = True
+RECIPIENT_EMAIL = "your_email_address@gmail.com"
+SENDER_EMAIL = "your_email_address@gmail.com"
+SENDER_PASSWORD = "your_Google_app_password"
+EMAIL_SUBJECT = f"pw ({datetime.now().strftime(DATE_FORMAT)})"
+EMAIL_BODY = "" # "Please find attached the KeePass export file."
+# endregion
+
+# region: Main code
+def write_to_file(full_file_name, file_contents):
+ with open(full_file_name, "w", encoding="utf-8") as f:
+ f.write(file_contents)
+
+def format_date(date_obj):
+ """
+ Format a datetime object as 'dd-mm-yyyy'.
+ If the date_obj is None, return 'N/A'.
+ """
+ return date_obj.strftime(DATE_FORMAT) if date_obj else 'N/A'
+
+def send_email(file_path, recipient_email, sender_email, sender_password, email_subject, email_body):
+ """
+ Send an email with the generated HTML file as an attachment.
+
+ :param file_path: Path to the HTML file to attach.
+ :param recipient_email: Recipient's email address.
+ :param sender_email: Sender's Gmail address.
+ :param sender_password: Sender's Gmail app password.
+ """
+
+ # Create a new email
+ message = MIMEMultipart()
+ message['From'] = sender_email
+ message['To'] = recipient_email
+ message['Subject'] = email_subject
+ message.attach(MIMEText(email_body, 'plain'))
+
+ # Attach the HTML file
+ with open(file_path, 'rb') as attachment:
+ part = MIMEBase('application', 'octet-stream')
+ part.set_payload(attachment.read())
+ encoders.encode_base64(part)
+ part.add_header('Content-Disposition', f'attachment; filename={os.path.basename(file_path)}')
+ message.attach(part)
+
+ # Send the email
+ with smtplib.SMTP('smtp.gmail.com', 587) as server:
+ server.starttls()
+ server.login(sender_email, sender_password)
+ server.send_message(message)
+
+ print(f"Email sent successfully to {recipient_email}")
+
+def export_keepass_to_html(db_path, password, output_file, template_file):
+
+ try:
+ # Load the KeePass database
+ kp = PyKeePass(db_path, password=password)
+
+ def resolve_username(entry):
+ '''
+ Function to derive the username for an entry
+ '''
+ if isinstance(entry.username, str):
+ if re.match(r'^\{REF:U.*:[0-9A-Z]{32}\}', entry.username):
+ return entry.deref('username')
+ else:
+ return entry.username
+ elif entry.username is not None:
+ referenced_entry = kp.find_entries(uuid=entry.username, first=True)
+ return referenced_entry.username if referenced_entry else "Unresolved Reference"
+ return "No Username"
+
+ def resolve_password(entry):
+ '''
+ Function to derive the password for an entry
+ '''
+ if isinstance(entry.password, str):
+ if re.match(r'^\{REF:P.*:[0-9A-Z]{32}\}', entry.password):
+ return entry.deref('password')
+ else:
+ return entry.password
+ elif entry.password is not None:
+ referenced_entry = kp.find_entries(uuid=entry.password, first=True)
+ return referenced_entry.password if referenced_entry else "Unresolved Reference"
+ return "No Password"
+
+ def extract_group_iterative(root_groups):
+ '''
+ Iterative function to extract groups and their subgroups
+ '''
+ stack = [{"group": group, "parent_id": None} for group in root_groups]
+ group_data = []
+
+ while stack:
+ current = stack.pop()
+ group = current["group"]
+ parent_id = current["parent_id"]
+
+ group_info = {
+ "id": f"group-{uuid.uuid4().hex}",
+ "name": group.name,
+ "parent_id": parent_id,
+ "entries": sorted([
+ {
+ "title": entry.title or "No Title",
+ "username": resolve_username(entry) or "No Username",
+ "password": resolve_password(entry) or "No Password",
+ "url": entry.url or "No URL",
+ "notes": (entry.notes or "No Notes").replace("\n", "
"),
+ "custom_properties": entry.custom_properties or {},
+ "ctime": format_date(entry.ctime),
+ "mtime": format_date(entry.mtime),
+ "expiry_time": format_date(entry.expiry_time),
+ "expired": entry.expired
+ }
+ for entry in group.entries], key=lambda e: e["title"].lower())
+ }
+
+ # Add the current group's subgroups to the stack for processing
+ stack.extend( sorted(
+ [{"group": subgroup, "parent_id": group_info["id"]} for subgroup in group.subgroups],
+ key=lambda sg: sg["group"].name.lower()
+ ))
+ group_data.append(group_info)
+
+ # Sort all groups by their name (case insensitive)
+ return sorted(group_data, key=lambda g: g["name"].lower())
+
+ # Extract all groups iteratively
+ root_groups = [group for group in kp.groups if group.group is None]
+ grouped_entries = extract_group_iterative(root_groups)
+
+ # Load the HTML template
+ with open(template_file, "r", encoding="utf-8") as template_file_obj:
+ template_content = template_file_obj.read()
+ template = Template(template_content)
+
+ # Render the HTML and write to a file
+ html_output = template.render(grouped_entries=grouped_entries)
+ write_to_file(output_file, html_output)
+
+ print(f"Export completed. HTML saved to: {output_file}")
+
+ except Exception as e:
+ print(f"Error occurred: {e}")
+# endregion
+
+# region: Call the script
+if __name__ == "__main__":
+
+ example_template = """
+
+
+