<commit_before># valuerep.py
"""Special classes for DICOM value representations (VR)"""
# Copyright (c) 2008-2012 <NAME>
# This file is part of pydicom, released under a modified MIT license.
#    See the file license.txt included with this distribution, also
#    available at https://github.com/darcymason/pydicom

from decimal import Decimal

from pydicom import config  # don't import datetime_conversion directly
from pydicom import compat
from pydicom.multival import MultiValue
from pydicom.config import logger

from datetime import date, datetime, time
from dateutil.tz import tzoffset
import re

from pydicom.config import logger

default_encoding = "iso8859"  # can't import from charset or get circular import

# For reading/writing data elements, these ones have longer explicit VR format
extra_length_VRs = ('OB', 'OW', 'OF', 'SQ', 'UN', 'UT')

# VRs that can be affected by character repertoire in (0008,0005) Specific Character Set
# See PS-3.5 (2011), section 6.1.2 Graphic Characters
text_VRs = ('SH', 'LO', 'ST', 'LT', 'UT')  # and PN, but it is handled separately.

match_string = b''.join([
    b'(?P<single_byte>',
    b'(?P<family_name>[^=\^]*)',
    b'\^?(?P<given_name>[^=\^]*)',
    b'\^?(?P<middle_name>[^=\^]*)',
    b'\^?(?P<name_prefix>[^=\^]*)',
    b'\^?(?P<name_suffix>[^=\^]*)',
    b')',
    b'=?(?P<ideographic>[^=]*)',
    b'=?(?P<phonetic>[^=]*)$'])

match_string_uni = re.compile(match_string.decode('iso8859'))
match_string_bytes = re.compile(match_string)


class DA(date):
    """Store value for DICOM VR DA (Date) as datetime.date.

    Note that the datetime.date base class is immutable.

    """
    __slots__ = 'original_string'

    def __new__(cls, val):
        """Create an instance of DA object.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the DA definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            if len(val) == 8:
                year = int(val[0:4])
                month = int(val[4:6])
                day = int(val[6:8])
                val = super(DA, cls).__new__(cls, year, month, day)
            elif len(val) == 10 and val[4] == '.' and val[7] == '.':
                # ACR-NEMA Standard 300, predecessor to DICOM
                # for compatibility with a few old pydicom example files
                year = int(val[0:4])
                month = int(val[5:7])
                day = int(val[8:10])
                val = super(DA, cls).__new__(cls, year, month, day)
            elif val == '':
                val = None  # empty date
            else:
                raise ValueError("Cannot convert to date: '" + val + "'")
        elif isinstance(val, date):
            val = super(DA, cls).__new__(cls, val.year, val.month, val.day)
        else:
            val = super(DA, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, DA) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DA, self).__str__()


class DT(datetime):
    """Store value for DICOM VR DT (DateTime) as datetime.datetime.

    Note that the datetime.datetime base class is immutable.

    """
    __slots__ = 'original_string'
    _regex_dt = re.compile(r"((\d{4,14})(\.(\d{1,6}))?)([+-]\d{4})?")

    def __new__(cls, val):
        """Create an instance of DT object.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the DT definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            match = DT._regex_dt.match(val)
            if match and len(val) <= 26:
                dt_match = match.group(2)
                year = int(dt_match[0:4])
                if len(dt_match) < 6:
                    month = 1
                else:
                    month = int(dt_match[4:6])
                if len(dt_match) < 8:
                    day = 1
                else:
                    day = int(dt_match[6:8])
                if len(dt_match) < 10:
                    hour = 0
                else:
                    hour = int(dt_match[8:10])
                if len(dt_match) < 12:
                    minute = 0
                else:
                    minute = int(dt_match[10:12])
                if len(dt_match) < 14:
                    second = 0
                    microsecond = 0
                else:
                    second = int(dt_match[12:14])
                    ms_match = match.group(4)
                    if ms_match:
                        microsecond = int(ms_match.rstrip().ljust(6, '0'))
                    else:
                        microsecond = 0
                tz_match = match.group(5)
                if tz_match:
                    offset = (int(tz_match[1:3]) * 60 + int(tz_match[3:5])) * 60
                    if tz_match[0] == '-':
                        offset = -offset
                    tzinfo = tzoffset(tz_match, offset)
                else:
                    tzinfo = None
                val = super(DT, cls).__new__(cls, year, month, day,
                                             hour, minute, second,
                                             microsecond, tzinfo)
            else:
                raise ValueError("Cannot convert to datetime: '" + val + "'")
        elif isinstance(val, datetime):
            val = super(DT, cls).__new__(cls, val.year, val.month, val.day,
                                         val.hour, val.minute, val.second,
                                         val.microsecond, val.tzinfo)
        else:
            val = super(DT, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, DT) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DT, self).__str__()


