From 2ebb389ad1672bdd99aa98ad96f1847cd2b0555d Mon Sep 17 00:00:00 2001 From: KnugiHK <24708955+KnugiHK@users.noreply.github.com> Date: Wed, 1 Apr 2026 01:04:51 +0800 Subject: [PATCH] Fix the support on grouped vCard properties (#207 ) Parse and match vCard properties that use grouping prefixes (e.g. item1.TEL) by extracting the property name correctly. Regression caused by the removal of the vobject dependency. --- Whatsapp_Chat_Exporter/vcards_contacts.py | 20 ++++++++++------ tests/data/contacts.vcf | 9 +++++++ tests/test_vcards_contacts.py | 29 +++++++++++++++++++++-- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Whatsapp_Chat_Exporter/vcards_contacts.py b/Whatsapp_Chat_Exporter/vcards_contacts.py index c9eb0e1..2bf0977 100644 --- a/Whatsapp_Chat_Exporter/vcards_contacts.py +++ b/Whatsapp_Chat_Exporter/vcards_contacts.py @@ -66,13 +66,18 @@ def _parse_vcard_line(line: str) -> tuple[str, dict[str, str], str] | None: value = line[colon_index + 1:].strip() # Split property name from parameters - parts = prop_and_params.split(';') - property_name = parts[0].upper() + property_part, *params = prop_and_params.split(';') + + # We only care about property name for now, but the grouping mechanism may be + # useful in the future if we want to associate multiple properties together. + parts = property_part.split('.') + _, property_name = parts if len(parts) == 2 else (None, parts[0]) + property_name = property_name.upper() parameters = {} - for part in parts[1:]: - if '=' in part: - key, val = part.split('=', 1) + for param in params: + if '=' in param: + key, val = param.split('=', 1) parameters[key.upper()] = val.strip('"') # Remove potential quotes from value return property_name, parameters, value @@ -98,8 +103,9 @@ def get_vcard_value(entry: str, field_name: str) -> list[str]: values.append(decode_quoted_printable(cached_line + line, charset)) cached_line = "" else: - # Skip empty lines or lines that don't start with the target field (after stripping) - if not line or not line.upper().startswith(target_name): + # Skip empty lines or lines that don't start with the target + # field (after stripping), considering potential grouping prefixes + if not line or (not line.upper().startswith(target_name) and f".{target_name}" not in line.upper().split(':')[0]): continue parsed = _parse_vcard_line(line) diff --git a/tests/data/contacts.vcf b/tests/data/contacts.vcf index 1e872be..c303ee6 100644 --- a/tests/data/contacts.vcf +++ b/tests/data/contacts.vcf @@ -42,3 +42,12 @@ VERSION:2.1 TEL;CELL:8889990001 ORG:AAA Car Service END:VCARD + +BEGIN:VCARD +VERSION:2.1 +item1.TEL;CELL:7777777778 +item2.TEL;CELL:7777777779 +item1.FN:Racing Team +item2.FN:Racing Team +END:VCARD + diff --git a/tests/test_vcards_contacts.py b/tests/test_vcards_contacts.py index 2eac4d9..91792f2 100644 --- a/tests/test_vcards_contacts.py +++ b/tests/test_vcards_contacts.py @@ -1,7 +1,7 @@ # from contacts_names_from_vcards import readVCardsFile import os -from Whatsapp_Chat_Exporter.vcards_contacts import normalize_number, read_vcards_file +from Whatsapp_Chat_Exporter.vcards_contacts import normalize_number, read_vcards_file, get_vcard_value def test_readVCardsFile(): @@ -17,7 +17,7 @@ def test_readVCardsFile(): # Print the count and the name print(f"{count}. {name}") print(data) - assert len(data) == 6 + assert len(data) == 8 # Test simple contact name assert data[0][1] == "Sample Contact" # Test complex name @@ -30,6 +30,31 @@ def test_readVCardsFile(): assert data[4][1] == "James Peacock Elementary" # Test business entry using ORG but not F/FN assert data[5][1] == "AAA Car Service" + # Test grouped entry + assert data[6][1] == "Racing Team (1)" + assert data[7][1] == "Racing Team (2)" + + +def test_grouping_mechanism(): + no_group_vcf = """ +BEGIN:VCARD +VERSION:2.1 +TEL;CELL:7777777778 +TEL;CELL:7777777779 +TEL;CELL:7777777780 +ORG:Racing Team +END:VCARD""" + group_vcf = """ +BEGIN:VCARD +VERSION:2.1 +item1.TEL;CELL:7777777778 +item2.TEL;CELL:7777777779 +item3.TEL;CELL:7777777780 +ORG:Racing Team +END:VCARD""" + assert get_vcard_value(no_group_vcf, "TEL") == ["7777777778", "7777777779", "7777777780"] + assert get_vcard_value(group_vcf, "TEL") == ["7777777778", "7777777779", "7777777780"] + def test_create_number_to_name_dicts():