Skip to content

Conversation

@kunal763
Copy link
Contributor

@kunal763 kunal763 commented Jan 11, 2026

This PR adds support for converting VCF (vCard) contact files to CSV format, addressing issue #396. The implementation provides a pure TypeScript solution without external dependencies.

  • New Features
    • Register vcf -> csv converter in the main registry and update README.
    • Parse FN, N, TEL/EMAIL (including TYPE params), and ORG; generate CSV with proper quote escaping.
    • Add tests covering single/multiple cards, TYPE parameters, quote escaping, and empty data.

VCF Parsing

  • Multi-contact support: Handles multiple vCards in single file
  • Field extraction: Parses FN, N, TEL, EMAIL, ORG fields
  • TYPE parameter handling: Ignores TYPE attributes (WORK, HOME, PREF, etc.)
  • Robust parsing: Regex-based splitting with error resilience

Add support for converting VCF contact files to CSV format
- Implement TypeScript-based VCF parsing without external dependencies
- Create CSV export with proper quote escaping
- Add comprehensive test coverage for various VCF formats
- Update documentation and converter registry

Closes C4illin#396
Add support for converting VCF contact files to CSV format
- Implement TypeScript-based VCF parsing without external dependencies
- Create CSV export with proper quote escaping
- Add comprehensive test coverage for various VCF formats
- Update documentation and converter registry

Closes C4illin#396
@kunal763 kunal763 changed the title feat: add VCF to CSV converter #396 feat: add VCF to CSV converter Jan 11, 2026
@github-actions github-actions bot added Feature and removed Feature labels Jan 11, 2026
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/converters/vcf.ts">

<violation number="1" location="src/converters/vcf.ts:28">
P1: VCF parsing ignores vCard parameters and folded lines, dropping FN/N values and truncating long fields</violation>

<violation number="2" location="src/converters/vcf.ts:51">
P1: CSV headers are derived only from the first contact, so fields present only in later contacts are omitted and their values are dropped from the CSV output.</violation>

<violation number="3" location="src/converters/vcf.ts:52">
P1: CSV output does not neutralize leading formula characters, allowing spreadsheet formula injection if VCF data starts with =, +, - or @.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const first = data[0];
if (!first) return "";
const headers = Object.keys(first);
const escape = (str: string) => `"${str.replace(/"/g, '""')}"`;
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: CSV output does not neutralize leading formula characters, allowing spreadsheet formula injection if VCF data starts with =, +, - or @.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/converters/vcf.ts, line 52:

<comment>CSV output does not neutralize leading formula characters, allowing spreadsheet formula injection if VCF data starts with =, +, - or @.</comment>

<file context>
@@ -0,0 +1,69 @@
+  const first = data[0];
+  if (!first) return "";
+  const headers = Object.keys(first);
+  const escape = (str: string) => `"${str.replace(/"/g, '""')}"`;
+  const rows = data.map((row) => headers.map((h) => escape(row[h] || "")).join(","));
+  return [headers.join(","), ...rows].join("\n");
</file context>
Fix with Cubic

if (!data.length) return "";
const first = data[0];
if (!first) return "";
const headers = Object.keys(first);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: CSV headers are derived only from the first contact, so fields present only in later contacts are omitted and their values are dropped from the CSV output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/converters/vcf.ts, line 51:

<comment>CSV headers are derived only from the first contact, so fields present only in later contacts are omitted and their values are dropped from the CSV output.</comment>

<file context>
@@ -0,0 +1,69 @@
+  if (!data.length) return "";
+  const first = data[0];
+  if (!first) return "";
+  const headers = Object.keys(first);
+  const escape = (str: string) => `"${str.replace(/"/g, '""')}"`;
+  const rows = data.map((row) => headers.map((h) => escape(row[h] || "")).join(","));
</file context>
Suggested change
const headers = Object.keys(first);
const headers = Array.from(new Set(data.flatMap((row) => Object.keys(row))));
Fix with Cubic

if (colonIndex === -1) continue;
const key = line.slice(0, colonIndex).trim();
const value = line.slice(colonIndex + 1).trim();
if (key === "FN") {
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: VCF parsing ignores vCard parameters and folded lines, dropping FN/N values and truncating long fields

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/converters/vcf.ts, line 28:

<comment>VCF parsing ignores vCard parameters and folded lines, dropping FN/N values and truncating long fields</comment>

<file context>
@@ -0,0 +1,69 @@
+        if (colonIndex === -1) continue;
+        const key = line.slice(0, colonIndex).trim();
+        const value = line.slice(colonIndex + 1).trim();
+        if (key === "FN") {
+          contact["Full Name"] = value;
+        } else if (key === "N") {
</file context>
Fix with Cubic

@github-actions github-actions bot added Feature and removed Feature labels Jan 11, 2026
@C4illin
Copy link
Owner

C4illin commented Jan 11, 2026

Nice work! Everything seems to work with the vCards I have tested. Should I merge it?

@kunal763
Copy link
Contributor Author

Yeah I think everything is good from my end

@C4illin C4illin merged commit c3f17cc into C4illin:main Jan 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants