python-stdnum branch master updated. 1.13-42-g180788a
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
python-stdnum branch master updated. 1.13-42-g180788a
- From: Commits of the python-stdnum project <python-stdnum-commits [at] lists.arthurdejong.org>
- To: python-stdnum-commits [at] lists.arthurdejong.org
- Reply-to: python-stdnum-users [at] lists.arthurdejong.org, python-stdnum-commits [at] lists.arthurdejong.org
- Subject: python-stdnum branch master updated. 1.13-42-g180788a
- Date: Sat, 8 Aug 2020 16:34:41 +0200 (CEST)
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-stdnum".
The branch, master has been updated
via 180788af207f394e38b458ad14fb68f4853f4a9a (commit)
from c2284f322679e9527794aaa81e8fbb57792c5a21 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
https://arthurdejong.org/git/python-stdnum/commit/?id=180788af207f394e38b458ad14fb68f4853f4a9a
commit 180788af207f394e38b458ad14fb68f4853f4a9a
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Aug 8 15:44:08 2020 +0200
Add GS1-128 format
This adds validation, parsing and encoding functions for GS1-128. It is
based on the lists of formats as published by the GS1 organisation.
Based on the implementation provided by Sergi Almacellas Abellana
<sergi@koolpi.com>.
Closes https://github.com/arthurdejong/python-stdnum/pull/144
diff --git a/stdnum/gs1_128.py b/stdnum/gs1_128.py
new file mode 100644
index 0000000..aee11d2
--- /dev/null
+++ b/stdnum/gs1_128.py
@@ -0,0 +1,269 @@
+# gs1_128.py - functions for handling GS1-128 codes
+#
+# Copyright (C) 2019 Sergi Almacellas Abellana
+# Copyright (C) 2020 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""GS1-128 (Standard to encode product information in Code 128 barcodes).
+
+The GS1-128 (also called EAN-128, UCC/EAN-128 or UCC-128) is an international
+standard for embedding data such as best before dates, weights, etc. with
+Application Identifiers (AI).
+
+The GS1-128 standard is used as a product identification code on bar codes.
+It embeds data with Application Identifiers (AI) that defines the kind of
+data, the type and length. The standard is also known as UCC/EAN-128, UCC-128
+and EAN-128.
+
+GS1-128 is a subset of Code 128 symbology.
+
+More information:
+
+* https://en.wikipedia.org/wiki/GS1-128
+* https://www.gs1.org/standards/barcodes/application-identifiers
+* https://www.gs1.org/docs/barcodes/GS1_General_Specifications.pdf
+
+>>> compact('(01)38425876095074(17)181119(37)1 ')
+'013842587609507417181119371'
+>>> encode({'01': '38425876095074'})
+'0138425876095074'
+>>> info('0138425876095074')
+{'01': '38425876095074'}
+>>> validate('(17)181119(01)38425876095074(37)1')
+'013842587609507417181119371'
+"""
+
+import datetime
+import decimal
+import re
+
+from stdnum import numdb
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+# our open copy of the application identifier database
+_gs1_aidb = numdb.get('gs1_ai')
+
+
+# Extra validation modules based on the application identifier
+_ai_validators = {
+ '01': 'stdnum.ean',
+ '02': 'stdnum.ean',
+ '8007': 'stdnum.iban',
+}
+
+
+def compact(number):
+ """Convert the GS1-128 to the minimal representation.
+
+ This strips the number of any valid separators and removes surrounding
+ whitespace. For a more consistent compact representation use
+ :func:`validate()`.
+ """
+ return clean(number, '()').strip()
+
+
+def _encode_value(fmt, _type, value):
+ """Encode the specified value given the format and type."""
+ if _type == 'decimal':
+ if isinstance(value, (list, tuple)) and fmt.startswith('N3+'):
+ number = _encode_value(fmt[3:], _type, value[1])
+ return number[0] + value[0].rjust(3, '0') + number[1:]
+ value = str(value)
+ if fmt.startswith('N..'):
+ length = int(fmt[3:])
+ value = value[:length + 1]
+ number, digits = (value.split('.') + [''])[:2]
+ digits = digits[:9]
+ return str(len(digits)) + number + digits
+ else:
+ length = int(fmt[1:])
+ value = value[:length + 1]
+ number, digits = (value.split('.') + [''])[:2]
+ digits = digits[:9]
+ return str(len(digits)) + (number + digits).rjust(length, '0')
+ elif _type == 'date':
+ if isinstance(value, (list, tuple)) and fmt == 'N6..12':
+ return '%s%s' % (
+ _encode_value('N6', _type, value[0]),
+ _encode_value('N6', _type, value[1]))
+ elif isinstance(value, datetime.date):
+ if fmt == 'N10':
+ return value.strftime('%y%m%d%H%M')
+ elif fmt == 'N8+N..4':
+ value = datetime.datetime.strftime(value, '%y%m%d%H%M%S')
+ if value.endswith('00'):
+ value = value[:-2]
+ if value.endswith('00'):
+ value = value[:-2]
+ return value
+ return value.strftime('%y%m%d')
+ return str(value)
+
+
+def _max_length(fmt, _type):
+ """Determine the maximum length based on the format ad type."""
+ length = sum(int(re.match(r'^[NXY][0-9]*?[.]*([0-9]+)$', x).group(1)) for
x in fmt.split('+'))
+ if _type == 'decimal':
+ length += 1
+ return length
+
+
+def _pad_value(fmt, _type, value):
+ """Pad the value to the maximum length for the format."""
+ if _type in ('decimal', 'int'):
+ return value.rjust(_max_length(fmt, _type), '0')
+ return value.ljust(_max_length(fmt, _type))
+
+
+def _decode_value(fmt, _type, value):
+ """Decode the specified value given the fmt and type."""
+ if _type == 'decimal':
+ if fmt.startswith('N3+'):
+ return (value[1:4], _decode_value(fmt[3:], _type, value[0] +
value[4:]))
+ digits = int(value[0])
+ value = value[1:]
+ if digits:
+ value = value[:-digits] + '.' + value[-digits:]
+ return decimal.Decimal(value)
+ elif _type == 'date':
+ if fmt == 'N8+N..4':
+ return datetime.datetime.strptime(value,
'%y%m%d%H%M%S'[:len(value)])
+ elif len(value) == 10:
+ return datetime.datetime.strptime(value, '%y%m%d%H%M')
+ elif len(value) == 12:
+ return (_decode_value(fmt, _type, value[:6]), _decode_value(fmt,
_type, value[6:]))
+ return datetime.datetime.strptime(value, '%y%m%d').date()
+ elif _type == 'int':
+ return int(value)
+ return value.strip()
+
+
+def info(number, separator=''):
+ """Return a dictionary containing the information from the GS1-128 code.
+
+ The returned dictionary maps application identifiers to values with the
+ appropriate type (`str`, `int`, `Decimal`, `datetime.date` or
+ `datetime.datetime`).
+
+ If a `separator` is provided it will be used as FNC1 to determine the end
+ of variable-sized values.
+ """
+ number = compact(number)
+ data = {}
+ identifier = ''
+ # skip separator
+ if separator and number.startswith(separator):
+ number = number[len(separator):]
+ while number:
+ # extract the application identifier
+ ai, info = _gs1_aidb.info(number)[0]
+ if not info or not number.startswith(ai):
+ raise InvalidComponent()
+ number = number[len(ai):]
+ # figure out the value part
+ value = number[:_max_length(info['format'], info['type'])]
+ if separator and info.get('fnc1', False):
+ idx = number.find(separator)
+ if idx > 0:
+ value = number[:idx]
+ number = number[len(value):]
+ # validate the value if we have a custom module for it
+ if ai in _ai_validators:
+ mod = __import__(_ai_validators[ai], globals(), locals(),
['validate'])
+ mod.validate(value)
+ # convert the number
+ data[ai] = _decode_value(info['format'], info['type'], value)
+ # skip separator
+ if separator and number.startswith(separator):
+ number = number[len(separator):]
+ return data
+
+
+def encode(data, separator='', parentheses=False):
+ """Generate a GS1-128 for the application identifiers supplied.
+
+ The provided dictionary is expected to map application identifiers to
+ values. The supported value types and formats depend on the application
+ identifier.
+
+ If a `separator` is provided it will be used as FNC1 representation,
+ otherwise variable-sized values will be expanded to their maximum size
+ with appropriate padding.
+
+ If `parentheses` is set the application identifiers will be surrounded
+ by parentheses for readability.
+ """
+ ai_fmt = '(%s)' if parentheses else '%s'
+ # we keep items sorted and keep fixed-sized values separate tot output
+ # them first
+ fixed_values = []
+ variable_values = []
+ for inputai, value in sorted(data.items()):
+ ai, info = _gs1_aidb.info(inputai)[0]
+ if not info:
+ raise InvalidComponent()
+ # validate the value if we have a custom module for it
+ if ai in _ai_validators:
+ mod = __import__(_ai_validators[ai], globals(), locals(),
['validate'])
+ mod.validate(value)
+ value = _encode_value(info['format'], info['type'], value)
+ # store variable-sized values separate from fixed-size values
+ if info.get('fnc1', False):
+ variable_values.append((ai_fmt % ai, info['format'], info['type'],
value))
+ else:
+ fixed_values.append(ai_fmt % ai + value)
+ # we need the separator for all but the last variable-sized value
+ # (or pad values if we don't have a separator)
+ return ''.join(
+ fixed_values + [
+ ai + (value if separator else _pad_value(fmt, _type, value)) +
separator
+ for ai, fmt, _type, value in variable_values[:-1]
+ ] + [
+ ai + value
+ for ai, fmt, _type, value in variable_values[-1:]
+ ])
+
+
+def validate(number, separator=''):
+ """Check if the number provided is a valid GS1-128.
+
+ This checks formatting of the number and values and returns a stable
+ representation.
+
+ If a separator is provided it will be used as FNC1 for both parsing the
+ provided number and for encoding the returned number.
+ """
+ try:
+ return encode(info(number, separator), separator)
+ except ValidationError:
+ raise
+ except Exception:
+ # We wrap all other exceptions to ensure that we only return
+ # exceptions that are a subclass of ValidationError
+ # (the info() and encode() functions expect some semblance of valid
+ # input)
+ raise InvalidFormat()
+
+
+def is_valid(number, separator=''):
+ """Check if the number provided is a valid GS1-128."""
+ try:
+ return bool(validate(number))
+ except ValidationError:
+ return False
diff --git a/stdnum/gs1_ai.dat b/stdnum/gs1_ai.dat
new file mode 100644
index 0000000..0b1ae6c
--- /dev/null
+++ b/stdnum/gs1_ai.dat
@@ -0,0 +1,170 @@
+# generated from https://www.gs1.org/standards/barcodes/application-identifiers
+# on 2020-07-12 19:36:00.576283
+00 format="N18" type="str" name="SSCC" description="Serial Shipping Container
Code (SSCC)"
+01 format="N14" type="str" name="GTIN" description="Global Trade Item Number
(GTIN)"
+02 format="N14" type="str" name="CONTENT" description="GTIN of contained trade
items"
+10 format="X..20" type="str" fnc1="1" name="BATCH/LOT" description="Batch or
lot number"
+11 format="N6" type="date" name="PROD DATE" description="Production date
(YYMMDD)"
+12 format="N6" type="date" name="DUE DATE" description="Due date (YYMMDD)"
+13 format="N6" type="date" name="PACK DATE" description="Packaging date
(YYMMDD)"
+15 format="N6" type="date" name="BEST BEFORE or BEST BY" description="Best
before date (YYMMDD)"
+16 format="N6" type="date" name="SELL BY" description="Sell by date (YYMMDD)"
+17 format="N6" type="date" name="USE BY OR EXPIRY" description="Expiration
date (YYMMDD)"
+20 format="N2" type="str" name="VARIANT" description="Internal product variant"
+21 format="X..20" type="str" fnc1="1" name="SERIAL" description="Serial number"
+22 format="X..20" type="str" fnc1="1" name="CPV" description="Consumer product
variant"
+235 format="X..28" type="str" fnc1="1" name="TPX" description="Third Party
Controlled, Serialised Extension of GTIN (TPX)"
+240 format="X..30" type="str" fnc1="1" name="ADDITIONAL ID"
description="Additional product identification assigned by the manufacturer"
+241 format="X..30" type="str" fnc1="1" name="CUST. PART NO."
description="Customer part number"
+242 format="N..6" type="str" fnc1="1" name="MTO VARIANT"
description="Made-to-Order variation number"
+243 format="X..20" type="str" fnc1="1" name="PCN" description="Packaging
component number"
+250 format="X..30" type="str" fnc1="1" name="SECONDARY SERIAL"
description="Secondary serial number"
+251 format="X..30" type="str" fnc1="1" name="REF. TO SOURCE"
description="Reference to source entity"
+253 format="N13+X..17" type="str" fnc1="1" name="GDTI" description="Global
Document Type Identifier (GDTI)"
+254 format="X..20" type="str" fnc1="1" name="GLN EXTENSION COMPONENT"
description="GLN extension component"
+255 format="N13+N..12" type="str" fnc1="1" name="GCN" description="Global
Coupon Number (GCN)"
+30 format="N..8" type="int" fnc1="1" name="VAR. COUNT" description="Variable
count of items (variable measure trade item)"
+310 format="N6" type="decimal" name="NET WEIGHT (kg)" description="Net weight,
kilograms (variable measure trade item)"
+311 format="N6" type="decimal" name="LENGTH (m)" description="Length or first
dimension, metres (variable measure trade item)"
+312 format="N6" type="decimal" name="WIDTH (m)" description="Width, diameter,
or second dimension, metres (variable measure trade item)"
+313 format="N6" type="decimal" name="HEIGHT (m)" description="Depth,
thickness, height, or third dimension, metres (variable measure trade item)"
+314 format="N6" type="decimal" name="AREA (m<sup>2</sup>)" description="Area,
square metres (variable measure trade item)"
+315 format="N6" type="decimal" name="NET VOLUME (l)" description="Net volume,
litres (variable measure trade item)"
+316 format="N6" type="decimal" name="NET VOLUME (m<sup>3</sup>)"
description="Net volume, cubic metres (variable measure trade item)"
+320 format="N6" type="decimal" name="NET WEIGHT (lb)" description="Net weight,
pounds (variable measure trade item)"
+321 format="N6" type="decimal" name="LENGTH (in)" description="Length or first
dimension, inches (variable measure trade item)"
+322 format="N6" type="decimal" name="LENGTH (ft)" description="Length or first
dimension, feet (variable measure trade item)"
+323 format="N6" type="decimal" name="LENGTH (yd)" description="Length or first
dimension, yards (variable measure trade item)"
+324 format="N6" type="decimal" name="WIDTH (in)" description="Width, diameter,
or second dimension, inches (variable measure trade item)"
+325 format="N6" type="decimal" name="WIDTH (ft)" description="Width, diameter,
or second dimension, feet (variable measure trade item)"
+326 format="N6" type="decimal" name="WIDTH (yd)" description="Width, diameter,
or second dimension, yards (variable measure trade item)"
+327 format="N6" type="decimal" name="HEIGHT (in)" description="Depth,
thickness, height, or third dimension, inches (variable measure trade item)"
+328 format="N6" type="decimal" name="HEIGHT (ft)" description="Depth,
thickness, height, or third dimension, feet (variable measure trade item)"
+329 format="N6" type="decimal" name="HEIGHT (yd)" description="Depth,
thickness, height, or third dimension, yards (variable measure trade item)"
+330 format="N6" type="decimal" name="GROSS WEIGHT (kg)" description="Logistic
weight, kilograms"
+331 format="N6" type="decimal" name="LENGTH (m), log" description="Length or
first dimension, metres"
+332 format="N6" type="decimal" name="WIDTH (m), log" description="Width,
diameter, or second dimension, metres"
+333 format="N6" type="decimal" name="HEIGHT (m), log" description="Depth,
thickness, height, or third dimension, metres"
+334 format="N6" type="decimal" name="AREA (m<sup>2</sup>), log"
description="Area, square metres"
+335 format="N6" type="decimal" name="VOLUME (l), log" description="Logistic
volume, litres"
+336 format="N6" type="decimal" name="VOLUME (m<sup>3</sup>), log"
description="Logistic volume, cubic metres"
+337 format="N6" type="decimal" name="KG PER m<sup>2</sup>"
description="Kilograms per square metre"
+340 format="N6" type="decimal" name="GROSS WEIGHT (lb)" description="Logistic
weight, pounds"
+341 format="N6" type="decimal" name="LENGTH (in), log" description="Length or
first dimension, inches"
+342 format="N6" type="decimal" name="LENGTH (ft), log" description="Length or
first dimension, feet"
+343 format="N6" type="decimal" name="LENGTH (yd), log" description="Length or
first dimension, yards"
+344 format="N6" type="decimal" name="WIDTH (in), log" description="Width,
diameter, or second dimension, inches"
+345 format="N6" type="decimal" name="WIDTH (ft), log" description="Width,
diameter, or second dimension, feet"
+346 format="N6" type="decimal" name="WIDTH (yd), log" description="Width,
diameter, or second dimension, yard"
+347 format="N6" type="decimal" name="HEIGHT (in), log" description="Depth,
thickness, height, or third dimension, inches"
+348 format="N6" type="decimal" name="HEIGHT (ft), log" description="Depth,
thickness, height, or third dimension, feet"
+349 format="N6" type="decimal" name="HEIGHT (yd), log" description="Depth,
thickness, height, or third dimension, yards"
+350 format="N6" type="decimal" name="AREA (in<sup>2</sup>)" description="Area,
square inches (variable measure trade item)"
+351 format="N6" type="decimal" name="AREA (ft<sup>2</sup>)" description="Area,
square feet (variable measure trade item)"
+352 format="N6" type="decimal" name="AREA (yd<sup>2</sup>)" description="Area,
square yards (variable measure trade item)"
+353 format="N6" type="decimal" name="AREA (in<sup>2</sup>), log"
description="Area, square inches"
+354 format="N6" type="decimal" name="AREA (ft<sup>2</sup>), log"
description="Area, square feet"
+355 format="N6" type="decimal" name="AREA (yd<sup>2</sup>), log"
description="Area, square yards"
+356 format="N6" type="decimal" name="NET WEIGHT (t oz)" description="Net
weight, troy ounces (variable measure trade item)"
+357 format="N6" type="decimal" name="NET VOLUME (oz)" description="Net weight
(or volume), ounces (variable measure trade item)"
+360 format="N6" type="decimal" name="NET VOLUME (qt)" description="Net volume,
quarts (variable measure trade item)"
+361 format="N6" type="decimal" name="NET VOLUME (gal.)" description="Net
volume, gallons U.S. (variable measure trade item)"
+362 format="N6" type="decimal" name="VOLUME (qt), log" description="Logistic
volume, quarts"
+363 format="N6" type="decimal" name="VOLUME (gal.), log" description="Logistic
volume, gallons U.S."
+364 format="N6" type="decimal" name="VOLUME (in<sup>3</sup>)" description="Net
volume, cubic inches (variable measure trade item)"
+365 format="N6" type="decimal" name="VOLUME (ft<sup>3</sup>)" description="Net
volume, cubic feet (variable measure trade item)"
+366 format="N6" type="decimal" name="VOLUME (yd<sup>3</sup>)" description="Net
volume, cubic yards (variable measure trade item)"
+367 format="N6" type="decimal" name="VOLUME (in<sup>3</sup>), log"
description="Logistic volume, cubic inches"
+368 format="N6" type="decimal" name="VOLUME (ft<sup>3</sup>), log"
description="Logistic volume, cubic feet"
+369 format="N6" type="decimal" name="VOLUME (yd<sup>3</sup>), log"
description="Logistic volume, cubic yards"
+37 format="N..8" type="int" fnc1="1" name="COUNT" description="Count of trade
items or trade item pieces contained in a logistic unit"
+390 format="N..15" type="decimal" fnc1="1" name="AMOUNT"
description="Applicable amount payable or Coupon value, local currency"
+391 format="N3+N..15" type="decimal" fnc1="1" name="AMOUNT"
description="Applicable amount payable with ISO currency code"
+392 format="N..15" type="decimal" fnc1="1" name="PRICE"
description="Applicable amount payable, single monetary area (variable measure
trade item)"
+393 format="N3+N..15" type="decimal" fnc1="1" name="PRICE"
description="Applicable amount payable with ISO currency code (variable measure
trade item)"
+394 format="N4" type="decimal" fnc1="1" name="PRCNT OFF"
description="Percentage discount of a coupon"
+400 format="X..30" type="str" fnc1="1" name="ORDER NUMBER"
description="Customers purchase order number"
+401 format="X..30" type="str" fnc1="1" name="GINC" description="Global
Identification Number for Consignment (GINC)"
+402 format="N17" type="str" fnc1="1" name="GSIN" description="Global Shipment
Identification Number (GSIN)"
+403 format="X..30" type="str" fnc1="1" name="ROUTE" description="Routing code"
+410 format="N13" type="str" name="SHIP TO LOC" description="Ship to - Deliver
to Global Location Number"
+411 format="N13" type="str" name="BILL TO" description="Bill to - Invoice to
Global Location Number"
+412 format="N13" type="str" name="PURCHASE FROM" description="Purchased from
Global Location Number"
+413 format="N13" type="str" name="SHIP FOR LOC" description="Ship for -
Deliver for - Forward to Global Location Number"
+414 format="N13" type="str" name="LOC No" description="Identification of a
physical location - Global Location Number"
+415 format="N13" type="str" name="PAY TO" description="Global Location Number
of the invoicing party"
+416 format="N13" type="str" name="PROD/SERV LOC" description="GLN of the
production or service location"
+417 format="N13" type="str" name="PARTY" description="Party GLN"
+420 format="X..20" type="str" fnc1="1" name="SHIP TO POST" description="Ship
to - Deliver to postal code within a single postal authority"
+421 format="N3+X..9" type="str" fnc1="1" name="SHIP TO POST" description="Ship
to - Deliver to postal code with ISO country code"
+422 format="N3" type="int" fnc1="1" name="ORIGIN" description="Country of
origin of a trade item"
+423 format="N3+N..12" type="str" fnc1="1" name="COUNTRY - INITIAL PROCESS."
description="Country of initial processing"
+424 format="N3" type="int" fnc1="1" name="COUNTRY - PROCESS."
description="Country of processing"
+425 format="N3+N..12" type="str" fnc1="1" name="COUNTRY - DISASSEMBLY"
description="Country of disassembly"
+426 format="N3" type="int" fnc1="1" name="COUNTRY - FULL PROCESS"
description="Country covering full process chain"
+427 format="X..3" type="str" fnc1="1" name="ORIGIN SUBDIVISION"
description="Country subdivision Of origin"
+7001 format="N13" type="str" fnc1="1" name="NSN" description="NATO Stock
Number (NSN)"
+7002 format="X..30" type="str" fnc1="1" name="MEAT CUT" description="UN/ECE
meat carcasses and cuts classification"
+7003 format="N10" type="date" fnc1="1" name="EXPIRY TIME"
description="Expiration date and time"
+7004 format="N..4" type="str" fnc1="1" name="ACTIVE POTENCY"
description="Active potency"
+7005 format="X..12" type="str" fnc1="1" name="CATCH AREA" description="Catch
area"
+7006 format="N6" type="date" fnc1="1" name="FIRST FREEZE DATE"
description="First freeze date"
+7007 format="N6..12" type="date" fnc1="1" name="HARVEST DATE"
description="Harvest date"
+7008 format="X..3" type="str" fnc1="1" name="AQUATIC SPECIES"
description="Species for fishery purposes"
+7009 format="X..10" type="str" fnc1="1" name="FISHING GEAR TYPE"
description="Fishing gear type"
+7010 format="X..2" type="str" fnc1="1" name="PROD METHOD"
description="Production method"
+7020 format="X..20" type="str" fnc1="1" name="REFURB LOT"
description="Refurbishment lot ID"
+7021 format="X..20" type="str" fnc1="1" name="FUNC STAT"
description="Functional status"
+7022 format="X..20" type="str" fnc1="1" name="REV STAT" description="Revision
status"
+7023 format="X..30" type="str" fnc1="1" name="GIAI - ASSEMBLY"
description="Global Individual Asset Identifier (GIAI) of an assembly"
+7030 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 0"
description="Number of processor with ISO Country Code"
+7031 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 1"
description="Number of processor with ISO Country Code"
+7032 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 2"
description="Number of processor with ISO Country Code"
+7033 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 3"
description="Number of processor with ISO Country Code"
+7034 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 4"
description="Number of processor with ISO Country Code"
+7035 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 5"
description="Number of processor with ISO Country Code"
+7036 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 6"
description="Number of processor with ISO Country Code"
+7037 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 7"
description="Number of processor with ISO Country Code"
+7038 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 8"
description="Number of processor with ISO Country Code"
+7039 format="N3+X..27" type="str" fnc1="1" name="PROCESSOR # 9"
description="Number of processor with ISO Country Code"
+7040 format="N1+X3" type="str" fnc1="1" name="UIC+EXT" description="GS1 UIC
with Extension 1 and Importer index"
+710 format="X..20" type="str" fnc1="1" name="NHRN PZN" description="National
Healthcare Reimbursement Number (NHRN) - Germany PZN"
+711 format="X..20" type="str" fnc1="1" name="NHRN CIP" description="National
Healthcare Reimbursement Number (NHRN) - France CIP"
+712 format="X..20" type="str" fnc1="1" name="NHRN CN" description="National
Healthcare Reimbursement Number (NHRN) - Spain CN"
+713 format="X..20" type="str" fnc1="1" name="NHRN DRN" description="National
Healthcare Reimbursement Number (NHRN) - Brasil DRN"
+714 format="X..20" type="str" fnc1="1" name="NHRN AIM" description="National
Healthcare Reimbursement Number (NHRN) - Portugal AIM"
+7230 format="X2+X..28" type="str" fnc1="1" name="CERT #1"
description="Certification reference"
+7231 format="X2+X..28" type="str" fnc1="1" name="CERT #2"
description="Certification reference"
+7232 format="X2+X..28" type="str" fnc1="1" name="CERT #3"
description="Certification reference"
+7233 format="X2+X..28" type="str" fnc1="1" name="CERT #4"
description="Certification reference"
+7234 format="X2+X..28" type="str" fnc1="1" name="CERT #5"
description="Certification reference"
+7235 format="X2+X..28" type="str" fnc1="1" name="CERT #6"
description="Certification reference"
+7236 format="X2+X..28" type="str" fnc1="1" name="CERT #7"
description="Certification reference"
+7237 format="X2+X..28" type="str" fnc1="1" name="CERT #8"
description="Certification reference"
+7238 format="X2+X..28" type="str" fnc1="1" name="CERT #9"
description="Certification reference"
+7239 format="X2+X..28" type="str" fnc1="1" name="CERT #10"
description="Certification reference"
+7240 format="X..20" type="str" fnc1="1" name="PROTOCOL" description="Protocol
ID"
+8001 format="N14" type="str" fnc1="1" name="DIMENSIONS" description="Roll
products (width, length, core diameter, direction, splices)"
+8002 format="X..20" type="str" fnc1="1" name="CMT No" description="Cellular
mobile telephone identifier"
+8003 format="N14+X..16" type="str" fnc1="1" name="GRAI" description="Global
Returnable Asset Identifier (GRAI)"
+8004 format="X..30" type="str" fnc1="1" name="GIAI" description="Global
Individual Asset Identifier (GIAI)"
+8005 format="N6" type="str" fnc1="1" name="PRICE PER UNIT" description="Price
per unit of measure"
+8006 format="N14+N2+N2" type="str" fnc1="1" name="ITIP"
description="Identification of an individual trade item piece"
+8007 format="X..34" type="str" fnc1="1" name="IBAN" description="International
Bank Account Number (IBAN)"
+8008 format="N8+N..4" type="date" fnc1="1" name="PROD TIME" description="Date
and time of production"
+8009 format="X..50" type="str" fnc1="1" name="OPTSEN" description="Optically
Readable Sensor Indicator"
+8010 format="Y..30" type="str" fnc1="1" name="CPID"
description="Component/Part Identifier (CPID)"
+8011 format="N..12" type="str" fnc1="1" name="CPID SERIAL"
description="Component/Part Identifier serial number (CPID SERIAL)"
+8012 format="X..20" type="str" fnc1="1" name="VERSION" description="Software
version"
+8013 format="X..30" type="str" fnc1="1" name="GMN (for medical devices, the
default, global data title is BUDI-DI)" description="Global Model Number (GMN)"
+8017 format="N18" type="str" fnc1="1" name="GSRN - PROVIDER"
description="Global Service Relation Number to identify the relationship
between an organisation offering services and the provider of services"
+8018 format="N18" type="str" fnc1="1" name="GSRN - RECIPIENT"
description="Global Service Relation Number to identify the relationship
between an organisation offering services and the recipient of services"
+8019 format="N..10" type="str" fnc1="1" name="SRIN" description="Service
Relation Instance Number (SRIN)"
+8020 format="X..25" type="str" fnc1="1" name="REF No" description="Payment
slip reference number"
+8026 format="N14+N2+N2" type="str" fnc1="1" name="ITIP CONTENT"
description="Identification of pieces of a trade item (ITIP) contained in a
logistic unit"
+8110 format="X..70" type="str" fnc1="1" name="" description="Coupon code
identification for use in North America"
+8111 format="N4" type="str" fnc1="1" name="POINTS" description="Loyalty points
of a coupon"
+8112 format="X..70" type="str" fnc1="1" name="" description="Paperless coupon
code identification for use in North America"
+8200 format="X..70" type="str" fnc1="1" name="PRODUCT URL"
description="Extended Packaging URL"
+90 format="X..30" type="str" fnc1="1" name="INTERNAL" description="Information
mutually agreed between trading partners"
+91-99 format="X..90" type="str" fnc1="1" name="INTERNAL" description="Company
internal information"
diff --git a/tests/test_gs1_128.doctest b/tests/test_gs1_128.doctest
new file mode 100644
index 0000000..8bfa9af
--- /dev/null
+++ b/tests/test_gs1_128.doctest
@@ -0,0 +1,148 @@
+test_gs1_128.doctest - more detailed doctests for the stdnum.gs1_128 module
+
+Copyright (C) 2019 Sergi Almacellas Abellaan
+Copyright (C) 2020 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.gs1_128 module. It
+tries to test more corner cases and detailed functionality that is not
+really useful as module documentation.
+
+>>> from decimal import Decimal
+>>> import datetime
+>>> import pprint
+>>> from stdnum import gs1_128
+
+
+>>> gs1_128.compact('(01)38425876095074(17)181119(37)1 ')
+'013842587609507417181119371'
+
+
+We can create a GS1-128 code based on data we provide. Various data types
+will be converted to the correct representation.
+
+>>> gs1_128.encode({'01': '38425876095074', '17': datetime.date(2018, 11, 19),
'37': 1}, parentheses=True)
+'(01)38425876095074(17)181119(37)1'
+>>> gs1_128.encode({'02': '98412345678908', '310': 17.23, '37': 32})
+'029841234567890831020017233732'
+>>> gs1_128.encode({'03': '1234'}) # unknown AI
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+
+If we have a separator we use it to separate variable-length values, otherwise
+we pad all variable-length values to the maximum length (except the last one).
+
+>>> gs1_128.encode({'01': '58425876097843', '10': '123456', '37': 18, '390':
42, '392': 12}, parentheses=True)
+'(01)58425876097843(10)123456
(37)00000018(390)0000000000000042(392)012'
+>>> gs1_128.encode({'01': '58425876097843', '10': '123456', '37': 18, '390':
42, '392': 12}, parentheses=True, separator='[FNC1]')
+'(01)58425876097843(10)123456[FNC1](37)18[FNC1](390)042[FNC1](392)012'
+
+Numeric values can be provided in several forms and precision is encoded
+properly.
+
+>>> gs1_128.encode({
+... '310': 17.23, # float
+... '311': 456, # int
+... '312': 1.0 / 3.0, # float with lots of digits
+... '313': '123.456', # str
+... '391': ('123', Decimal('123.456')), # currency number combo
+... }, parentheses=True)
+'(310)2001723(311)0000456(312)5033333(313)3123456(391)3123123456'
+
+We generate dates in various formats, depending on the AI.
+
+>>> gs1_128.encode({
+... '11': datetime.datetime(2018, 11, 19, 0, 0, 0),
+... '12': '181119', # if you provide a string value, it is expected to be
correct
+... '7003': datetime.datetime(2018, 11, 19, 12, 45, 13),
+... '7007': (datetime.date(2018, 11, 19), datetime.date(2018, 11, 21)),
+... }, parentheses=True)
+'(11)181119(12)181119(7003)1811191245(7007)181119181121'
+>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 45, 13)},
parentheses=True)
+'(8008)181119124513'
+>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 45)},
parentheses=True)
+'(8008)1811191245'
+>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 0)},
parentheses=True)
+'(8008)18111912'
+>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 0, 0)},
parentheses=True)
+'(8008)18111900'
+
+If we try to encode an invalid EAN we will get an error.
+
+>>> gs1_128.encode({'01': '38425876095079'}, parentheses=True)
+Traceback (most recent call last):
+ ...
+InvalidChecksum: ...
+
+
+We can decode (parse) the GS1-128 code to a dictionary with information about
+the structure of the number.
+
+pprint.pprint(gs1_128.info('(01)38425876095074(17)181119(37)1 '))
+{'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1}
+>>> pprint.pprint(gs1_128.info('013842587609507417181119371'))
+{'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1}
+>>> pprint.pprint(gs1_128.info('(02)98412345678908(310)3017230(37)32'))
+{'02': '98412345678908', '310': Decimal('17.230'), '37': 32}
+>>> pprint.pprint(gs1_128.info('(01)58425876097843(10)123456
(17)181119(37)18'))
+{'01': '58425876097843', '10': '123456', '17': datetime.date(2018, 11, 19),
'37': 18}
+>>>
pprint.pprint(gs1_128.info('|(01)58425876097843|(10)123456|(17)181119(37)18',
separator='|'))
+{'01': '58425876097843', '10': '123456', '17': datetime.date(2018, 11, 19),
'37': 18}
+>>> gs1_128.info('(03)38425876095074') # unknown AI
+Traceback (most recent call last):
+ ...
+InvalidComponent: ...
+
+We can decode decimal values from various formats.
+
+>>> pprint.pprint(gs1_128.info('(310)5033333'))
+{'310': Decimal('0.33333')}
+>>> pprint.pprint(gs1_128.info('(310)0033333'))
+{'310': Decimal('33333')}
+>>> pprint.pprint(gs1_128.info('(391)3123123456'))
+{'391': ('123', Decimal('123.456'))}
+
+We an decode date files from various formats.
+
+>>> pprint.pprint(gs1_128.info('(11)181119'))
+{'11': datetime.date(2018, 11, 19)}
+>>> pprint.pprint(gs1_128.info('(7003)1811191245'))
+{'7003': datetime.datetime(2018, 11, 19, 12, 45)}
+>>> pprint.pprint(gs1_128.info('(7007)181119'))
+{'7007': datetime.date(2018, 11, 19)}
+>>> pprint.pprint(gs1_128.info('(7007)181119181121'))
+{'7007': (datetime.date(2018, 11, 19), datetime.date(2018, 11, 21))}
+>>> pprint.pprint(gs1_128.info('(8008)18111912'))
+{'8008': datetime.datetime(2018, 11, 19, 12, 0)}
+
+
+While the compact() function can clean up the number somewhat the validate()
+function calls info() and then encode() to ensure an even more compact and
+consistent format.
+
+>>> gs1_128.compact('(01)58425876097843(10)123456 (37)00000018')
+'015842587609784310123456 3700000018'
+>>> gs1_128.validate('(01)58425876097843(10)123456 (37)00000018')
+'015842587609784310123456 3718'
+>>> gs1_128.validate('(01)58425876097843(10)123456 (37)00000018',
separator='|')
+'015842587609784310123456|3718'
+>>> gs1_128.validate('30aa')
+Traceback (most recent call last):
+ ...
+InvalidFormat: ...
diff --git a/update/gs1_ai.py b/update/gs1_ai.py
new file mode 100755
index 0000000..527a1c5
--- /dev/null
+++ b/update/gs1_ai.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+
+# update/gs1_ai.py - script to get GS1 application identifiers
+#
+# Copyright (C) 2019 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""This script downloads GS1 application identifiers from the GS1 web site."""
+
+import datetime
+import json
+import re
+
+import lxml.html
+import requests
+
+
+# the location of the GS1 application identifiers
+download_url = 'https://www.gs1.org/standards/barcodes/application-identifiers'
+
+
+def fetch_ais():
+ """Download application identifiers frm the GS1 website."""
+ response = requests.get(download_url)
+ document = lxml.html.document_fromstring(response.content)
+ element = document.findall('.//script[@type="application/ld+json"]')[1]
+ for entry in json.loads(element.text)['@graph']:
+ yield (
+ entry['skos:prefLabel'].strip(), # AI
+ entry['gs1meta:formatAIvalue'].strip()[3:], # format
+ entry['gs1meta:requiresFNC1'], # require FNC1
+ [x['@value'] for x in entry['schema:name'] if x['@language'] ==
'en'][0].strip(),
+ [x['@value'] for x in entry['schema:description'] if
x['@language'] == 'en'][0].strip())
+
+
+def group_ai_ranges():
+ """Combine downloaded application identifiers into ranges."""
+ first = None
+ prev = (None, ) * 5
+ for value in sorted(fetch_ais()):
+ if value[1:] != prev[1:]:
+ if first:
+ yield first, *prev
+ first = value[0]
+ prev = value
+ yield first, *prev
+
+
+if __name__ == '__main__':
+ print('# generated from %s' % download_url)
+ print('# on %s' % datetime.datetime.utcnow())
+ for ai1, ai2, format, require_fnc1, name, description in group_ai_ranges():
+ _type = 'str'
+ if re.match(r'^(N8\+)?N[0-9]*[.]*[0-9]+$', format) and 'date' in
description.lower():
+ _type = 'date'
+ elif re.match(r'^N[.]*[0-9]+$', format) and 'count' in
description.lower():
+ _type = 'int'
+ ai = ai1
+ if ai1 != ai2:
+ if len(ai1) == 4:
+ ai = ai1[:3]
+ _type = 'decimal'
+ else:
+ ai = '%s-%s' % (ai1, ai2)
+ print('%s format="%s" type="%s"%s name="%s" description="%s"' % (
+ ai, format, _type,
+ ' fnc1="1"' if require_fnc1 else '',
+ name, description))
-----------------------------------------------------------------------
Summary of changes:
stdnum/gs1_128.py | 269 +++++++++++++++++++++++++++++++++++++++++++++
stdnum/gs1_ai.dat | 170 ++++++++++++++++++++++++++++
tests/test_gs1_128.doctest | 148 +++++++++++++++++++++++++
update/gs1_ai.py | 82 ++++++++++++++
4 files changed, 669 insertions(+)
create mode 100644 stdnum/gs1_128.py
create mode 100644 stdnum/gs1_ai.dat
create mode 100644 tests/test_gs1_128.doctest
create mode 100755 update/gs1_ai.py
hooks/post-receive
--
python-stdnum
- python-stdnum branch master updated. 1.13-42-g180788a,
Commits of the python-stdnum project