Skip to content

Commit cdcf4a6

Browse files
committed
Update GS1 AI validations per 2025 specifications, closes #116
1 parent 7e7784a commit cdcf4a6

File tree

3 files changed

+117
-9
lines changed

3 files changed

+117
-9
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ and data.
155155

156156
### Recent Releases
157157

158+
#### Okapi Barcode 0.5.0
159+
- Update GS1 AI validations per 2025 specifications
160+
158161
#### Okapi Barcode 0.4.9
159162
- Codablock F: allow user to customize row height
160163
- Codablock F: allow user to customize module width

src/main/java/uk/org/okapibarcode/util/Gs1.java

+32-9
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ public final class Gs1 {
2828

2929
private static final byte DIGITS = 1; // 0-9
3030
private static final byte CHARS_82 = 2; // GS1 charset 82
31-
private static final byte CHARS_39 = 3; // GS1 charset 39
32-
private static final byte CHARS_UPPERCASE = 4; // A-Z
33-
private static final byte FLAG = 5; // 0-1
31+
private static final byte CHARS_64 = 3; // GS1 charset 64 (file-safe / URI-safe)
32+
private static final byte CHARS_39 = 4; // GS1 charset 39
33+
private static final byte CHARS_UPPERCASE = 5; // A-Z
34+
private static final byte FLAG = 6; // 0-1
35+
private static final byte MINUS = 7; // minus character ("-")
3436

3537
private static final Map< Integer, byte[] > AIS = new HashMap<>();
3638

3739
static {
3840
AIS.put(00, new byte[] { DIGITS, 18, 18 }); // N2+N18
3941
AIS.put(01, new byte[] { DIGITS, 14, 14 }); // N2+N14
4042
AIS.put(02, new byte[] { DIGITS, 14, 14 }); // N2+N14
43+
AIS.put(03, new byte[] { DIGITS, 14, 14 }); // N2+N14
4144
AIS.put(10, new byte[] { CHARS_82, 0, 20 }); // N2+X..20
4245
AIS.put(11, new byte[] { DIGITS, 6, 6 }); // N2+N6
4346
AIS.put(12, new byte[] { DIGITS, 6, 6 }); // N2+N6
@@ -428,10 +431,6 @@ public final class Gs1 {
428431
AIS.put(3953, new byte[] { DIGITS, 6, 6 }); // N4+N6
429432
AIS.put(3954, new byte[] { DIGITS, 6, 6 }); // N4+N6
430433
AIS.put(3955, new byte[] { DIGITS, 6, 6 }); // N4+N6
431-
AIS.put(3956, new byte[] { DIGITS, 6, 6 }); // N4+N6
432-
AIS.put(3957, new byte[] { DIGITS, 6, 6 }); // N4+N6
433-
AIS.put(3958, new byte[] { DIGITS, 6, 6 }); // N4+N6
434-
AIS.put(3959, new byte[] { DIGITS, 6, 6 }); // N4+N6
435434
AIS.put(400, new byte[] { CHARS_82, 0, 30 }); // N3+X..30
436435
AIS.put(401, new byte[] { CHARS_82, 0, 30 }); // N3+X..30
437436
AIS.put(402, new byte[] { DIGITS, 17, 17 }); // N3+N17
@@ -461,6 +460,7 @@ public final class Gs1 {
461460
AIS.put(4306, new byte[] { CHARS_82, 0, 70 }); // N4+X..70
462461
AIS.put(4307, new byte[] { CHARS_UPPERCASE, 2, 2 }); // N4+X2
463462
AIS.put(4308, new byte[] { CHARS_82, 0, 30 }); // N4+X..30
463+
AIS.put(4309, new byte[] { DIGITS, 20, 20 }); // N4+N20
464464
AIS.put(4310, new byte[] { CHARS_82, 0, 35 }); // N4+X..35
465465
AIS.put(4311, new byte[] { CHARS_82, 0, 35 }); // N4+X..35
466466
AIS.put(4312, new byte[] { CHARS_82, 0, 70 }); // N4+X..70
@@ -478,6 +478,10 @@ public final class Gs1 {
478478
AIS.put(4324, new byte[] { DIGITS, 10, 10 }); // N4+N10
479479
AIS.put(4325, new byte[] { DIGITS, 10, 10 }); // N4+N10
480480
AIS.put(4326, new byte[] { DIGITS, 6, 6 }); // N4+N6
481+
AIS.put(4330, new byte[] { DIGITS, 6, 6, MINUS, 0, 1 }); // N4+N6+[-]
482+
AIS.put(4331, new byte[] { DIGITS, 6, 6, MINUS, 0, 1 }); // N4+N6+[-]
483+
AIS.put(4332, new byte[] { DIGITS, 6, 6, MINUS, 0, 1 }); // N4+N6+[-]
484+
AIS.put(4333, new byte[] { DIGITS, 6, 6, MINUS, 0, 1 }); // N4+N6+[-]
481485
AIS.put(7001, new byte[] { DIGITS, 13, 13 }); // N4+N13
482486
AIS.put(7002, new byte[] { CHARS_82, 0, 30 }); // N4+X..30
483487
AIS.put(7003, new byte[] { DIGITS, 10, 10 }); // N4+N10
@@ -488,6 +492,7 @@ public final class Gs1 {
488492
AIS.put(7008, new byte[] { CHARS_82, 0, 3 }); // N4+X..3
489493
AIS.put(7009, new byte[] { CHARS_82, 0, 10 }); // N4+X..10
490494
AIS.put(7010, new byte[] { CHARS_82, 0, 2 }); // N4+X..2
495+
AIS.put(7011, new byte[] { DIGITS, 6, 10 }); // N4+N6..10
491496
AIS.put(7020, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
492497
AIS.put(7021, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
493498
AIS.put(7022, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
@@ -503,12 +508,14 @@ public final class Gs1 {
503508
AIS.put(7038, new byte[] { DIGITS, 3, 3, CHARS_82, 0, 27 }); // N4+N3+X..27
504509
AIS.put(7039, new byte[] { DIGITS, 3, 3, CHARS_82, 0, 27 }); // N4+N3+X..27
505510
AIS.put(7040, new byte[] { DIGITS, 1, 1, CHARS_82, 3, 3 }); // N4+N1+X3
511+
AIS.put(7041, new byte[] { CHARS_82, 0, 4 }); // N4+X..4
506512
AIS.put(710, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
507513
AIS.put(711, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
508514
AIS.put(712, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
509515
AIS.put(713, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
510516
AIS.put(714, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
511517
AIS.put(715, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
518+
AIS.put(716, new byte[] { CHARS_82, 0, 20 }); // N3+X..20
512519
AIS.put(7230, new byte[] { CHARS_82, 2, 30 }); // N4+X2+X..28
513520
AIS.put(7231, new byte[] { CHARS_82, 2, 30 }); // N4+X2+X..28
514521
AIS.put(7232, new byte[] { CHARS_82, 2, 30 }); // N4+X2+X..28
@@ -520,6 +527,18 @@ public final class Gs1 {
520527
AIS.put(7238, new byte[] { CHARS_82, 2, 30 }); // N4+X2+X..28
521528
AIS.put(7239, new byte[] { CHARS_82, 2, 30 }); // N4+X2+X..28
522529
AIS.put(7240, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
530+
AIS.put(7241, new byte[] { DIGITS, 2, 2 }); // N4+N2
531+
AIS.put(7242, new byte[] { CHARS_82, 0, 25 }); // N4+X..25
532+
AIS.put(7250, new byte[] { DIGITS, 8, 8 }); // N4+N8
533+
AIS.put(7251, new byte[] { DIGITS, 12, 12 }); // N4+N12
534+
AIS.put(7252, new byte[] { DIGITS, 1, 1 }); // N4+N1
535+
AIS.put(7253, new byte[] { CHARS_82, 0, 40 }); // N4+X..40
536+
AIS.put(7254, new byte[] { CHARS_82, 0, 40 }); // N4+X..40
537+
AIS.put(7255, new byte[] { CHARS_82, 0, 10 }); // N4+X..10
538+
AIS.put(7256, new byte[] { CHARS_82, 0, 90 }); // N4+X..90
539+
AIS.put(7257, new byte[] { CHARS_82, 0, 70 }); // N4+X..70
540+
AIS.put(7258, new byte[] { DIGITS, 1, 1, CHARS_82, 1, 1, DIGITS, 1, 1 }); // N4+N1+X1+N1
541+
AIS.put(7259, new byte[] { CHARS_82, 0, 40 }); // N4+X..40
523542
AIS.put(8001, new byte[] { DIGITS, 14, 14 }); // N4+N14
524543
AIS.put(8002, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
525544
AIS.put(8003, new byte[] { DIGITS, 14, 14, CHARS_82, 0, 16 }); // N4+N14+X..16
@@ -533,11 +552,13 @@ public final class Gs1 {
533552
AIS.put(8011, new byte[] { DIGITS, 0, 12 }); // N4+N..12
534553
AIS.put(8012, new byte[] { CHARS_82, 0, 20 }); // N4+X..20
535554
AIS.put(8013, new byte[] { CHARS_82, 0, 25 }); // N4+X..25
555+
AIS.put(8014, new byte[] { CHARS_82, 0, 25 }); // N4+X..25
536556
AIS.put(8017, new byte[] { DIGITS, 18, 18 }); // N4+N18
537557
AIS.put(8018, new byte[] { DIGITS, 18, 18 }); // N4+N18
538558
AIS.put(8019, new byte[] { DIGITS, 0, 10 }); // N4+N..10
539559
AIS.put(8020, new byte[] { CHARS_82, 0, 25 }); // N4+X..25
540560
AIS.put(8026, new byte[] { DIGITS, 18, 18 }); // N4+N14+N2+N2
561+
AIS.put(8030, new byte[] { CHARS_64, 0, 90 }); // N4+Z..90
541562
AIS.put(8110, new byte[] { CHARS_82, 0, 70 }); // N4+X..70
542563
AIS.put(8111, new byte[] { DIGITS, 4, 4 }); // N4+N4
543564
AIS.put(8112, new byte[] { CHARS_82, 0, 70 }); // N4+X..70
@@ -568,7 +589,7 @@ private Gs1() {
568589
* @param fnc1 the string to use to represent FNC1 in the output
569590
* @return the input data, verified and with FNC1 strings added at the appropriate positions
570591
* @see <a href="https://sourceforge.net/p/zint/code/ci/master/tree/backend/gs1.c">Corresponding Zint code</a>
571-
* @see <a href="http://www.gs1.org/docs/gsmp/barcodes/GS1_General_Specifications.pdf">GS1 specification</a>
592+
* @see <a href="https://ref.gs1.org/standards/genspecs/">GS1 specification</a>
572593
*/
573594
public static String verify(String s, String fnc1) {
574595

@@ -714,9 +735,11 @@ public static String verify(String s, String fnc1) {
714735
boolean valid =
715736
(type == DIGITS && c >= '0' && c <= '9') ||
716737
(type == CHARS_82 && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '%' && c <= '?') || c == '_' || c == '!' || c == '"')) ||
738+
(type == CHARS_64 && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '=')) ||
717739
(type == CHARS_39 && ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '#' || c == '-' || c == '/')) ||
718740
(type == FLAG && c >= '0' && c <= '1') ||
719-
(type == CHARS_UPPERCASE && c >= 'A' && c <= 'Z');
741+
(type == CHARS_UPPERCASE && c >= 'A' && c <= 'Z') ||
742+
(type == MINUS && c == '-');
720743
if (!valid) {
721744
throw new OkapiInputException("Invalid data value for AI " + ai);
722745
}

src/test/java/uk/org/okapibarcode/util/Gs1Test.java

+82
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ public void testVerify() {
6868
checkVerify("[02]123456789012345", "Invalid data length for AI 2");
6969
checkVerify("[02]1234567890123/", "Invalid data value for AI 2");
7070

71+
checkVerify("[03]", "Invalid data length for AI 3");
72+
checkVerify("[03]1234567890123", "Invalid data length for AI 3");
73+
checkVerify("[03]12345678901234", "0312345678901234");
74+
checkVerify("[03]123456789012345", "Invalid data length for AI 3");
75+
checkVerify("[03]1234567890123/", "Invalid data value for AI 3");
76+
7177
checkVerify("[10]", "10");
7278
checkVerify("[10]1", "101");
7379
checkVerify("[10]123", "10123");
@@ -268,6 +274,12 @@ public void testVerify() {
268274
checkVerify("[4307]U@", "Invalid data value for AI 4307");
269275
checkVerify("[4307]U^", "Invalid data value for AI 4307");
270276

277+
checkVerify("[4309]", "Invalid data length for AI 4309");
278+
checkVerify("[4309]1234567890123456789", "Invalid data length for AI 4309");
279+
checkVerify("[4309]12345678901234567890", "430912345678901234567890");
280+
checkVerify("[4309]123456789012345678901", "Invalid data length for AI 4309");
281+
checkVerify("[4309]1234567890123456789X", "Invalid data value for AI 4309");
282+
271283
checkVerify("[4321]", "Invalid data length for AI 4321");
272284
checkVerify("[4321]0", "43210");
273285
checkVerify("[4321]1", "43211");
@@ -276,16 +288,68 @@ public void testVerify() {
276288
checkVerify("[4321]X", "Invalid data value for AI 4321");
277289
checkVerify("[4321]/", "Invalid data value for AI 4321");
278290

291+
checkVerify("[4330]", "Invalid data length for AI 4330");
292+
checkVerify("[4330]12345", "Invalid data length for AI 4330");
293+
checkVerify("[4330]123456", "4330123456");
294+
checkVerify("[4330]123456-", "4330123456-");
295+
checkVerify("[4330]12345-", "Invalid data value for AI 4330");
296+
checkVerify("[4330]1234567", "Invalid data value for AI 4330");
297+
checkVerify("[4330]1234567-", "Invalid data length for AI 4330");
298+
299+
checkVerify("[4333]", "Invalid data length for AI 4333");
300+
checkVerify("[4333]12345", "Invalid data length for AI 4333");
301+
checkVerify("[4333]123456", "4333123456");
302+
checkVerify("[4333]123456-", "4333123456-");
303+
checkVerify("[4333]12345-", "Invalid data value for AI 4333");
304+
checkVerify("[4333]1234567", "Invalid data value for AI 4333");
305+
checkVerify("[4333]1234567-", "Invalid data length for AI 4333");
306+
279307
checkVerify("[7007]12345", "Invalid data length for AI 7007");
280308
checkVerify("[7007]123456", "7007123456");
281309
checkVerify("[7007]123456789012", "7007123456789012");
282310
checkVerify("[7007]1234567890123", "Invalid data length for AI 7007");
283311

312+
checkVerify("[7011]", "Invalid data length for AI 7011");
313+
checkVerify("[7011]12345", "Invalid data length for AI 7011");
314+
checkVerify("[7011]123456", "7011123456");
315+
checkVerify("[7011]1234567890", "70111234567890");
316+
checkVerify("[7011]12345678901", "Invalid data length for AI 7011");
317+
checkVerify("[7011]123456789X", "Invalid data value for AI 7011");
318+
284319
checkVerify("[7030]12", "Invalid data length for AI 7030");
285320
checkVerify("[7030]123", "7030123");
286321
checkVerify("[7030]123456789012345678901234567890", "7030123456789012345678901234567890");
287322
checkVerify("[7030]1234567890123456789012345678901", "Invalid data length for AI 7030");
288323

324+
checkVerify("[7041]", "7041");
325+
checkVerify("[7041]" + chars82(1), "7041" + chars82(1));
326+
checkVerify("[7041]" + chars82(4), "7041" + chars82(4));
327+
checkVerify("[7041]" + chars82(5), "Invalid data length for AI 7041");
328+
329+
checkVerify("[716]", "716");
330+
checkVerify("[716]" + chars82(1), "716" + chars82(1));
331+
checkVerify("[716]" + chars82(20), "716" + chars82(20));
332+
checkVerify("[716]" + chars82(21), "Invalid data length for AI 716");
333+
334+
checkVerify("[7241]", "Invalid data length for AI 7241");
335+
checkVerify("[7241]1", "Invalid data length for AI 7241");
336+
checkVerify("[7241]12", "724112");
337+
checkVerify("[7241]123", "Invalid data length for AI 7241");
338+
checkVerify("[7241]1X", "Invalid data value for AI 7241");
339+
340+
checkVerify("[7258]", "Invalid data length for AI 7258");
341+
checkVerify("[7258]1", "Invalid data length for AI 7258");
342+
checkVerify("[7258]1X", "Invalid data length for AI 7258");
343+
checkVerify("[7258]1X1", "72581X1");
344+
checkVerify("[7258]1X11", "Invalid data length for AI 7258");
345+
checkVerify("[7258]1XX", "Invalid data value for AI 7258");
346+
checkVerify("[7258]XX1", "Invalid data value for AI 7258");
347+
348+
checkVerify("[7259]", "7259");
349+
checkVerify("[7259]" + chars82(1), "7259" + chars82(1));
350+
checkVerify("[7259]" + chars82(40), "7259" + chars82(40));
351+
checkVerify("[7259]" + chars82(41), "Invalid data length for AI 7259");
352+
289353
checkVerify("[8003]1234567890123", "Invalid data length for AI 8003");
290354
checkVerify("[8003]12345678901234", "800312345678901234");
291355
checkVerify("[8003]123456789012345678901234567890", "8003123456789012345678901234567890");
@@ -330,6 +394,13 @@ public void testVerify() {
330394
checkVerify("[8013]" + chars82(26), "Invalid data length for AI 8013");
331395
checkVerify("[8013]#", "Invalid data value for AI 8013");
332396

397+
checkVerify("[8014]", "8014");
398+
checkVerify("[8014]1", "80141");
399+
checkVerify("[8014]123", "8014123");
400+
checkVerify("[8014]" + chars82(25), "8014" + chars82(25));
401+
checkVerify("[8014]" + chars82(26), "Invalid data length for AI 8014");
402+
checkVerify("[8014]#", "Invalid data value for AI 8014");
403+
333404
checkVerify("[8017]" + digits(17), "Invalid data length for AI 8017");
334405
checkVerify("[8017]" + digits(18), "8017" + digits(18));
335406
checkVerify("[8017]" + digits(19), "Invalid data length for AI 8017");
@@ -360,6 +431,12 @@ public void testVerify() {
360431
checkVerify("[8026]12345678901234567:", "Invalid data value for AI 8026");
361432
checkVerify("[8026]12345678901234567/", "Invalid data value for AI 8026");
362433

434+
checkVerify("[8030]", "8030");
435+
checkVerify("[8030]" + chars64(1), "8030" + chars64(1));
436+
checkVerify("[8030]" + chars64(90), "8030" + chars64(90));
437+
checkVerify("[8030]" + chars64(91), "Invalid data length for AI 8030");
438+
checkVerify("[8030]" + chars82(90), "Invalid data value for AI 8030");
439+
363440
checkVerify("[8110]", "8110");
364441
checkVerify("[8110]1", "81101");
365442
checkVerify("[8110]123", "8110123");
@@ -466,6 +543,11 @@ private static String chars82(int length) {
466543
return chars(length, charset82);
467544
}
468545

546+
private static String chars64(int length) {
547+
String charset64 = "-0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; // see GS1 spec section 7.11
548+
return chars(length, charset64);
549+
}
550+
469551
private static String chars39(int length) {
470552
String charset39 = "#-/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // see GS1 spec section 7.11
471553
return chars(length, charset39);

0 commit comments

Comments
 (0)