├── .gitignore ├── Barcodes ├── ExamplesForBarcodes.odt └── ExamplesForBarcodes.pdf ├── Example ├── BarcodeParserUsageExample.html ├── BarcodeParserUsageExample.js ├── ScannedBarcode.txt └── css │ └── BarcodeParser.css ├── Gruntfile.js ├── KeypressDetecting ├── KeypressDetecting.html ├── css │ └── KeypressDetecting.css └── scripts │ └── KeypressDetecting.js ├── LICENSE.md ├── README.md ├── package-lock.json ├── package.json ├── spec └── BarcodeParserSpec.js └── src ├── BarcodeParser.js └── README_scripts.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .grunt/ 4 | -------------------------------------------------------------------------------- /Barcodes/ExamplesForBarcodes.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximBelov/BarcodeParser/d6e0b2b3955551d79bf6ad54fc74d213f306a44b/Barcodes/ExamplesForBarcodes.odt -------------------------------------------------------------------------------- /Barcodes/ExamplesForBarcodes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaximBelov/BarcodeParser/d6e0b2b3955551d79bf6ad54fc74d213f306a44b/Barcodes/ExamplesForBarcodes.pdf -------------------------------------------------------------------------------- /Example/BarcodeParserUsageExample.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | GS1 barcode parser 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |

Example: parsing GS1 barcodes

36 |
37 |

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 |
34 | 35 | 36 |
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 | --------------------------------------------------------------------------------