Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/icalendar/prop/n.py: 74%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""N property from :rfc:`6350`."""
3from typing import Any, ClassVar, NamedTuple
5from icalendar.compatibility import Self
6from icalendar.error import JCalParsingError
7from icalendar.parser import Parameters
8from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode
11class NFields(NamedTuple):
12 """Named fields for vCard N (Name) property per :rfc:`6350#section-6.2.2`.
14 Provides named access to the five name components.
15 """
17 family: str
18 """Family names (also known as surnames)."""
19 given: str
20 """Given names."""
21 additional: str
22 """Additional names."""
23 prefix: str
24 """Honorific prefixes."""
25 suffix: str
26 """Honorific suffixes."""
29class vN:
30 r"""vCard N (Name) structured property per :rfc:`6350#section-6.2.2`.
32 The N property represents a person's name.
33 It consists of a single structured text value.
34 Each component in the structure may have multiple values, separated by commas.
36 The structured property value corresponds, in sequence, to the following fields:
38 - family names (also known as surnames)
39 - given names
40 - additional names
41 - honorific prefixes
42 - honorific suffixes
44 Semicolons are field separators and are NOT escaped.
45 Commas and backslashes within field values ARE escaped per :rfc:`6350`.
47 Examples:
49 .. code-block:: pycon
51 >>> from icalendar.prop import vN
52 >>> n = vN(("Doe", "John", "M.", "Dr.", "Jr.,M.D.,A.C.P."))
53 >>> n.to_ical()
54 b'Doe;John;M.;Dr.;Jr.\\,M.D.\\,A.C.P.'
55 >>> vN.from_ical(r"Doe;John;M.;Dr.;Jr.\,M.D.\,A.C.P.")
56 NFields(family='Doe', given='John', additional='M.', prefix='Dr.', suffix='Jr.,M.D.,A.C.P.')
57 """
59 default_value: ClassVar[str] = "TEXT"
60 params: Parameters
61 fields: NFields
63 def __init__(
64 self,
65 fields: tuple[str, ...] | list[str] | str | NFields,
66 /,
67 params: dict[str, Any] | None = None,
68 ):
69 """Initialize N with five fields or parse from vCard format string.
71 Parameters:
72 fields: Either an NFields, tuple, or list of five strings, one per field,
73 or a vCard format string with semicolon-separated fields
74 params: Optional property parameters
75 """
76 if isinstance(fields, str):
77 fields = self.from_ical(fields)
78 if isinstance(fields, NFields):
79 self.fields = fields
80 else:
81 if len(fields) != 5:
82 raise ValueError(f"N must have exactly 5 fields, got {len(fields)}")
83 self.fields = NFields(*(str(f) for f in fields))
84 self.params = Parameters(params)
86 def to_ical(self) -> bytes:
87 """Generate vCard format with semicolon-separated fields."""
88 from icalendar.prop.text import vText
90 parts = [vText(f).to_ical().decode(DEFAULT_ENCODING) for f in self.fields]
91 return ";".join(parts).encode(DEFAULT_ENCODING)
93 @staticmethod
94 def from_ical(ical: str | bytes) -> NFields:
95 """Parse vCard N format into an NFields named tuple.
97 Parameters:
98 ical: vCard format string with semicolon-separated fields
100 Returns:
101 NFields named tuple with five field values.
102 """
103 from icalendar.parser import split_on_unescaped_semicolon
105 ical = to_unicode(ical)
106 fields = split_on_unescaped_semicolon(ical)
107 if len(fields) != 5:
108 raise ValueError(f"N must have exactly 5 fields, got {len(fields)}: {ical}")
109 return NFields(*fields)
111 def __eq__(self, other: object) -> bool:
112 """self == other"""
113 return isinstance(other, vN) and self.fields == other.fields
115 def __repr__(self) -> str:
116 """String representation."""
117 return f"{self.__class__.__name__}({self.fields}, params={self.params})"
119 @property
120 def ical_value(self) -> NFields:
121 """The name fields as a named tuple."""
122 return self.fields
124 from icalendar.param import VALUE
126 def to_jcal(self, name: str) -> list:
127 """The jCal representation of this property according to :rfc:`7265`."""
128 result = [name, self.params.to_jcal(), self.VALUE.lower()]
129 result.extend(self.fields)
130 return result
132 @classmethod
133 def from_jcal(cls, jcal_property: list) -> Self:
134 """Parse jCal from :rfc:`7265`.
136 Parameters:
137 jcal_property: The jCal property to parse.
139 Raises:
140 ~error.JCalParsingError: If the provided jCal is invalid.
141 """
142 JCalParsingError.validate_property(jcal_property, cls)
143 if len(jcal_property) != 8: # name, params, value_type, 5 fields
144 raise JCalParsingError(
145 f"N must have 8 elements (name, params, value_type, 5 fields), "
146 f"got {len(jcal_property)}"
147 )
148 for i, field in enumerate(jcal_property[3:], start=3):
149 JCalParsingError.validate_value_type(field, str, cls, i)
150 return cls(
151 tuple(jcal_property[3:]),
152 Parameters.from_jcal_property(jcal_property),
153 )
155 @classmethod
156 def examples(cls) -> list[Self]:
157 """Examples of vN."""
158 return [cls(("Doe", "John", "M.", "Dr.", "Jr."))]
161__all__ = ["NFields", "vN"]