1# Copyright 2008-2020 pydicom authors. See LICENSE file for details.
2"""Special classes for DICOM value representations (VR)"""
3
4import datetime
5import re
6import sys
7import warnings
8from decimal import Decimal
9from math import floor, isfinite, log10
10from typing import (
11    TypeVar, Type, Tuple, Optional, List, Dict, Union, Any, Callable,
12    MutableSequence, Sequence, cast, Iterator
13)
14
15# don't import datetime_conversion directly
16from pydicom import config
17from pydicom.multival import MultiValue
18
19
20# can't import from charset or get circular import
21default_encoding = "iso8859"
22
23# For reading/writing data elements,
24# these ones have longer explicit VR format
25# Taken from PS3.5 Section 7.1.2
26extra_length_VRs = ('OB', 'OD', 'OF', 'OL', 'OW', 'SQ', 'UC', 'UN', 'UR', 'UT')
27
28# VRs that can be affected by character repertoire
29# in (0008,0005) Specific Character Set
30# See PS-3.5 (2011), section 6.1.2 Graphic Characters
31# and PN, but it is handled separately.
32text_VRs: Tuple[str, ...] = ('SH', 'LO', 'ST', 'LT', 'UC', 'UT')
33
34# Delimiters for text strings and person name that reset the encoding.
35# See PS3.5, Section 6.1.2.5.3
36# Note: We use character codes for Python 3
37# because those are the types yielded if iterating over a byte string.
38
39# Characters/Character codes for text VR delimiters: LF, CR, TAB, FF
40TEXT_VR_DELIMS = {0x0d, 0x0a, 0x09, 0x0c}
41
42# Character/Character code for PN delimiter: name part separator '^'
43# (the component separator '=' is handled separately)
44PN_DELIMS = {0x5e}
45
46
47class _DateTimeBase:
48    """Base class for DT, DA and TM element sub-classes."""
49    original_string: str
50
51    # Add pickling support for the mutable additions
52    def __getstate__(self) -> Dict[str, Any]:
53        return self.__dict__.copy()
54
55    def __setstate__(self, state: Dict[str, Any]) -> None:
56        self.__dict__.update(state)
57
58    def __reduce_ex__(self, protocol: int) -> Tuple[Any, ...]:
59        # datetime.time, and datetime.datetime return Tuple[Any, ...]
60        # datetime.date doesn't define __reduce_ex__
61        reduce_ex = cast(Tuple[Any, ...], super().__reduce_ex__(protocol))
62        return reduce_ex + (self.__getstate__(),)
63
64    def __str__(self) -> str:
65        if hasattr(self, 'original_string'):
66            return self.original_string
67
68        return super().__str__()
69
70    def __repr__(self) -> str:
71        return f'"{str(self)}"'
72
73
74class DA(_DateTimeBase, datetime.date):
75    """Store value for an element with VR **DA** as :class:`datetime.date`.
76
77    Note that the :class:`datetime.date` base class is immutable.
78    """
79    def __new__(  # type: ignore[misc]
80        cls: Type["DA"], *args: Any, **kwargs: Any
81    ) -> Optional["DA"]:
82        """Create an instance of DA object.
83
84        Raise an exception if the string cannot be parsed or the argument
85        is otherwise incompatible.
86
87        The arguments (``*args`` and ``**kwargs``) are either the ones
88        inherited from :class:`datetime.date`, or the first argument is
89        a string conformant to the DA definition in the DICOM Standard,
90        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
91        or it is a :class:`datetime.date` object, or an object of type
92        :class:`~pydicom.valuerep.DA`.
93        """
94        if not args or args[0] is None:
95            return None
96
97        val = args[0]
98        if isinstance(val, str):
99            if val.strip() == '':
100                return None  # empty date
101
102            if len(val) == 8:
103                year = int(val[0:4])
104                month = int(val[4:6])
105                day = int(val[6:8])
106                return super().__new__(cls, year, month, day)
107
108            if len(val) == 10 and val[4] == '.' and val[7] == '.':
109                # ACR-NEMA Standard 300, predecessor to DICOM
110                # for compatibility with a few old pydicom example files
111                year = int(val[0:4])
112                month = int(val[5:7])
113                day = int(val[8:10])
114                return super().__new__(cls, year, month, day)
115
116        if isinstance(val, datetime.date):
117            return super().__new__(cls, val.year, val.month, val.day)
118
119        try:
120            return super().__new__(cls, *args, **kwargs)
121        except Exception as exc:
122            raise ValueError(
123                f"Unable to convert '{val}' to 'DA' object"
124            ) from exc
125
126    def __init__(self, *args: Any, **kwargs: Any) -> None:
127        """Create a new **DA** element value."""
128        val = args[0]
129        if isinstance(val, str):
130            self.original_string = val
131        elif isinstance(val, DA) and hasattr(val, 'original_string'):
132            self.original_string = val.original_string
133        elif isinstance(val, datetime.date):
134            self.original_string = f"{val.year}{val.month:02}{val.day:02}"
135
136
137class DT(_DateTimeBase, datetime.datetime):
138    """Store value for an element with VR **DT** as :class:`datetime.datetime`.
139
140    Note that the :class:`datetime.datetime` base class is immutable.
141    """
142    _regex_dt = re.compile(r"((\d{4,14})(\.(\d{1,6}))?)([+-]\d{4})?")
143
144    @staticmethod
145    def _utc_offset(value: str) -> datetime.timezone:
146        """Return the UTC Offset suffix as a :class:`datetime.timezone`.
147
148        Parameters
149        ----------
150        value : str
151            The value of the UTC offset suffix, such as ``'-1000'`` or
152            ``'+0245'``.
153
154        Returns
155        -------
156        datetime.timezone
157        """
158        # Format is &ZZXX, & = '+' or '-', ZZ is hours, XX is minutes
159        hour = int(value[1:3]) * 60  # Convert hours to minutes
160        minute = int(value[3:5])  # In minutes
161        offset = (hour + minute) * 60  # Convert minutes to seconds
162        offset = -offset if value[0] == '-' else offset
163
164        return datetime.timezone(
165            datetime.timedelta(seconds=offset),
166            name=value
167        )
168
169    def __new__(  # type: ignore[misc]
170        cls: Type["DT"], *args: Any, **kwargs: Any
171    ) -> Optional["DT"]:
172        """Create an instance of DT object.
173
174        Raise an exception if the string cannot be parsed or the argument
175        is otherwise incompatible.
176
177        The arguments (``*args`` and ``**kwargs``) are either the ones
178        inherited from :class:`datetime.datetime`, or the first argument is
179        a string conformant to the DT definition in the DICOM Standard,
180        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
181        or it is a :class:`datetime.datetime` object, or an object of type
182        :class:`~pydicom.valuerep.DT`.
183        """
184        if not args or args[0] is None:
185            return None
186
187        val = args[0]
188        if isinstance(val, str):
189            if val.strip() == '':
190                return None
191
192            match = cls._regex_dt.match(val)
193            if not match or len(val) > 26:
194                raise ValueError(
195                    f"Unable to convert non-conformant value '{val}' to 'DT' "
196                    "object"
197                )
198
199            dt_match = match.group(2)
200            args = (
201                int(dt_match[0:4]),  # year
202                1 if len(dt_match) < 6 else int(dt_match[4:6]),  # month
203                1 if len(dt_match) < 8 else int(dt_match[6:8]),  # day
204            )
205            kwargs = {
206                'hour': 0 if len(dt_match) < 10 else int(dt_match[8:10]),
207                'minute': 0 if len(dt_match) < 12 else int(dt_match[10:12]),
208                'second': 0 if len(dt_match) < 14 else int(dt_match[12:14]),
209                'microsecond': 0
210            }
211            if len(dt_match) >= 14 and match.group(4):
212                kwargs['microsecond'] = int(
213                    match.group(4).rstrip().ljust(6, '0')
214                )
215
216            # Timezone offset
217            tz_match = match.group(5)
218            kwargs['tzinfo'] = cls._utc_offset(tz_match) if tz_match else None
219
220            # DT may include a leap second which isn't allowed by datetime
221            if kwargs['second'] == 60:
222                warnings.warn(
223                    "'datetime.datetime' doesn't allow a value of '60' for "
224                    "the seconds component, changing to '59'"
225                )
226                kwargs['second'] = 59
227
228            return super().__new__(cls, *args, **kwargs)
229
230        if isinstance(val, datetime.datetime):
231            return super().__new__(
232                cls, *val.timetuple()[:6], val.microsecond, val.tzinfo
233            )
234
235        try:
236            return super().__new__(cls, *args, **kwargs)
237        except Exception as exc:
238            raise ValueError(
239                f"Unable to convert '{val}' to 'DT' object"
240            ) from exc
241
242    def __init__(self, *args: Any, **kwargs: Any) -> None:
243        """Create a new **DT** element value."""
244        val = args[0]
245        if isinstance(val, str):
246            self.original_string = val
247        elif isinstance(val, DT) and hasattr(val, 'original_string'):
248            self.original_string = val.original_string
249        elif isinstance(val, datetime.datetime):
250            self.original_string = (
251                f"{val.year:04}{val.month:02}{val.day:02}"
252                f"{val.hour:02}{val.minute:02}{val.second:02}"
253            )
254            # milliseconds are seldom used, add them only if needed
255            if val.microsecond > 0:
256                self.original_string += f".{val.microsecond:06}"
257
258            if val.tzinfo is not None:
259                # offset: Optional[datetime.timedelta]
260                offset = val.tzinfo.utcoffset(val)
261                if offset is not None:
262                    offset_min = offset.days * 24 * 60 + offset.seconds // 60
263                    sign = "+" if offset_min >= 0 else "-"
264                    offset_min = abs(offset_min)
265                    self.original_string += (
266                        f"{sign}{offset_min // 60:02}{offset_min % 60:02}"
267                    )
268
269
270class TM(_DateTimeBase, datetime.time):
271    """Store value for an element with VR **TM** as :class:`datetime.time`.
272
273    Note that the :class:`datetime.time` base class is immutable.
274    """
275    _RE_TIME = re.compile(
276        r"(?P<h>^([01][0-9]|2[0-3]))"
277        r"((?P<m>([0-5][0-9]))?"
278        r"(?(5)(?P<s>([0-5][0-9]|60))?)"
279        r"(?(7)(\.(?P<ms>([0-9]{1,6})?))?))$"
280    )
281
282    def __new__(  # type: ignore[misc]
283        cls: Type["TM"], *args: Any, **kwargs: Any
284    ) -> Optional["TM"]:
285        """Create an instance of TM object from a string.
286
287        Raise an exception if the string cannot be parsed or the argument
288        is otherwise incompatible.
289
290        The arguments (``*args`` and ``**kwargs``) are either the ones
291        inherited from :class:`datetime.time`, or the first argument is
292        a string conformant to the TM definition in the DICOM Standard,
293        Part 5, :dcm:`Table 6.2-1<part05/sect_6.2.html#table_6.2-1>`,
294        or it is a :class:`datetime.time` object, or an object of type
295        :class:`~pydicom.valuerep.TM`.
296        """
297        if not args or args[0] is None:
298            return None
299
300        val = args[0]
301        if isinstance(val, str):
302            if val.strip() == '':
303                return None  # empty time
304
305            match = cls._RE_TIME.match(val)
306            if not match:
307                raise ValueError(
308                    f"Unable to convert non-conformant value '{val}' to 'TM' "
309                    "object"
310                )
311
312            hour = int(match.group('h'))
313            minute = 0 if match.group('m') is None else int(match.group('m'))
314            second = 0 if match.group('s') is None else int(match.group('s'))
315
316            if second == 60:
317                warnings.warn(
318                    "'datetime.time' doesn't allow a value of '60' for the "
319                    "seconds component, changing to '59'"
320                )
321                second = 59
322
323            microsecond = 0
324            if match.group('ms'):
325                microsecond = int(match.group('ms').rstrip().ljust(6, '0'))
326
327            return super().__new__(  # type: ignore[call-arg, no-any-return]
328                cls, hour, minute, second, microsecond
329            )
330
331        if isinstance(val, datetime.time):
332            return super().__new__(  # type: ignore[call-arg, no-any-return]
333                cls, val.hour, val.minute, val.second, val.microsecond
334            )
335
336        try:
337            return super().__new__(  # type: ignore[call-arg, no-any-return]
338                cls, *args, **kwargs
339            )
340        except Exception as exc:
341            raise ValueError(
342                f"Unable to convert '{val}' to 'TM' object"
343            ) from exc
344
345    def __init__(self, *args: Any, **kwargs: Any) -> None:
346        super().__init__()
347        val = args[0]
348        if isinstance(val, str):
349            self.original_string = val
350        elif isinstance(val, TM) and hasattr(val, 'original_string'):
351            self.original_string = val.original_string
352        elif isinstance(val, datetime.time):
353            self.original_string = (
354                f"{val.hour:02}{val.minute:02}{val.second:02}"
355            )
356            # milliseconds are seldom used, add them only if needed
357            if val.microsecond > 0:
358                self.original_string += f".{val.microsecond:06}"
359
360
361# Regex to match strings that represent valid DICOM decimal strings (DS)
362_DS_REGEX = re.compile(r'\s*[\+\-]?\d+(\.\d+)?([eE][\+\-]?\d+)?\s*$')
363
364
365def is_valid_ds(s: str) -> bool:
366    """Check whether this string is a valid decimal string.
367
368    Valid decimal strings must be 16 characters or fewer, and contain only
369    characters from a limited set.
370
371    Parameters
372    ----------
373    s: str
374        String to test.
375
376    Returns
377    -------
378    bool
379        True if the string is a valid decimal string. Otherwise False.
380    """
381    # Check that the length is within the limits
382    if len(s) > 16:
383        return False
384
385    return _DS_REGEX.match(s) is not None
386
387
388def format_number_as_ds(val: Union[float, Decimal]) -> str:
389    """Truncate a float's representation to give a valid Decimal String (DS).
390
391    DICOM's decimal string (DS) representation is limited to strings with 16
392    characters and a limited set of characters. This function represents a
393    float that satisfies these constraints while retaining as much
394    precision as possible. Some floats are represented using scientific
395    notation to make more efficient use of the limited number of characters.
396
397    Note that this will incur a loss of precision if the number cannot be
398    represented with 16 characters. Furthermore, non-finite floats (infs and
399    nans) cannot be represented as decimal strings and will cause an error to
400    be raised.
401
402    Parameters
403    ----------
404    val: Union[float, Decimal]
405        The floating point value whose representation is required.
406
407    Returns
408    -------
409    str
410        String representation of the float satisfying the constraints of the
411        decimal string representation.
412
413    Raises
414    ------
415    ValueError
416        If val does not represent a finite value
417
418    """
419    if not isinstance(val, (float, Decimal)):
420        raise TypeError("'val' must be of type float or decimal.Decimal")
421    if not isfinite(val):
422        raise ValueError(
423            "Cannot encode non-finite floats as DICOM decimal strings. "
424            f"Got '{val}'"
425        )
426
427    valstr = str(val)
428
429    # In the simple case, the default python string representation
430    # will do
431    if len(valstr) <= 16:
432        return valstr
433
434    # Decide whether to use scientific notation
435    logval = log10(cast(Union[float, Decimal], abs(val)))
436
437    # Characters needed for '-' at start
438    sign_chars = 1 if val < 0.0 else 0
439
440    # Numbers larger than 1e14 cannot be correctly represented by truncating
441    # their string representations to 16 chars, e.g pi * 10^13 would become
442    # '314159265358979.', which may not be universally understood. This limit
443    # is 1e13 for negative numbers because of the minus sign.
444    # For negative exponents, the point of equal precision between scientific
445    # and standard notation is 1e-4 e.g. '0.00031415926535' and
446    # '3.1415926535e-04' are both 16 chars
447    use_scientific = logval < -4 or logval >= (14 - sign_chars)
448
449    if use_scientific:
450        # In principle, we could have a number where the exponent
451        # needs three digits to be represented (bigger than this cannot be
452        # represented by floats). Due to floating point limitations
453        # this is best checked for by doing the string conversion
454        remaining_chars = 10 - sign_chars
455        trunc_str = f'%.{remaining_chars}e' % val
456        if len(trunc_str) > 16:
457            trunc_str = f'%.{remaining_chars - 1}e' % val
458        return trunc_str
459    else:
460        if logval >= 1.0:
461            # chars remaining for digits after sign, digits left of '.' and '.'
462            remaining_chars = 14 - sign_chars - int(floor(logval))
463        else:
464            remaining_chars = 14 - sign_chars
465        return f'%.{remaining_chars}f' % val
466
467
468class DSfloat(float):
469    """Store value for an element with VR **DS** as :class:`float`.
470
471    If constructed from an empty string, return the empty string,
472    not an instance of this class.
473
474    Parameters
475    ----------
476    val: Union[str, int, float, Decimal]
477        Value to store as a DS.
478    auto_format: bool
479        If True, automatically format the string representation of this
480        number to ensure it satisfies the constraints in the DICOM standard.
481        Note that this will lead to loss of precision for some numbers.
482
483    """
484    auto_format: bool
485
486    def __new__(  # type: ignore[misc]
487        cls: Type["DSfloat"],
488        val: Union[None, str, int, float, Decimal],
489        auto_format: bool = False
490    ) -> Optional[Union[str, "DSfloat"]]:
491        if val is None:
492            return val
493
494        if isinstance(val, str) and val.strip() == '':
495            return val
496
497        return super().__new__(cls, val)
498
499    def __init__(
500        self, val: Union[str, int, float, Decimal],
501        auto_format: bool = False
502    ) -> None:
503        """Store the original string if one given, for exact write-out of same
504        value later.
505        """
506        # ... also if user changes a data element value, then will get
507        # a different object, because float is immutable.
508        has_attribute = hasattr(val, 'original_string')
509        pre_checked = False
510        if isinstance(val, str):
511            self.original_string = val.strip()
512        elif isinstance(val, (DSfloat, DSdecimal)):
513            if val.auto_format:
514                auto_format = True  # override input parameter
515                pre_checked = True
516            if has_attribute:
517                self.original_string = val.original_string
518
519        self.auto_format = auto_format
520        if self.auto_format and not pre_checked:
521            # If auto_format is True, keep the float value the same, but change
522            # the string representation stored in original_string if necessary
523            if hasattr(self, 'original_string'):
524                if not is_valid_ds(self.original_string):
525                    self.original_string = format_number_as_ds(
526                        float(self.original_string)
527                    )
528            else:
529                self.original_string = format_number_as_ds(self)
530
531        if config.enforce_valid_values and not self.auto_format:
532            if len(repr(self)[1:-1]) > 16:
533                raise OverflowError(
534                    "Values for elements with a VR of 'DS' must be <= 16 "
535                    "characters long, but the float provided requires > 16 "
536                    "characters to be accurately represented. Use a smaller "
537                    "string, set 'config.enforce_valid_values' to False to "
538                    "override the length check, or explicitly construct a DS "
539                    "object with 'auto_format' set to True"
540                )
541            if not is_valid_ds(repr(self)[1:-1]):
542                # This will catch nan and inf
543                raise ValueError(
544                    f'Value "{str(self)}" is not valid for elements with a VR '
545                    'of DS'
546                )
547
548    def __eq__(self, other: Any) -> Any:
549        """Override to allow string equality comparisons."""
550        if isinstance(other, str):
551            return str(self) == other
552
553        return super().__eq__(other)
554
555    def __hash__(self) -> int:
556        return super().__hash__()
557
558    def __ne__(self, other: Any) -> Any:
559        return not self == other
560
561    def __str__(self) -> str:
562        if hasattr(self, 'original_string') and not self.auto_format:
563            return self.original_string
564
565        # Issue #937 (Python 3.8 compatibility)
566        return repr(self)[1:-1]
567
568    def __repr__(self) -> str:
569        if self.auto_format and hasattr(self, 'original_string'):
570            return f"'{self.original_string}'"
571
572        return f"'{super().__repr__()}'"
573
574
575class DSdecimal(Decimal):
576    """Store value for an element with VR **DS** as :class:`decimal.Decimal`.
577
578    Parameters
579    ----------
580    val: Union[str, int, float, Decimal]
581        Value to store as a DS.
582    auto_format: bool
583        If True, automatically format the string representation of this
584        number to ensure it satisfies the constraints in the DICOM standard.
585        Note that this will lead to loss of precision for some numbers.
586
587    Notes
588    -----
589    If constructed from an empty string, returns the empty string, not an
590    instance of this class.
591
592    """
593    auto_format: bool
594
595    def __new__(  # type: ignore[misc]
596        cls: Type["DSdecimal"],
597        val: Union[None, str, int, float, Decimal],
598        auto_format: bool = False
599    ) -> Optional[Union[str, "DSdecimal"]]:
600        """Create an instance of DS object, or return a blank string if one is
601        passed in, e.g. from a type 2 DICOM blank value.
602
603        Parameters
604        ----------
605        val : str or numeric
606            A string or a number type which can be converted to a decimal.
607        """
608        if val is None:
609            return val
610
611        if isinstance(val, str) and val.strip() == '':
612            return val
613
614        if isinstance(val, float) and not config.allow_DS_float:
615            raise TypeError(
616                "'DS' cannot be instantiated with a float value unless "
617                "'config.allow_DS_float' is set to True. You should convert "
618                "the value to a string with the desired number of digits, "
619                "or use 'Decimal.quantize()' and pass a 'Decimal' instance."
620            )
621
622        return super().__new__(cls, val)
623
624    def __init__(
625        self,
626        val: Union[str, int, float, Decimal],
627        auto_format: bool = False
628    ) -> None:
629        """Store the original string if one given, for exact write-out of same
630        value later. E.g. if set ``'1.23e2'``, :class:`~decimal.Decimal` would
631        write ``'123'``, but :class:`DS` will use the original.
632        """
633        # ... also if user changes a data element value, then will get
634        # a different Decimal, as Decimal is immutable.
635        pre_checked = False
636        if isinstance(val, str):
637            self.original_string = val.strip()
638        elif isinstance(val, (DSfloat, DSdecimal)):
639            if val.auto_format:
640                auto_format = True  # override input parameter
641                pre_checked = True
642
643            if hasattr(val, 'original_string'):
644                self.original_string = val.original_string
645
646        self.auto_format = auto_format
647        if self.auto_format and not pre_checked:
648            # If auto_format is True, keep the float value the same, but change
649            # the string representation stored in original_string if necessary
650            if hasattr(self, 'original_string'):
651                if not is_valid_ds(self.original_string):
652                    self.original_string = format_number_as_ds(
653                        float(self.original_string)
654                    )
655            else:
656                self.original_string = format_number_as_ds(self)
657
658        if config.enforce_valid_values:
659            if len(repr(self).strip("'")) > 16:
660                raise OverflowError(
661                    "Values for elements with a VR of 'DS' values must be "
662                    "<= 16 characters long. Use a smaller string, set "
663                    "'config.enforce_valid_values' to False to override the "
664                    "length check, use 'Decimal.quantize()' and initialize "
665                    "with a 'Decimal' instance, or explicitly construct a DS "
666                    "instance with 'auto_format' set to True"
667                )
668            if not is_valid_ds(repr(self).strip("'")):
669                # This will catch nan and inf
670                raise ValueError(
671                    f'Value "{str(self)}" is not valid for elements with a VR '
672                    'of DS'
673                )
674
675    def __eq__(self, other: Any) -> Any:
676        """Override to allow string equality comparisons."""
677        if isinstance(other, str):
678            return str(self) == other
679
680        return super().__eq__(other)
681
682    def __hash__(self) -> int:
683        return super().__hash__()
684
685    def __ne__(self, other: Any) -> Any:
686        return not self == other
687
688    def __str__(self) -> str:
689        has_str = hasattr(self, 'original_string')
690        if has_str and len(self.original_string) <= 16:
691            return self.original_string
692
693        return super().__str__()
694
695    def __repr__(self) -> str:
696        if self.auto_format and hasattr(self, 'original_string'):
697            return f"'{self.original_string}'"
698        return f"'{str(self)}'"
699
700
701# CHOOSE TYPE OF DS
702DSclass: Any
703if config.use_DS_decimal:
704    DSclass = DSdecimal
705else:
706    DSclass = DSfloat
707
708
709def DS(
710    val: Union[None, str, int, float, Decimal], auto_format: bool = False
711) -> Union[None, str, DSfloat, DSdecimal]:
712    """Factory function for creating DS class instances.
713
714    Checks for blank string; if so, returns that, else calls :class:`DSfloat`
715    or :class:`DSdecimal` to create the class instance. This avoids overriding
716    ``DSfloat.__new__()`` (which carries a time penalty for large arrays of
717    DS).
718
719    Similarly the string clean and check can be avoided and :class:`DSfloat`
720    called directly if a string has already been processed.
721    """
722    if val is None:
723        return val
724
725    if isinstance(val, str) and val.strip() == '':
726        return val
727
728    if config.use_DS_decimal:
729        return DSdecimal(val, auto_format=auto_format)
730
731    return DSfloat(val, auto_format=auto_format)
732
733
734class IS(int):
735    """Store value for an element with VR **IS** as :class:`int`.
736
737    Stores original integer string for exact rewriting of the string
738    originally read or stored.
739    """
740
741    def __new__(  # type: ignore[misc]
742        cls: Type["IS"], val: Union[None, str, int, float, Decimal]
743    ) -> Optional[Union[str, "IS"]]:
744        """Create instance if new integer string"""
745        if val is None:
746            return val
747
748        if isinstance(val, str) and val.strip() == '':
749            return val
750
751        try:
752            newval = super().__new__(cls, val)
753        except ValueError:
754            # accept float strings when no integer loss, e.g. "1.0"
755            newval = super().__new__(cls, float(val))
756
757        # check if a float or Decimal passed in, then could have lost info,
758        # and will raise error. E.g. IS(Decimal('1')) is ok, but not IS(1.23)
759        #   IS('1.23') will raise ValueError
760        if isinstance(val, (float, Decimal, str)) and newval != float(val):
761            raise TypeError("Could not convert value to integer without loss")
762
763        # Checks in case underlying int is >32 bits, DICOM does not allow this
764        if not -2**31 <= newval < 2**31 and config.enforce_valid_values:
765            raise OverflowError(
766                "Elements with a VR of IS must have a value between -2**31 "
767                "and (2**31 - 1). Set 'config.enforce_valid_values' to False "
768                "to override the value check"
769            )
770
771        return newval
772
773    def __init__(self, val: Union[str, int, float, Decimal]) -> None:
774        # If a string passed, then store it
775        if isinstance(val, str):
776            self.original_string = val.strip()
777        elif isinstance(val, IS) and hasattr(val, 'original_string'):
778            self.original_string = val.original_string
779
780    def __eq__(self, other: Any) -> Any:
781        """Override to allow string equality comparisons."""
782        if isinstance(other, str):
783            return str(self) == other
784
785        return super().__eq__(other)
786
787    def __hash__(self) -> int:
788        return super().__hash__()
789
790    def __ne__(self, other: Any) -> Any:
791        return not self == other
792
793    def __str__(self) -> str:
794        if hasattr(self, 'original_string'):
795            return self.original_string
796
797        # Issue #937 (Python 3.8 compatibility)
798        return repr(self)[1:-1]
799
800    def __repr__(self) -> str:
801        return f"'{super().__repr__()}'"
802
803
804_T = TypeVar('_T')
805
806
807def MultiString(
808    val: str, valtype: Optional[Callable[[str], _T]] = None
809) -> Union[_T, MutableSequence[_T]]:
810    """Split a string by delimiters if there are any
811
812    Parameters
813    ----------
814    val : str
815        The string to split up.
816    valtype : type or callable, optional
817        Default :class:`str`, but can be e.g. :class:`~pydicom.uid.UID` to
818        overwrite to a specific type.
819
820    Returns
821    -------
822    valtype or MultiValue of valtype
823        The split value as `valtype` or a :class:`list` of `valtype`.
824    """
825    if valtype is None:
826        valtype = cast(Callable[[str], _T], str)
827
828    # Remove trailing blank used to pad to even length
829    # 2005.05.25: also check for trailing 0, error made
830    # in PET files we are converting
831    while val and val.endswith((' ', '\x00')):
832        val = val[:-1]
833
834    splitup: List[str] = val.split("\\")
835    if len(splitup) == 1:
836        return valtype(splitup[0])
837
838    return MultiValue(valtype, splitup)
839
840
841def _verify_encodings(
842    encodings: Optional[Union[str, Sequence[str]]]
843) -> Optional[Tuple[str, ...]]:
844    """Checks the encoding to ensure proper format"""
845    if encodings is None:
846        return None
847
848    if isinstance(encodings, str):
849        return (encodings,)
850
851    return tuple(encodings)
852
853
854def _decode_personname(
855    components: Sequence[bytes], encodings: Sequence[str]
856) -> Tuple[str, ...]:
857    """Return a list of decoded person name components.
858
859    Parameters
860    ----------
861    components : list of bytes
862        The list of the up to three encoded person name components
863    encodings : list of str
864        The Python encodings uses to decode `components`.
865
866    Returns
867    -------
868    text type
869        The unicode string representing the person name.
870        If the decoding of some component parts is not possible using the
871        given encodings, they are decoded with the first encoding using
872        replacement characters for bytes that cannot be decoded.
873    """
874    from pydicom.charset import decode_bytes
875
876    comps = [decode_bytes(c, encodings, PN_DELIMS) for c in components]
877
878    # Remove empty elements from the end to avoid trailing '='
879    while len(comps) and not comps[-1]:
880        comps.pop()
881
882    return tuple(comps)
883
884
885def _encode_personname(
886    components: Sequence[str], encodings: Sequence[str]
887) -> bytes:
888    """Encode a list of text string person name components.
889
890    Parameters
891    ----------
892    components : list of str
893        The list of the up to three unicode person name components
894    encodings : list of str
895        The Python encodings uses to encode `components`.
896
897    Returns
898    -------
899    byte string
900        The byte string that can be written as a PN DICOM tag value.
901        If the encoding of some component parts is not possible using the
902        given encodings, they are encoded with the first encoding using
903        replacement bytes for characters that cannot be encoded.
904    """
905    from pydicom.charset import encode_string
906
907    encoded_comps = []
908    for comp in components:
909        groups = [
910            encode_string(group, encodings) for group in comp.split('^')
911        ]
912        encoded_comps.append(b'^'.join(groups))
913
914    # Remove empty elements from the end
915    while len(encoded_comps) and not encoded_comps[-1]:
916        encoded_comps.pop()
917    return b'='.join(encoded_comps)
918
919
920class PersonName:
921    """Representation of the value for an element with VR **PN**."""
922    def __new__(  # type: ignore[misc]
923        cls: Type["PersonName"], *args: Any, **kwargs: Any
924    ) -> Optional["PersonName"]:
925        if len(args) and args[0] is None:
926            return None
927
928        return cast("PersonName", super().__new__(cls))
929
930    def __init__(
931        self,
932        val: Union[bytes, str, "PersonName"],
933        encodings: Optional[Sequence[str]] = None,
934        original_string: Optional[bytes] = None
935    ) -> None:
936        """Create a new ``PersonName``.
937
938        Parameters
939        ----------
940        val: str, bytes, PersonName
941            The value to use for the **PN** element.
942        encodings: list of str, optional
943            A list of the encodings used for the value.
944        original_string: bytes, optional
945            When creating a ``PersonName`` using a decoded string, this is the
946            original encoded value.
947
948        Notes
949        -----
950        A :class:`PersonName` may also be constructed by specifying individual
951        components using the :meth:`from_named_components` and
952        :meth:`from_named_components_veterinary` class methods.
953        """
954        self.original_string: bytes
955        self._components: Optional[Tuple[str, ...]] = None
956        self.encodings: Optional[Tuple[str, ...]]
957
958        if isinstance(val, PersonName):
959            encodings = val.encodings
960            self.original_string = val.original_string
961            self._components = tuple(str(val).split('='))
962        elif isinstance(val, bytes):
963            # this is the raw byte string - decode it on demand
964            self.original_string = val
965            self._components = None
966        else:
967            # val: str
968            # `val` is the decoded person name value
969            # `original_string`  should be the original encoded value
970            self.original_string = cast(bytes, original_string)
971            components = val.split('=')
972            # Remove empty elements from the end to avoid trailing '='
973            while len(components) and not components[-1]:
974                components.pop()
975            self._components = tuple(components)
976
977            # if the encoding is not given, leave it as undefined (None)
978        self.encodings = _verify_encodings(encodings)
979
980    def _create_dict(self) -> Dict[str, str]:
981        """Creates a dictionary of person name group and component names.
982
983        Used exclusively for `formatted` for backwards compatibility.
984        """
985        parts = [
986            'family_name', 'given_name', 'middle_name', 'name_prefix',
987            'name_suffix', 'ideographic', 'phonetic'
988        ]
989        return {c: getattr(self, c, '') for c in parts}
990
991    @property
992    def components(self) -> Tuple[str, ...]:
993        """Returns up to three decoded person name components as a
994        :class:`tuple` of :class:`str`.
995
996        .. versionadded:: 1.2
997
998        Returns
999        -------
1000        Tuple[str, ...]
1001            The (alphabetic, ideographic, phonetic) components of the
1002            decoded person name. Any of the components may be absent.
1003        """
1004        if self._components is None:
1005            groups = self.original_string.split(b'=')
1006            encodings = self.encodings or [default_encoding]
1007            self._components = _decode_personname(groups, encodings)
1008
1009        return self._components
1010
1011    def _name_part(self, i: int) -> str:
1012        """Return the `i`th part of the name."""
1013        try:
1014            return self.components[0].split('^')[i]
1015        except IndexError:
1016            return ''
1017
1018    @property
1019    def family_name(self) -> str:
1020        """Return the first (family name) group of the alphabetic person name
1021        representation as a unicode string
1022
1023        .. versionadded:: 1.2
1024        """
1025        return self._name_part(0)
1026
1027    @property
1028    def given_name(self) -> str:
1029        """Return the second (given name) group of the alphabetic person name
1030        representation as a unicode string
1031
1032        .. versionadded:: 1.2
1033        """
1034        return self._name_part(1)
1035
1036    @property
1037    def middle_name(self) -> str:
1038        """Return the third (middle name) group of the alphabetic person name
1039        representation as a unicode string
1040
1041        .. versionadded:: 1.2
1042        """
1043        return self._name_part(2)
1044
1045    @property
1046    def name_prefix(self) -> str:
1047        """Return the fourth (name prefix) group of the alphabetic person name
1048        representation as a unicode string
1049
1050        .. versionadded:: 1.2
1051        """
1052        return self._name_part(3)
1053
1054    @property
1055    def name_suffix(self) -> str:
1056        """Return the fifth (name suffix) group of the alphabetic person name
1057        representation as a unicode string
1058
1059        .. versionadded:: 1.2
1060        """
1061        return self._name_part(4)
1062
1063    @property
1064    def ideographic(self) -> str:
1065        """Return the second (ideographic) person name component as a
1066        unicode string
1067
1068        .. versionadded:: 1.2
1069        """
1070        try:
1071            return self.components[1]
1072        except IndexError:
1073            return ''
1074
1075    @property
1076    def phonetic(self) -> str:
1077        """Return the third (phonetic) person name component as a
1078        unicode string
1079
1080        .. versionadded:: 1.2
1081        """
1082        try:
1083            return self.components[2]
1084        except IndexError:
1085            return ''
1086
1087    def __eq__(self, other: Any) -> Any:
1088        """Return ``True`` if `other` equals the current name."""
1089        return str(self) == other
1090
1091    def __ne__(self, other: Any) -> Any:
1092        """Return ``True`` if `other` doesn't equal the current name."""
1093        return not self == other
1094
1095    def __str__(self) -> str:
1096        """Return a string representation of the name."""
1097        return '='.join(self.components).__str__()
1098
1099    def __iter__(self) -> Iterator[str]:
1100        """Iterate through the name."""
1101        yield from self.__str__()
1102
1103    def __len__(self) -> int:
1104        """Return the length of the person name."""
1105        return len(self.__str__())
1106
1107    def __contains__(self, x: Any) -> bool:
1108        """Return ``True`` if `x` is in the name."""
1109        return x in self.__str__()
1110
1111    def __repr__(self) -> str:
1112        """Return a representation of the name."""
1113        return '='.join(self.components).__repr__()
1114
1115    def __hash__(self) -> int:
1116        """Return a hash of the name."""
1117        return hash(self.components)
1118
1119    def decode(
1120        self, encodings: Optional[Sequence[str]] = None
1121    ) -> "PersonName":
1122        """Return the patient name decoded by the given `encodings`.
1123
1124        Parameters
1125        ----------
1126        encodings : list of str, optional
1127            The list of encodings used for decoding the byte string. If not
1128            given, the initial encodings set in the object are used.
1129
1130        Returns
1131        -------
1132        valuerep.PersonName
1133            A person name object that will return the decoded string with
1134            the given encodings on demand. If the encodings are not given,
1135            the current object is returned.
1136        """
1137        # in the common case (encoding did not change) we decode on demand
1138        if encodings is None or encodings == self.encodings:
1139            return self
1140
1141        # the encoding was unknown or incorrect - create a new
1142        # PersonName object with the changed encoding
1143        encodings = _verify_encodings(encodings)
1144        if self.original_string is None:
1145            # if the original encoding was not set, we set it now
1146            self.original_string = _encode_personname(
1147                self.components, self.encodings or [default_encoding]
1148            )
1149
1150        return PersonName(self.original_string, encodings)
1151
1152    def encode(self, encodings: Optional[Sequence[str]] = None) -> bytes:
1153        """Return the patient name decoded by the given `encodings`.
1154
1155        Parameters
1156        ----------
1157        encodings : list of str, optional
1158            The list of encodings used for encoding the unicode string. If
1159            not given, the initial encodings set in the object are used.
1160
1161        Returns
1162        -------
1163        bytes
1164            The person name encoded with the given encodings as a byte string.
1165            If no encoding is given, the original byte string is returned, if
1166            available, otherwise each group of the patient name is encoded
1167            with the first matching of the given encodings.
1168        """
1169        encodings = _verify_encodings(encodings) or self.encodings
1170
1171        # if the encoding is not the original encoding, we have to return
1172        # a re-encoded string (without updating the original string)
1173        if encodings != self.encodings and self.encodings is not None:
1174            return _encode_personname(
1175                self.components, cast(Sequence[str], encodings)
1176            )
1177
1178        if self.original_string is None:
1179            # if the original encoding was not set, we set it now
1180            self.original_string = _encode_personname(
1181                self.components, encodings or [default_encoding]
1182            )
1183
1184        return self.original_string
1185
1186    def family_comma_given(self) -> str:
1187        """Return the name as "Family, Given"."""
1188        return f"{self.family_name}, {self.given_name}"
1189
1190    def formatted(self, format_str: str) -> str:
1191        """Return the name as a :class:`str` formatted using `format_str`."""
1192        return format_str % self._create_dict()
1193
1194    def __bool__(self) -> bool:
1195        """Return ``True`` if the name is not empty."""
1196        if not self.original_string:
1197            return (
1198                bool(self.components)
1199                and (len(self.components) > 1 or bool(self.components[0]))
1200            )
1201
1202        return bool(self.original_string)
1203
1204    @staticmethod
1205    def _encode_component_groups(
1206        alphabetic_group: Sequence[Union[str, bytes]],
1207        ideographic_group: Sequence[Union[str, bytes]],
1208        phonetic_group: Sequence[Union[str, bytes]],
1209        encodings: Optional[List[str]] = None,
1210    ) -> bytes:
1211        """Creates a byte string for a person name from lists of parts.
1212
1213        Each of the three component groups (alphabetic, ideographic, phonetic)
1214        are supplied as a list of components.
1215
1216        Parameters
1217        ----------
1218        alphabetic_group: Sequence[Union[str, bytes]]
1219            List of components for the alphabetic group.
1220        ideographic_group: Sequence[Union[str, bytes]]
1221            List of components for the ideographic group.
1222        phonetic_group: Sequence[Union[str, bytes]]
1223            List of components for the phonetic group.
1224        encodings: Optional[List[str]]
1225            A list of encodings used for the other input parameters.
1226
1227        Returns
1228        -------
1229        bytes:
1230            Bytes string representation of the person name.
1231
1232        Raises
1233        ------
1234        ValueError:
1235            If any of the input strings contain disallowed characters:
1236            '\\' (single backslash), '^', '='.
1237        """
1238        from pydicom.charset import encode_string, decode_bytes
1239
1240        def enc(s: str) -> bytes:
1241            return encode_string(s, encodings or [default_encoding])
1242
1243        def dec(s: bytes) -> str:
1244            return decode_bytes(s, encodings or [default_encoding], set())
1245
1246        encoded_component_sep = enc('^')
1247        encoded_group_sep = enc('=')
1248
1249        disallowed_chars = ['\\', '=', '^']
1250
1251        def standardize_encoding(val: Union[str, bytes]) -> bytes:
1252            # Return a byte encoded string regardless of the input type
1253            # This allows the user to supply a mixture of str and bytes
1254            # for different parts of the input
1255            if isinstance(val, bytes):
1256                val_enc = val
1257                val_dec = dec(val)
1258            else:
1259                val_enc = enc(val)
1260                val_dec = val
1261
1262            # Check for disallowed chars in the decoded string
1263            for c in disallowed_chars:
1264                if c in val_dec:
1265                    raise ValueError(
1266                        f'Strings may not contain the {c} character'
1267                    )
1268
1269            # Return the encoded string
1270            return val_enc
1271
1272        def make_component_group(
1273            components: Sequence[Union[str, bytes]]
1274        ) -> bytes:
1275            encoded_components = [standardize_encoding(c) for c in components]
1276            joined_components = encoded_component_sep.join(encoded_components)
1277            return joined_components.rstrip(encoded_component_sep)
1278
1279        component_groups: List[bytes] = [
1280            make_component_group(alphabetic_group),
1281            make_component_group(ideographic_group),
1282            make_component_group(phonetic_group)
1283        ]
1284        joined_groups: bytes = encoded_group_sep.join(component_groups)
1285        joined_groups = joined_groups.rstrip(encoded_group_sep)
1286        return joined_groups
1287
1288    @classmethod
1289    def from_named_components(
1290        cls,
1291        family_name: Union[str, bytes] = '',
1292        given_name: Union[str, bytes] = '',
1293        middle_name: Union[str, bytes] = '',
1294        name_prefix: Union[str, bytes] = '',
1295        name_suffix: Union[str, bytes] = '',
1296        family_name_ideographic: Union[str, bytes] = '',
1297        given_name_ideographic: Union[str, bytes] = '',
1298        middle_name_ideographic: Union[str, bytes] = '',
1299        name_prefix_ideographic: Union[str, bytes] = '',
1300        name_suffix_ideographic: Union[str, bytes] = '',
1301        family_name_phonetic: Union[str, bytes] = '',
1302        given_name_phonetic: Union[str, bytes] = '',
1303        middle_name_phonetic: Union[str, bytes] = '',
1304        name_prefix_phonetic: Union[str, bytes] = '',
1305        name_suffix_phonetic: Union[str, bytes] = '',
1306        encodings: Optional[List[str]] = None,
1307    ) -> 'PersonName':
1308        """Construct a PersonName from explicit named components.
1309
1310        The DICOM standard describes human names using five components:
1311        family name, given name, middle name, name prefix, and name suffix.
1312        Any component may be an empty string (the default) if not used.
1313        A component may contain multiple space-separated words if there
1314        are, for example, multiple given names, middle names, or titles.
1315
1316        Additionally, each component may be represented in ideographic or
1317        phonetic form in addition to (or instead of) alphabetic form.
1318
1319        For more information see the following parts of the DICOM standard:
1320        - :dcm:`Value Representations <part05/sect_6.2.html>`
1321        - :dcm:`PN Examples <part05/sect_6.2.html#sect_6.2.1.1>`
1322        - :dcm:`PN Precise semantics <part05/sect_6.2.html#sect_6.2.1.2>`
1323
1324        Example
1325        -------
1326        A case with multiple given names and suffixes (DICOM standard,
1327        part 5, sect 6.2.1.1):
1328
1329        >>> pn = PersonName.from_named_components(
1330                family_name='Adams',
1331                given_name='John Robert Quincy',
1332                name_prefix='Rev.',
1333                name_suffix='B.A. M.Div.'
1334            )
1335
1336        A Korean case with phonetic and ideographic representations (PS3.5-2008
1337        section I.2 p. 108):
1338
1339        >>> pn = PersonName.from_named_components(
1340                family_name='Hong',
1341                given_name='Gildong',
1342                family_name_ideographic='洪',
1343                given_name_ideographic='吉洞',
1344                family_name_phonetic='홍',
1345                given_name_phonetic='길동',
1346                encodings=[default_encoding, 'euc_kr']
1347            )
1348
1349        Parameters
1350        ----------
1351        family_name: Union[str, bytes]
1352            Family name in alphabetic form.
1353        given_name: Union[str, bytes]
1354            Given name in alphabetic form.
1355        middle_name: Union[str, bytes]
1356            Middle name in alphabetic form.
1357        name_prefix: Union[str, bytes]
1358            Name prefix in alphabetic form, e.g. 'Mrs.', 'Dr.', 'Sr.', 'Rev.'.
1359        name_suffix: Union[str, bytes]
1360            Name prefix in alphabetic form, e.g. 'M.D.', 'B.A., M.Div.',
1361            'Chief Executive Officer'.
1362        family_name_ideographic: Union[str, bytes]
1363            Family name in ideographic form.
1364        given_name_ideographic: Union[str, bytes]
1365            Given name in ideographic form.
1366        middle_name_ideographic: Union[str, bytes]
1367            Middle name in ideographic form.
1368        name_prefix_ideographic: Union[str, bytes]
1369            Name prefix in ideographic form.
1370        name_suffix_ideographic: Union[str, bytes]
1371            Name suffix in ideographic form.
1372        family_name_phonetic: Union[str, bytes]
1373            Family name in phonetic form.
1374        given_name_phonetic: Union[str, bytes]
1375            Given name in phonetic form.
1376        middle_name_phonetic: Union[str, bytes]
1377            Middle name in phonetic form.
1378        name_prefix_phonetic: Union[str, bytes]
1379            Name prefix in phonetic form.
1380        name_suffix_phonetic: Union[str, bytes]
1381            Name suffix in phonetic form.
1382        encodings: Optional[List[str]]
1383            A list of encodings used for the other input parameters.
1384
1385        Returns
1386        -------
1387        PersonName:
1388            PersonName constructed from the supplied components.
1389
1390        Notes
1391        -----
1392        Strings may not contain the following characters: '^', '=',
1393        or the backslash character.
1394        """
1395        alphabetic_group: List[Union[str, bytes]] = [
1396            family_name,
1397            given_name,
1398            middle_name,
1399            name_prefix,
1400            name_suffix,
1401        ]
1402
1403        # Ideographic component group
1404        ideographic_group: List[Union[str, bytes]] = [
1405            family_name_ideographic,
1406            given_name_ideographic,
1407            middle_name_ideographic,
1408            name_prefix_ideographic,
1409            name_suffix_ideographic,
1410        ]
1411
1412        # Phonetic component group
1413        phonetic_group: List[Union[str, bytes]] = [
1414            family_name_phonetic,
1415            given_name_phonetic,
1416            middle_name_phonetic,
1417            name_prefix_phonetic,
1418            name_suffix_phonetic,
1419        ]
1420
1421        encoded_value: bytes = cls._encode_component_groups(
1422            alphabetic_group,
1423            ideographic_group,
1424            phonetic_group,
1425            encodings,
1426        )
1427
1428        return cls(encoded_value, encodings=encodings)
1429
1430    @classmethod
1431    def from_named_components_veterinary(
1432        cls,
1433        responsible_party_name: Union[str, bytes] = '',
1434        patient_name: Union[str, bytes] = '',
1435        responsible_party_name_ideographic: Union[str, bytes] = '',
1436        patient_name_ideographic: Union[str, bytes] = '',
1437        responsible_party_name_phonetic: Union[str, bytes] = '',
1438        patient_name_phonetic: Union[str, bytes] = '',
1439        encodings: Optional[List[str]] = None,
1440    ) -> 'PersonName':
1441        """Construct a PersonName from explicit named components following the
1442        veterinary usage convention.
1443
1444        The DICOM standard describes names for veterinary use with two components:
1445        responsible party family name OR responsible party organization name,
1446        and patient name.
1447        Any component may be an empty string (the default) if not used.
1448        A component may contain multiple space-separated words if necessary.
1449
1450        Additionally, each component may be represented in ideographic or
1451        phonetic form in addition to (or instead of) alphabetic form.
1452
1453        For more information see the following parts of the DICOM standard:
1454        - :dcm:`Value Representations <part05/sect_6.2.html>`
1455        - :dcm:`PN Examples <part05/sect_6.2.html#sect_6.2.1.1>`
1456        - :dcm:`PN Precise semantics <part05/sect_6.2.html#sect_6.2.1.1>`
1457
1458        Example
1459        -------
1460
1461        A horse whose responsible organization is named "ABC Farms", and whose
1462        name is "Running On Water"
1463
1464        >>> pn = PersonName.from_named_components_veterinary(
1465                responsible_party_name='ABC Farms',
1466                patient_name='Running on Water'
1467            )
1468
1469        Parameters
1470        ----------
1471        responsible_party_name: Union[str, bytes]
1472            Name of the responsible party in alphabetic form. This may be
1473            either the family name of the responsible party, or the
1474            name of the responsible organization.
1475        patient_name: Union[str, bytes]
1476            Patient name in alphabetic form.
1477        responsible_party_name_ideographic: Union[str, bytes]
1478            Name of the responsible party in ideographic form.
1479        patient_name_ideographic: Union[str, bytes]
1480            Patient name in ideographic form.
1481        responsible_party_name_phonetic: Union[str, bytes]
1482            Name of the responsible party in phonetic form.
1483        patient_name_phonetic: Union[str, bytes]
1484            Patient name in phonetic form.
1485        encodings: Optional[List[str]]
1486            A list of encodings used for the other input parameters
1487
1488        Returns
1489        -------
1490        PersonName:
1491            PersonName constructed from the supplied components
1492
1493        Notes
1494        -----
1495        Strings may not contain the following characters: '^', '=',
1496        or the backslash character.
1497        """
1498        alphabetic_group: List[Union[str, bytes]] = [
1499            responsible_party_name,
1500            patient_name,
1501        ]
1502
1503        ideographic_group: List[Union[str, bytes]] = [
1504            responsible_party_name_ideographic,
1505            patient_name_ideographic,
1506        ]
1507
1508        phonetic_group: List[Union[str, bytes]] = [
1509            responsible_party_name_phonetic,
1510            patient_name_phonetic,
1511        ]
1512
1513        encoded_value: bytes = cls._encode_component_groups(
1514            alphabetic_group,
1515            ideographic_group,
1516            phonetic_group,
1517            encodings
1518        )
1519
1520        return cls(encoded_value, encodings=encodings)
1521
1522
1523# Alias old class names for backwards compat in user code
1524def __getattr__(name: str) -> Any:
1525    if name == "PersonNameUnicode":
1526        warnings.warn(
1527            "'PersonNameUnicode' is deprecated and will be removed in "
1528            "pydicom v3.0, use 'PersonName' instead",
1529            DeprecationWarning
1530        )
1531        return globals()['PersonName']
1532
1533    raise AttributeError(f"module {__name__} has no attribute {name}")
1534
1535
1536if sys.version_info[:2] < (3, 7):
1537    PersonNameUnicode = PersonName
1538