Source code for canlib.ean

PRODUCT_EAN_LENGTH = 7


[docs]def bcd_digits(bcd_bytes): """Split a byte sequence into four-bit BCD digits Used internally by `EAN` to decode BCD. For example 0x12345 is turned into 1, 2, 3, 4, 5. `bcd_bytes` must be an iterable of eight bit objects supporting `&` and `>>`. Note: The byteorder is currently assumed to be 'little'. """ # byteorder is assumed to be 'little' for char in bcd_bytes: # Python 2 doesn't have bytes, only string if isinstance(char, str): char = ord(char) yield char & 0xF
yield char >> 4
[docs]def bcd_int(bcd_bytes): """Calls `bcd_digits`, and then `int_from_digits` with the result Used internally by `EAN` to decode BCD. """
return int_from_digits(bcd_digits(bcd_bytes))
[docs]def int_from_digits(digits): """Joins a sequence of decimal digits into a decimal number Used internally by `EAN` to decode BCD. For example (1, 2, 3, 4, 5) is turned into 12345. Iterating through `digits` is assumed to only yield integers between 0 and 9, inclusive. """ decimal = 0 for pos, digit in enumerate(digits): decimal += digit * 10**pos
return decimal
[docs]class IllegalEAN(ValueError): """Could not parse EAN"""
pass
[docs]class EAN(object): """Helper object for dealing with European Article Numbers Depending on the format the ean is in, `EAN` objects are created in different ways; For strings:: EAN.from_string('01-2345-67890-1') For BCD-coded bytes or bytearrays (str in python 2):: EAN.from_bcd(b'\x00\x01 ... ') For "hi-lo" format, i.e. two 32-bit integers containing half the ean each, both BCD-coded:: EAN.from_hilo([eanHi, eanLo]) Sometimes it is easier to only use the last six digits of the ean, the product code and check digit. This is supported when working with string representations; `from_string` supports six-digit (seven-character) input:: EAN.from_string('67890-1') In that cases, the country and manufacturer code is assumed to be that of Kvaser AB (73-30130). A string containing only the product code and check digit can also be retrieved:: ean.product() Note: The byteorder is currently always assumed to be 'little'. """ fmt = "##-#####-#####-#" num_digits = len([s for s in fmt if s == '#'])
[docs] @classmethod def from_bcd(cls, bcd_bytes): """Create an EAN object from a bytes-like object"""
return cls(bcd_int(bcd_bytes))
[docs] @classmethod def from_string(cls, ean_string): """Create an EAN object from a specially formatted string""" if len(ean_string) == PRODUCT_EAN_LENGTH: ean_string = "73-30130-" + ean_string if not all( s.isdecimal() if (f == '#') else (f == s) for f, s in zip(cls.fmt, ean_string)): raise IllegalEAN("Unreconized format for EAN string") decimal = int(''.join(s for f, s in zip(cls.fmt, ean_string) if f == '#'))
return cls(decimal)
[docs] @classmethod def from_hilo(cls, hilo): """Create an EAN object from a pair of 32-bit integers, (eanHi, eanLo)""" # Python 2 does not have int.to_bytes. And also has longs. import sys if sys.version_info < (3, 0): hi, lo = hilo conc = hi + (lo << 32) digits = [] for i in xrange(16): v = conc & 0xf digits.append(int(v)) conc = conc >> 4 return cls(int_from_digits(digits)) high, low = hilo high = tuple(bcd_digits(high.to_bytes(4, byteorder='little'))) low = tuple(bcd_digits(low.to_bytes(4, byteorder='little'))) ean = int_from_digits(high + low)
return cls(ean) def __init__(self, decimal): """Create an EAN object from an integer with the natural number""" self.decimal = decimal if __debug__: if len(str(decimal)) != self.num_digits: raise IllegalEAN("Wrong length of EAN") def __str__(self): num_only = iter(str(self.decimal)) out = ''.join( next(num_only) if s == '#' else s for s in self.fmt) if __debug__: assert len(tuple(num_only)) == 0 # check that all digits where printed return out def __repr__(self): # would rather have self.__class__.__qualname__, but that does not # exist in python 2 return "<{cls}: {s}>".format( cls=self.__class__.__name__, s=str(self))
[docs] def product(self): """Return only the product code and check digit of the string representation"""
return str(self)[PRODUCT_EAN_LENGTH:]