Move whole-card cardinality checks into overloaded validate helpers in
the vCard 3 and vCard 4 modules, and have readVCard invoke those
validators after parsing instead of hard-coding property checks in the
shared parser.
For vCard 3, validate property cardinality directly from propertyCardMap
and add regression coverage for duplicate single-cardinality properties.
For vCard 4, validate from propertyCardMap as well, while preserving
ALTID semantics for single-cardinality properties so alternate
representations remain valid and duplicate ungrouped properties are
rejected.
Rework VERSION handling to treat it as parser metadata rather than
stored card content. Parsing still requires exactly one canonical
VERSION line and rejects missing or duplicate occurrences, but parsed
cards no longer retain VC3_Version or VC4_Version objects in content.
Accessors and serializers continue to expose and emit the canonical
version for the concrete card type, which keeps parsed and
programmatically constructed cards on the same model.
Update the vCard 3 and vCard 4 test suites to cover the new validation
path, confirm VERSION is not persisted in content, and adjust fixture
expectations to match the canonical VERSION model.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
Parse PROFILE as the concrete VC3_Profile type instead of a bare
VC3_Property, and validate PROFILE parameters under the correct
content-type name.
Preserve AGENT values in newVC3_Agent so both parsed and constructed
AGENT properties serialize with their actual URI or inline content.
Expand regression coverage to verify PROFILE is exposed and serialized
as the standard property, parsed AGENT URI values round-trip correctly,
and constructed AGENT values retain their content.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
Bring the remaining vCard 3 REV and KEY behavior into line with RFC
2426.
For REV, serialize VALUE=date using the date form instead of incorrectly
emitting VALUE=date-time with a timestamp payload.
For KEY, stop defaulting constructed values to VALUE=uri. The vCard 3
specification defines KEY as binary by default and allows it to be reset
to text, but not to uri. Tighten both construction and parsing
accordingly: reject VALUE=uri for KEY, enforce the relationship between
VALUE=binary and ENCODING=b, and reject VALUE=text when ENCODING=b is
present.
Update the regression coverage to reflect the spec boundary: PHOTO,
LOGO, and SOUND may round-trip as uris; KEY may contain text that looks
like a URI; KEY does not allow VALUE=uri; and vCard 3 KEY parameters
still require name=value syntax.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
Correct the vCard 3 parameter parser so quoted parameter values are
consumed according to the MIME-DIR grammar instead of failing
immediately on the opening double quote.
The fix explicitly advances past the opening quote, reads the quoted
qsafe-char sequence, and strips the surrounding quotes from the returned
parameter value. Unquoted parameter handling is unchanged.
Add private parser coverage for quoted parameter values and quoted
values containing commas, plus a public regression test showing that
quoted LANGUAGE and TYPE parameters are accepted by the vCard 3 parser.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
Implement RFC 2426 text escaping consistently across vCard 3
serialization and parsing.
On serialization, escape backslashes, newlines, semicolons, and commas
for simple text properties, structured text components, and list-valued
text properties so generated FN, N, ADR, ORG, CATEGORIES, and related
properties are spec-compliant on the wire.
On parsing, decode escaped text for the properties that were previously
read as raw values: FN, NICKNAME, LABEL, MAILER, TITLE, ROLE, PRODID,
and SORT-STRING. This preserves existing structured-text parsing for N,
ADR, NOTE, ORG, and CATEGORIES while fixing the direct raw-value
mismatch identified in the review.
Add regression coverage for both directions: parsing escaped text values
and serializing escaped simple, structured, and list text values.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
Decode ENCODING=b payloads for PHOTO, LOGO, SOUND, and KEY when parsing
so serializing a parsed card does not base64-encode already encoded wire
data a second time. Add regression coverage for both inline binary and
VALUE=uri round-trips.
AI-Assisted: yes
AI-Tool: OpenAI Codex /gpt-5.4 xhigh
Accept horizontal-tab continuations when unfolding content lines and
allow hyphens in group names, matching the MIME-DIR and vCard grammar.
Also add focused private tests covering both cases.
AI-Assisted: yes
AI-Tool: OpenAI Codex / gpt-5.4 xhigh
- The documentation is cluttered enough as it is with the large number
of procedures supporting vCard 3 and 4. Split common out into the
publicly exposed bits and the private internals. This makes it obvious
which common functionality a client can expect to have exposed on the
main vcard module.
- Add documentation (WIP) on the vcard3 module.
- Parsers and serializers are now present for all property types.
- Tests exist to cover parsing for most value types. Many property types
share the same parsing logic based on their value type. We have
created unit tests to cover each value type, not neccesarily all
properties individually.
- Unify the naming pattern of types and enums. Specifically:
- use `VC_Param` instead of `VCParam`. The goal here is to make the
important information (Param, Source, PropertyName, etc.) easy to
see at a glance while preserving a prefix that allows multiple
implementation to coexist (VC3_Source vs. VC4_Source) and also be
easily distinguishable at a glance.
- use `pnName` instead of `cnName`. The VCard standard refers to each
line of data as a "content line," so the original name of each was
"Content." However, the spec more commonly refers to each piece of
data as a "property." There is a 1-to-1 mapping of content lines to
property instances, but property is a more accurate name.
- Introduce the idea of property cardinality to the VCard3
implementation. The spec does not tightly define property cardinality,
but implies it with statements like "if the NAME type is present, then
*its* value is *the* displayable, presentation text associated..."
(emphasis added). Similar language implies some properties must be
present exactly once (FN, N, VERSION) or at most once (NAME, PROFILE,
SOURCE, BDAY, CATEGORIES, PRODID, REV, SORT-STRING, UID). Any other
properties are assumed to be allowed any number of times (including
0).
In the case of a VCard that contains multiple instances of properties
expected to be singular, the parser will still parse and store these
properties. They can be accessed via the `vcard#allPropsOfType`
function. For example:
# vc3 is a VCard3
allPropsOfType[VC3_N](vc3)
If we see over the course of time that other implementations regularly
use multiple instances of properties we have expected to be singular,
we are open to changing the contract to treat them so (though this
may be a breaking change).
- Refactor the VCard3 implementation to use generated property
accessors, following a similar pattern to the new VCard4
implementation.
- Remove the accessor definitions that allow access via the content seq
directly (`vc3.content.name` for example). There really isn't a reason
for this use-case and the library is simpler without exposing this.
The lexer now tracks the data that has been read since the start of the
current line. While this may have use in parsers, the immediate use is
by the common error reporting procedure.
The `common#error` procedure already reports the column and line number
where an error occurs. The `common#expect` function is broadly used by
parsers and generates the majority of parser errors. It now uses the
lexer's record of the current line to format its error message with a
direct pointer to the location of the unmet expectation.
The `unittest` library still contains a number of bare exceptions, which
now result in warnings from the compliler. Patching the standard library
to remove these warnings is outside the scope of this project, so we're
going to ignore these warnings.
- newVC3_Tel was not assigning the value provided to the constructed
object.
- Private unit tests were run every time the code was compiled due to
how the unittest library works. These now only run as part of the unit
tests with `nimble test`.
- `parseDateOrDateTime attempts to parse times starting from the most
specific (date and time) to least specific.
- `set` and `add` functions allow adding multiple content items at once
using varargs.