gen_gs1_lint.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. /* Generate GS1 verify include "backend/gs1_lint.h" for "backend/gs1.c" */
  3. /*
  4. libzint - the open source barcode library
  5. Copyright (C) 2021-2024 <rstuart114@gmail.com>
  6. */
  7. /* SPDX-License-Identifier: BSD-3-Clause */
  8. /* To create "backend/gs1_lint.h" (from project directory):
  9. *
  10. * php backend/tools/gen_gs1_lint.php > backend/gs1_lint.h
  11. *
  12. * or to use local copy of "gs1-syntax-dictionary.txt":
  13. *
  14. * php backend/tools/gen_gs1_lint.php -f <local-path>/gs1-syntax-dictionary.txt backend/gs1_lint.h
  15. *
  16. *************************************************************************************************
  17. * NOTE: up-to-date version requires syntax dictionary available from
  18. * https://ref.gs1.org/tools/gs1-barcode-syntax-resource/syntax-dictionary/
  19. *************************************************************************************************
  20. */
  21. $basename = basename(__FILE__);
  22. $dirname = dirname(__FILE__);
  23. $dirdirname = basename(dirname($dirname)) . '/' . basename($dirname);
  24. $opts = getopt('c:f:h:l:t:');
  25. $print_copyright = isset($opts['c']) ? (bool) $opts['c'] : true;
  26. $file = isset($opts['f']) ? $opts['f']
  27. : 'https://raw.githubusercontent.com/gs1/gs1-syntax-dictionary/main/gs1-syntax-dictionary.txt';
  28. $print_h_guard = isset($opts['h']) ? (bool) $opts['h'] : true;
  29. $use_length_only = isset($opts['l']) ? (bool) $opts['l'] : true;
  30. $tab = isset($opts['t']) ? $opts['t'] : ' ';
  31. if (($get = file_get_contents($file)) === false) {
  32. exit("$basename:" . __LINE__ . " ERROR: Could not read file \"$file\"" . PHP_EOL);
  33. }
  34. if (strncasecmp($file, "http", 4) != 0) {
  35. // Strip to last 2 directories
  36. $stripped_dir = dirname($file);
  37. $stripped_file = basename(dirname($stripped_dir)) . '/' . basename($stripped_dir) . '/' . basename($file);
  38. } else {
  39. $stripped_file = $file;
  40. }
  41. $lines = explode("\n", $get);
  42. $spec_ais = $spec_parts = $spec_funcs = $spec_comments = $fixed_ais = array();
  43. $batches = array_fill(0, 100, array());
  44. // Parse the lines into AIs and specs
  45. $line_no = 0;
  46. foreach ($lines as $line) {
  47. $line_no++;
  48. if ($line === '' || $line[0] === '#') {
  49. continue;
  50. }
  51. if (!preg_match('/^([0-9]+(?:-[0-9]+)?) +([ *?]* )([NXYZ][0-9.][ NXYZ0-9.,a-z=|+\[\]]*)(?:# (.+))?$/',
  52. $line, $matches)) {
  53. print $line . PHP_EOL;
  54. exit("$basename:" . __LINE__ . " ERROR: Could not parse line $line_no" . PHP_EOL);
  55. }
  56. $ai = $matches[1];
  57. $flags = trim($matches[2]);
  58. $fixed = strpos($flags, "*") !== false;
  59. $spec = preg_replace('/ +req=[0-9,n+]*/', '', trim($matches[3])); // Strip mandatory association info
  60. $spec = preg_replace('/ +ex=[0-9,n]*/', '', $spec); // Strip invalid pairings info
  61. $spec = preg_replace('/ +dlpkey[=0-9,|]*/', '', $spec); // Strip Digital Link primary key info
  62. $comment = isset($matches[4]) ? trim($matches[4]) : '';
  63. if (isset($spec_ais[$spec])) {
  64. $ais = $spec_ais[$spec];
  65. } else {
  66. $ais = array();
  67. }
  68. if (($hyphen = strpos($ai, '-')) !== false) {
  69. if ($fixed !== '') {
  70. $fixed_ais[substr($ai, 0, 2)] = true;
  71. }
  72. $ai_s = (int) substr($ai, 0, $hyphen);
  73. $ai_e = (int) substr($ai, $hyphen + 1);
  74. $ais[] = array($ai_s, $ai_e);
  75. $batch_s_idx = (int) ($ai_s / 100);
  76. $batch_e_idx = (int) ($ai_e / 100);
  77. if ($batch_s_idx !== $batch_e_idx) {
  78. if (!in_array($spec, $batches[$batch_s_idx])) {
  79. $batches[$batch_s_idx][] = $spec;
  80. }
  81. if (!in_array($spec, $batches[$batch_e_idx])) {
  82. $batches[$batch_e_idx][] = $spec;
  83. }
  84. } else {
  85. if (!in_array($spec, $batches[$batch_s_idx])) {
  86. $batches[$batch_s_idx][] = $spec;
  87. }
  88. }
  89. } else {
  90. if ($fixed !== '') {
  91. $fixed_ais[substr($ai, 0, 2)] = true;
  92. }
  93. $ai = (int) $ai;
  94. $ais[] = $ai;
  95. $batch_idx = (int) ($ai / 100);
  96. if (!in_array($spec, $batches[$batch_idx])) {
  97. $batches[$batch_idx][] = $spec;
  98. }
  99. }
  100. $spec_ais[$spec] = $ais;
  101. if ($comment !== '') {
  102. if (isset($spec_comments[$spec])) {
  103. if (!in_array($comment, $spec_comments[$spec])) {
  104. $spec_comments[$spec][] = $comment;
  105. }
  106. } else {
  107. $spec_comments[$spec] = array($comment);
  108. }
  109. }
  110. $spec_parts[$spec] = array();
  111. $parts = explode(' ', $spec);
  112. foreach ($parts as $part) {
  113. $checkers = explode(',', $part);
  114. $validator = array_shift($checkers);
  115. if (preg_match('/^([NXYZ])([0-9]+)?(\.\.[0-9|]+)?$/', $validator, $matches)) {
  116. if (count($matches) === 3) {
  117. $min = $max = (int) $matches[2];
  118. } else {
  119. $min = $matches[2] === '' ? 1 : (int) $matches[2];
  120. $max = (int) substr($matches[3], 2);
  121. }
  122. if ($matches[1] === 'N') {
  123. $validator = "numeric";
  124. } elseif ($matches[1] === 'X') {
  125. $validator = "cset82";
  126. } elseif ($matches[1] === 'Y') {
  127. $validator = "cset39";
  128. } else { // 'Z'
  129. $validator = "cset64";
  130. }
  131. } else if (preg_match('/^\[([NXYZ])([1-9]+)?(\.\.[0-9|]+)?\]$/', $validator, $matches)) {
  132. if (count($matches) === 3) {
  133. $min = 0;
  134. $max = (int) $matches[2];
  135. } else {
  136. $min = $matches[2] === '' ? 0 : (int) $matches[2];
  137. $max = (int) substr($matches[3], 2);
  138. }
  139. if ($matches[1] === 'N') {
  140. $validator = "numeric";
  141. } elseif ($matches[1] === 'X') {
  142. $validator = "cset82";
  143. } elseif ($matches[1] === 'Y') {
  144. $validator = "cset39";
  145. } else { // 'Z'
  146. $validator = "cset64";
  147. }
  148. } else {
  149. exit("$basename:" . __LINE__ . " ERROR: Could not parse validator \"$validator\" line $line_no"
  150. . PHP_EOL);
  151. }
  152. $spec_parts[$spec][] = array($min, $max, $validator, $checkers);
  153. }
  154. }
  155. // Calculate total min/maxs and convert the AIs into ranges
  156. foreach ($spec_ais as $spec => $ais) {
  157. // Total min/maxs
  158. $total_min = $total_max = 0;
  159. foreach ($spec_parts[$spec] as list($min, $max)) {
  160. $total_min += $min;
  161. $total_max += $max;
  162. }
  163. // Sort the AIs
  164. $sort_ais = array();
  165. foreach ($ais as $ai) {
  166. if (is_array($ai)) {
  167. $sort_ais[] = $ai[0];
  168. } else {
  169. $sort_ais[] = $ai;
  170. }
  171. }
  172. array_multisort($sort_ais, $ais);
  173. // Consolidate contiguous AIs into ranges
  174. $tmp_ais = array();
  175. foreach ($ais as $ai) {
  176. $cnt = count($tmp_ais);
  177. if ($cnt === 0) {
  178. $tmp_ais[] = $ai;
  179. } else {
  180. $prev_ai = $tmp_ais[$cnt - 1];
  181. if (is_array($prev_ai)) {
  182. $prev_s = $prev_ai[0];
  183. $prev_e = $prev_ai[1];
  184. } else {
  185. $prev_e = $prev_s = $prev_ai;
  186. }
  187. if (is_array($ai)) {
  188. $this_s = $ai[0];
  189. $this_e = $ai[1];
  190. } else {
  191. $this_s = $this_e = $ai;
  192. }
  193. if ($this_s === $prev_e + 1 && $this_e - $prev_s < 100) { // Confine to batches of 100
  194. $tmp_ais[$cnt - 1] = array($prev_s, $this_e);
  195. } else {
  196. $tmp_ais[] = $ai;
  197. }
  198. }
  199. }
  200. // Unconsolidate ranges of 1 into separate entries
  201. $ais = array();
  202. foreach ($tmp_ais as $ai) {
  203. if (is_array($ai) && $ai[1] === $ai[0] + 1) {
  204. $ais[] = $ai[0];
  205. $ais[] = $ai[1];
  206. } else {
  207. $ais[] = $ai;
  208. }
  209. }
  210. $spec_ais[$spec] = array($total_min, $total_max, $ais);
  211. }
  212. // Print output
  213. print <<<EOD
  214. /*
  215. * GS1 AI checker generated by "$dirdirname/$basename" from
  216. * $stripped_file
  217. */
  218. EOD;
  219. if ($print_copyright) {
  220. print <<<'EOD'
  221. /*
  222. libzint - the open source barcode library
  223. Copyright (C) 2021-2024 Robin Stuart <rstuart114@gmail.com>
  224. Redistribution and use in source and binary forms, with or without
  225. modification, are permitted provided that the following conditions
  226. are met:
  227. 1. Redistributions of source code must retain the above copyright
  228. notice, this list of conditions and the following disclaimer.
  229. 2. Redistributions in binary form must reproduce the above copyright
  230. notice, this list of conditions and the following disclaimer in the
  231. documentation and/or other materials provided with the distribution.
  232. 3. Neither the name of the project nor the names of its contributors
  233. may be used to endorse or promote products derived from this software
  234. without specific prior written permission.
  235. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  236. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  237. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  238. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  239. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  240. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  241. OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  242. HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  243. LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  244. OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  245. SUCH DAMAGE.
  246. */
  247. /* SPDX-License-Identifier: BSD-3-Clause */
  248. EOD;
  249. }
  250. if ($print_h_guard) {
  251. print <<<'EOD'
  252. #ifndef Z_GS1_LINT_H
  253. #define Z_GS1_LINT_H
  254. EOD;
  255. }
  256. // Print the spec validator/checkers functions
  257. foreach ($spec_parts as $spec => $spec_part) {
  258. $spec_funcs[$spec] = $spec_func = str_replace(array(' ', '.', ',', '[', ']'), '_', strtolower($spec));
  259. $comment = '';
  260. if (isset($spec_comments[$spec])) {
  261. $comment = ' (Used by';
  262. foreach ($spec_comments[$spec] as $i => $spec_comment) {
  263. if ($i) {
  264. $comment .= ', ';
  265. } else {
  266. $comment .= ' ';
  267. }
  268. $comment .= $spec_comment;
  269. }
  270. if (strlen($comment) > 118 - 3 /*start comment*/ - 4 /*)end comment*/ - strlen($spec)) {
  271. $comment = substr($comment, 0, 118 - 3 - 4 - strlen($spec) - 3) . '...';
  272. }
  273. $comment .= ')';
  274. }
  275. print <<<EOD
  276. /* $spec$comment */
  277. static int $spec_func(const unsigned char *data,
  278. $tab$tab{$tab}const int data_len, int *p_err_no, int *p_err_posn, char err_msg[50]) {
  279. {$tab}return
  280. EOD;
  281. list($total_min, $total_max) = $spec_ais[$spec];
  282. if ($total_min === $total_max) {
  283. print "data_len == $total_max";
  284. } else {
  285. print "data_len >= $total_min && data_len <= $total_max";
  286. }
  287. if ($use_length_only) {
  288. // Call checkers checking for length only first
  289. $length_only_arg = ", 1 /*length_only*/";
  290. $offset = 0;
  291. foreach ($spec_part as list($min, $max, $validator, $checkers)) {
  292. foreach ($checkers as $checker) {
  293. print <<<EOD
  294. $tab$tab{$tab}&& $checker(data, data_len, $offset, $min, $max, p_err_no, p_err_posn, err_msg$length_only_arg)
  295. EOD;
  296. }
  297. $offset += $max;
  298. }
  299. }
  300. // Validator and full checkers
  301. $length_only_arg = $use_length_only ? ", 0" : "";
  302. $offset = 0;
  303. foreach ($spec_part as list($min, $max, $validator, $checkers)) {
  304. print <<<EOD
  305. $tab$tab{$tab}&& $validator(data, data_len, $offset, $min, $max, p_err_no, p_err_posn, err_msg)
  306. EOD;
  307. foreach ($checkers as $checker) {
  308. print <<<EOD
  309. $tab$tab{$tab}&& $checker(data, data_len, $offset, $min, $max, p_err_no, p_err_posn, err_msg$length_only_arg)
  310. EOD;
  311. }
  312. $offset += $max;
  313. }
  314. print ";\n}\n\n";
  315. }
  316. // Print main routine
  317. print <<<EOD
  318. /* Entry point. Returns 1 on success, 0 on failure: `*p_err_no` set to 1 if unknown AI, 2 if bad data length */
  319. static int gs1_lint(const int ai, const unsigned char *data, const int data_len, int *p_err_no, int *p_err_posn,
  320. $tab$tab{$tab}char err_msg[50]) {
  321. $tab/* Assume data length failure */
  322. $tab*p_err_no = 2;
  323. EOD;
  324. // Split AIs into batches of 100 to lessen the number of comparisons
  325. $not_first_batch = false;
  326. $last_batch_e = -1;
  327. foreach ($batches as $batch => $batch_specs) {
  328. if (empty($batch_specs)) {
  329. continue;
  330. }
  331. $batch_s = $batch * 100;
  332. $batch_e = $batch_s + 100;
  333. if ($not_first_batch) {
  334. print "\n$tab} else if (ai < $batch_e) {\n\n";
  335. } else {
  336. print "\n{$tab}if (ai < $batch_e) {\n\n";
  337. $not_first_batch = true;
  338. }
  339. foreach ($batch_specs as $spec) {
  340. $total_min = $spec_ais[$spec][0];
  341. $total_max = $spec_ais[$spec][1];
  342. $ais = $spec_ais[$spec][2];
  343. $str = "$tab{$tab}if (";
  344. print $str;
  345. $width = strlen($str);
  346. // Count the applicable AIs
  347. $ais_cnt = 0;
  348. foreach ($ais as $ai) {
  349. if (is_array($ai)) {
  350. if ($ai[1] < $batch_s || $ai[0] >= $batch_e) {
  351. continue;
  352. }
  353. } else {
  354. if ($ai < $batch_s || $ai >= $batch_e) {
  355. continue;
  356. }
  357. }
  358. $ais_cnt++;
  359. }
  360. // Output
  361. $not_first_ai = false;
  362. foreach ($ais as $ai) {
  363. if (is_array($ai)) {
  364. if ($ai[1] < $batch_s || $ai[0] >= $batch_e) {
  365. continue;
  366. }
  367. } else {
  368. if ($ai < $batch_s || $ai >= $batch_e) {
  369. continue;
  370. }
  371. }
  372. $str = '';
  373. if ($not_first_ai) {
  374. $str .= " || ";
  375. } else {
  376. $not_first_ai = true;
  377. }
  378. if (is_array($ai)) {
  379. if ($ai[0] === $last_batch_e) { // Don't need 1st element of range if excluded by previous batch
  380. $str .= "ai <= " . $ai[1];
  381. } else if ($ai[1] + 1 == $batch_e) { // Don't need 2nd element of range if excluded by this batch
  382. $str .= "ai >= " . $ai[0];
  383. } else {
  384. if ($ais_cnt > 1) {
  385. $str .= "(ai >= " . $ai[0] . " && ai <= " . $ai[1] . ")";
  386. } else {
  387. $str .= "ai >= " . $ai[0] . " && ai <= " . $ai[1];
  388. }
  389. }
  390. } else {
  391. $str .= "ai == " . $ai;
  392. }
  393. if ($width + strlen($str) > 118) {
  394. print "\n";
  395. $str2 = "$tab$tab$tab ";
  396. print $str2;
  397. $width = strlen($str2);
  398. }
  399. print $str;
  400. $width += strlen($str);
  401. }
  402. $spec_func = $spec_funcs[$spec];
  403. $str = "$tab$tab{$tab}return $spec_func(data, data_len, p_err_no, p_err_posn, err_msg);";
  404. if (strlen($str) > 118) {
  405. print ") {\n$tab$tab{$tab}return $spec_func(data,\n";
  406. print "$tab$tab$tab$tab$tab{$tab}data_len, p_err_no, p_err_posn, err_msg);\n";
  407. } else {
  408. print ") {\n$str\n";
  409. }
  410. print <<<EOD
  411. $tab$tab}
  412. EOD;
  413. }
  414. $last_batch_e = $batch_e;
  415. }
  416. print <<<EOD
  417. $tab}
  418. {$tab}/* Unknown AI */
  419. {$tab}*p_err_no = 1;
  420. {$tab}return 0;
  421. }
  422. EOD;
  423. if ($print_h_guard) {
  424. print <<<'EOD'
  425. #endif /* Z_GS1_LINT_H */
  426. EOD;
  427. }
  428. /* vim: set ts=4 sw=4 et : */