The example here assumes a barcode scanning device which emulates a german keyboard. Adjust the JavaScript "BarcodeParserUsageExample.js" to your device.
38 |
39 | Scan here:
40 |
41 |
42 |
43 |
44 |
45 |
Symbology identification:
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Example/BarcodeParserUsageExample.js:
--------------------------------------------------------------------------------
1 | /**
2 | * P. Brockfeld, 2014-06-18
3 | *
4 | * Example for parsing GS1 barcodes,
5 | *
6 | * see
7 | *
8 | * https://github.com/PeterBrockfeld/BarcodeParser
9 | *
10 | * for details.
11 | */
12 |
13 | var inputField = {},
14 | parsedElementsOutput = {},
15 | sendbutton = {},
16 | fncChar = String.fromCharCode(29),
17 | showstring = "",
18 | tableRow = "",
19 | tableCell = "";
20 |
21 | function interpreteBarcode() {
22 | 'use strict';
23 |
24 | function addTableRow(element, index, array) {
25 | /* builds up a single row for the output table
26 | */
27 |
28 | /*first cell: the AI of the element:
29 | */
30 | tableRow = document.createElement("tr");
31 | tableCell = document.createElement("td");
32 | tableCell.innerHTML = element.ai;
33 | tableRow.appendChild(tableCell);
34 | /*second cell: the title or name of the element
35 | */
36 | tableCell = document.createElement("td");
37 | tableCell.innerHTML = element.dataTitle;
38 | tableRow.appendChild(tableCell);
39 | /* third cell: the value or contents of the element*/
40 | tableCell = document.createElement("td");
41 | tableCell.innerHTML = element.data;
42 | tableRow.appendChild(tableCell);
43 | /* fourth cell: the unit of measurement/the currency*/
44 | tableCell = document.createElement("td");
45 | tableCell.innerHTML = element.unit;
46 | tableRow.appendChild(tableCell);
47 | /*row finished: append to table
48 | */
49 | parsedElementsOutput.appendChild(tableRow);
50 | }
51 |
52 |
53 |
54 |
55 | // here, we finally use the library function ...
56 | try {
57 | var barcode = document.getElementById("barcode").value,
58 | /**
59 | * sometimes malconfigured scanners replace the FNC1 by
60 | * some other character or sequence of characters.
61 | *
62 | * Here you could fix this behaviour.
63 | *
64 | * Example: the scanner sends "^" instead of ASCII 29:
65 | *
66 | * var re = /\^/g;
67 | *
68 | * barcode = barcode.replace(re, String.fromCharCode(29));
69 | *
70 | */
71 |
72 | symbologyIdentification = document.getElementById("symbologyIdentification"),
73 | answer = parseBarcode(barcode);
74 |
75 | symbologyIdentification.innerHTML = answer.codeName;
76 |
77 | // clear previous entries of "parsedElementsOutput":
78 | var prevRows = document.getElementsByTagName("tr"),
79 | numberOfPrevRows = prevRows.length,
80 | i = 0;
81 |
82 | for (i = 0; i < numberOfPrevRows; i = i + 1) {
83 | // delete the first element
84 | prevRows[0].parentNode.removeChild(prevRows[0]);
85 | }
86 |
87 | // attach headerlines:
88 | tableRow = document.createElement("tr");
89 | tableCell = document.createElement("th");
90 | tableCell.innerHTML = "AI";
91 | tableRow.appendChild(tableCell);
92 |
93 | tableCell = document.createElement("th");
94 | tableCell.innerHTML = "Title";
95 | tableRow.appendChild(tableCell);
96 |
97 | tableCell = document.createElement("th");
98 | tableCell.innerHTML = "Contents";
99 | tableRow.appendChild(tableCell);
100 |
101 | tableCell = document.createElement("th");
102 | tableCell.innerHTML = "Unit/Currency";
103 | tableRow.appendChild(tableCell);
104 |
105 | /* header row finished: append to table
106 | */
107 | parsedElementsOutput.appendChild(tableRow);
108 | answer.parsedCodeItems.forEach(addTableRow);
109 | } catch (e) {
110 | alert(e);
111 | }
112 | }
113 |
114 | /**
115 | * barcode scanners operating as a HID send some control sequence
116 | * when they encounter a in the scanned barcode. This behaviour may
117 | * cause unpleasant results:
118 | *
119 | * - the scanner sends the keycode for "Ctrl" + "]"
120 | * - the HID driver transforms the code according to the current keyboard layout
121 | * - for a german keyboard this is the sequence "Ctrl" + "+", which increases the
122 | * zoom factor of the browser, for other keyboards ... who knows?
123 | *
124 | * The function here catches these control sequences and transforms them to
125 | * a proper within the input field.
126 | */
127 | function catchGroupSeparatorAndEnter(event) {
128 | 'use strict';
129 | /**
130 | * !!! adjust here for YOUR scanning device !!!
131 | *
132 | * event.which === 43 is "+" on a german keyboard
133 | */
134 | if (event.ctrlKey && event.which === 43) {
135 | inputField.value = inputField.value + fncChar;
136 | event.preventDefault();
137 | event.stopPropagation();
138 | }
139 | if (event.which === 13) {
140 | interpreteBarcode();
141 | }
142 | }
143 |
144 | function init() {
145 | 'use strict';
146 | inputField = document.getElementById("barcode");
147 | sendbutton = document.getElementById("sendbutton");
148 | parsedElementsOutput = document.getElementById("parsedElementsOutput");
149 | sendbutton.onclick = interpreteBarcode;
150 | inputField.onkeypress = catchGroupSeparatorAndEnter;
151 | }
152 |
153 | // initialize after loading:
154 | window.onload = init;
--------------------------------------------------------------------------------
/Example/ScannedBarcode.txt:
--------------------------------------------------------------------------------
1 | ]C101040123456789011715012910ABC1233932971471131030005253922471142127649716
2 |
3 |
--------------------------------------------------------------------------------
/Example/css/BarcodeParser.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 | h1 {
5 | text-align: center;
6 | font-family: serif;
7 | font-size: 16pt;
8 | }
9 | th {
10 | padding: 5px;
11 | text-align: left;
12 | background-color: #9999FF;
13 | }
14 | tr {
15 | background-color: #fffbf0;
16 | }
17 | tr:nth-child(odd) {
18 | background-color: #e4ebf2;
19 | }
20 | td {
21 | padding: 5px;
22 | text-align: left;
23 | }
24 | #barcodeInputDiv {
25 | color: #FFFFFF;
26 | background-color: #888888;
27 | border-radius: 10pt;
28 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
29 | padding: 20pt;
30 | }
31 | #parsedCodeOutput {
32 | background-color: #dddddd;
33 | border-radius: 10pt;
34 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
35 | ;
36 | padding: 20pt;
37 | }
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | grunt.initConfig({
4 | pkg: grunt.file.readJSON('package.json'),
5 |
6 | jshint: {
7 | all: ['src/**/*.js', 'spec/**/*.js'],
8 | options: {
9 | esnext: true
10 | }
11 | },
12 | uglify: {
13 | options: {
14 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
15 | },
16 | dist: {
17 | src: 'src/BarcodeParser.js',
18 | dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.min.js'
19 | }
20 | },
21 | jasmine: {
22 | src: 'src/**/*.js',
23 | options: {
24 | specs: 'spec/**/*Spec.js'
25 | }
26 | }
27 | });
28 |
29 | grunt.loadNpmTasks('grunt-contrib-jshint');
30 | grunt.loadNpmTasks('grunt-contrib-jasmine');
31 | grunt.loadNpmTasks('grunt-contrib-uglify');
32 |
33 | grunt.registerTask('default', ['jshint', 'jasmine', 'uglify']);
34 | grunt.registerTask('test', ['jshint', 'jasmine']);
35 | };
--------------------------------------------------------------------------------
/KeypressDetecting/KeypressDetecting.html:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 | Analyze keypress
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Example: analysing keypresses
29 |
30 |
31 | Scan here:
32 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/KeypressDetecting/css/KeypressDetecting.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | }
4 | h1 {
5 | text-align: center;
6 | font-family: serif;
7 | font-size: 16pt;
8 | }
9 | th {
10 | padding: 5px;
11 | text-align: left;
12 | background-color: #9999FF;
13 | }
14 | td {
15 | padding: 5px;
16 | text-align: left;
17 | }
18 | #barcodeInputDiv {
19 | color: #FFFFFF;
20 | background-color: #888888;
21 | border-radius: 10pt;
22 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
23 | padding: 20pt;
24 | }
25 | #parsedCodeOutput {
26 | background-color: #dddddd;
27 | border-radius: 10pt;
28 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
29 | ;
30 | padding: 20pt;
31 | }
--------------------------------------------------------------------------------
/KeypressDetecting/scripts/KeypressDetecting.js:
--------------------------------------------------------------------------------
1 | /**
2 | * P. Brockfeld, 2014-12-27
3 | *
4 | * Testing keypress events,
5 | * see
6 | *
7 | * https://github.com/PeterBrockfeld/BarcodeParser
8 | *
9 | * for details.
10 | *
11 | */
12 | var outputString = "";
13 |
14 | function logInput(event) {
15 | 'use strict';
16 | var myKeyCode = event.keyCode,
17 | myCharCode = event.charCode,
18 | myWhich = event.which,
19 | inputField = document.getElementById("barcode");
20 |
21 | /**
22 | * Here is some code which tries to catch the "Ctrl"+"+" keypress. Try out with
23 | * the values _your_ scanner sends.
24 | */
25 |
26 | /* ============== try to catch Ctrl sequences ========
27 | if (event.ctrlKey && event.which === 43) {
28 | inputField.value = inputField.value + '';
29 | event.preventDefault();
30 | event.stopPropagation();
31 | }
32 | if (event.which === 13) {
33 | alert(inputField.value);
34 | }
35 | */
36 |
37 | outputString = 'keyCode: ' + myKeyCode + ' charCode: ' + myCharCode + ' which: ' + myWhich + '\n';
38 | console.log(outputString);
39 | }
40 |
41 | function init() {
42 | 'use strict';
43 | var sendbutton = document.getElementById("sendbutton"),
44 | inputField = document.getElementById("barcode");
45 | inputField.onkeypress = logInput;
46 | }
47 |
48 | // initialize after loading:
49 | window.onload = init;
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Peter Brockfeld
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript GS1 barcode parser
2 |
3 | **IMPORTANT**: this is a fork of Peter Brockfeld's [original repository](https://github.com/PeterBrockfeld/BarcodeParser).
4 |
5 | **IMPORTANT**: [active fork](https://github.com/MaximBelov/BarcodeParser).
6 |
7 | https://www.gs1.org/docs/barcodes/GS1_DataMatrix_Guideline.pdf
8 |
9 | ## Table of Contents
10 |
11 | * [Purpose](#purpose)
12 | * [Disclaimer](#disclaimer)
13 | * [The Specification](#the-specification)
14 | * [About GS1 barcodes](#about-gs1-barcodes)
15 | * [about the structure of GS1 barcodes](#about-the-structure-of-gs1-barcodes)
16 | * [Use case](#use-case)
17 | * [How to use it](#how-to-use-it)
18 | * [Limitations](#limitations)
19 | * [A simple scanning application as example](#a-simple-scanning-application-as-example)
20 | * [About barcode scanning devices](#about-barcode-scanning-devices)
21 | * [Keypress detecting](#key-press-detecting)
22 | * [The Barcodes](#the-barcodes)
23 | * [License](#license)
24 |
25 |
26 | ## Purpose
27 |
28 | The barcode parser is a library for handling the contents of GS1 barcodes. GS1 barcodes are used for several purposes, from a humble barcode on a product you buy to complex barcodes describing the contents of a whole pallet. Especially the two dimensional barcodes can hold a *lot* of information.
29 |
30 | The barcode parser makes it easier to access it.
31 |
32 | The barcode parser contains a single function for parsing GS1-barcodes, yielding the single elements in a format processable by JavaScript.
33 |
34 | The barcode parser is meant to be used in JavaScript applications which
35 |
36 | * take data from a barcode scanning device or a barcode reading application
37 | * process the data and
38 | * perform some action based on the contents of the barcode
39 |
40 | ## Disclaimer
41 |
42 | The library is my own humble interpretation of the GS1 specification. Neither is it endorsed, supported, approved or recommended by GS1, nor do I have any affiliations with GS1.
43 |
44 | ## The Specification
45 |
46 | The full "GS1 General Specifications" can be found on http://www.gs1.org/genspecs. It's a 478 pages document. The barcode parser is based on the *Version 14, Issue 1, Jan 2014* of this specification.
47 |
48 | ## About GS1 barcodes
49 |
50 | GS1 barcodes are able to contain information about a product: its GTIN ("Global Trade Item Number", formerly known as UPC or EAN), the weight or dimensions of the item, its price, the lot/batch code, the date when it was fabricated and so on.
51 |
52 | ### About the structure of GS1 barcodes
53 |
54 | A GS1 barcode is a concatenation of *data elements*. Each single element starts with an *application identifier* ("AI"), a two to four digits number. The number is followed by the actual information.
55 |
56 | A *data element* is delimited either by
57 |
58 | * the end of the whole barcode,
59 | * a specification that states that this information has a fixed count of characters or digits
60 | * a special character (named FNC1)
61 |
62 | The *application identifiers* and their properties are described in the third chapter of the "GS1 General Specifications" (see below).
63 |
64 | The GS1 barcode is started by a *symbology identifier*; a three character sequence denoting the type of barcode used. The *symbology identifier* is followed by an arbitrary number of *data elements*, thus the whole barcode represents a long string of digits, letters and some interspersed "FNC1"s.
65 |
66 | The BarcodeParser takes this string and decomposes it into its single elements.
67 |
68 | ### Use case
69 |
70 | You have a JavaScript application which takes barcodes in one of the GS1-formats. The conversion barcode → string has been made by a barcode scanning device or some other application. You got a string looking somehow like that:
71 |
72 | ]C101040123456789011715012910ABC1233932978471131030005253922471142127649716
73 |
74 | You want to extract some data out of the scanned code, e.g. the lot/batch number or the Best Before Date, and process it. The library takes the string and dissects it to an array of single elements:
75 |
76 | |AI | Title | Contents | Unit/Currency |
77 | |:-- |:-----|:-------|:--------------|
78 | |01 |GTIN | 04012345678901 | |
79 | |17 |USE BY OR EXPIRY | Thu Jan 29 2015 00:00:00 GMT+0100 (CET) | |
80 | |10 |BATCH/LOT | ABC123 | |
81 | |3932 |PRICE | 47.11 | 978 |
82 | |3103 |NET WEIGHT (kg) | 0.525 | KGM |
83 | |3922 |PRICE | 47.11 | |
84 | |421 |SHIP TO POST | 49716 | 276 |
85 |
86 |
87 | ## How to use it
88 |
89 | The library is located in the `src` directory in its uncompressed form. There is also a version minified with the `uglifyjs` tool (see https://github.com/mishoo/UglifyJS2) in the `dist` directory.
90 |
91 | Load the library into your application:
92 |
93 | ```html
94 |
95 | ```
96 |
97 | and use the single one function `parseBarcode()` of the library, handling over the barcode string:
98 |
99 | ```javascript
100 | try {
101 | var barcode = document.getElementById("barcode").value,
102 | answer = parseBarcode(barcode);
103 | // handle the answer ...
104 | } catch (e) {
105 | alert(e);
106 | }
107 | ```
108 |
109 | The function returns an object containing two elements:
110 |
111 | * `codeName`: a barcode type identifier (a simple string denoting the type of barcode) and
112 | * `parsedCodeItems`: an array of objects, each with four attributes:
113 | * `ai`: the application identifier
114 | * `title`: the title of the element, i.e. a short description
115 | * `data`: the contents, either a string, a number or a date
116 | * `unit`: the unit of measurement, a country code, a currency; denoted in ISO codes.
117 |
118 | From the example above: `parseBarcode()` will return an object with "GS1-128" in its attribute `codeName`, the fourth element of `parsedCodeItems` is an object which has the attributes
119 |
120 | * "3932" as `ai`,
121 | * "PRICE" as `title`,
122 | * "47.11" as `data` (a floating point number) and
123 | * "978" as `unit` (the ISO code for €)
124 |
125 | Some remarks about how the function works can be found in `README_scripts.md` within the `scripts` folder.
126 |
127 | ### Limitations
128 |
129 | The `parseBarcode()` function doesn't do any checks for plausibility. If the code you handle over to the function contains e.g. an invalid GTIN or some invalid ISO code the function will happily return this invalid content.
130 |
131 | ## A simple scanning application as example
132 |
133 | The directory `Example` contains an example using the barcode parser. It has three components:
134 |
135 | * a HTML page with
136 | * a form for input and
137 | * a (empty) `
` for the output,
138 | * some JavaScript code for
139 | * accessing the input,
140 | * calling the `parseBarcode()` function and
141 | * filling the table using the returned object
142 | * some CSS for styling the page
143 |
144 | If you have no scanning device at hand, you can use the string in "ScannedBarcode.txt" to copy & paste it into the input field of the example.
145 |
146 | ## About barcode scanning devices
147 |
148 | GS1 barcodes are usually scanned using a barcode scanning device. If you use GS1 barcodes with a web application, you'll probably have a setup where the barcode scanner behaves as a keyboard ("HID").
149 |
150 | This works fine for most characters, but has one big drawback: the FNC1.
151 |
152 | ### The FNC1
153 |
154 | The FNC1 is a non-printable control character used to delimit GS1 Element Strings of variable length.
155 |
156 | The barcode types GS1 DataMatrix and GS1 QR Code use the ASCII group separator ("GS", ASCII 29, 0x1D; Unicode U+001D) as FNC1.
157 |
158 | This non-printable character won't be found on any keyboard. So the scanner sends a Ctrl-sequence as a replacement. The canonical sequence for GS is "Ctrl"+"]".
159 |
160 | So if you use a `````` field in your website the browser will receive the control sequence "Ctrl" + "]". Depending on your setup the browser will react in some way to this control sequence.
161 |
162 | Things get messy when you use a non-english keyboard. For example: on german keyboards the key left beside the enter key is used for the "+" sign. On an english keyboard there is the "]". If the scanner is operated as a HID **and** configured to behave like a *german* keyboard, the scanner sends "Ctrl" + "+", which causes most browsers to increase their zoom factor.
163 |
164 | So you have two things to do:
165 |
166 | * identify the sequence your scanner sends to the browser when a group separator is scanned
167 | * catch these keyboard events and transform them into a group separator within the input field
168 |
169 | The ```BarcodeParserUsageExample.js``` does the latter part for a scanner which sends (emulating a german keyboard) a "Ctrl" + "+" if it encounters a group separator in the barcode.
170 |
171 | ### Key Press Detecting
172 |
173 | The directory ```KeypressDetecting``` contains a simple HTML page to explore what kind of control sequence *your* scanner sends. It has an input field and logs the values (```keyCode```, ```charCode``` and ```which```) of the keypresses to the console.
174 |
175 | ### The Barcodes
176 |
177 | As an example the directory `Barcodes` contains five barcodes, three of them containing the same data:
178 |
179 | * a GS1-128-Code,
180 | * a GS1-DataMatrix-Code and
181 | * a GS1-QR-Code.
182 |
183 | The other two just contain three characters: "1", the "<GS>" group separator and "3". They can be used to find out what *your* scanner sends when it encounters a "<GS>" in a barcode.
184 |
185 | You can print them using the "ExamplesForBarcode.pdf".
186 |
187 | ## License
188 |
189 | Copyright (c) 2014-2019 Peter Brockfeld. See the LICENSE.md file for license rights and limitations (MIT).
190 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gs1-barcode-parser-mod",
3 | "version": "1.0.7",
4 | "description": "The barcode parser is a library for handling the contents of GS1 barcodes.",
5 | "main": "src/BarcodeParser.js",
6 | "scripts": {
7 | "test": "grunt test"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/MaximBelov/BarcodeParser.git"
12 | },
13 | "keywords": [
14 | "javascript",
15 | "GS1",
16 | "barcode",
17 | "parser"
18 | ],
19 | "author": "Peter Brockfeld",
20 | "license": "MIT",
21 | "homepage": "https://github.com/MaximBelov/BarcodeParser.git",
22 | "devDependencies": {
23 | "grunt": "^1.0.3",
24 | "grunt-contrib-jasmine": "^2.0.2",
25 | "grunt-contrib-jshint": "^1.1.0",
26 | "grunt-contrib-uglify": "^3.4.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/spec/BarcodeParserSpec.js:
--------------------------------------------------------------------------------
1 | describe("A parsed GS1 barcode", () => {
2 | let result;
3 |
4 | beforeEach(() => {
5 | const fncChar = String.fromCharCode(29); // the ASCII "group separator"
6 | const barcode = `]C101040123456789011715012910ABC123${fncChar}39329714711${fncChar}310300052539224711${fncChar}42127649716`;
7 |
8 | result = parseBarcode(barcode);
9 | });
10 |
11 | it("has 7 elements", () => {
12 | expect(result.parsedCodeItems.length).toBe(7);
13 | });
14 |
15 | it("has the GTIN element", () => {
16 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
17 | ai: "01",
18 | dataTitle: "GTIN",
19 | data: "04012345678901"
20 | })]));
21 | });
22 |
23 | it("has the EXPIRY element", () => {
24 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
25 | ai: "17",
26 | dataTitle: "USE BY OR EXPIRY",
27 | data: new Date(2015, 0, 29, 0, 0, 0, 0)
28 | })]));
29 | });
30 |
31 | it("has the BATCH/LOT element", () => {
32 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
33 | ai: "10",
34 | dataTitle: "BATCH/LOT",
35 | data: "ABC123"
36 | })]));
37 | });
38 |
39 | it("has the PRICE (ISO) element", () => {
40 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
41 | ai: "3932",
42 | dataTitle: "PRICE",
43 | data: 47.11,
44 | unit: "971"
45 | })]));
46 | });
47 |
48 | it("has the PRICE element", () => {
49 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
50 | ai: "3922",
51 | dataTitle: "PRICE",
52 | data: 47.11
53 | })]));
54 | });
55 |
56 | it("has the NET WEIGHT element", () => {
57 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
58 | ai: "3103",
59 | dataTitle: "NET WEIGHT (kg)",
60 | data: 0.525,
61 | unit: "KGM"
62 | })]));
63 | });
64 |
65 | it("has the SHIP TO POST element", () => {
66 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
67 | ai: "421",
68 | dataTitle: "SHIP TO POST",
69 | data: "49716",
70 | unit: "276"
71 | })]));
72 | });
73 | });
74 |
75 | describe("A parsed GS1 barcode including the consumer product variant", () => {
76 | it("has the CPV element", () => {
77 | const barcode = "]C1229000481118CDB1950224";
78 | const result = parseBarcode(barcode);
79 |
80 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
81 | ai: "22",
82 | dataTitle: "CPV",
83 | data: "9000481118CDB1950224"
84 | })]));
85 | });
86 | });
87 |
88 | describe("A parsed GS1 barcode", () => {
89 | it("should behave...", () => {
90 | const barcode = "]C10100307130640862171905001010059243000024";
91 | const result = parseBarcode(barcode);
92 |
93 | expect(result.parsedCodeItems).toEqual(jasmine.arrayContaining([jasmine.objectContaining({
94 | ai: "17",
95 | dataTitle: "USE BY OR EXPIRY",
96 | data: new Date(2019, 4, 31, 0, 0, 0, 0)
97 | })]));
98 | });
99 | });
--------------------------------------------------------------------------------
/src/BarcodeParser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * P. Brockfeld, 2014-02-05
3 | *
4 | * JavaScript for parsing GS1 barcodes, see
5 | *
6 | * https://github.com/MaximBelov/BarcodeParser (active fork)
7 | * https://github.com/PeterBrockfeld/BarcodeParser (original repo)
8 | *
9 | * for details.
10 | */
11 |
12 | /**
13 | * @typedef {Object} ParsedBarcode
14 | * @property {string} codeName - Symbology identifier
15 | * @property {ParsedElement[]} parsedCodeItems - an array with elements which are objects of type "ParsedElement"
16 | */
17 |
18 | /**
19 | *
20 | * encapsulating the barcode parsing function in an anonymous, self-executing function
21 | */
22 | const parseBarcode = (function () {
23 | 'use strict';
24 | /**
25 | * This is the main routine provided by the parseBarcode library. It takes a string,
26 | * splices it from left to right into its elements and tries to parse it as an
27 | * GS1 - element. If it succeeds, the result is returned as an object composed of
28 | * an identifier and an array.It accepts
29 | * @param {String} barcode is the contents of the barcode you'd like to get parsed
30 | * @returns {ParsedBarcode} with code name and an array with elements which are objects of type "ParsedElement"
31 | */
32 |
33 | function parseBarcode(barcode) {
34 | var i = 0, // counter
35 | fncChar = String.fromCharCode(29), // the ASCII "group separator"
36 | barcodelength = barcode.length,
37 | answer = {}, // the object to return
38 | restOfBarcode = "", // the rest of the barcode, when first
39 | // elements are spliced away
40 | symbologyIdentifier = barcode
41 | .replace(fncChar,'')
42 | .slice(0, 3),
43 | firstElement = {};
44 |
45 | //auxilliary functions
46 |
47 | /**
48 | * "ParsedElement" is the
49 | *
50 | * @constructor for ParsedElements, the components of the array returned by parseBarcode
51 | * @param {String} elementAI the AI of the recognized element
52 | * @param {String} elementDataTitle the title of the element, i.e. its short description
53 | * @param {String} elementType a one-letter string describing the type of the element.
54 | * allowed values are
55 | * "S" for strings,
56 | * "N" for numbers and
57 | * "D" for dates
58 | */
59 | function ParsedElement(elementAI, elementDataTitle, elementType) {
60 | /* defines the object which represents a single element
61 | */
62 | this.ai = elementAI; //application identifier
63 | this.dataTitle = elementDataTitle; //title
64 | switch (elementType) {
65 | case "S":
66 | this.data = ""; // the contents
67 | break;
68 | case "N":
69 | this.data = 0;
70 | break;
71 | case "D":
72 | this.data = new Date();
73 | this.data.setHours(0, 0, 0, 0);
74 | break;
75 | default:
76 | this.data = "";
77 | break;
78 | }
79 | this.unit = ""; // some elements are accompaigned by an unit of
80 | // measurement or currency
81 | }
82 |
83 | /**
84 | *
85 | * ================== BEGIN of identifyAI =======================
86 | *
87 | * does the main work:
88 | * what AI is in the beginning of the restOfBarcode?
89 | * If identified:
90 | * which function to call with
91 | * which parameters to parse the element?[Description]]
92 | * @param {String} codestring a string; the function tries to
93 | * identify an AI in the beginning of this string
94 | * @returns {Object} if it succeeds in identifying an AI the
95 | * ParsedElement is returned, together with the
96 | * still unparsed rest of codestring.
97 | */
98 | function identifyAI(codestring) {
99 | // find first identifier. AIs have a minimum length of 2
100 | // digits, some have 3, some even 4.
101 | var firstNumber = codestring.slice(0, 1),
102 | secondNumber = codestring.slice(1, 2),
103 | thirdNumber = "",
104 | fourthNumber = "",
105 | codestringToReturn = "",
106 | codestringLength = codestring.length,
107 | elementToReturn = "";
108 |
109 | /**
110 | * ============ auxiliary functions for identifyAI =============
111 | */
112 | /**
113 | * some data items are followed by an FNC even in case of
114 | * fixed length, so the codestringToReturn may have
115 | * leading FNCs.
116 | *
117 | * This function eleminates these leading FNCs.
118 | *
119 | * @param {String} stringToClean string which has to be cleaned
120 | * @returns {String} the cleaned string
121 | */
122 | function cleanCodestring(stringToClean) {
123 | //
124 | var firstChar = stringToClean.slice(0, 1);
125 | while (firstChar === fncChar) {
126 | stringToClean = stringToClean.slice(1, stringToClean.length);
127 | firstChar = stringToClean.slice(0, 1);
128 | }
129 | return stringToClean;
130 | }
131 | /**
132 | * Used for calculating numbers which are given as string
133 | * with a given number of fractional decimals.
134 | *
135 | * To avoid conversion errors binary <-> decimal I _don't_
136 | * just divide by 10 numberOfFractionals times.
137 | */
138 | function parseFloatingPoint(stringToParse, numberOfFractionals) {
139 | var auxString = "",
140 | offset = stringToParse.length - numberOfFractionals,
141 | auxFloat = 0.0;
142 |
143 | auxString = stringToParse.slice(0, offset) +
144 | '.' +
145 | stringToParse.slice(offset, stringToParse.length);
146 | try {
147 | auxFloat = parseFloat(auxString);
148 | } catch (e36) {
149 | throw "36";
150 | }
151 |
152 | return auxFloat;
153 | }
154 | /**
155 | * ======== END of auxiliary function for identifyAI =======
156 | */
157 |
158 | /**
159 | *
160 | * ======== BEGIN of parsing functions in identifyAI =======
161 | *
162 | * Some functions to parse the various GS1 formats. They
163 | * create a new ParsedElement and set its properties.
164 | *
165 | * They all modify the variables "elementToReturn" and
166 | * "codestringToReturn".
167 | */
168 |
169 | /**
170 | * dates in GS1-elements have the format "YYMMDD".
171 | * This function generates a new ParsedElement and tries to fill a
172 | * JS-date into the "data"-part.
173 | * @param {String} ai the AI to use for the ParsedElement
174 | * @param {String} title the title to use for the ParsedElement
175 | */
176 | function parseDate(ai, title) {
177 | elementToReturn = new ParsedElement(ai, title, "D");
178 | var offSet = ai.length,
179 | dateYYMMDD = codestring.slice(offSet, offSet + 6),
180 | yearAsNumber = 0,
181 | monthAsNumber = 0,
182 | dayAsNumber = 0;
183 | var currentCCYY = new Date().getUTCFullYear(),
184 | currentYear = currentCCYY % 100,
185 | currentCentury = currentCCYY - currentYear,
186 | yearGap = 0;
187 |
188 | try {
189 | yearAsNumber = parseInt(dateYYMMDD.slice(0, 2), 10);
190 | yearGap = yearAsNumber - currentYear;
191 | } catch (e33) {
192 | throw "33";
193 | }
194 |
195 | try {
196 | monthAsNumber = parseInt(dateYYMMDD.slice(2, 4), 10);
197 | } catch (e34) {
198 | throw "34";
199 | }
200 |
201 | try {
202 | dayAsNumber = parseInt(dateYYMMDD.slice(4, 6), 10);
203 | } catch (e35) {
204 | throw "35";
205 | }
206 | // Year determination
207 | // use a sliding window going from -49 years to +50 years
208 | // as specified in section 7.1.2. (see: https://ref.gs1.org/standards/genspecs/)
209 | //
210 | // 2024 2074
211 | // ----------------|------------]------->
212 | //
213 | // In 2024, the horizon for 21th century is 2074
214 | //
215 | // Example:
216 | // If 2-digits year is 78 -> 1978 (closer to 2024 than 2078)
217 | if (yearGap >= 51) {
218 | yearAsNumber = yearAsNumber + currentCentury - 100;
219 | } else if (yearGap <= -50) {
220 | yearAsNumber = yearAsNumber + currentCentury + 100;
221 | } else {
222 | yearAsNumber = yearAsNumber + currentCentury;
223 | }
224 |
225 | if (dayAsNumber > 0) {
226 | // Dates in Javascript are funny. Months start at 0. Days, on the other
227 | // hand, start at 1. We need to decrement the month by 1. Otherwise,
228 | // the date will be wrong by one month. E.g., month 11 and day 15
229 | // become Dec 15th. If the day is equal to 0, however, we use a Javascript
230 | // trick to turn the date into the last day of the previous month.
231 | // So, e.g., month 11 and day 0 become Nov 30th.
232 | monthAsNumber--;
233 | }
234 |
235 | elementToReturn.data.setFullYear(yearAsNumber, monthAsNumber, dayAsNumber);
236 | codestringToReturn = codestring.slice(offSet + 6, codestringLength);
237 | elementToReturn.raw = codestring.slice(offSet, offSet+6);
238 | }
239 |
240 | /**
241 | * simple: the element has a fixed length AND is not followed by an FNC1.
242 | * @param {String} ai the AI to use
243 | * @param {String} title its title, i.e. its short description
244 | * @param {Number} length the fixed length
245 | */
246 | function parseFixedLength(ai, title, length) {
247 |
248 | elementToReturn = new ParsedElement(ai, title, "S");
249 | var offSet = ai.length;
250 | elementToReturn.data = codestring.slice(offSet, length + offSet);
251 | codestringToReturn = codestring.slice(length + offSet, codestringLength);
252 | elementToReturn.raw = codestring.slice(offSet, offSet + length);
253 | }
254 |
255 | /**
256 | * tries to parse an element of variable length
257 | * some fixed length AIs are terminated by FNC1, so this function
258 | * is used even for fixed length items
259 | * @param {String} ai the AI to use
260 | * @param {String} title its title, i.e. its short description
261 | */
262 | function parseVariableLength(ai, title) {
263 | //
264 | elementToReturn = new ParsedElement(ai, title, "S");
265 | var offSet = ai.length,
266 | posOfFNC = codestring.indexOf(fncChar);
267 |
268 | if (posOfFNC === -1) { //we've got the last element of the barcode
269 | elementToReturn.data = codestring.slice(offSet, codestringLength);
270 | elementToReturn.raw = codestring.slice(offSet);
271 | codestringToReturn = "";
272 | } else {
273 | elementToReturn.data = codestring.slice(offSet, posOfFNC);
274 | codestringToReturn = codestring.slice(posOfFNC + 1, codestringLength);
275 | elementToReturn.raw = codestring.slice(offSet, posOfFNC);
276 | }
277 |
278 | }
279 |
280 | /**
281 | * the place of the decimal fraction is given by the fourth number, that's
282 | * the first after the identifier itself.
283 | *
284 | * All of theses elements have a length of 6 characters.
285 | * @param {String} ai_stem the first digits of the AI, _not_ the fourth digit
286 | * @param {Number} fourthNumber the 4th number indicating the count of valid fractionals
287 | * @param {String} title the title of the AI
288 | * @param {String} unit often these elements have an implicit unit of measurement
289 | */
290 | function parseFixedLengthMeasure(ai_stem, fourthNumber, title, unit) {
291 | //
292 | elementToReturn = new ParsedElement(ai_stem + fourthNumber, title, "N");
293 | var offSet = ai_stem.length + 1,
294 | numberOfDecimals = parseInt(fourthNumber, 10),
295 | numberPart = codestring.slice(offSet, offSet + 6);
296 |
297 | elementToReturn.data = parseFloatingPoint(numberPart, numberOfDecimals);
298 |
299 | elementToReturn.unit = unit;
300 | codestringToReturn = codestring.slice(offSet + 6, codestringLength);
301 | elementToReturn.raw = codestring.slice(offSet, offSet + 6);
302 | }
303 |
304 | /**
305 | * parses data elements of variable length, which additionally have
306 | *
307 | * - an indicator for the number of valid decimals
308 | * - an implicit unit of measurement
309 | *
310 | * These data elements contain e.g. a weight or length.
311 | *
312 | */
313 | function parseVariableLengthMeasure(ai_stem, fourthNumber, title, unit) {
314 | // the place of the decimal fraction is given by the fourth number, that's
315 | // the first after the identifier itself.
316 | elementToReturn = new ParsedElement(ai_stem + fourthNumber, title, "N");
317 | var offSet = ai_stem.length + 1,
318 | posOfFNC = codestring.indexOf(fncChar),
319 | numberOfDecimals = parseInt(fourthNumber, 10),
320 | numberPart = "";
321 |
322 | if (posOfFNC === -1) {
323 | numberPart = codestring.slice(offSet, codestringLength);
324 | elementToReturn.raw = codestring.slice(offSet);
325 | codestringToReturn = "";
326 | } else {
327 | numberPart = codestring.slice(offSet, posOfFNC);
328 | codestringToReturn = codestring.slice(posOfFNC + 1, codestringLength);
329 | elementToReturn.raw = codestring.slice(offSet, posOfFNC);
330 | }
331 | // adjust decimals according to fourthNumber:
332 |
333 | elementToReturn.data = parseFloatingPoint(numberPart, numberOfDecimals);
334 | elementToReturn.unit = unit;
335 | }
336 |
337 | /**
338 | * parses data elements of variable length, which additionally have
339 | *
340 | * - an indicator for the number of valid decimals
341 | * - an explicit unit of measurement
342 | *
343 | * These data element contain amounts to pay or prices.
344 | *
345 | */
346 | function parseVariableLengthWithISONumbers(ai_stem, fourthNumber, title) {
347 | // an element of variable length, representing a number, followed by
348 | // some ISO-code.
349 | elementToReturn = new ParsedElement(ai_stem + fourthNumber, title, "N");
350 | var offSet = ai_stem.length + 1,
351 | posOfFNC = codestring.indexOf(fncChar),
352 | numberOfDecimals = parseInt(fourthNumber, 10),
353 | isoPlusNumbers = "",
354 | numberPart = "";
355 |
356 | if (posOfFNC === -1) {
357 | isoPlusNumbers = codestring.slice(offSet, codestringLength);
358 | elementToReturn.raw = codestring.slice(offSet);
359 | codestringToReturn = "";
360 | } else {
361 | isoPlusNumbers = codestring.slice(offSet, posOfFNC);
362 | codestringToReturn = codestring.slice(posOfFNC + 1, codestringLength);
363 | elementToReturn.raw = codestring.slice(offSet, posOfFNC);
364 | }
365 | // cut off ISO-Code
366 | numberPart = isoPlusNumbers.slice(3, isoPlusNumbers.length);
367 | elementToReturn.data = parseFloatingPoint(numberPart, numberOfDecimals);
368 |
369 | elementToReturn.unit = isoPlusNumbers.slice(0, 3);
370 |
371 | }
372 | /**
373 | * parses data elements of variable length, which additionally have
374 | *
375 | * - an explicit unit of measurement or reference
376 | *
377 | * These data element contain countries, authorities within countries.
378 | *
379 | */
380 | function parseVariableLengthWithISOChars(ai_stem, title) {
381 | // an element of variable length, representing a sequence of chars, followed by
382 | // some ISO-code.
383 | elementToReturn = new ParsedElement(ai_stem, title, "S");
384 | var offSet = ai_stem.length,
385 | posOfFNC = codestring.indexOf(fncChar),
386 | isoPlusNumbers = "";
387 |
388 | if (posOfFNC === -1) {
389 | isoPlusNumbers = codestring.slice(offSet, codestringLength);
390 | elementToReturn.raw = codestring.slice(offSet);
391 | codestringToReturn = "";
392 | } else {
393 | isoPlusNumbers = codestring.slice(offSet, posOfFNC);
394 | codestringToReturn = codestring.slice(posOfFNC + 1, codestringLength);
395 | elementToReturn.raw = codestring.slice(offSet, posOfFNC);
396 | }
397 | // cut off ISO-Code
398 | elementToReturn.data = isoPlusNumbers.slice(3, isoPlusNumbers.length);
399 | elementToReturn.unit = isoPlusNumbers.slice(0, 3);
400 | }
401 | /**
402 | *
403 | * ======== END of parsing functions in identifyAI =======
404 | *
405 | */
406 | /**
407 | *
408 | * ======= BEGIN of the big switch =======================
409 | *
410 | * and now a very big "switch", which tries to find a valid
411 | * AI within the first digits of the codestring.
412 | *
413 | * See the documentation for an explanation why it is made
414 | * this way (and not by some configuration file).
415 | */
416 |
417 | switch (firstNumber) {
418 | case "0":
419 | switch (secondNumber) {
420 | case "0":
421 | // SSCC (Serial Shipping Container Code)
422 | parseFixedLength("00", "SSCC", 18);
423 | break;
424 | case "1":
425 | // Global Trade Item Number (GTIN)
426 | parseFixedLength("01", "GTIN", 14);
427 | break;
428 | case "2":
429 | // GTIN of Contained Trade Items
430 | parseFixedLength("02", "CONTENT", 14);
431 | break;
432 | default:
433 | throw "01";
434 | }
435 | break;
436 | case "1":
437 | switch (secondNumber) {
438 | case "0":
439 | // Batch or Lot Number
440 | parseVariableLength("10", "BATCH/LOT");
441 | break;
442 | case "1":
443 | // Production Date (YYMMDD)
444 | parseDate("11", "PROD DATE");
445 | break;
446 | case "2":
447 | // Due Date (YYMMDD)
448 | parseDate("12", "DUE DATE");
449 | break;
450 | case "3":
451 | // Packaging Date (YYMMDD)
452 | parseDate("13", "PACK DATE");
453 | break;
454 | // AI "14" isn't defined
455 | case "5":
456 | // Best Before Date (YYMMDD)
457 | parseDate("15", "BEST BEFORE or BEST BY");
458 | break;
459 | case "6":
460 | // Sell By Date (YYMMDD)
461 | parseDate("16", "SELL BY");
462 | break;
463 | case "7":
464 | // Expiration Date (YYMMDD)
465 | parseDate("17", "USE BY OR EXPIRY");
466 | break;
467 | default:
468 | throw "02";
469 | }
470 | break;
471 | case "2":
472 | switch (secondNumber) {
473 | case "0":
474 | // Variant Number
475 | parseFixedLength("20", "VARIANT", 2);
476 | break;
477 | case "1":
478 | // Serial Number
479 | parseVariableLength("21", "SERIAL");
480 | break;
481 | case "2":
482 | // Consumer product variant
483 | parseVariableLength("22", "CPV");
484 | break;
485 | case "3":
486 | // from now, the third number matters:
487 | thirdNumber = codestring.slice(2, 3);
488 | switch (thirdNumber) {
489 | case "5":
490 | // Third Party Controlled, Serialised Extension of Global Trade Item Number (GTIN) (TPX)
491 | parseVariableLength("235", "TPX");
492 | break;
493 | }
494 | break;
495 | case "4":
496 | // from now, the third number matters:
497 | thirdNumber = codestring.slice(2, 3);
498 | switch (thirdNumber) {
499 | case "0":
500 | // Additional Item Identification
501 | parseVariableLength("240", "ADDITIONAL ID");
502 | break;
503 | case "1":
504 | // Customer Part Number
505 | parseVariableLength("241", "CUST. PART NO.");
506 | break;
507 | case "2":
508 | // Made-to-Order Variation Number
509 | parseVariableLength("242", "MTO VARIANT");
510 | break;
511 | case "3":
512 | // Packaging Component Number
513 | parseVariableLength("243", "PCN");
514 | break;
515 | default:
516 | throw "03";
517 | }
518 | break;
519 | case "5":
520 | // from now, the third number matters:
521 | thirdNumber = codestring.slice(2, 3);
522 | switch (thirdNumber) {
523 | case "0":
524 | // Secondary Serial Number
525 | parseVariableLength("250", "SECONDARY SERIAL");
526 | break;
527 | case "1":
528 | // Reference to Source Entity
529 | parseVariableLength("251", "REF. TO SOURCE");
530 | break;
531 | // AI "252" isn't defined
532 | case "3":
533 | // Global Document Type Identifier (GDTI)
534 | parseVariableLength("253", "GDTI");
535 | break;
536 | case "4":
537 | // GLN Extension Component
538 | parseVariableLength("254", "GLN EXTENSION COMPONENT");
539 | break;
540 | case "5":
541 | // Global Coupon Number (GCN)
542 | parseVariableLength("255", "GCN");
543 | break;
544 | default:
545 | throw "04";
546 | }
547 | break;
548 | // AI "26" to "29" aren't defined
549 | default:
550 | throw "05";
551 | }
552 | break;
553 | case "3":
554 | switch (secondNumber) {
555 | case "0":
556 | // Count of Items (Variable Measure Trade Item)
557 | parseVariableLength("30", "VAR. COUNT");
558 | break;
559 | case "1":
560 | // third and fourth numbers matter:
561 | thirdNumber = codestring.slice(2, 3);
562 | fourthNumber = codestring.slice(3, 4);
563 |
564 | switch (thirdNumber) {
565 | case "0":
566 | // Net weight, kilograms (Variable Measure Trade Item)
567 | parseFixedLengthMeasure("310", fourthNumber, "NET WEIGHT (kg)", "KGM");
568 | break;
569 | case "1":
570 | // Length or first dimension, metres (Variable Measure Trade Item)
571 | parseFixedLengthMeasure("311", fourthNumber, "LENGTH (m)", "MTR");
572 | break;
573 | case "2":
574 | // Width, diameter, or second dimension, metres (Variable Measure Trade Item)
575 | parseFixedLengthMeasure("312", fourthNumber, "WIDTH (m)", "MTR");
576 | break;
577 | case "3":
578 | // Depth, thickness, height, or third dimension, metres (Variable Measure Trade Item)
579 | parseFixedLengthMeasure("313", fourthNumber, "HEIGHT (m)", "MTR");
580 | break;
581 | case "4":
582 | // Area, square metres (Variable Measure Trade Item)
583 | parseFixedLengthMeasure("314", fourthNumber, "AREA (m2)", "MTK");
584 | break;
585 | case "5":
586 | // Net volume, litres (Variable Measure Trade Item)
587 | parseFixedLengthMeasure("315", fourthNumber, "NET VOLUME (l)", "LTR");
588 | break;
589 | case "6":
590 | // Net volume, cubic metres (Variable Measure Trade Item)
591 | parseFixedLengthMeasure("316", fourthNumber, "NET VOLUME (m3)", "MTQ");
592 | break;
593 | default:
594 | throw "06";
595 | }
596 | break;
597 | case "2":
598 | // third and fourth numbers matter:
599 | thirdNumber = codestring.slice(2, 3);
600 | fourthNumber = codestring.slice(3, 4);
601 |
602 | switch (thirdNumber) {
603 | case "0":
604 | // Net weight, pounds (Variable Measure Trade Item)
605 | parseFixedLengthMeasure("320", fourthNumber, "NET WEIGHT (lb)", "LBR");
606 | break;
607 | case "1":
608 | // Length or first dimension, inches (Variable Measure Trade Item)
609 | parseFixedLengthMeasure("321", fourthNumber, "LENGTH (i)", "INH");
610 | break;
611 | case "2":
612 | // Length or first dimension, feet (Variable Measure Trade Item)
613 | parseFixedLengthMeasure("322", fourthNumber, "LENGTH (f)", "FOT");
614 | break;
615 | case "3":
616 | // Length or first dimension, yards (Variable Measure Trade Item)
617 | parseFixedLengthMeasure("323", fourthNumber, "LENGTH (y)", "YRD");
618 | break;
619 | case "4":
620 | // Width, diameter, or second dimension, inches (Variable Measure Trade Item)
621 | parseFixedLengthMeasure("324", fourthNumber, "WIDTH (i)", "INH");
622 | break;
623 | case "5":
624 | // Width, diameter, or second dimension, feet (Variable Measure Trade Item)
625 | parseFixedLengthMeasure("325", fourthNumber, "WIDTH (f)", "FOT");
626 | break;
627 | case "6":
628 | // Width, diameter, or second dimension, yards (Variable Measure Trade Item
629 | parseFixedLengthMeasure("326", fourthNumber, "WIDTH (y)", "YRD");
630 | break;
631 | case "7":
632 | // Depth, thickness, height, or third dimension, inches (Variable Measure Trade Item)
633 | parseFixedLengthMeasure("327", fourthNumber, "HEIGHT (i)", "INH");
634 | break;
635 | case "8":
636 | // Depth, thickness, height, or third dimension, feet (Variable Measure Trade Item)
637 | parseFixedLengthMeasure("328", fourthNumber, "HEIGHT (f)", "FOT");
638 | break;
639 | case "9":
640 | // Depth, thickness, height, or third dimension, yards (Variable Measure Trade Item)
641 | parseFixedLengthMeasure("329", fourthNumber, "HEIGHT (y)", "YRD");
642 | break;
643 | default:
644 | throw "07";
645 | }
646 | break;
647 | case "3":
648 | // third and fourth numbers matter:
649 | thirdNumber = codestring.slice(2, 3);
650 | fourthNumber = codestring.slice(3, 4);
651 |
652 | switch (thirdNumber) {
653 | case "0":
654 | // Logistic weight, kilograms
655 | parseFixedLengthMeasure("330", fourthNumber, "GROSS WEIGHT (kg)", "KGM");
656 | break;
657 | case "1":
658 | // Length or first dimension, metres
659 | parseFixedLengthMeasure("331", fourthNumber, "LENGTH (m), log", "MTR");
660 | break;
661 | case "2":
662 | // Width, diameter, or second dimension, metres
663 | parseFixedLengthMeasure("332", fourthNumber, "WIDTH (m), log", "MTR");
664 | break;
665 | case "3":
666 | // Depth, thickness, height, or third dimension, metres
667 | parseFixedLengthMeasure("333", fourthNumber, "HEIGHT (m), log", "MTR");
668 | break;
669 | case "4":
670 | // Area, square metres
671 | parseFixedLengthMeasure("334", fourthNumber, "AREA (m2), log", "MTK");
672 | break;
673 | case "5":
674 | // Logistic volume, litres
675 | parseFixedLengthMeasure("335", fourthNumber, "VOLUME (l), log", "LTR");
676 | break;
677 | case "6":
678 | // Logistic volume, cubic metres
679 | parseFixedLengthMeasure("336", fourthNumber, "VOLUME (m3), log", "MTQ");
680 | break;
681 | case "7":
682 | // Kilograms per square metre, yes, the ISO code for this _is_ "28".
683 | parseFixedLengthMeasure("337", fourthNumber, "KG PER m²", "28");
684 | break;
685 | default:
686 | throw "08";
687 | }
688 | break;
689 | case "4":
690 | // third and fourth numbers matter:
691 | thirdNumber = codestring.slice(2, 3);
692 | fourthNumber = codestring.slice(3, 4);
693 |
694 | switch (thirdNumber) {
695 | case "0":
696 | // Logistic weight, pounds
697 | parseFixedLengthMeasure("340", fourthNumber, "GROSS WEIGHT (lb)", "LBR");
698 | break;
699 | case "1":
700 | // Length or first dimension, inches
701 | parseFixedLengthMeasure("341", fourthNumber, "LENGTH (i), log", "INH");
702 | break;
703 | case "2":
704 | // Length or first dimension, feet
705 | parseFixedLengthMeasure("342", fourthNumber, "LENGTH (f), log", "FOT");
706 | break;
707 | case "3":
708 | // Length or first dimension, yards
709 | parseFixedLengthMeasure("343", fourthNumber, "LENGTH (y), log", "YRD");
710 | break;
711 | case "4":
712 | // Width, diameter, or second dimension, inches
713 | parseFixedLengthMeasure("344", fourthNumber, "WIDTH (i), log", "INH");
714 | break;
715 | case "5":
716 | // Width, diameter, or second dimension, feet
717 | parseFixedLengthMeasure("345", fourthNumber, "WIDTH (f), log", "FOT");
718 | break;
719 | case "6":
720 | // Width, diameter, or second dimension, yard
721 | parseFixedLengthMeasure("346", fourthNumber, "WIDTH (y), log", "YRD");
722 | break;
723 | case "7":
724 | // Depth, thickness, height, or third dimension, inches
725 | parseFixedLengthMeasure("347", fourthNumber, "HEIGHT (i), log", "INH");
726 | break;
727 | case "8":
728 | // Depth, thickness, height, or third dimension, feet
729 | parseFixedLengthMeasure("348", fourthNumber, "HEIGHT (f), log", "FOT");
730 | break;
731 | case "9":
732 | // Depth, thickness, height, or third dimension, yards
733 | parseFixedLengthMeasure("349", fourthNumber, "HEIGHT (y), log", "YRD");
734 | break;
735 | default:
736 | throw "09";
737 | }
738 | break;
739 | case "5":
740 | // third and fourth numbers matter:
741 | thirdNumber = codestring.slice(2, 3);
742 | fourthNumber = codestring.slice(3, 4);
743 |
744 | switch (thirdNumber) {
745 | case "0":
746 | // Area, square inches (Variable Measure Trade Item)
747 | parseFixedLengthMeasure("350", fourthNumber, "AREA (i2)", "INK");
748 | break;
749 | case "1":
750 | // Area, square feet (Variable Measure Trade Item)
751 | parseFixedLengthMeasure("351", fourthNumber, "AREA (f2)", "FTK");
752 | break;
753 | case "2":
754 | // Area, square yards (Variable Measure Trade Item)
755 | parseFixedLengthMeasure("352", fourthNumber, "AREA (y2)", "YDK");
756 | break;
757 | case "3":
758 | // Area, square inches
759 | parseFixedLengthMeasure("353", fourthNumber, "AREA (i2), log", "INK");
760 | break;
761 | case "4":
762 | // Area, square feet
763 | parseFixedLengthMeasure("354", fourthNumber, "AREA (f2), log", "FTK");
764 | break;
765 | case "5":
766 | // Area, square yards
767 | parseFixedLengthMeasure("355", fourthNumber, "AREA (y2), log", "YDK");
768 | break;
769 | case "6":
770 | // Net weight, troy ounces (Variable Measure Trade Item)
771 | parseFixedLengthMeasure("356", fourthNumber, "NET WEIGHT (t)", "APZ");
772 | break;
773 | case "7":
774 | // Net weight (or volume), ounces (Variable Measure Trade Item)
775 | parseFixedLengthMeasure("357", fourthNumber, "NET VOLUME (oz)", "ONZ");
776 | break;
777 | default:
778 | throw "10";
779 | }
780 | break;
781 | case "6":
782 | // third and fourth numbers matter:
783 | thirdNumber = codestring.slice(2, 3);
784 | fourthNumber = codestring.slice(3, 4);
785 |
786 | switch (thirdNumber) {
787 | case "0":
788 | // Net volume, quarts (Variable Measure Trade Item)
789 | parseFixedLengthMeasure("360", fourthNumber, "NET VOLUME (q)", "QT");
790 | break;
791 | case "1":
792 | // Net volume, gallons U.S. (Variable Measure Trade Item)
793 | parseFixedLengthMeasure("361", fourthNumber, "NET VOLUME (g)", "GLL");
794 | break;
795 | case "2":
796 | // Logistic volume, quarts
797 | parseFixedLengthMeasure("362", fourthNumber, "VOLUME (q), log", "QT");
798 | break;
799 | case "3":
800 | // Logistic volume, gallons U.S.
801 | parseFixedLengthMeasure("363", fourthNumber, "VOLUME (g), log", "GLL");
802 | break;
803 | case "4":
804 | // Net volume, cubic inches (Variable Measure Trade Item)
805 | parseFixedLengthMeasure("364", fourthNumber, "VOLUME (i3)", "INQ");
806 | break;
807 | case "5":
808 | // Net volume, cubic feet (Variable Measure Trade Item)
809 | parseFixedLengthMeasure("365", fourthNumber, "VOLUME (f3)", "FTQ");
810 | break;
811 | case "6":
812 | // Net volume, cubic yards (Variable Measure Trade Item)
813 | parseFixedLengthMeasure("366", fourthNumber, "VOLUME (y3)", "YDQ");
814 | break;
815 | case "7":
816 | // Logistic volume, cubic inches
817 | parseFixedLengthMeasure("367", fourthNumber, "VOLUME (i3), log", "INQ");
818 | break;
819 | case "8":
820 | // Logistic volume, cubic feet
821 | parseFixedLengthMeasure("368", fourthNumber, "VOLUME (f3), log", "FTQ");
822 | break;
823 | case "9":
824 | // Logistic volume, cubic yards
825 | parseFixedLengthMeasure("369", fourthNumber, "VOLUME (y3), log", "YDQ");
826 | break;
827 | default:
828 | throw "11";
829 | }
830 | break;
831 | case "7":
832 | // Count of Trade Items
833 | parseVariableLength("37", "COUNT");
834 | break;
835 | // AI "38" isn't defined
836 | case "9":
837 | // third and fourth numbers matter:
838 | thirdNumber = codestring.slice(2, 3);
839 | fourthNumber = codestring.slice(3, 4);
840 |
841 | switch (thirdNumber) {
842 | case "0":
843 | // Applicable Amount Payable, local currency
844 | parseVariableLengthMeasure("390", fourthNumber, "AMOUNT", "");
845 | break;
846 | case "1":
847 | // Applicable Amount Payable with ISO Currency Code
848 | parseVariableLengthWithISONumbers("391", fourthNumber, "AMOUNT");
849 | break;
850 | case "2":
851 | // Applicable Amount Payable, single monetary area (Variable Measure Trade Item)
852 | parseVariableLengthMeasure("392", fourthNumber, "PRICE", "");
853 | break;
854 | case "3":
855 | // Applicable Amount Payable with ISO Currency Code (Variable Measure Trade Item)
856 | parseVariableLengthWithISONumbers("393", fourthNumber, "PRICE");
857 | break;
858 | case "4":
859 | // Percentage discount of a coupon
860 | parseVariableLengthMeasure("394", fourthNumber, "COUPON DISCOUNT", "PRCNT OFF");
861 | break;
862 | case "5":
863 | // Amount Payable per unit of measure single monetary area (variable measure trade item)
864 | parseVariableLengthMeasure("395", fourthNumber, "AMOUNT PAYABLE", "PRICE/UoM");
865 | break;
866 | default:
867 | throw "12";
868 | }
869 | break;
870 | default:
871 | throw "13";
872 | }
873 | break;
874 | case "4":
875 | switch (secondNumber) {
876 | case "0":
877 | // third number matters:
878 | thirdNumber = codestring.slice(2, 3);
879 | switch (thirdNumber) {
880 | case "0":
881 | // Customer's Purchase Order Number
882 | parseVariableLength("400", "ORDER NUMBER");
883 | break;
884 | case "1":
885 | // Global Identification Number for Consignment (GINC)
886 | parseVariableLength("401", "GINC");
887 | break;
888 | case "2":
889 | // Global Shipment Identification Number (GSIN)
890 | parseVariableLength("402", "GSIN"); // should be 17 digits long
891 | break;
892 | case "3":
893 | // Routing Code
894 | parseVariableLength("403", "ROUTE");
895 | break;
896 | default:
897 | throw "14";
898 | }
899 | break;
900 | case "1":
901 | //third number matters:
902 | thirdNumber = codestring.slice(2, 3);
903 | switch (thirdNumber) {
904 | case "0":
905 | // Ship to - Deliver to Global Location Number
906 | parseFixedLength("410", "SHIP TO LOC", 13);
907 | break;
908 | case "1":
909 | // Bill to - Invoice to Global Location Number
910 | parseFixedLength("411", "BILL TO", 13);
911 | break;
912 | case "2":
913 | // Purchased from Global Location Number
914 | parseFixedLength("412", "PURCHASE FROM", 13);
915 | break;
916 | case "3":
917 | // Ship for - Deliver for - Forward to Global Location Number
918 | parseFixedLength("413", "SHIP FOR LOC", 13);
919 | break;
920 | case "4":
921 | // Ship for - Deliver for - Forward to Global Location Number
922 | parseFixedLength("414", "LOC No", 13);
923 | break;
924 | case "5":
925 | // Ship for - Deliver for - Forward to Global Location Number
926 | parseFixedLength("415", "PAY TO", 13);
927 | break;
928 | case "6":
929 | // Ship for - Deliver for - Forward to Global Location Number
930 | parseFixedLength("416", "PROD/SERV LOC", 13);
931 | break;
932 | case "7":
933 | // Ship for - Deliver for - Forward to Global Location Number
934 | parseFixedLength("417", "PARTY", 13);
935 | break;
936 | default:
937 | throw "15";
938 | }
939 | break;
940 | case "2":
941 | //third number matters:
942 | thirdNumber = codestring.slice(2, 3);
943 | switch (thirdNumber) {
944 | case "0":
945 | // Ship to - Deliver to Postal Code Within a Single Postal Authority
946 | parseVariableLength("420", "SHIP TO POST");
947 | break;
948 | case "1":
949 | // Ship to - Deliver to Postal Code with ISO Country Code
950 | parseVariableLengthWithISOChars("421", "SHIP TO POST");
951 | break;
952 | case "2":
953 | // Country of Origin of a Trade Item
954 | parseFixedLength("422", "ORIGIN", 3);
955 | break;
956 | case "3":
957 | // Country of Initial Processing
958 | // Up to 5 3-digit ISO-countrycodes
959 | parseVariableLength("423", "COUNTRY - INITIAL PROCESS.");
960 | break;
961 | case "4":
962 | // Country of Processing
963 | parseFixedLength("424", "COUNTRY - PROCESS.", 3);
964 | break;
965 | case "5":
966 | // Country of Disassembly
967 | parseFixedLength("425", "COUNTRY - DISASSEMBLY", 3);
968 | break;
969 | case "6":
970 | // Country Covering full Process Chain
971 | parseFixedLength("426", "COUNTRY – FULL PROCESS", 3);
972 | break;
973 | case "7":
974 | // Country Subdivision of Origin
975 | parseVariableLength("427", "ORIGIN SUBDIVISION");
976 | break;
977 | default:
978 | throw "16";
979 | }
980 | break;
981 |
982 | case "3":
983 | //third and fourth number matter:
984 | thirdNumber = codestring.slice(2, 3);
985 | fourthNumber = codestring.slice(3, 4);
986 |
987 | switch (thirdNumber) {
988 | case "0":
989 | switch (fourthNumber) {
990 | case "0":
991 | // Ship-to - Deliver-to Company name
992 | parseVariableLength("4300", "SHIP TO COMP");
993 | break;
994 | case "1":
995 | // Ship-to - Deliver-to contact name
996 | parseVariableLength("4301", "SHIP TO NAME");
997 | break;
998 | case "2":
999 | // Ship-to - Deliver-to address line 1
1000 | parseVariableLength("4302", "SHIP TO ADD1");
1001 | break;
1002 | case "3":
1003 | // Ship-to - Deliver-to address line 2
1004 | parseVariableLength("4303", "SHIP TO ADD2");
1005 | break;
1006 | case "4":
1007 | // Ship-to - Deliver-to suburb
1008 | parseVariableLength("4304", "SHIP TO SUB");
1009 | break;
1010 | case "5":
1011 | // Ship-to - Deliver-to locality
1012 | parseVariableLength("4305", "SHIP TO LOC");
1013 | break;
1014 | case "6":
1015 | // Ship-to - Deliver-to region
1016 | parseVariableLength("4306", "SHIP TO REG");
1017 | break;
1018 | case "7":
1019 | // Ship-to - Deliver-to country code
1020 | parseFixedLength("4307", "SHIP TO COUNTRY", 2);
1021 | break;
1022 | case "8":
1023 | // Ship-to - Deliver-to telephone number
1024 | parseVariableLength("4308", "SHIP TO PHONE");
1025 | break;
1026 | case "9":
1027 | // Ship-to - Deliver-to GEO location
1028 | parseFixedLength("4309", "SHIP TO GEO", 20);
1029 | break;
1030 | }
1031 | break;
1032 |
1033 | case "1":
1034 | switch (fourthNumber) {
1035 | case "0":
1036 | // Return-to company name
1037 | parseVariableLength("4310", "RTN TO COMP");
1038 | break;
1039 | case "1":
1040 | // Return-to contact name
1041 | parseVariableLength("4311", "RTN TO NAME");
1042 | break;
1043 | case "2":
1044 | // Return-to address line 1
1045 | parseVariableLength("4312", "RTN TO ADD1");
1046 | break;
1047 | case "3":
1048 | // Return-to address line 2
1049 | parseVariableLength("4313", "RTN TO ADD2");
1050 | break;
1051 | case "4":
1052 | // Return-to suburb
1053 | parseVariableLength("4314", "RTN TO SUB");
1054 | break;
1055 | case "5":
1056 | // Return-to locality
1057 | parseVariableLength("4315", "RTN TO LOC");
1058 | break;
1059 | case "6":
1060 | // Return-to region
1061 | parseVariableLength("4316", "RTN TO REG");
1062 | break;
1063 | case "7":
1064 | // Return-to country code
1065 | parseFixedLength("4317", "RTN TO COUNTRY", 2);
1066 | break;
1067 | case "8":
1068 | // Return-to postal code
1069 | parseVariableLength("4318", "RTN TO POST");
1070 | break;
1071 | case "9":
1072 | // Return-to telephone number
1073 | parseVariableLength("4319", "RTN TO PHONE");
1074 | break;
1075 | }
1076 | break;
1077 |
1078 | case "2":
1079 | switch (fourthNumber) {
1080 | case "0":
1081 | // Service code description
1082 | parseVariableLength("4320", "SRV DESCRIPTION");
1083 | break;
1084 | case "1":
1085 | // Dangerous goods flag
1086 | parseFixedLength("4321", "DANGEROUS GOODS", 1);
1087 | break;
1088 | case "2":
1089 | // Authority to leave
1090 | parseFixedLength("4322", "AUTH LEAVE", 1);
1091 | break;
1092 | case "3":
1093 | // Signature required flag
1094 | parseFixedLength("4323", "SIG REQUIRED", 1);
1095 | break;
1096 | case "4":
1097 | // Not before delivery date time
1098 | parseVariableLength("4324", "NBEF DEL DT");
1099 | break;
1100 | case "5":
1101 | // Not after delivery date time
1102 | parseVariableLength("4325", "NAFT DEL DT");
1103 | break;
1104 | case "6":
1105 | // Release date
1106 | parseDate("4326", "REL DATE");
1107 | break;
1108 | }
1109 | break;
1110 |
1111 | case "3":
1112 | switch (fourthNumber) {
1113 | case "0":
1114 | // Maximum temperature in Fahrenheit
1115 | parseVariableLength("4330", "MAX TEMP F");
1116 | break;
1117 | case "1":
1118 | // Maximum temperature in Celsius
1119 | parseVariableLength("4331", "MAX TEMP C");
1120 | break;
1121 | case "2":
1122 | // Minimum temperature in Fahrenheit
1123 | parseVariableLength("4332", "MIN TEMP F");
1124 | break;
1125 | case "3":
1126 | // Minimum temperature in Celsius
1127 | parseVariableLength("4333", "MIN TEMP C");
1128 | break;
1129 | }
1130 | break;
1131 |
1132 | default:
1133 | throw "16";
1134 | }
1135 |
1136 | default:
1137 | throw "17";
1138 | }
1139 | break;
1140 | // first digits 5 and 6 are not used
1141 | case "7":
1142 | switch (secondNumber) {
1143 | case "0":
1144 | //third and fourth number matter:
1145 | thirdNumber = codestring.slice(2, 3);
1146 | fourthNumber = codestring.slice(3, 4);
1147 |
1148 | switch (thirdNumber) {
1149 | case "0":
1150 | switch (fourthNumber) {
1151 | case "1":
1152 | // NATO Stock Number (NSN)
1153 | parseVariableLength("7001", "NSN"); //should be 13 digits long
1154 | break;
1155 | case "2":
1156 | // UN/ECE Meat Carcasses and Cuts Classification
1157 | parseVariableLength("7002", "MEAT CUT");
1158 | break;
1159 | case "3":
1160 | // Expiration Date and Time
1161 | parseVariableLength("7003", "EXPIRY TIME"); //should be 10 digits long
1162 | break;
1163 | case "4":
1164 | // Active Potency
1165 | parseVariableLength("7004", "ACTIVE POTENCY");
1166 | break;
1167 | case "5":
1168 | // Catch area
1169 | parseVariableLength("7005", "CATCH AREA");
1170 | break;
1171 | case "6":
1172 | // First freeze date
1173 | parseDate("7006", "FIRST FREEZE DATE");
1174 | break;
1175 | case "7":
1176 | // Harvest date
1177 | parseDate("7007", "HARVEST DATE");
1178 | break;
1179 | case "8":
1180 | // Species for fishery purposes
1181 | parseVariableLength("7008", "AQUATIC SPECIES");
1182 | break;
1183 | case "9":
1184 | // Fishing gear type
1185 | parseVariableLength("7009", "FISHING GEAR TYPE");
1186 | break;
1187 | default:
1188 | throw "18";
1189 | }
1190 | break;
1191 | case "1":
1192 | switch (fourthNumber) {
1193 | case "0":
1194 | // Production method
1195 | parseVariableLength("7010", "PROD METHOD");
1196 | break;
1197 | }
1198 | case "1":
1199 | // Test by date (and optional time)
1200 | parseVariableLength("7011", "TEST BY DATE");
1201 | break;
1202 | }
1203 | break;
1204 | case "2":
1205 | switch (fourthNumber) {
1206 | case "0":
1207 | // Refurbishment lot ID
1208 | parseVariableLength("7020", "REFURB LOT");
1209 | break;
1210 | case "1":
1211 | // Functional status
1212 | parseVariableLength("7021", "FUNC STAT");
1213 | break;
1214 | case "2":
1215 | // Revision status
1216 | parseVariableLength("7022", "REV STAT");
1217 | break;
1218 | case "3":
1219 | // Global Individual Asset Identifier (GIAI) of an assembly
1220 | parseVariableLength("7023", "GIAI - ASSEMBLY");
1221 | break;
1222 | }
1223 | break;
1224 | case "3":
1225 | // Approval Number of Processor with ISO Country Code
1226 |
1227 | // Title and stem for parsing are build from 4th number:
1228 |
1229 | parseVariableLengthWithISOChars("703" + fourthNumber, "PROCESSOR # " + fourthNumber);
1230 | break;
1231 | case "4":
1232 | switch (fourthNumber) {
1233 | case "0":
1234 | // GS1 UIC with Extension 1 and Importer index
1235 | parseFixedLength("7040", "UIC+EXT", 4);
1236 | break;
1237 | }
1238 | break;
1239 |
1240 | default:
1241 | throw "19";
1242 | }
1243 | break;
1244 |
1245 | case "1":
1246 | thirdNumber = codestring.slice(2, 3);
1247 | switch (thirdNumber) {
1248 | case "0":
1249 | // National Healthcare Reimbursement Number (NHRN) – Germany PZN
1250 | parseVariableLength("710", "NHRN PZN");
1251 | break;
1252 | case "1":
1253 | // National Healthcare Reimbursement Number (NHRN) – France CIP
1254 | parseVariableLength("711", "NHRN CIP");
1255 | break;
1256 | case "2":
1257 | // National Healthcare Reimbursement Number (NHRN) – Spain CN
1258 | parseVariableLength("712", "NHRN CN");
1259 | break;
1260 | case "3":
1261 | // National Healthcare Reimbursement Number (NHRN) – Brasil DRN
1262 | parseVariableLength("713", "NHRN DRN");
1263 | break;
1264 | case "4":
1265 | // National Healthcare Reimbursement Number (NHRN) - Portugal AIM
1266 | parseVariableLength("714", "NHRN PT");
1267 | break;
1268 | case "5":
1269 | // National Healthcare Reimbursement Number (NHRN) - United States of America NDC
1270 | parseVariableLength("715", "NHRN NDC");
1271 | break;
1272 | default:
1273 | throw "20";
1274 | }
1275 | break;
1276 |
1277 | case "2":
1278 | //third and fourth number matter:
1279 | thirdNumber = codestring.slice(2, 3);
1280 | fourthNumber = codestring.slice(3, 4);
1281 |
1282 | switch (thirdNumber) {
1283 | // 0, 1 and 2 are unused
1284 | case "3":
1285 | // Certification reference
1286 | parseVariableLength("723" + fourthNumber, "CERT # " + fourthNumber);
1287 | break;
1288 | case "4":
1289 | switch (fourthNumber) {
1290 | case "0":
1291 | // Protocol ID
1292 | parseVariableLength("7240", "PROTOCOL");
1293 | break;
1294 | case "1":
1295 | // AIDC media type
1296 | parseFixedLength("7241", "AIDC MEDIA TYPE", 2);
1297 | break;
1298 | case "2":
1299 | // Version Control Number (VCN)
1300 | parseVariableLength("7242", "VCN");
1301 | break;
1302 | }
1303 | default:
1304 | throw "21";
1305 | }
1306 | break;
1307 | case "8":
1308 | switch (secondNumber) {
1309 | case "0":
1310 | thirdNumber = codestring.slice(2, 3);
1311 | fourthNumber = codestring.slice(3, 4);
1312 |
1313 | switch (thirdNumber) {
1314 | case "0":
1315 | switch (fourthNumber) {
1316 | case "1":
1317 | // Roll Products (Width, Length, Core Diameter, Direction, Splices)
1318 | parseVariableLength("8001", "DIMENSIONS"); // should be 14 digits long
1319 | break;
1320 | case "2":
1321 | // Cellular Mobile Telephone Identifier
1322 | parseVariableLength("8002", "CMT No");
1323 | break;
1324 | case "3":
1325 | // Global Returnable Asset Identifier (GRAI)
1326 | parseVariableLength("8003", "GRAI"); // should contain at least 14 digits
1327 | break;
1328 | case "4":
1329 | // Global Individual Asset Identifier (GIAI)
1330 | parseVariableLength("8004", "GIAI");
1331 | break;
1332 | case "5":
1333 | // Price Per Unit of Measure
1334 | parseVariableLength("8005", "PRICE PER UNIT"); // should be 6 digits long
1335 | break;
1336 | case "6":
1337 | // Identification of the Components of a Trade Item
1338 | parseVariableLength("8006", "GCTIN"); // should be exactly 18 digits long
1339 | break;
1340 | case "7":
1341 | // International Bank Account Number (IBAN)
1342 | parseVariableLength("8007", "IBAN");
1343 | break;
1344 | case "8":
1345 | // Date and Time of Production
1346 | parseVariableLength("8008", "PROD TIME"); // should be exactly 12 digits long
1347 | break;
1348 | case "9":
1349 | // Optically Readable Sensor Indicator
1350 | parseVariableLength("8009", "OPTSEN");
1351 | break;
1352 | default:
1353 | throw "22";
1354 | }
1355 | break;
1356 | case "1":
1357 | switch (fourthNumber) {
1358 | case "0":
1359 | // Component / Part Identifier (CPID)
1360 | parseVariableLength("8010", "CPID");
1361 | break;
1362 | case "1":
1363 | // Component / Part Identifier Serial Number (CPID SERIAL)
1364 | parseVariableLength("8011", "CPID SERIAL");
1365 | break;
1366 | case "2":
1367 | // Software version
1368 | parseVariableLength("8012", "VERSION");
1369 | break;
1370 | case "3":
1371 | // Global Model Number (GMN)
1372 | parseVariableLength("8013", "GMN");
1373 | break;
1374 |
1375 | // 4, 5 and 6 are unused
1376 |
1377 | case "7":
1378 | // Global Service Relation Number to identify the relationship between an organisation offering services and the provider of services
1379 | parseVariableLength("8017", "GSRN - PROVIDER"); // should be 18 digits long
1380 | break;
1381 | case "8":
1382 | // Global Service Relation Number to identify the relationship between an organisation offering services and the recipient of services
1383 | parseVariableLength("8018", "GSRN - RECIPIENT"); // should be 18 digits long
1384 | break;
1385 | case "9":
1386 | // Service Relation Instance Number (SRIN)
1387 | parseVariableLength("8019", "SRIN");
1388 | break;
1389 | default:
1390 | throw "23";
1391 | }
1392 | break;
1393 | case "2":
1394 | switch (fourthNumber) {
1395 | case "0":
1396 | // Payment Slip Reference Number
1397 | parseVariableLength("8020", "REF No");
1398 | break;
1399 |
1400 | // 1, 2, 3, 4 and 5 are unused
1401 |
1402 | case "6":
1403 | // Identification of pieces of a trade item (ITIP) contained in a logistic unit
1404 | parseFixedLength("8026", "ITIP CONTENT", 14 + 2 + 2);
1405 | break;
1406 | default:
1407 | throw "24";
1408 | }
1409 | break;
1410 |
1411 | case "3":
1412 | switch (fourthNumber) {
1413 | case "0":
1414 | // Digital Signature (DigSig)
1415 | parseVariableLength("8030", "DIGSIG");
1416 | break;
1417 | }
1418 | break;
1419 |
1420 | default:
1421 | throw "25";
1422 | }
1423 | break;
1424 | case "1":
1425 | thirdNumber = codestring.slice(2, 3);
1426 | fourthNumber = codestring.slice(3, 4);
1427 | switch (thirdNumber) {
1428 | case "0":
1429 | switch (fourthNumber) {
1430 | case "0":
1431 | // GS1-128 Coupon Extended Code
1432 | parseVariableLength("8100", "-"); //should be 6 digits long
1433 | break;
1434 | case "1":
1435 | // GS1-128 Coupon Extended Code
1436 | parseVariableLength("8101", "-"); //should be 10 digits long
1437 | break;
1438 | case "2":
1439 | // GS1-128 Coupon Extended Code
1440 | parseVariableLength("8102", "-"); //should be 2 digits long
1441 | break;
1442 | default:
1443 | throw "26";
1444 | }
1445 | break;
1446 | case "1":
1447 | switch (fourthNumber) {
1448 | case "0":
1449 | // Coupon Code Identification for Use in North America
1450 | parseVariableLength("8110", "-");
1451 | break;
1452 | default:
1453 | throw "27";
1454 | }
1455 | break;
1456 | default:
1457 | throw "28";
1458 | }
1459 | break;
1460 | case "2":
1461 | thirdNumber = codestring.slice(2, 3);
1462 | switch (thirdNumber) {
1463 | case "0":
1464 | // Extended Packaging URL
1465 | parseVariableLength("8200", "PRODUCT URL");
1466 | break;
1467 | default:
1468 | throw "29";
1469 | }
1470 | break;
1471 | default:
1472 | throw "30";
1473 | }
1474 | break;
1475 | case "9":
1476 | switch (secondNumber) {
1477 | case "0":
1478 | // Information Mutually Agreed Between Trading Partners
1479 | parseVariableLength("90", "INTERNAL");
1480 | break;
1481 | case "1":
1482 | // Company Internal Information
1483 | parseVariableLength("91", "INTERNAL");
1484 | break;
1485 | case "2":
1486 | // Company Internal Information
1487 | parseVariableLength("92", "INTERNAL");
1488 | break;
1489 | case "3":
1490 | // Company Internal Information
1491 | parseVariableLength("93", "INTERNAL");
1492 | break;
1493 | case "4":
1494 | // Company Internal Information
1495 | parseVariableLength("94", "INTERNAL");
1496 | break;
1497 | case "5":
1498 | // Company Internal Information
1499 | parseVariableLength("95", "INTERNAL");
1500 | break;
1501 | case "6":
1502 | // Company Internal Information
1503 | parseVariableLength("96", "INTERNAL");
1504 | break;
1505 | case "7":
1506 | // Company Internal Information
1507 | parseVariableLength("97", "INTERNAL");
1508 | break;
1509 | case "8":
1510 | // Company Internal Information
1511 | parseVariableLength("98", "INTERNAL");
1512 | break;
1513 | case "9":
1514 | // Company Internal Information
1515 | parseVariableLength("99", "INTERNAL");
1516 | break;
1517 | default:
1518 | throw "31";
1519 | }
1520 | break;
1521 | default:
1522 | throw "32";
1523 | }
1524 | /**
1525 | *
1526 | * ======= END of the big switch =======================
1527 | *
1528 | * now identifyAI has just to return the new
1529 | * ParsedElement (create by one of the parsing
1530 | * functions) and the (cleaned) rest of codestring.
1531 | */
1532 |
1533 | return ({
1534 | element: elementToReturn,
1535 | codestring: cleanCodestring(codestringToReturn)
1536 | });
1537 | }
1538 |
1539 | /**
1540 | *
1541 | * =========== END of identifyAI =======================
1542 | *
1543 | */
1544 |
1545 | /**
1546 | * =========== BEGIN of main routine ===================
1547 | */
1548 |
1549 | /**
1550 | *
1551 | * ==== First step: ====
1552 | *
1553 | * IF there is any symbology identifier
1554 | * chop it off;
1555 | * put as "codeName" into the answer;
1556 | * fill restOfBarcode with the rest
1557 | * after the symbology identifier;
1558 | * ELSE
1559 | * leave "codeName" empty;
1560 | * put the whole barcode into restOfBarcode;
1561 | */
1562 |
1563 | switch (symbologyIdentifier) {
1564 | case "]C1":
1565 | answer.codeName = "GS1-128";
1566 | restOfBarcode = barcode.slice(3, barcodelength);
1567 | break;
1568 | case "]e0":
1569 | answer.codeName = "GS1 DataBar";
1570 | restOfBarcode = barcode.slice(3, barcodelength);
1571 | break;
1572 | case "]e1":
1573 | answer.codeName = "GS1 Composite";
1574 | restOfBarcode = barcode.slice(3, barcodelength);
1575 | break;
1576 | case "]e2":
1577 | answer.codeName = "GS1 Composite";
1578 | restOfBarcode = barcode.slice(3, barcodelength);
1579 | break;
1580 | case "]d2":
1581 | answer.codeName = "GS1 DataMatrix";
1582 | restOfBarcode = barcode.slice(3, barcodelength);
1583 | break;
1584 | case "]Q3":
1585 | answer.codeName = "GS1 QR Code";
1586 | restOfBarcode = barcode.slice(3, barcodelength);
1587 | break;
1588 | default:
1589 | answer.codeName = "";
1590 | restOfBarcode = barcode;
1591 | break;
1592 | }
1593 |
1594 | /**
1595 | * we have chopped off any symbology identifier. Now we can
1596 | * try to parse the rest. It should give us an array of
1597 | * ParsedElements.
1598 | */
1599 |
1600 | /**
1601 | * ===== Second step: ====
1602 | *
1603 | * Parse "barcode" data element by data element using
1604 | * identifyAI.
1605 | *
1606 | */
1607 |
1608 | answer.parsedCodeItems = [];
1609 |
1610 | /**
1611 | * The follwoing part calls "identifyAI" in a loop, until
1612 | * the whole barcode is parsed (or an error occurs).
1613 | *
1614 | * It uses the following strategy:
1615 | *
1616 | * try to parse the part after the symbology identifier:
1617 | * - identify the first AI;
1618 | * - make a parsed element from the part after the AI;
1619 | * - append the parsed element to answer;
1620 | * - chop off the parsed part;
1621 | * do so while there is left something to parse;
1622 | */
1623 |
1624 | while (restOfBarcode.length > 0) {
1625 | try {
1626 | firstElement = identifyAI(restOfBarcode);
1627 | restOfBarcode = firstElement.codestring;
1628 | answer.parsedCodeItems.push(firstElement.element);
1629 | } catch (e) {
1630 | switch (e) {
1631 | case "01":
1632 | throw "invalid AI after '0'";
1633 | case "02":
1634 | throw "invalid AI after '1'";
1635 | case "03":
1636 | throw "invalid AI after '24'";
1637 | case "04":
1638 | throw "invalid AI after '25'";
1639 | case "05":
1640 | throw "invalid AI after '2'";
1641 | case "06":
1642 | throw "invalid AI after '31'";
1643 | case "07":
1644 | throw "invalid AI after '32'";
1645 | case "08":
1646 | throw "invalid AI after '33'";
1647 | case "09":
1648 | throw "invalid AI after '34'";
1649 | case "10":
1650 | throw "invalid AI after '35'";
1651 | case "11":
1652 | throw "invalid AI after '36'";
1653 | case "12":
1654 | throw "invalid AI after '39'";
1655 | case "13":
1656 | throw "invalid AI after '3'";
1657 | case "14":
1658 | throw "invalid AI after '40'";
1659 | case "15":
1660 | throw "invalid AI after '41'";
1661 | case "16":
1662 | throw "invalid AI after '42'";
1663 | case "17":
1664 | throw "invalid AI after '4'";
1665 | case "18":
1666 | throw "invalid AI after '700'";
1667 | case "19":
1668 | throw "invalid AI after '70'";
1669 | case "20":
1670 | throw "invalid AI after '71'";
1671 | case "21":
1672 | throw "invalid AI after '7'";
1673 | case "22":
1674 | throw "invalid AI after '800'";
1675 | case "23":
1676 | throw "invalid AI after '801'";
1677 | case "24":
1678 | throw "invalid AI after '802'";
1679 | case "25":
1680 | throw "invalid AI after '80'";
1681 | case "26":
1682 | throw "invalid AI after '810'";
1683 | case "27":
1684 | throw "invalid AI after '811'";
1685 | case "28":
1686 | throw "invalid AI after '81'";
1687 | case "29":
1688 | throw "invalid AI after '82'";
1689 | case "30":
1690 | throw "invalid AI after '8'";
1691 | case "31":
1692 | throw "invalid AI after '9'";
1693 | case "32":
1694 | throw "no valid AI";
1695 | case "33":
1696 | throw "invalid year in date";
1697 | case "34":
1698 | throw "invalid month in date";
1699 | case "35":
1700 | throw "invalid day in date";
1701 | case "36":
1702 | throw "invalid number";
1703 | default:
1704 | throw "unknown error";
1705 | }
1706 | }
1707 | }
1708 | /**
1709 | * ==== Third and last step: =====
1710 | *
1711 | */
1712 | return answer;
1713 | }
1714 | return parseBarcode;
1715 | }());
1716 |
1717 | if (typeof exports === 'object') {
1718 | exports.parseBarcode = parseBarcode;
1719 | }
1720 |
--------------------------------------------------------------------------------
/src/README_scripts.md:
--------------------------------------------------------------------------------
1 | # What's that script about?
2 |
3 | ## Table of Contents
4 |
5 | * [How it works](#how-it-works)
6 | * [The errors thrown](#the-errors-thrown)
7 | * [The ISO codes returned](#the-iso-codes-returned)
8 |
9 | ## How it works
10 |
11 | The script is a self executing anonymous function which gives you the variable (and function) `parseBarcode()`. This function accepts exactly one parameter: `barcode`, the string with the barcode data.
12 |
13 | The function starts declaring some variables:
14 |
15 | * the `fncChar`: simply the ASCII group separator, which is used for delimiting variable length data elements
16 | * the `answer`: declared as an empty object, it will be populated during the execution of the code
17 | * the `restOfBarcode`: the function will parse the `barcode` element by element. `restOfBarcode` holds, umm ... the rest, the part not parsed yet. At the end, it will be empty again.
18 | * the `symbologyIdentifier`: simply the three first characters of `barcode`. If the scanning device doesn't suppress them, they identify which barcode symbology (DataMatrix, GS1-128, QR code ...) was used.
19 | * the `firstElement`: initialized as empty, it holds the results of parsing the first element of `restOfBarcode`.
20 |
21 | Then two auxiliary functions are declared:
22 |
23 |
24 | * [the constructor ParsedElement](#the-constructor-parsed-element) and
25 | * [the function identifyAI](#the-function-identifyAI)
26 |
27 | The latter does the heavy lifting: it looks for the first digits of the `codestring` given to it as a parameter and tries to identify these first digits as a valid AI. If this succeeds, it will return an object consisting of
28 |
29 | * a new `ParsedElement` which contains the data followed by the identified AI and its interpretation and
30 | * the `codestring` **without** the part already interpreted
31 |
32 | If it doesn't succeed it throws an error.
33 |
34 | The main routine of `parseBarcode()` is pretty simple:
35 |
36 | 1. if there is a known `symbologyIdentifier` chop it off from `barcode`,
37 | 2. move the rest to `restOfBarcode`,
38 | 3. intialize `answer.parsedCodeItems` as an empty array
39 | 4. apply `identifyAI` to `restOfBarcode`,
40 | 5. collect the new `ParsedElement` returned by `identifyAI` into `answer.parsedCodeItems`
41 | 6. assign the (shortened) rest (also returned by `identifyAI`) to `restOfBarcode`
42 | 7. if `restOfBarcode` isn't empty yet go back to point 4.
43 | 8. Return the `answer`.
44 |
45 | ### the constructor ParsedElement
46 |
47 | This function constructs new *parsed elements*, the single elements which will form the returned array of interpreted data elements.
48 |
49 | ### the function identifyAI
50 |
51 | This function does the main work load: it tries to find a valid AI within the first digits of the `codestring` handled over to it and calls the approbiate parsing function to fill a new `ParsedElement`.
52 |
53 | It defines two local auxiliary functions:
54 |
55 | * cleanCodestring
56 | * parseFloatingPoint
57 |
58 | and seven parsing function for the seven types of data elements:
59 |
60 | * parseDate,
61 | * parseFixedLength,
62 | * parseVariableLength,
63 | * parseFixedLengthMeasure,
64 | * parseVariableLengthMeasure,
65 | * parseVariableLengthWithISONumbers and
66 | * parseVariableLengthWithISOChars.
67 |
68 | All of these seven function create a new `ParsedElement` and shorten the `codestring`, cutting off the part just parsed.
69 |
70 | You'll find some remarks about the single functions within the script.
71 |
72 | Finding a valid AI is done by a very big switch; cascading from the first digit to the second and if necaessary to the third and fourth. Once the cascade hits a valid AI, the approbiate parsing function is called with parameters approbiate for the AI.
73 |
74 | This *could* have been done by scanning some configuration object, with the AI as a key to the parsing functions and their parameters. On initialization this configuration object could have returned a curry-ed version of one of the parsing functions, which then would have been applied to the `codestring`.
75 |
76 | The construction of this configuration object would have been pretty complicated, and you would have had up to three loops over it: one for two-digit AIs, a second for three-digit AIs and perhaps a third round for four-digit AIs.
77 |
78 | So I decided to use the switch: it's long and deeply nested, but straightforward. Just jump from one decision to the next (max. three hops), find the parsing function ready for execution and you're done.
79 |
80 | ## The errors thrown
81 |
82 | If `identifyAI` encounters a problem it throws an error. The errors of `identifyAI` are simple numbered, catched by `parseBarcode` and transformed to an error message.
83 |
84 | The errors "01" to "31" represent an invalid AI in `codestring`, the message text indicates where the error occured. The error texts start with "invalid AI after".
85 |
86 | The errors "32" to "35" are thrown when a data element supposed to contain a valid date *doesn't* contain a valid date. The error texts have the form "invalid ... in date" with "..." as "year", "month" or "day".
87 |
88 | The error "36" is thrown when a data element supposed to contain a floating point number *doesn't* contain a floating point number.
89 |
90 | ## The ISO codes returned
91 |
92 | The ISO codes come either
93 |
94 | * from the data element itself or
95 | * are set by the parsing functions because they are implicit to the AI
96 |
97 | In the former case they are just handled through, without any checkings. For your convenience the used ISO codes:
98 |
99 | |common name | ISO code |
100 | |:-- |:--------------|
101 | | kilograms | KGM |
102 | | meter | MTR |
103 | | square metres | MTK |
104 | | liters | LTR |
105 | | cubic metres | MTQ |
106 | | pounds | LBR |
107 | | inches | INH |
108 | | feet | FOT |
109 | | yard | YRD |
110 | | kilograms per sqare meter | 28 |
111 | | square inches | INK |
112 | | square feet | FTK |
113 | | square yard | YDK |
114 | | troy ounces | APZ |
115 | | U.S. ounces | ONZ |
116 | | U.S. quarts | QT |
117 | | U.S. gallons | GLL |
118 | | cubic inches | INQ |
119 | | cubic feet | FTQ |
120 | | cubic yard | YDQ |
121 |
--------------------------------------------------------------------------------