Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(isEAN): implement isEAN validator #1244

Merged
merged 3 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Validator | Description
**isIP(str [, version])** | check if the string is an IP (version 4 or 6).
**isIPRange(str)** | check if the string is an IP Range(version 4 only).
**isISBN(str [, version])** | check if the string is an ISBN (version 10 or 13).
**isEAN(str)** | check if the string is an EAN (European Article Number).
**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier).
**isISO31661Alpha2(str)** | check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
**isISO31661Alpha3(str)** | check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ var _isCreditCard = _interopRequireDefault(require("./lib/isCreditCard"));

var _isIdentityCard = _interopRequireDefault(require("./lib/isIdentityCard"));

var _isEAN = _interopRequireDefault(require("./lib/isEAN"));

var _isISIN = _interopRequireDefault(require("./lib/isISIN"));

var _isISBN = _interopRequireDefault(require("./lib/isISBN"));
Expand Down Expand Up @@ -225,6 +227,7 @@ var validator = {
isIn: _isIn.default,
isCreditCard: _isCreditCard.default,
isIdentityCard: _isIdentityCard.default,
isEAN: _isEAN.default,
isISIN: _isISIN.default,
isISBN: _isISBN.default,
isISSN: _isISSN.default,
Expand Down
80 changes: 80 additions & 0 deletions lib/isEAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = isEAN;

var _assertString = _interopRequireDefault(require("./util/assertString"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/

/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/
var LENGTH_EAN_8 = 8;
var validEanRegex = /(?<!\d)(\d{8}|\d{13})(?!\d)$/;
/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/

function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return index % 2 === 0 ? 3 : 1;
}

return index % 2 === 0 ? 1 : 3;
}
/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/


function calculateCheckDigit(ean) {
var checksum = ean.slice(0, -1).split('').map(function (char, index) {
return Number(char) * getPositionWeightThroughLengthAndIndex(ean.length, index);
}).reduce(function (acc, partialSum) {
return acc + partialSum;
}, 0);
var remainder = 10 - checksum % 10;
return remainder < 10 ? remainder : 0;
}
/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/


function isEAN(str) {
(0, _assertString.default)(str);
var actualCheckDigit = Number(str.slice(-1));
return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}

module.exports = exports.default;
module.exports.default = exports.default;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import isIn from './lib/isIn';
import isCreditCard from './lib/isCreditCard';
import isIdentityCard from './lib/isIdentityCard';

import isEAN from './lib/isEAN';
import isISIN from './lib/isISIN';
import isISBN from './lib/isISBN';
import isISSN from './lib/isISSN';
Expand Down Expand Up @@ -160,6 +161,7 @@ const validator = {
isIn,
isCreditCard,
isIdentityCard,
isEAN,
isISIN,
isISBN,
isISSN,
Expand Down
70 changes: 70 additions & 0 deletions src/lib/isEAN.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/

import assertString from './util/assertString';

/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/
const LENGTH_EAN_8 = 8;
const validEanRegex = /(?<!\d)(\d{8}|\d{13})(?!\d)$/;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RegExp lookbehind assertions are an ES2018 feature. They are not supported in all platforms.
By the way, can't a much simpler regex like /^(\d{8}|\d{13})$/ do the trick?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tux-tn Yeah you're right, the /^(\d{8}|\d{13})$/ is sufficient to detect a consecutive sequence of 8-digits or 13-digits only ... Forgot about the lookbehind assertions being an ES2018 feature. Thanks! I've just modified it 👍



/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/
function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return (index % 2 === 0) ? 3 : 1;
}

return (index % 2 === 0) ? 1 : 3;
}

/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/
function calculateCheckDigit(ean) {
const checksum = ean
.slice(0, -1)
.split('')
.map((char, index) => Number(char) * getPositionWeightThroughLengthAndIndex(ean.length, index))
.reduce((acc, partialSum) => acc + partialSum, 0);

const remainder = 10 - (checksum % 10);

return remainder < 10 ? remainder : 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case where remainder equals 10 is uncovered in mocha tests. Can you add a test case?

}

/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/
export default function isEAN(str) {
assertString(str);
const actualCheckDigit = Number(str.slice(-1));

return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}
18 changes: 18 additions & 0 deletions test/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -3475,6 +3475,24 @@ describe('Validators', () => {
});
});

it('should validate EANs', () => {
test({
validator: 'isEAN',
valid: [
'9421023610112',
'1234567890128',
'4012345678901',
'9771234567003',
'73513537',
],
invalid: [
'5901234123451',
'079777681629',
'0705632085948',
],
});
});

it('should validate ISSNs', () => {
test({
validator: 'isISSN',
Expand Down
70 changes: 68 additions & 2 deletions validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
}(this, (function () { 'use strict';

function _typeof(obj) {
"@babel/helpers - typeof";

if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
Expand Down Expand Up @@ -1446,6 +1444,73 @@ function isIdentityCard(str, locale) {
throw new Error("Invalid locale '".concat(locale, "'"));
}

/**
* The most commonly used EAN standard is
* the thirteen-digit EAN-13, while the
* less commonly used 8-digit EAN-8 barcode was
* introduced for use on small packages.
* EAN consists of:
* GS1 prefix, manufacturer code, product code and check digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number
*/
/**
* Define EAN Lenghts; 8 for EAN-8; 13 for EAN-13
* and Regular Expression for valid EANs (EAN-8, EAN-13),
* with exact numberic matching of 8 or 13 digits [0-9]
*/

var LENGTH_EAN_8 = 8;
var validEanRegex = /(?<!\d)(\d{8}|\d{13})(?!\d)$/;
/**
* Get position weight given:
* EAN length and digit index/position
*
* @param {number} length
* @param {number} index
* @return {number}
*/

function getPositionWeightThroughLengthAndIndex(length, index) {
if (length === LENGTH_EAN_8) {
return index % 2 === 0 ? 3 : 1;
}

return index % 2 === 0 ? 1 : 3;
}
/**
* Calculate EAN Check Digit
* Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit
*
* @param {string} ean
* @return {number}
*/


function calculateCheckDigit(ean) {
var checksum = ean.slice(0, -1).split('').map(function (_char, index) {
return Number(_char) * getPositionWeightThroughLengthAndIndex(ean.length, index);
}).reduce(function (acc, partialSum) {
return acc + partialSum;
}, 0);
var remainder = 10 - checksum % 10;
return remainder < 10 ? remainder : 0;
}
/**
* Check if string is valid EAN:
* Matches EAN-8/EAN-13 regex
* Has valid check digit.
*
* @param {string} str
* @return {boolean}
*/


function isEAN(str) {
assertString(str);
var actualCheckDigit = Number(str.slice(-1));
return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str);
}

var isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/;
function isISIN(str) {
assertString(str);
Expand Down Expand Up @@ -2313,6 +2378,7 @@ var validator = {
isIn: isIn,
isCreditCard: isCreditCard,
isIdentityCard: isIdentityCard,
isEAN: isEAN,
isISIN: isISIN,
isISBN: isISBN,
isISSN: isISSN,
Expand Down
2 changes: 1 addition & 1 deletion validator.min.js

Large diffs are not rendered by default.