class TM(time):
    """Store value for DICOM VR of TM (Time) as datetime.time.

    Note that the datetime.time base class is immutable.

    """
    __slots__ = 'original_string'
    _regex_tm = re.compile(r"(\d{2,6})(\.(\d{1,6}))?")

    def __new__(cls, val):
        """Create an instance of TM object from a string.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the TM definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            match = TM._regex_tm.match(val)
            if match and len(val) <= 16:
                tm_match = match.group(1)
                hour = int(tm_match[0:2])
                if len(tm_match) < 4:
                    minute = 0
                else:
                    minute = int(tm_match[2:4])
                if len(tm_match) < 6:
                    second = 0
                    microsecond = 0
                else:
                    second = int(tm_match[4:6])
                    ms_match = match.group(3)
                    if ms_match:
                        microsecond = int(ms_match.rstrip().ljust(6, '0'))
                    else:
                        microsecond = 0
                val = super(TM, cls).__new__(cls, hour, minute, second,
                                             microsecond)
            elif val == '':
                val = None  # empty time
            else:
                raise ValueError("Cannot convert to time: '" + val + "'")
        elif isinstance(val, time):
            val = super(TM, cls).__new__(cls, val.hour, val.minute, val.second,
                                         val.microsecond)
        else:
            val = super(TM, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, TM) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(TM, self).__str__()


class DSfloat(float):
    """Store values for DICOM VR of DS (Decimal String) as a float.

    If constructed from an empty string, return the empty string,
    not an instance of this class.

    """
    __slots__ = 'original_string'

    def __init__(self, val):
        """Store the original string if one given, for exact write-out of same
        value later.
        """
        # ... also if user changes a data element value, then will get
        # a different object, because float is immutable.

        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, (DSfloat, DSdecimal)) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DSfloat, self).__str__()

    def __repr__(self):
        return "'" + str(self) + "'"


class DSdecimal(Decimal):
    """Store values for DICOM VR of DS (Decimal String).
    Note: if constructed by an empty string, returns the empty string,
    not an instance of this class.
    """
    __slots__ = 'original_string'

    def __new__(cls, val):
        """Create an instance of DS object, or return a blank string if one is
        passed in, e.g. from a type 2 DICOM blank value.

        :param val: val must be a string or a number type which can be
                   converted to a decimal
        """
        # Store this value here so that if the input string is actually a valid
        # string but decimal.Decimal transforms it to an invalid string it will
        # still be initialized properly
        enforce_length = config.enforce_valid_values
        # DICOM allows spaces around the string, but python doesn't, so clean it
        if isinstance(val, (str, compat.text_type)):
            val = val.strip()
            # If the input string is actually invalid that we relax the valid
            # value constraint for this particular instance
            if len(val) <= 16:
                enforce_length = False
        if val == '':
            return val
        if isinstance(val, float) and not config.allow_DS_float:
            msg = ("DS cannot be instantiated with a float value, unless "
                   "config.allow_DS_float is set to True. It is recommended to "
                   "convert to a string instead, with the desired number of digits, "
                   "or use Decimal.quantize and pass a Decimal instance.")
            raise TypeError(msg)
        if not isinstance(val, Decimal):
            val = super(DSdecimal, cls).__new__(cls, val)
        if len(str(val)) > 16 and enforce_length:
            msg = ("DS value representation must be <= 16 characters by DICOM "
                   "standard. Initialize with a smaller string, or set config.enforce_valid_values "
                   "to False to override, "
                   "or use Decimal.quantize() and initialize with a Decimal instance.")
            raise OverflowError(msg)
        return val

    def __init__(self, val):
        """Store the original string if one given, for exact write-out of same
        value later. E.g. if set '1.23e2', Decimal would write '123', but DS
        will use the original
        """
        # ... also if user changes a data element value, then will get
        # a different Decimal, as Decimal is immutable.
        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, (DSfloat, DSdecimal)) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string') and len(self.original_string) <= 16:
            return self.original_string
        else:
            return super(DSdecimal, self).__str__()

    def __repr__(self):
        return "'" + str(self) + "'"

# CHOOSE TYPE OF DS
if config.use_DS_decimal:
    DSclass = DSdecimal
else:
    DSclass = DSfloat


def DS(val):
    """Factory function for creating DS class instances.
    Checks for blank string; if so, return that. Else calls DSfloat or DSdecimal
    to create the class instance. This avoids overriding __new__ in DSfloat
    (which carries a time penalty for large arrays of DS).
    Similarly the string clean and check can be avoided and DSfloat called
    directly if a string has already been processed.
    """
    if isinstance(val, (str, compat.text_type)):
        val = val.strip()
    if val == '':
        return val
    return DSclass(val)


class IS(int):
    """Derived class of int. Stores original integer string for exact rewriting
    of the string originally read or stored.
    """
    if compat.in_py2:
        __slots__ = 'original_string'
    # Unlikely that str(int) will not be the same as the original, but could happen
    # with leading zeros.

    def __new__(cls, val):
        """Create instance if new integer string"""
        if isinstance(val, (str, compat.text_type)) and val.strip() == '':
            return ''
        newval = super(IS, cls).__new__(cls, val)
        # check if a float or Decimal passed in, then could have lost info,
        # and will raise error. E.g. IS(Decimal('1')) is ok, but not IS(1.23)
        if isinstance(val, (float, Decimal)) and newval != val:
            raise TypeError("Could not convert value to integer without loss")
        # Checks in case underlying int is >32 bits, DICOM does not allow this
        if (newval < -2 ** 31 or newval >= 2 ** 31) and config.enforce_valid_values:
            message = "Value exceeds DICOM limits of -2**31 to (2**31 - 1) for IS"
            raise OverflowError(message)
        return newval

    def __init__(self, val):
        # If a string passed, then store it
        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, IS) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __repr__(self):
        if hasattr(self, 'original_string'):
            return "'" + self.original_string + "'"
        else:
            return "'" + int.__str__(self) + "'"


def MultiString(val, valtype=str):
    """Split a bytestring by delimiters if there are any

    val -- DICOM bytestring to split up
    valtype -- default str, but can be e.g. UID to overwrite to a specific type
    """
    # Remove trailing blank used to pad to even length
    # 2005.05.25: also check for trailing 0, error made in PET files we are converting

    if val and (val.endswith(' ') or val.endswith('\x00')):
        val = val[:-1]
    splitup = val.split("\\")

    if len(splitup) == 1:
        val = splitup[0]<commit_msg>change __repr__ to use double quotes for numerics<commit_after># valuerep.py
"""Special classes for DICOM value representations (VR)"""
# Copyright (c) 2008-2012 <NAME>
# This file is part of pydicom, released under a modified MIT license.
#    See the file license.txt included with this distribution, also
#    available at https://github.com/darcymason/pydicom

from decimal import Decimal

from pydicom import config  # don't import datetime_conversion directly
from pydicom import compat
from pydicom.multival import MultiValue
from pydicom.config import logger

from datetime import date, datetime, time
from dateutil.tz import tzoffset
import re

from pydicom.config import logger

default_encoding = "iso8859"  # can't import from charset or get circular import

# For reading/writing data elements, these ones have longer explicit VR format
extra_length_VRs = ('OB', 'OW', 'OF', 'SQ', 'UN', 'UT')

# VRs that can be affected by character repertoire in (0008,0005) Specific Character Set
# See PS-3.5 (2011), section 6.1.2 Graphic Characters
text_VRs = ('SH', 'LO', 'ST', 'LT', 'UT')  # and PN, but it is handled separately.

match_string = b''.join([
    b'(?P<single_byte>',
    b'(?P<family_name>[^=\^]*)',
    b'\^?(?P<given_name>[^=\^]*)',
    b'\^?(?P<middle_name>[^=\^]*)',
    b'\^?(?P<name_prefix>[^=\^]*)',
    b'\^?(?P<name_suffix>[^=\^]*)',
    b')',
    b'=?(?P<ideographic>[^=]*)',
    b'=?(?P<phonetic>[^=]*)$'])

match_string_uni = re.compile(match_string.decode('iso8859'))
match_string_bytes = re.compile(match_string)


class DA(date):
    """Store value for DICOM VR DA (Date) as datetime.date.

    Note that the datetime.date base class is immutable.

    """
    __slots__ = 'original_string'

    def __new__(cls, val):
        """Create an instance of DA object.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the DA definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            if len(val) == 8:
                year = int(val[0:4])
                month = int(val[4:6])
                day = int(val[6:8])
                val = super(DA, cls).__new__(cls, year, month, day)
            elif len(val) == 10 and val[4] == '.' and val[7] == '.':
                # ACR-NEMA Standard 300, predecessor to DICOM
                # for compatibility with a few old pydicom example files
                year = int(val[0:4])
                month = int(val[5:7])
                day = int(val[8:10])
                val = super(DA, cls).__new__(cls, year, month, day)
            elif val == '':
                val = None  # empty date
            else:
                raise ValueError("Cannot convert to date: '" + val + "'")
        elif isinstance(val, date):
            val = super(DA, cls).__new__(cls, val.year, val.month, val.day)
        else:
            val = super(DA, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, DA) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DA, self).__str__()


