Source code for vcf_to_obsidian.markdown_writer

"""
Markdown Writer module for generating Markdown content from VCF data.
"""

import base64
from datetime import datetime, timezone


[docs] class MarkdownWriter: """Class responsible for generating Markdown content from vCard objects.""" def __init__(self): """Initialize the Markdown writer.""" pass
[docs] def generate_obsidian_markdown(self, vcard): """ Generate Markdown content compatible with obsidian-vcf-contacts plugin. Works directly with vobject instead of intermediate representation. Args: vcard: vobject vCard object Returns: str: Markdown content with frontmatter """ # Build the markdown content directly from vobject lines = ["---"] # Extract structured name if hasattr(vcard, 'n') and vcard.n.value: n = vcard.n.value if hasattr(n, 'family') and n.family: lines.append(f"N.FN: {n.family}") if hasattr(n, 'given') and n.given: lines.append(f"N.GN: {n.given}") # Extract Full Name if hasattr(vcard, 'fn') and vcard.fn.value: lines.append(f"FN: {vcard.fn.value}") # Extract photo if hasattr(vcard, 'photo') and vcard.photo.value: # if vcard.photo.value data type is bytes if isinstance(vcard.photo.value, bytes): # convert bytes to base64 photo_data = base64.b64encode(vcard.photo.value).decode('utf-8') lines.append(f"PHOTO: data:image/jpeg;base64,{photo_data}") elif isinstance(vcard.photo.value, str): # write if PHOTO is a URL if vcard.photo.value.startswith("http"): lines.append(f"PHOTO: {vcard.photo.value}") # Extract email addresses with type information if hasattr(vcard, 'email_list'): for email in vcard.email_list: type_label = 'DEFAULT' if hasattr(email, 'params') and 'TYPE' in email.params: type_values = email.params['TYPE'] if isinstance(type_values, list) and type_values: type_label = type_values[0].upper() elif isinstance(type_values, str): type_label = type_values.upper() field_key = f"EMAIL[{type_label}]" lines.append(f'"{field_key}": {email.value}') # Extract phone numbers with type information if hasattr(vcard, 'tel_list'): for tel in vcard.tel_list: type_label = 'DEFAULT' if hasattr(tel, 'params') and 'TYPE' in tel.params: type_values = tel.params['TYPE'] if isinstance(type_values, list) and type_values: type_label = type_values[0].upper() elif isinstance(type_values, str): type_label = type_values.upper() field_key = f"TEL[{type_label}]" lines.append(f'"{field_key}": "{tel.value}"') # Extract birthday if hasattr(vcard, 'bday') and vcard.bday.value: lines.append(f"BDAY: {vcard.bday.value}") # Extract URLs with type information if hasattr(vcard, 'url') and vcard.url.value: type_label = 'DEFAULT' if hasattr(vcard.url, 'params') and 'TYPE' in vcard.url.params: type_values = vcard.url.params['TYPE'] if isinstance(type_values, list) and type_values: type_label = type_values[0].upper() elif isinstance(type_values, str): type_label = type_values.upper() field_key = f"URL[{type_label}]" lines.append(f'"{field_key}": {vcard.url.value}') # Extract organization if hasattr(vcard, 'org') and vcard.org.value: org_value = vcard.org.value if isinstance(org_value, list) and org_value: lines.append(f"ORG: {org_value[0]}") elif isinstance(org_value, str): lines.append(f"ORG: {org_value}") # Extract addresses with type information if hasattr(vcard, 'adr_list'): for adr in vcard.adr_list: adr_value = adr.value type_label = 'DEFAULT' if hasattr(adr, 'params') and 'TYPE' in adr.params: type_values = adr.params['TYPE'] if isinstance(type_values, list) and type_values: type_label = type_values[0].upper() elif isinstance(type_values, str): type_label = type_values.upper() base_key = f"ADR[{type_label}]" if hasattr(adr_value, 'box') and adr_value.box: lines.append(f'"{base_key}.POBOX": {adr_value.box}') if hasattr(adr_value, 'extended') and adr_value.extended: lines.append(f'"{base_key}.EXTENDED": {adr_value.extended}') if hasattr(adr_value, 'street') and adr_value.street: lines.append(f'"{base_key}.STREET": {adr_value.street}') if hasattr(adr_value, 'city') and adr_value.city: lines.append(f'"{base_key}.LOCALITY": {adr_value.city}') if hasattr(adr_value, 'region') and adr_value.region: lines.append(f'"{base_key}.REGION": {adr_value.region}') if hasattr(adr_value, 'code') and adr_value.code: lines.append(f'"{base_key}.POSTAL": "{adr_value.code}"') if hasattr(adr_value, 'country') and adr_value.country: lines.append(f'"{base_key}.COUNTRY": {adr_value.country}') # Extract categories if hasattr(vcard, 'categories') and vcard.categories.value: lines.append(f"CATEGORIES: {vcard.categories.value}") # Extract UID if hasattr(vcard, 'uid') and vcard.uid.value: lines.append(f"UID: {vcard.uid.value}") # Extract version if hasattr(vcard, 'version') and vcard.version.value: lines.append(f'VERSION: "{vcard.version.value}"') # Add REV timestamp - always current time when markdown is created/updated current_time = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") lines.append(f"REV: {current_time}") lines.append("") lines.append("---") # Add notes section if available notes_added = False if hasattr(vcard, 'note') and vcard.note.value: notes_added = True if hasattr(vcard, 'categories') and vcard.categories.value: notes_added = True if notes_added: lines.append("#### Notes") lines.append("") if hasattr(vcard, 'categories') and vcard.categories.value: category_list = vcard.categories.value.split(',') contact_line = "#Contact" for category in category_list: contact_line += f" #{category.strip()}" lines.append(contact_line) else: lines.append("#Contact") return '\n'.join(lines) + '\n'