add a method to check barcode code format
ensure that the code to print is compatible with the ESC/POS formats and also automatically check this format before printing (barcode() method).
This commit is contained in:
parent
19663ec574
commit
36195674c1
@ -225,6 +225,24 @@ BARCODE_TYPE_B = {
|
||||
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78),
|
||||
}
|
||||
|
||||
BARCODE_FORMATS = {
|
||||
'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"),
|
||||
'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
|
||||
'EAN13': ([(12, 13)], "^[0-9]{12,13}$"),
|
||||
'EAN8': ([(7, 8)], "^[0-9]{7,8}$"),
|
||||
'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"),
|
||||
'ITF': ([(2, 255)], "^([0-9]{2})+$"),
|
||||
'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"),
|
||||
'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7
|
||||
'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"),
|
||||
'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"),
|
||||
'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128
|
||||
'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"),
|
||||
'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional
|
||||
'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"),
|
||||
'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"),
|
||||
}
|
||||
|
||||
BARCODE_TYPES = {
|
||||
'A': BARCODE_TYPE_A,
|
||||
'B': BARCODE_TYPE_B,
|
||||
|
@ -19,13 +19,14 @@ import qrcode
|
||||
import textwrap
|
||||
import six
|
||||
import time
|
||||
from re import match as re_match
|
||||
|
||||
import barcode
|
||||
from barcode.writer import ImageWriter
|
||||
|
||||
from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q
|
||||
from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH
|
||||
from .constants import BARCODE_FONT_A, BARCODE_FONT_B
|
||||
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
|
||||
from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
|
||||
from .constants import TXT_SIZE, TXT_NORMAL
|
||||
from .constants import SET_FONT
|
||||
@ -275,17 +276,42 @@ class Escpos(object):
|
||||
else:
|
||||
self.magic.force_encoding(code)
|
||||
|
||||
@staticmethod
|
||||
def check_barcode(bc, code):
|
||||
"""
|
||||
This method checks if the barcode is in the proper format.
|
||||
The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum.
|
||||
The full set of requirement for each barcode type is available in the ESC/POS documentation.
|
||||
|
||||
As an example, using EAN13, the barcode `12345678901` will be correct, because it can be rendered by the
|
||||
printer. But it does not suit the EAN13 standard, because the checksum digit is missing. Adding a wrong
|
||||
checksum in the end will also be considered correct, but adding a letter won't (EAN13 is numeric only).
|
||||
|
||||
.. todo:: Add a method to compute the checksum for the different standards
|
||||
|
||||
.. todo:: For fixed-length standards with mandatory checksum (EAN, UPC),
|
||||
compute and add the checksum automatically if missing.
|
||||
|
||||
:param bc: barcode format, see :py:func`~escpos.Escpos.barcode`
|
||||
:param code: alphanumeric data to be printed as bar code, see :py:func`~escpos.Escpos.barcode`
|
||||
:return: bool
|
||||
"""
|
||||
if bc not in BARCODE_FORMATS:
|
||||
return False
|
||||
|
||||
bounds, regex = BARCODE_FORMATS[bc]
|
||||
return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code)
|
||||
|
||||
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
|
||||
align_ct=True, function_type=None):
|
||||
align_ct=True, function_type=None, check=True):
|
||||
""" Print Barcode
|
||||
|
||||
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
|
||||
be supported by the unit. Currently you have to check manually whether your barcode text is correct. Uncorrect
|
||||
barcodes may lead to unexpected printer behaviour. There are two forms of the barcode function. Type A is
|
||||
default but has fewer barcodes, while type B has some more to choose from.
|
||||
|
||||
.. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each
|
||||
barcode-type. Research whether the check digits can be computed autmatically.
|
||||
be supported by the unit. By default, this method will check whether your barcode text is correct, that is
|
||||
the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but
|
||||
note that uncorrect barcodes may lead to unexpected printer behaviour.
|
||||
There are two forms of the barcode function. Type A is default but has fewer barcodes,
|
||||
while type B has some more to choose from.
|
||||
|
||||
Use the parameters `height` and `width` for adjusting of the barcode size. Please take notice that the barcode
|
||||
will not be printed if it is outside of the printable area. (Which should be impossible with this method, so
|
||||
@ -353,6 +379,10 @@ class Escpos(object):
|
||||
function based on the current profile.
|
||||
*default*: A
|
||||
|
||||
:param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc
|
||||
requirements as defigned in the esc/pos documentation. See py:func:`~escpos.Escpos.check_barcode`
|
||||
for more information. *default*: True.
|
||||
|
||||
:raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeTypeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||
@ -375,12 +405,19 @@ class Escpos(object):
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
if bc.upper() not in bc_types.keys():
|
||||
raise BarcodeTypeError((
|
||||
"Barcode type '{bc}' not valid for barcode function type "
|
||||
"Barcode '{bc}' not valid for barcode function type "
|
||||
"{function_type}").format(
|
||||
bc=bc,
|
||||
function_type=function_type,
|
||||
))
|
||||
|
||||
if check and not self.check_barcode(bc, code):
|
||||
raise BarcodeCodeError((
|
||||
"Barcode '{code}' not in a valid format for type '{bc}'").format(
|
||||
code=code,
|
||||
bc=bc,
|
||||
))
|
||||
|
||||
# Align Bar Code()
|
||||
if align_ct:
|
||||
self._raw(TXT_STYLE['align']['center'])
|
||||
|
@ -77,9 +77,10 @@ class BarcodeSizeError(Error):
|
||||
|
||||
|
||||
class BarcodeCodeError(Error):
|
||||
""" No Barcode code was supplied.
|
||||
""" No Barcode code was supplied, or it is incorrect.
|
||||
|
||||
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`.
|
||||
No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter
|
||||
was True and the check failed.
|
||||
The returncode for this exception is `30`.
|
||||
"""
|
||||
def __init__(self, msg=""):
|
||||
|
272
test/test_function_check_barcode.py
Normal file
272
test/test_function_check_barcode.py
Normal file
@ -0,0 +1,272 @@
|
||||
#!/usr/bin/python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import escpos.printer as printer
|
||||
import pytest
|
||||
|
||||
|
||||
def test_barcode_upca():
|
||||
bc = 'UPC-A'
|
||||
|
||||
valid_codes = [
|
||||
"01234567890",
|
||||
"012345678905"
|
||||
]
|
||||
|
||||
invalid_codes = [
|
||||
"01234567890123", # too long
|
||||
"0123456789", # too short
|
||||
"72527273-711", # invalid '-'
|
||||
"A12345678901", # invalid 'A'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_upce():
|
||||
bc = 'UPC-E'
|
||||
|
||||
valid_codes = [
|
||||
"01234567",
|
||||
"0123456",
|
||||
"012345678905"
|
||||
]
|
||||
invalid_codes = [
|
||||
"01234567890123", # too long
|
||||
"012345", # too short
|
||||
"72527-2", # invalid '-'
|
||||
"A123456", # invalid 'A'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_ean13():
|
||||
bc = 'EAN13'
|
||||
|
||||
valid_codes = [
|
||||
"0123456789012",
|
||||
"012345678901"
|
||||
]
|
||||
invalid_codes = [
|
||||
"0123456789", # too short
|
||||
"A123456789012" # invalid 'A'
|
||||
"012345678901234", # too long
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_ean8():
|
||||
bc = 'EAN8'
|
||||
|
||||
valid_codes = [
|
||||
"01234567",
|
||||
"0123456"
|
||||
]
|
||||
invalid_codes = [
|
||||
"012345", # too short
|
||||
"A123456789012" # invalid 'A'
|
||||
"012345678901234", # too long
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_code39():
|
||||
bc = 'CODE39'
|
||||
|
||||
valid_codes = [
|
||||
"ABC-1234",
|
||||
"ABC-1234-$$-+A",
|
||||
"*WIKIPEDIA*" # the '*' symbol is not part of the actual code, but it is handled properly by ESCPOS
|
||||
]
|
||||
invalid_codes = [
|
||||
"ALKJ_34", # invalid '_'
|
||||
"A" * 256, # too long
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_itf():
|
||||
bc = 'ITF'
|
||||
|
||||
valid_codes = [
|
||||
"010203040506070809",
|
||||
"11221133113344556677889900",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # odd length
|
||||
"0" * 256, # too long
|
||||
"AB01", # invalid 'A'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_codabar():
|
||||
bc = 'CODABAR'
|
||||
|
||||
valid_codes = [
|
||||
"A2030405060B",
|
||||
"C11221133113344556677889900D",
|
||||
"D0D",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # no start/stop
|
||||
"0" * 256, # too long
|
||||
"AB-01F", # invalid 'B'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_nw7():
|
||||
bc = 'NW7' # same as CODABAR
|
||||
|
||||
valid_codes = [
|
||||
"A2030405060B",
|
||||
"C11221133113344556677889900D",
|
||||
"D0D",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # no start/stop
|
||||
"0" * 256, # too long
|
||||
"AB-01F", # invalid 'B'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_code93():
|
||||
bc = 'CODE93'
|
||||
|
||||
valid_codes = [
|
||||
"A2030405060B",
|
||||
"+:$&23-7@$",
|
||||
"D0D",
|
||||
]
|
||||
invalid_codes = [
|
||||
"é010203040", # invalid 'é'
|
||||
"0" * 256, # too long
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_code128():
|
||||
bc = 'CODE128'
|
||||
|
||||
valid_codes = [
|
||||
"{A2030405060B",
|
||||
"{C+:$&23-7@$",
|
||||
"{B0D",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # missing leading {
|
||||
"0" * 256, # too long
|
||||
"{D2354AA", # second char not between A-C
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_gs1_128():
|
||||
bc = 'GS1-128' # same as code 128
|
||||
|
||||
valid_codes = [
|
||||
"{A2030405060B",
|
||||
"{C+:$&23-7@$",
|
||||
"{B0D",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # missing leading {
|
||||
"0" * 256, # too long
|
||||
"{D2354AA", # second char not between A-C
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_gs1_omni():
|
||||
bc = 'GS1 DATABAR OMNIDIRECTIONAL'
|
||||
|
||||
valid_codes = [
|
||||
"0123456789123",
|
||||
]
|
||||
invalid_codes = [
|
||||
"01234567891234", # too long
|
||||
"012345678912", # too short
|
||||
"012345678A1234", # invalid 'A'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_gs1_trunc():
|
||||
bc = 'GS1 DATABAR TRUNCATED' # same as OMNIDIRECTIONAL
|
||||
|
||||
valid_codes = [
|
||||
"0123456789123",
|
||||
]
|
||||
invalid_codes = [
|
||||
"01234567891234", # too long
|
||||
"012345678912", # too short
|
||||
"012345678A1234", # invalid 'A'
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_gs1_limited():
|
||||
bc = 'GS1 DATABAR LIMITED'
|
||||
|
||||
valid_codes = [
|
||||
"0123456789123",
|
||||
"0123456789123",
|
||||
]
|
||||
invalid_codes = [
|
||||
"01234567891234", # too long
|
||||
"012345678912", # too short
|
||||
"012345678A1234", # invalid 'A'
|
||||
"02345678912341", # invalid start (should be 01)
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
||||
|
||||
|
||||
def test_barcode_gs1_expanded():
|
||||
bc = 'GS1 DATABAR EXPANDED'
|
||||
|
||||
valid_codes = [
|
||||
"(9A{A20304+-%&06a0B",
|
||||
"(1 {C+:$a23-7%",
|
||||
"(00000001234567678",
|
||||
]
|
||||
invalid_codes = [
|
||||
"010203040", # missing leading {
|
||||
"0" * 256, # too long
|
||||
"0{D2354AA", # second char not between A-za-z0-9
|
||||
"IT will fail", # first char not between 0-9
|
||||
]
|
||||
|
||||
assert (all([printer.Escpos.check_barcode(bc, code) for code in valid_codes]))
|
||||
assert (not any([printer.Escpos.check_barcode(bc, code) for code in invalid_codes]))
|
Loading…
x
Reference in New Issue
Block a user