class DT(datetime):
    """Store value for DICOM VR DT (DateTime) as datetime.datetime.

    Note that the datetime.datetime base class is immutable.

    """
    __slots__ = 'original_string'
    _regex_dt = re.compile(r"((\d{4,14})(\.(\d{1,6}))?)([+-]\d{4})?")

    def __new__(cls, val):
        """Create an instance of DT object.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the DT definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            match = DT._regex_dt.match(val)
            if match and len(val) <= 26:
                dt_match = match.group(2)
                year = int(dt_match[0:4])
                if len(dt_match) < 6:
                    month = 1
                else:
                    month = int(dt_match[4:6])
                if len(dt_match) < 8:
                    day = 1
                else:
                    day = int(dt_match[6:8])
                if len(dt_match) < 10:
                    hour = 0
                else:
                    hour = int(dt_match[8:10])
                if len(dt_match) < 12:
                    minute = 0
                else:
                    minute = int(dt_match[10:12])
                if len(dt_match) < 14:
                    second = 0
                    microsecond = 0
                else:
                    second = int(dt_match[12:14])
                    ms_match = match.group(4)
                    if ms_match:
                        microsecond = int(ms_match.rstrip().ljust(6, '0'))
                    else:
                        microsecond = 0
                tz_match = match.group(5)
                if tz_match:
                    offset = (int(tz_match[1:3]) * 60 + int(tz_match[3:5])) * 60
                    if tz_match[0] == '-':
                        offset = -offset
                    tzinfo = tzoffset(tz_match, offset)
                else:
                    tzinfo = None
                val = super(DT, cls).__new__(cls, year, month, day,
                                             hour, minute, second,
                                             microsecond, tzinfo)
            else:
                raise ValueError("Cannot convert to datetime: '" + val + "'")
        elif isinstance(val, datetime):
            val = super(DT, cls).__new__(cls, val.year, val.month, val.day,
                                         val.hour, val.minute, val.second,
                                         val.microsecond, val.tzinfo)
        else:
            val = super(DT, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, DT) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DT, self).__str__()


class TM(time):
    """Store value for DICOM VR of TM (Time) as datetime.time.

    Note that the datetime.time base class is immutable.

    """
    __slots__ = 'original_string'
    _regex_tm = re.compile(r"(\d{2,6})(\.(\d{1,6}))?")

    def __new__(cls, val):
        """Create an instance of TM object from a string.

        Raise an exception if the string cannot be parsed or the argument
        is otherwise incompatible.

        :param val: val must be a string conformant to the TM definition
        in the DICOM Standard PS 3.5-2011
        """
        if isinstance(val, (str, compat.string_types)):
            match = TM._regex_tm.match(val)
            if match and len(val) <= 16:
                tm_match = match.group(1)
                hour = int(tm_match[0:2])
                if len(tm_match) < 4:
                    minute = 0
                else:
                    minute = int(tm_match[2:4])
                if len(tm_match) < 6:
                    second = 0
                    microsecond = 0
                else:
                    second = int(tm_match[4:6])
                    ms_match = match.group(3)
                    if ms_match:
                        microsecond = int(ms_match.rstrip().ljust(6, '0'))
                    else:
                        microsecond = 0
                val = super(TM, cls).__new__(cls, hour, minute, second,
                                             microsecond)
            elif val == '':
                val = None  # empty time
            else:
                raise ValueError("Cannot convert to time: '" + val + "'")
        elif isinstance(val, time):
            val = super(TM, cls).__new__(cls, val.hour, val.minute, val.second,
                                         val.microsecond)
        else:
            val = super(TM, cls).__new__(cls, val)
        return val

    def __init__(self, val):
        if isinstance(val, (str, compat.string_types)):
            self.original_string = val
        elif isinstance(val, TM) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(TM, self).__str__()


class DSfloat(float):
    """Store values for DICOM VR of DS (Decimal String) as a float.

    If constructed from an empty string, return the empty string,
    not an instance of this class.

    """
    __slots__ = 'original_string'

    def __init__(self, val):
        """Store the original string if one given, for exact write-out of same
        value later.
        """
        # ... also if user changes a data element value, then will get
        # a different object, because float is immutable.

        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, (DSfloat, DSdecimal)) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string'):
            return self.original_string
        else:
            return super(DSfloat, self).__str__()

    def __repr__(self):
        return "\"" + str(self) + "\""


class DSdecimal(Decimal):
    """Store values for DICOM VR of DS (Decimal String).
    Note: if constructed by an empty string, returns the empty string,
    not an instance of this class.
    """
    __slots__ = 'original_string'

    def __new__(cls, val):
        """Create an instance of DS object, or return a blank string if one is
        passed in, e.g. from a type 2 DICOM blank value.

        :param val: val must be a string or a number type which can be
                   converted to a decimal
        """
        # Store this value here so that if the input string is actually a valid
        # string but decimal.Decimal transforms it to an invalid string it will
        # still be initialized properly
        enforce_length = config.enforce_valid_values
        # DICOM allows spaces around the string, but python doesn't, so clean it
        if isinstance(val, (str, compat.text_type)):
            val = val.strip()
            # If the input string is actually invalid that we relax the valid
            # value constraint for this particular instance
            if len(val) <= 16:
                enforce_length = False
        if val == '':
            return val
        if isinstance(val, float) and not config.allow_DS_float:
            msg = ("DS cannot be instantiated with a float value, unless "
                   "config.allow_DS_float is set to True. It is recommended to "
                   "convert to a string instead, with the desired number of digits, "
                   "or use Decimal.quantize and pass a Decimal instance.")
            raise TypeError(msg)
        if not isinstance(val, Decimal):
            val = super(DSdecimal, cls).__new__(cls, val)
        if len(str(val)) > 16 and enforce_length:
            msg = ("DS value representation must be <= 16 characters by DICOM "
                   "standard. Initialize with a smaller string, or set config.enforce_valid_values "
                   "to False to override, "
                   "or use Decimal.quantize() and initialize with a Decimal instance.")
            raise OverflowError(msg)
        return val

    def __init__(self, val):
        """Store the original string if one given, for exact write-out of same
        value later. E.g. if set '1.23e2', Decimal would write '123', but DS
        will use the original
        """
        # ... also if user changes a data element value, then will get
        # a different Decimal, as Decimal is immutable.
        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, (DSfloat, DSdecimal)) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __str__(self):
        if hasattr(self, 'original_string') and len(self.original_string) <= 16:
            return self.original_string
        else:
            return super(DSdecimal, self).__str__()

    def __repr__(self):
        return "\"" + str(self) + "\""

# CHOOSE TYPE OF DS
if config.use_DS_decimal:
    DSclass = DSdecimal
else:
    DSclass = DSfloat


def DS(val):
    """Factory function for creating DS class instances.
    Checks for blank string; if so, return that. Else calls DSfloat or DSdecimal
    to create the class instance. This avoids overriding __new__ in DSfloat
    (which carries a time penalty for large arrays of DS).
    Similarly the string clean and check can be avoided and DSfloat called
    directly if a string has already been processed.
    """
    if isinstance(val, (str, compat.text_type)):
        val = val.strip()
    if val == '':
        return val
    return DSclass(val)


class IS(int):
    """Derived class of int. Stores original integer string for exact rewriting
    of the string originally read or stored.
    """
    if compat.in_py2:
        __slots__ = 'original_string'
    # Unlikely that str(int) will not be the same as the original, but could happen
    # with leading zeros.

    def __new__(cls, val):
        """Create instance if new integer string"""
        if isinstance(val, (str, compat.text_type)) and val.strip() == '':
            return ''
        newval = super(IS, cls).__new__(cls, val)
        # check if a float or Decimal passed in, then could have lost info,
        # and will raise error. E.g. IS(Decimal('1')) is ok, but not IS(1.23)
        if isinstance(val, (float, Decimal)) and newval != val:
            raise TypeError("Could not convert value to integer without loss")
        # Checks in case underlying int is >32 bits, DICOM does not allow this
        if (newval < -2 ** 31 or newval >= 2 ** 31) and config.enforce_valid_values:
            message = "Value exceeds DICOM limits of -2**31 to (2**31 - 1) for IS"
            raise OverflowError(message)
        return newval

    def __init__(self, val):
        # If a string passed, then store it
        if isinstance(val, (str, compat.text_type)):
            self.original_string = val
        elif isinstance(val, IS) and hasattr(val, 'original_string'):
            self.original_string = val.original_string

    def __repr__(self):
        if hasattr(self, 'original_string'):
            return "'" + self.original_string + "'"
        else:
            return "\"" + int.__str__(self) + "\""


def MultiString(val, valtype=str):
    """Split a bytestring by delimiters if there are any

    val -- DICOM bytestring to split up
    valtype -- default str, but can be e.g. UID to overwrite to a specific type
    """
    # Remove trailing blank used to pad to even length
    # 2005.05.25: also check for trailing 0, error made in PET files we are converting

    if val and (val.endswith(' ') or val.endswith('\x00')):
        val = val[:-1]
    splitup = val.split("\\")

    if len(splitup) == 1:
        val = splitup[0]
{'fraction_non_alphanumeric': 0.10285860132720775, 'fraction_numerical': 0.012633996937212864, 'mean_word_length': 1.898021449704142, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6772226', 'n_tokens_mistral': 9185, 'n_tokens_neox': 8626, 'n_words': 2966}