mirror of
https://git.code.sf.net/p/zint/code
synced 2026-01-08 20:41:59 +00:00
Integrate GS1 Syntax Engine
This commit is contained in:
542
backend/gs1.c
542
backend/gs1.c
@@ -33,6 +33,9 @@
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#ifdef ZINT_HAVE_GS1SE
|
||||
#include <gs1encoders.h>
|
||||
#endif
|
||||
#include "common.h"
|
||||
#include "gs1.h"
|
||||
|
||||
@@ -1609,52 +1612,227 @@ static int gs1_packagetype(const unsigned char *data, int data_len, int offset,
|
||||
/* Generated by "php backend/tools/gen_gs1_linter.php > backend/gs1_lint.h" */
|
||||
#include "gs1_lint.h"
|
||||
|
||||
/* Whether starts with Digital Link URI */
|
||||
static int gs1_is_digital_link(const unsigned char *source, const int length) {
|
||||
return (length >= 8 && (memcmp(source, "https://", 8) == 0 || memcmp(source, "HTTPS://", 8) == 0))
|
||||
|| (length >= 7 && (memcmp(source, "http://", 7) == 0 || memcmp(source, "HTTP://", 7) == 0));
|
||||
}
|
||||
|
||||
/* If built with GS1 Syntax Engine library */
|
||||
#ifdef ZINT_HAVE_GS1SE
|
||||
|
||||
/* Use GS1 Syntax Engine to verify */
|
||||
static int gs1se_verify(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
||||
unsigned char reduced[], int *p_reduced_length) {
|
||||
int i, j;
|
||||
const int is_composite = z_is_composite(symbol->symbology);
|
||||
const int primary_len = is_composite ? (int) strlen(symbol->primary) : 0;
|
||||
const int gs1parens_mode = symbol->input_mode & GS1PARENS_MODE;
|
||||
const char obracket = gs1parens_mode ? '(' : '[';
|
||||
const int parens_cnt = !gs1parens_mode ? z_chr_cnt(source, length, '(') : 0;
|
||||
int local_length = length;
|
||||
const unsigned char *local_source = source;
|
||||
const unsigned char *local_source2 = source;
|
||||
unsigned char *local_source_buf = (unsigned char *) z_alloca(ARRAY_SIZE(symbol->primary) + length + 1);
|
||||
unsigned char *local_source2_buf = (unsigned char *) z_alloca(ARRAY_SIZE(symbol->primary) * 2 + length
|
||||
+ parens_cnt + 1);
|
||||
int is_digital_link = 0;
|
||||
gs1_encoder *ctx;
|
||||
int gs1se_ret;
|
||||
|
||||
/* Need "linear|composite" format to check required AIs */
|
||||
if (is_composite && primary_len) {
|
||||
if (symbol->symbology == BARCODE_GS1_128_CC || symbol->symbology == BARCODE_DBAR_EXP_CC
|
||||
|| symbol->symbology == BARCODE_DBAR_EXPSTK_CC) {
|
||||
memcpy(local_source_buf, symbol->primary, primary_len);
|
||||
local_source_buf[primary_len] = '|';
|
||||
memcpy(local_source_buf + 1 + primary_len, source, length + 1); /* Include terminating NUL */
|
||||
local_length += 1 + primary_len;
|
||||
} else {
|
||||
/* Just use dummy "01" linear */
|
||||
memcpy(local_source_buf, gs1parens_mode ? "(01)12345678901231|" : "[01]12345678901231|", 19);
|
||||
memcpy(local_source_buf + 19, source, length + 1); /* Include terminating NUL */
|
||||
local_length += 19;
|
||||
}
|
||||
local_source = local_source_buf;
|
||||
local_source2 = local_source_buf;
|
||||
}
|
||||
|
||||
if (local_source[0] != obracket) {
|
||||
if (!z_is_fixed_ratio(symbol->symbology)) { /* Only matrix symbols can encode Digital Link URIs */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 264, "Data does not start with an AI");
|
||||
}
|
||||
if (!(is_digital_link = gs1_is_digital_link(local_source, local_length))) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 265,
|
||||
"Data does not start with an AI or a Digital Link URI");
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_digital_link) {
|
||||
/* Convert to GS1 Syntax Engine parenthesis mode */
|
||||
if (!gs1parens_mode) {
|
||||
/* Replace '[' and ']' with '(' and ')' & escape any data opening parentheses (not closing parentheses) */
|
||||
int bracket_level = 0; /* Non-compliant closing square brackets may be in text */
|
||||
for (i = 0, j = 0; i < local_length; i++) {
|
||||
if (local_source[i] == '[') {
|
||||
local_source2_buf[j++] = '(';
|
||||
bracket_level++;
|
||||
} else if (local_source[i] == ']' && bracket_level) {
|
||||
local_source2_buf[j++] = ')';
|
||||
bracket_level--;
|
||||
} else {
|
||||
if (local_source[i] == '(') {
|
||||
local_source2_buf[j++] = '\\';
|
||||
}
|
||||
local_source2_buf[j++] = local_source[i];
|
||||
}
|
||||
}
|
||||
local_source2_buf[j] = '\0';
|
||||
local_source2 = local_source2_buf;
|
||||
/* Unescape any backslashed closing parentheses */
|
||||
} else if (strchr(ZCCP(local_source), '\\') != NULL) {
|
||||
for (i = 0, j = 0; i < local_length; i++) {
|
||||
if (local_source[i] == '\\' && i + 1 < local_length && local_source[i + 1] == ')') {
|
||||
i++;
|
||||
}
|
||||
local_source2_buf[j++] = local_source[i];
|
||||
}
|
||||
local_source2_buf[j] = '\0';
|
||||
local_source2 = local_source2_buf;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ctx = gs1_encoder_init(NULL))) {
|
||||
return z_errtxt(ZINT_ERROR_MEMORY, symbol, 266, "Insufficient memory for GS1 Syntax Engine");
|
||||
}
|
||||
|
||||
if (is_digital_link) {
|
||||
gs1se_ret = gs1_encoder_setDataStr(ctx, ZCCP(local_source2));
|
||||
} else {
|
||||
gs1se_ret = gs1_encoder_setAIdataStr(ctx, ZCCP(local_source2));
|
||||
}
|
||||
if (!gs1se_ret) {
|
||||
const char *errmsg = gs1_encoder_getErrMsg(ctx);
|
||||
const int errmsg_len = (int) strlen(errmsg);
|
||||
const char *errmarkup = gs1_encoder_getErrMarkup(ctx);
|
||||
int errmarkup_len = (int) strlen(errmarkup);
|
||||
if (errmarkup_len && errmsg_len + 1 + errmarkup_len < ARRAY_SIZE(symbol->errtxt)) {
|
||||
char *local_errmarkup = (char *) z_alloca(errmarkup_len * 4 + 1);
|
||||
z_debug_print_escape(ZCUCP(errmarkup), errmarkup_len, local_errmarkup);
|
||||
errmarkup_len = (int) strlen(local_errmarkup);
|
||||
if (errmsg_len + 1 + errmarkup_len < ARRAY_SIZE(symbol->errtxt)) {
|
||||
ZEXT z_errtxtf(0, symbol, 267, "%1$s %2$s", errmsg, local_errmarkup);
|
||||
} else {
|
||||
z_errtxt(0, symbol, 268, errmsg);
|
||||
}
|
||||
} else {
|
||||
z_errtxt(0, symbol, 268, errmsg);
|
||||
}
|
||||
gs1_encoder_free(ctx);
|
||||
return ZINT_ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
/* If Digital Link, set `reduced` */
|
||||
if (is_digital_link) {
|
||||
const char *scan_data;
|
||||
|
||||
gs1_encoder_setSym(ctx, gs1_encoder_sDM); /* Required for `gs1_encoder_getScanData()` */
|
||||
|
||||
if (!(scan_data = gs1_encoder_getScanData(ctx))) { /* Shouldn't happen */
|
||||
const char *errmsg = gs1_encoder_getErrMsg(ctx);
|
||||
z_errtxt(0, symbol, 269, strlen(errmsg) ? errmsg : "Internal error");
|
||||
gs1_encoder_free(ctx);
|
||||
return ZINT_ERROR_ENCODING_PROBLEM;
|
||||
}
|
||||
*p_reduced_length = (int) strlen(scan_data);
|
||||
|
||||
/* Skip over Symbology Identifier */
|
||||
if (*p_reduced_length >= 3 && scan_data[0] == ']') {
|
||||
scan_data += 3;
|
||||
*p_reduced_length -= 3;
|
||||
}
|
||||
|
||||
memcpy(reduced, scan_data, *p_reduced_length + 1); /* Include terminating NUL */
|
||||
} else {
|
||||
*p_reduced_length = 0; /* Not Digital Link & `reduced` not set */
|
||||
}
|
||||
|
||||
gs1_encoder_free(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* ZINT_HAVE_GS1SE */
|
||||
|
||||
#define GS1_PARENS_PLACEHOLDER_MASK 0x20 /* Mask `(` or `)` by this if escaped (get BS (\x08) or HT (\x09)) */
|
||||
|
||||
/* Verify a GS1 input string */
|
||||
INTERNAL int zint_gs1_verify(struct zint_symbol *symbol, unsigned char source[], int *p_length,
|
||||
INTERNAL int zint_gs1_verify(struct zint_symbol *symbol, const unsigned char source[], const int length,
|
||||
unsigned char reduced[], int *p_reduced_length) {
|
||||
int i, j;
|
||||
int error_value = 0;
|
||||
int bracket_level = 0, max_bracket_level = 0;
|
||||
int ai_length, ai_latch;
|
||||
int max_ai_length = 0, min_ai_length = 5;
|
||||
int max_ai_pos = 0, min_ai_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positives */
|
||||
int ai_zero_len_no_data = 0, ai_single_digit = 0, ai_nonnumeric = 0;
|
||||
int ai_nonnumeric_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positive */
|
||||
int length = *p_length;
|
||||
const char obracket = symbol->input_mode & GS1PARENS_MODE ? '(' : '[';
|
||||
const char cbracket = symbol->input_mode & GS1PARENS_MODE ? ')' : ']';
|
||||
const int ai_max = z_chr_cnt(source, length, obracket) + 1; /* Plus 1 so non-zero */
|
||||
int *ai_value = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *ai_location = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *data_location = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *data_length = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int error_number = 0;
|
||||
int bracket_level = 0;
|
||||
int ai_latch;
|
||||
int local_length = length;
|
||||
const unsigned char *local_source = source;
|
||||
unsigned char *local_source_buf = (unsigned char *) z_alloca(length + 1);
|
||||
const int gs1parens_mode = symbol->input_mode & GS1PARENS_MODE;
|
||||
const char obracket = gs1parens_mode ? '(' : '[';
|
||||
const char cbracket = gs1parens_mode ? ')' : ']';
|
||||
int done_gs1se = 0;
|
||||
int is_digital_link = 0; /* Is Digital Link? */
|
||||
|
||||
assert(p_length != p_reduced_length); /* Make sure we don't overwrite one with the other */
|
||||
/* Note: Digital Link URIs only validated if have GS1 Syntax Engine and GS1SYNTAXENGINE_MODE set */
|
||||
|
||||
/* Detect control and extended ASCII characters */
|
||||
for (i = 0; i < length; i++) {
|
||||
if (source[i] >= 128) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 250,
|
||||
"Extended ASCII characters are not supported by GS1");
|
||||
#ifdef ZINT_HAVE_GS1SE
|
||||
if ((symbol->input_mode & GS1SYNTAXENGINE_MODE) && !(symbol->input_mode & GS1NOCHECK_MODE)) {
|
||||
/* Strict verification */
|
||||
if ((error_number = gs1se_verify(symbol, source, length, reduced, p_reduced_length))) {
|
||||
return error_number;
|
||||
}
|
||||
if (source[i] == '\0') {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 262, "NUL characters not permitted in GS1 mode");
|
||||
done_gs1se = 1;
|
||||
is_digital_link = *p_reduced_length != 0; /* `p_reduced_length` will be set if Digital Link */
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!done_gs1se) {
|
||||
/* Detect control and extended ASCII characters */
|
||||
for (i = 0; i < length; i++) {
|
||||
if (!z_isascii(source[i])) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 250,
|
||||
"Extended ASCII characters are not supported by GS1");
|
||||
}
|
||||
if (source[i] == '\0') {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 262, "NUL characters not permitted in GS1 mode");
|
||||
}
|
||||
if (source[i] < 32) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 251, "Control characters are not supported by GS1");
|
||||
}
|
||||
if (source[i] == 127) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 263, "DEL characters are not supported by GS1");
|
||||
}
|
||||
}
|
||||
if (source[i] < 32) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 251, "Control characters are not supported by GS1");
|
||||
}
|
||||
if (source[i] == 127) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 263, "DEL characters are not supported by GS1");
|
||||
|
||||
if (source[0] != obracket) {
|
||||
if (!z_is_fixed_ratio(symbol->symbology)) { /* Only matrix symbols can encode Digital Link URIs */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 252, "Data does not start with an AI");
|
||||
}
|
||||
if (!(is_digital_link = gs1_is_digital_link(source, length))) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 855,
|
||||
"Data does not start with an AI or Digital Link URI");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (source[0] != obracket) {
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 252, "Data does not start with an AI");
|
||||
if (is_digital_link) {
|
||||
if (!done_gs1se) {
|
||||
/* Just copy over Digital Link URI - no verification */
|
||||
memcpy(reduced, source, length + 1); /* Include terminating NUL */
|
||||
*p_reduced_length = length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((symbol->input_mode & (ESCAPE_MODE | GS1PARENS_MODE)) == (ESCAPE_MODE | GS1PARENS_MODE)) {
|
||||
if (gs1parens_mode) {
|
||||
/* Check for escaped parentheses */
|
||||
for (i = 0; i < length; i++) {
|
||||
if (source[i] == '\\' && i + 1 < length && (source[i + 1] == '(' || source[i + 1] == ')')) {
|
||||
@@ -1662,151 +1840,167 @@ INTERNAL int zint_gs1_verify(struct zint_symbol *symbol, unsigned char source[],
|
||||
}
|
||||
}
|
||||
if (i != length) {
|
||||
local_source = local_source_buf;
|
||||
/* Replace with control-char placeholders */
|
||||
for (i = 0, j = 0; i < length; i++) {
|
||||
if (source[i] == '\\' && i + 1 < length && (source[i + 1] == '(' || source[i + 1] == ')')) {
|
||||
source[j++] = source[++i] & ~GS1_PARENS_PLACEHOLDER_MASK; /* BS (\x08) or HT (\x09) */
|
||||
local_source_buf[j++] = source[++i] & ~GS1_PARENS_PLACEHOLDER_MASK; /* BS (\x08) or HT (\x09) */
|
||||
} else {
|
||||
source[j++] = source[i];
|
||||
local_source_buf[j++] = source[i];
|
||||
}
|
||||
}
|
||||
source[j] = '\0';
|
||||
length = j;
|
||||
local_source_buf[j] = '\0';
|
||||
local_length = j;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the balance of the brackets & AI lengths */
|
||||
ai_length = 0;
|
||||
ai_latch = 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
if (source[i] == obracket) {
|
||||
bracket_level++;
|
||||
if (bracket_level > max_bracket_level) {
|
||||
max_bracket_level = bracket_level;
|
||||
}
|
||||
ai_latch = 1;
|
||||
} else if (source[i] == cbracket && bracket_level) {
|
||||
bracket_level--;
|
||||
if (ai_length > max_ai_length) {
|
||||
max_ai_length = ai_length;
|
||||
max_ai_pos = i - ai_length;
|
||||
}
|
||||
if (ai_length < min_ai_length) {
|
||||
min_ai_length = ai_length;
|
||||
min_ai_pos = i - ai_length;
|
||||
}
|
||||
/* Check zero-length AI has data */
|
||||
if (ai_length == 0 && (i + 1 == length || source[i + 1] == obracket)) {
|
||||
ai_zero_len_no_data = 1;
|
||||
} else if (ai_length == 1) {
|
||||
ai_single_digit = 1;
|
||||
}
|
||||
ai_length = 0;
|
||||
ai_latch = 0;
|
||||
} else if (ai_latch) {
|
||||
ai_length++;
|
||||
if (!z_isdigit(source[i])) {
|
||||
ai_nonnumeric = 1;
|
||||
ai_nonnumeric_pos = i - ai_length + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!done_gs1se) {
|
||||
/* Verify AIs */
|
||||
int max_ai_length = 0, min_ai_length = 5;
|
||||
int max_bracket_level = 0;
|
||||
int ai_length = 0;
|
||||
int max_ai_pos = 0, min_ai_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positives */
|
||||
int ai_zero_len_no_data = 0, ai_single_digit = 0, ai_nonnumeric = 0;
|
||||
int ai_nonnumeric_pos = 0; /* Suppress gcc 14 "-Wmaybe-uninitialized" false positive */
|
||||
const int ai_max = z_chr_cnt(local_source, local_length, obracket) + 1; /* Plus 1 so non-zero */
|
||||
int *ai_value = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *ai_location = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *data_location = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
int *data_length = (int *) z_alloca(sizeof(int) * ai_max);
|
||||
|
||||
if (bracket_level != 0) {
|
||||
/* Not all brackets are closed */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 253, "Malformed AI in input (brackets don\'t match)");
|
||||
}
|
||||
|
||||
if (max_bracket_level > 1) {
|
||||
/* Nested brackets */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 254, "Found nested brackets in input");
|
||||
}
|
||||
|
||||
if (max_ai_length > 4) {
|
||||
/* AI is too long */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 255,
|
||||
"Invalid AI at position %d in input (AI too long)", max_ai_pos);
|
||||
}
|
||||
|
||||
if (min_ai_length <= 1) {
|
||||
/* Allow too short AI if GS1NOCHECK_MODE and no single-digit AIs and all zero-length AIs have some data
|
||||
- permits dummy "[]" workaround for ticket #204 data with no valid AI */
|
||||
if (!(symbol->input_mode & GS1NOCHECK_MODE) || ai_single_digit || ai_zero_len_no_data) {
|
||||
/* AI is too short */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 256,
|
||||
"Invalid AI at position %d in input (AI too short)", min_ai_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (ai_nonnumeric) {
|
||||
/* Non-numeric data in AI */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 257,
|
||||
"Invalid AI at position %d in input (non-numeric characters in AI)", ai_nonnumeric_pos);
|
||||
}
|
||||
|
||||
if (!(symbol->input_mode & GS1NOCHECK_MODE)) {
|
||||
unsigned char *local_source = source;
|
||||
unsigned char *local_source_buf = (unsigned char *) z_alloca(length + 1);
|
||||
int ai_count = 0;
|
||||
for (i = 1; i < length; i++) {
|
||||
if (source[i - 1] == obracket) {
|
||||
ai_location[ai_count] = i;
|
||||
for (j = 1; source[i + j] != cbracket; j++);
|
||||
ai_value[ai_count] = z_to_int(source + i, j);
|
||||
ai_count++;
|
||||
i += j;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ai_count; i++) {
|
||||
if (ai_value[i] >= 1000) {
|
||||
data_location[i] = ai_location[i] + 5;
|
||||
} else if (ai_value[i] >= 100) {
|
||||
data_location[i] = ai_location[i] + 4;
|
||||
} else {
|
||||
data_location[i] = ai_location[i] + 3;
|
||||
}
|
||||
data_length[i] = 0;
|
||||
while (data_location[i] + data_length[i] < length
|
||||
&& source[data_location[i] + data_length[i]] != obracket) {
|
||||
data_length[i]++;
|
||||
}
|
||||
if (data_length[i] == 0) {
|
||||
/* No data for given AI */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 258, "Empty data field in input");
|
||||
}
|
||||
}
|
||||
|
||||
if (length != *p_length) {
|
||||
/* Temporarily re-instate escaped parentheses before linting */
|
||||
local_source = local_source_buf;
|
||||
memcpy(local_source, source, length + 1); /* Include terminating NUL */
|
||||
for (i = 0; i < length; i++) {
|
||||
if (local_source[i] < '\x1D') {
|
||||
local_source[i] |= GS1_PARENS_PLACEHOLDER_MASK;
|
||||
/* Check the balance of the brackets & AI lengths */
|
||||
ai_latch = 0;
|
||||
for (i = 0; i < local_length; i++) {
|
||||
if (local_source[i] == obracket) {
|
||||
bracket_level++;
|
||||
if (bracket_level > max_bracket_level) {
|
||||
max_bracket_level = bracket_level;
|
||||
}
|
||||
ai_latch = 1;
|
||||
} else if (local_source[i] == cbracket && bracket_level) {
|
||||
bracket_level--;
|
||||
if (ai_length > max_ai_length) {
|
||||
max_ai_length = ai_length;
|
||||
max_ai_pos = i - ai_length;
|
||||
}
|
||||
if (ai_length < min_ai_length) {
|
||||
min_ai_length = ai_length;
|
||||
min_ai_pos = i - ai_length;
|
||||
}
|
||||
/* Check zero-length AI has data */
|
||||
if (ai_length == 0 && (i + 1 == local_length || local_source[i + 1] == obracket)) {
|
||||
ai_zero_len_no_data = 1;
|
||||
} else if (ai_length == 1) {
|
||||
ai_single_digit = 1;
|
||||
}
|
||||
ai_length = 0;
|
||||
ai_latch = 0;
|
||||
} else if (ai_latch) {
|
||||
ai_length++;
|
||||
if (!z_isdigit(local_source[i])) {
|
||||
ai_nonnumeric = 1;
|
||||
ai_nonnumeric_pos = i - ai_length + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for valid AI values and data lengths according to GS1 General
|
||||
Specifications Release 25, January 2025 */
|
||||
for (i = 0; i < ai_count; i++) {
|
||||
int err_no, err_posn;
|
||||
char err_msg[50];
|
||||
if (!gs1_lint(ai_value[i], local_source + data_location[i], data_length[i], &err_no, &err_posn,
|
||||
err_msg)) {
|
||||
if (err_no == 1) {
|
||||
z_errtxtf(0, symbol, 260, "Invalid AI (%02d)", ai_value[i]);
|
||||
} else if (err_no == 2 || err_no == 4) { /* 4 is backward-incompatible bad length */
|
||||
z_errtxtf(0, symbol, 259, "Invalid data length for AI (%02d)", ai_value[i]);
|
||||
if (bracket_level != 0) {
|
||||
/* Not all brackets are closed */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 253, "Malformed AI in input (brackets don\'t match)");
|
||||
}
|
||||
|
||||
if (max_bracket_level > 1) {
|
||||
/* Nested brackets */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 254, "Found nested brackets in input");
|
||||
}
|
||||
|
||||
if (max_ai_length > 4) {
|
||||
/* AI is too long */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 255,
|
||||
"Invalid AI at position %d in input (AI too long)", max_ai_pos);
|
||||
}
|
||||
|
||||
if (min_ai_length <= 1) {
|
||||
/* Allow too short AI if GS1NOCHECK_MODE and no single-digit AIs and all zero-length AIs have some data
|
||||
- permits dummy "[]" workaround for ticket #204 data with no valid AI */
|
||||
if (!(symbol->input_mode & GS1NOCHECK_MODE) || ai_single_digit || ai_zero_len_no_data) {
|
||||
/* AI is too short */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 256,
|
||||
"Invalid AI at position %d in input (AI too short)", min_ai_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (ai_nonnumeric) {
|
||||
/* Non-numeric data in AI */
|
||||
return z_errtxtf(ZINT_ERROR_INVALID_DATA, symbol, 257,
|
||||
"Invalid AI at position %d in input (non-numeric characters in AI)", ai_nonnumeric_pos);
|
||||
}
|
||||
|
||||
if (!(symbol->input_mode & GS1NOCHECK_MODE)) {
|
||||
const unsigned char *local_source2 = local_source;
|
||||
unsigned char *local_source2_buf = (unsigned char *) z_alloca(local_length + 1);
|
||||
int ai_count = 0;
|
||||
for (i = 1; i < local_length; i++) {
|
||||
if (local_source[i - 1] == obracket) {
|
||||
ai_location[ai_count] = i;
|
||||
for (j = 1; local_source[i + j] != cbracket; j++);
|
||||
ai_value[ai_count] = z_to_int(local_source + i, j);
|
||||
ai_count++;
|
||||
i += j;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ai_count; i++) {
|
||||
if (ai_value[i] >= 1000) {
|
||||
data_location[i] = ai_location[i] + 5;
|
||||
} else if (ai_value[i] >= 100) {
|
||||
data_location[i] = ai_location[i] + 4;
|
||||
} else {
|
||||
ZEXT z_errtxtf(0, symbol, 261, "AI (%1$02d) position %2$d: %3$s", ai_value[i], err_posn, err_msg);
|
||||
data_location[i] = ai_location[i] + 3;
|
||||
}
|
||||
/* For backward compatibility only error on unknown AI or bad length */
|
||||
if (err_no == 1 || err_no == 2) {
|
||||
return ZINT_ERROR_INVALID_DATA;
|
||||
data_length[i] = 0;
|
||||
while (data_location[i] + data_length[i] < local_length
|
||||
&& local_source[data_location[i] + data_length[i]] != obracket) {
|
||||
data_length[i]++;
|
||||
}
|
||||
if (data_length[i] == 0) {
|
||||
/* No data for given AI */
|
||||
return z_errtxt(ZINT_ERROR_INVALID_DATA, symbol, 258, "Empty data field in input");
|
||||
}
|
||||
}
|
||||
|
||||
if (local_length != length) {
|
||||
/* Temporarily re-instate escaped parentheses before linting */
|
||||
local_source2 = local_source2_buf;
|
||||
memcpy(local_source2_buf, local_source, local_length + 1); /* Include terminating NUL */
|
||||
for (i = 0; i < local_length; i++) {
|
||||
if (local_source2_buf[i] < '\x1D') {
|
||||
local_source2_buf[i] |= GS1_PARENS_PLACEHOLDER_MASK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for valid AI values and data lengths according to GS1 General
|
||||
Specifications Release 25, January 2025 */
|
||||
for (i = 0; i < ai_count; i++) {
|
||||
int err_no, err_posn;
|
||||
char err_msg[50];
|
||||
if (!gs1_lint(ai_value[i], local_source2 + data_location[i], data_length[i], &err_no, &err_posn,
|
||||
err_msg)) {
|
||||
if (err_no == 1) {
|
||||
z_errtxtf(0, symbol, 260, "Invalid AI (%02d)", ai_value[i]);
|
||||
} else if (err_no == 2 || err_no == 4) { /* 4 is backward-incompatible bad length */
|
||||
z_errtxtf(0, symbol, 259, "Invalid data length for AI (%02d)", ai_value[i]);
|
||||
} else {
|
||||
ZEXT z_errtxtf(0, symbol, 261, "AI (%1$02d) position %2$d: %3$s", ai_value[i], err_posn,
|
||||
err_msg);
|
||||
}
|
||||
/* For backward compatibility only error on unknown AI or bad length */
|
||||
if (err_no == 1 || err_no == 2) {
|
||||
return ZINT_ERROR_INVALID_DATA;
|
||||
}
|
||||
error_number = ZINT_WARN_NONCOMPLIANT;
|
||||
}
|
||||
error_value = ZINT_WARN_NONCOMPLIANT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1814,53 +2008,47 @@ INTERNAL int zint_gs1_verify(struct zint_symbol *symbol, unsigned char source[],
|
||||
/* Resolve AI data - put resulting string in 'reduced' */
|
||||
j = 0;
|
||||
ai_latch = 1;
|
||||
for (i = 0; i < length; i++) {
|
||||
if (source[i] == obracket) {
|
||||
for (i = 0; i < local_length; i++) {
|
||||
if (local_source[i] == obracket) {
|
||||
bracket_level++;
|
||||
/* Start of an AI string */
|
||||
if (ai_latch == 0) {
|
||||
reduced[j++] = '\x1D';
|
||||
}
|
||||
if (i + 1 != length) {
|
||||
int last_ai = z_to_int(source + i + 1, 2);
|
||||
if (i + 1 != local_length) {
|
||||
int last_ai = z_to_int(local_source + i + 1, 2);
|
||||
ai_latch = 0;
|
||||
/* The following values from GS1 General Specifications Release 25.0
|
||||
Figure 7.8.5-2 "Element strings with predefined length using GS1 Application Identifiers" */
|
||||
if ((last_ai >= 0 && last_ai <= 4) || (last_ai >= 11 && last_ai <= 20)
|
||||
/* NOTE: as noted by Terry Burton the following complies with ISO/IEC 24724:2011 Table D.1,
|
||||
but clashes with TPX AI [235], introduced May 2019; awaiting feedback from GS1 */
|
||||
/* NOTE: as noted by Terry Burton the following complies with ISO/IEC 24724:2011 Table
|
||||
D.1, but clashes with TPX AI [235], introduced May 2019; awaiting feedback from GS1 */
|
||||
|| last_ai == 23 /* legacy support */ /* TODO: probably remove */
|
||||
|| (last_ai >= 31 && last_ai <= 36) || last_ai == 41) {
|
||||
ai_latch = 1;
|
||||
}
|
||||
}
|
||||
} else if (source[i] == cbracket && bracket_level) {
|
||||
} else if (local_source[i] == cbracket && bracket_level) {
|
||||
/* The closing bracket is simply dropped from the input */
|
||||
bracket_level--;
|
||||
} else {
|
||||
reduced[j++] = source[i];
|
||||
reduced[j++] = local_source[i];
|
||||
}
|
||||
}
|
||||
reduced[j] = '\0';
|
||||
*p_reduced_length = j;
|
||||
|
||||
if (length != *p_length) {
|
||||
if (local_length != length) {
|
||||
/* Re-instate escaped parentheses */
|
||||
for (i = 0; i < *p_reduced_length; i++) {
|
||||
if (reduced[i] < '\x1D') {
|
||||
reduced[i] |= GS1_PARENS_PLACEHOLDER_MASK;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < length; i++) {
|
||||
if (source[i] < '\x1D') {
|
||||
source[i] |= GS1_PARENS_PLACEHOLDER_MASK;
|
||||
}
|
||||
}
|
||||
*p_length = length;
|
||||
}
|
||||
|
||||
/* The character '\x1D' (GS) in the reduced string refers to the FNC1 character */
|
||||
return error_value;
|
||||
|
||||
return error_number;
|
||||
}
|
||||
|
||||
/* Helper to return standard GS1 check digit (GS1 General Specifications 7.9.1) */
|
||||
|
||||
Reference in New Issue
Block a user