nv-img-barcode-cmmn 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/gs1.js +387 -1
  2. package/package.json +1 -1
package/gs1.js CHANGED
@@ -356,6 +356,8 @@ const name_to_ai = (name)=>{
356
356
  return ai;
357
357
  }
358
358
  const ai_to_names = (ai)=>AI_TO_NAMES[ai];
359
+
360
+
359
361
  const encd_one =(name_or_ai,value)=>{
360
362
  let ai = name_to_ai(name_or_ai);
361
363
  if (!ai) {
@@ -434,6 +436,387 @@ const encd = (...args)=>{
434
436
  }
435
437
  }
436
438
 
439
+ const decd = (gs1_str, options = {}) => {
440
+ const {
441
+ strict = true, // 严格模式,验证每个字段
442
+ useChineseKeys = true, // 使用中文描述作为键
443
+ includeMeta = false, // 包含元数据
444
+ parseAll = true, // 解析所有字段,包括未知AI
445
+ } = options;
446
+
447
+ // 验证输入
448
+ if (!gs1_str || typeof gs1_str !== 'string') {
449
+ return [false, '输入必须是字符串'];
450
+ }
451
+
452
+ gs1_str = gs1_str.trim();
453
+ if (gs1_str === '') {
454
+ return [false, '输入字符串不能为空'];
455
+ }
456
+
457
+ // 验证GS1格式
458
+ if (!/^(?:\(\d{2,4}\)[^()]*)+$/.test(gs1_str)) {
459
+ if (strict) {
460
+ return [false, '无效的GS1格式'];
461
+ }
462
+ }
463
+
464
+ let result = {};
465
+ const errors = [];
466
+ const warnings = [];
467
+
468
+ // 匹配所有字段
469
+ const segments = gs1_str.match(/\((\d{2,4})\)([^()]*)/g) || [];
470
+
471
+ if (segments.length === 0) {
472
+ return [false, '未找到有效的GS1字段'];
473
+ }
474
+
475
+ for (const segment of segments) {
476
+ const match = segment.match(/\((\d{2,4})\)([^()]*)/);
477
+ if (!match) {
478
+ warnings.push(`无法解析段: ${segment}`);
479
+ continue;
480
+ }
481
+
482
+ const ai = match[1];
483
+ const rawValue = match[2];
484
+ const spec = GS1_AI_SPECS[ai];
485
+
486
+ // 解析值
487
+ let parsedValue = rawValue;
488
+ let isValid = true;
489
+ let error = null;
490
+
491
+ if (spec) {
492
+ // 已知字段
493
+ try {
494
+ // 根据字段类型验证长度
495
+ if (spec.length) {
496
+ if (spec.format === 'numeric' || spec.format === 'date') {
497
+ // numeric 和 date 必须是固定长度
498
+ if (rawValue.length !== spec.length) {
499
+ isValid = false;
500
+ error = `长度应为${spec.length}位,实际为${rawValue.length}位`;
501
+ }
502
+ } else if (spec.format === 'alphanumeric') {
503
+ // alphanumeric 只需要不超过最大长度 ✅
504
+ if (rawValue.length > spec.length) {
505
+ isValid = false;
506
+ error = `长度不能超过${spec.length}位,实际为${rawValue.length}位`;
507
+ }
508
+ }
509
+ }
510
+
511
+ if (spec.format === 'numeric') {
512
+ // 验证是否为纯数字
513
+ if (!/^\d+$/.test(rawValue)) {
514
+ isValid = false;
515
+ error = '数值字段必须为纯数字';
516
+ } else if (spec.decimal > 0) {
517
+ // 有小数位的数值
518
+ const num = parseInt(rawValue, 10) / Math.pow(10, spec.decimal);
519
+ parsedValue = Number(num.toFixed(spec.decimal));
520
+ } else {
521
+ // 整数
522
+ parsedValue = parseInt(rawValue, 10);
523
+ if (isNaN(parsedValue)) {
524
+ isValid = false;
525
+ error = '无效的整数值';
526
+ }
527
+ }
528
+ } else if (spec.format === 'date') {
529
+ // 日期验证
530
+ if (!/^\d{6}$/.test(rawValue)) {
531
+ isValid = false;
532
+ error = '日期必须是6位数字';
533
+ }
534
+ } else if (spec.format === 'alphanumeric') {
535
+ // 验证字符集
536
+ if (!GS1_ALPHANUMERIC_REGEX.test(rawValue)) {
537
+ warnings.push(`AI(${ai}) 包含不允许的字符`);
538
+ }
539
+ }
540
+
541
+ if (!isValid && strict) {
542
+ errors.push(`AI(${ai}) ${spec.desc || spec.name}: ${error}`);
543
+ continue;
544
+ }
545
+
546
+ // 确定键名
547
+ let key;
548
+ if (useChineseKeys && spec.desc) {
549
+ // 使用中文描述(清理后)
550
+ key = spec.desc
551
+ .replace(/\([^)]*\)/g, '')
552
+ .replace('m²', '平')
553
+ .replace('m³', '方')
554
+ .replace('kg', '公斤')
555
+ .replace('lb', '磅')
556
+ .replace('L', '升')
557
+ .replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '')
558
+ .trim();
559
+
560
+ if (key === '') {
561
+ key = spec.name ? spec.name.toLowerCase() : ai;
562
+ }
563
+ } else {
564
+ // 使用英文名
565
+ key = spec.name ? spec.name.toLowerCase() : ai;
566
+ }
567
+
568
+ // 添加到结果
569
+ if (isValid || !strict) {
570
+ result[key] = parsedValue;
571
+ }
572
+
573
+ if (includeMeta) {
574
+ result[`${key}_ai`] = ai;
575
+ result[`${key}_raw`] = rawValue;
576
+ if (!isValid) {
577
+ result[`${key}_error`] = error;
578
+ }
579
+ }
580
+
581
+ } catch (err) {
582
+ const errorMsg = `AI(${ai}) 解析错误: ${err.message}`;
583
+ if (strict) {
584
+ errors.push(errorMsg);
585
+ } else {
586
+ warnings.push(errorMsg);
587
+ }
588
+ }
589
+
590
+ } else if (parseAll) {
591
+ // 未知字段
592
+ result[`ai_${ai}`] = rawValue;
593
+ warnings.push(`未知AI代码: ${ai}`);
594
+ } else {
595
+ warnings.push(`跳过未知AI代码: ${ai}`);
596
+ }
597
+ }
598
+
599
+ // 构建最终结果
600
+ if (errors.length > 0) {
601
+ const errorMsg = errors.join('; ');
602
+ if (warnings.length > 0) {
603
+ return [false, `${errorMsg} (警告: ${warnings.join('; ')})`];
604
+ }
605
+ return [false, errorMsg];
606
+ }
607
+
608
+ if (includeMeta) {
609
+ const meta = {};
610
+ if (warnings.length > 0) {
611
+ meta.warnings = warnings;
612
+ }
613
+ if (Object.keys(result).length > 0) {
614
+ meta.fieldCount = Object.keys(result).filter(k => !k.endsWith('_ai') && !k.endsWith('_raw') && !k.endsWith('_error')).length;
615
+ }
616
+
617
+ result._meta = meta;
618
+ } else if (warnings.length > 0) {
619
+ console.warn('GS1解析警告:', warnings.join('; '));
620
+ }
621
+
622
+ return [true, result];
623
+ };
624
+
625
+ const rand_one = (onlyChineseName = false, onlyEnglishName = false) => {
626
+ // 获取所有可用的字段名
627
+ let availableNames = [];
628
+
629
+ if (onlyChineseName && onlyEnglishName) {
630
+ // 两个都true,就返回所有
631
+ availableNames = Object.keys(NAME_TO_AI);
632
+ } else if (onlyChineseName) {
633
+ // 只返回中文名
634
+ availableNames = Object.keys(NAME_TO_AI).filter(name => {
635
+ return /[\u4e00-\u9fa5]/.test(name) ||
636
+ ALIAS[name] && /[\u4e00-\u9fa5]/.test(Object.keys(ALIAS).find(k => ALIAS[k] === ALIAS[name]));
637
+ });
638
+ } else if (onlyEnglishName) {
639
+ // 只返回英文名
640
+ availableNames = Object.keys(NAME_TO_AI).filter(name => {
641
+ return /^[a-zA-Z]/.test(name) &&
642
+ !/[\u4e00-\u9fa5]/.test(name) &&
643
+ !/^\d+$/.test(name);
644
+ });
645
+ } else {
646
+ // 返回所有
647
+ availableNames = Object.keys(NAME_TO_AI);
648
+ }
649
+
650
+ if (availableNames.length === 0) {
651
+ return null;
652
+ }
653
+
654
+ // 随机选择一个字段
655
+ const randomName = availableNames[Math.floor(Math.random() * availableNames.length)];
656
+ const ai = NAME_TO_AI[randomName];
657
+ const spec = GS1_AI_SPECS[ai];
658
+
659
+ if (!spec) {
660
+ return null;
661
+ }
662
+
663
+ // 根据字段类型生成随机值
664
+ let randomValue;
665
+
666
+ switch (spec.format) {
667
+ case 'numeric':
668
+ if (spec.decimal > 0) {
669
+ // 带小数的数值
670
+ const maxValue = Math.pow(10, spec.length - spec.decimal) - 1;
671
+ const integer = Math.floor(Math.random() * maxValue);
672
+ const decimal = Math.floor(Math.random() * Math.pow(10, spec.decimal));
673
+ randomValue = integer + decimal / Math.pow(10, spec.decimal);
674
+
675
+ // 格式化为指定小数位
676
+ randomValue = Number(randomValue.toFixed(spec.decimal));
677
+ } else {
678
+ // 整数
679
+ const maxValue = Math.pow(10, spec.length) - 1;
680
+ randomValue = Math.floor(Math.random() * maxValue);
681
+ }
682
+ break;
683
+
684
+ case 'date':
685
+ // 生成未来2年内的随机日期
686
+ const now = new Date();
687
+ const futureDate = new Date(now.getTime() + Math.random() * 2 * 365 * 24 * 60 * 60 * 1000);
688
+ const year = futureDate.getFullYear() % 100;
689
+ const month = futureDate.getMonth() + 1;
690
+ const day = futureDate.getDate();
691
+ randomValue = `${year.toString().padStart(2, '0')}${month.toString().padStart(2, '0')}${day.toString().padStart(2, '0')}`;
692
+ break;
693
+
694
+ case 'alphanumeric':
695
+ // 随机长度(1到最大长度)
696
+ const length = Math.floor(Math.random() * spec.length) + 1;
697
+
698
+ // 修正:使用安全的字符集
699
+ // 只使用字母、数字、连字符、下划线
700
+ const safeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
701
+
702
+ randomValue = '';
703
+ for (let i = 0; i < length; i++) {
704
+ randomValue += safeChars[Math.floor(Math.random() * safeChars.length)];
705
+ }
706
+
707
+ break;
708
+
709
+ default:
710
+ randomValue = '';
711
+ }
712
+
713
+ return {
714
+ name: randomName,
715
+ ai: ai,
716
+ value: randomValue,
717
+ spec: spec
718
+ };
719
+ };
720
+
721
+ const rand = (max_cnt = 5, min_cnt = 1, options = {}) => {
722
+ // 参数处理
723
+ min_cnt = min_cnt < 1 ? 1 : min_cnt;
724
+ max_cnt = max_cnt > min_cnt ? max_cnt : min_cnt;
725
+
726
+ // 从选项中获取参数
727
+ const {
728
+ onlyChineseName = false,
729
+ onlyEnglishName = false,
730
+ includeCustomAI = false,
731
+ uniqueAI = true,
732
+ maxTotalLength = 48,
733
+ returnObject = false, // 是否返回对象而不是字符串
734
+ } = options;
735
+
736
+ // 随机生成实际数量
737
+ const actualCount = Math.floor(Math.random() * (max_cnt - min_cnt + 1)) + min_cnt;
738
+
739
+ const fields = [];
740
+ const usedAIs = new Set();
741
+ let totalLength = 0;
742
+
743
+ for (let i = 0; i < actualCount; i++) {
744
+ let field;
745
+ let attempts = 0;
746
+ const maxAttempts = 20;
747
+
748
+ do {
749
+ field = rand_one(onlyChineseName, onlyEnglishName);
750
+ attempts++;
751
+
752
+ if (!field) break;
753
+
754
+ // 检查是否排除自定义AI
755
+ if (!includeCustomAI && /^9[0-9]$/.test(field.ai)) {
756
+ continue;
757
+ }
758
+
759
+ // 检查AI是否唯一
760
+ if (uniqueAI && usedAIs.has(field.ai)) {
761
+ continue;
762
+ }
763
+
764
+ // 检查总长度
765
+ const fieldLength = `(${field.ai})${field.value}`.length;
766
+ if (totalLength + fieldLength > maxTotalLength) {
767
+ continue;
768
+ }
769
+
770
+ break;
771
+ } while (attempts < maxAttempts);
772
+
773
+ if (!field || attempts >= maxAttempts) {
774
+ break;
775
+ }
776
+
777
+ usedAIs.add(field.ai);
778
+ totalLength += `(${field.ai})${field.value}`.length;
779
+ fields.push(field);
780
+ }
781
+
782
+ if (fields.length === 0) {
783
+ return returnObject ? { success: false, error: "未能生成任何字段" } : null;
784
+ }
785
+
786
+ // 编码为GS1字符串
787
+ const gs1Parts = [];
788
+
789
+ for (const field of fields) {
790
+ // 使用 encd 函数编码
791
+ const encoded = x.gs1.encd(field.name, field.value);
792
+ if (encoded) {
793
+ gs1Parts.push(encoded);
794
+ }
795
+ }
796
+
797
+ if (gs1Parts.length === 0) {
798
+ return returnObject ? {
799
+ success: false,
800
+ error: "所有字段编码失败"
801
+ } : null;
802
+ }
803
+
804
+ const gs1String = gs1Parts.join("");
805
+
806
+ if (returnObject) {
807
+ return {
808
+ success: true,
809
+ gs1: gs1String,
810
+ fields: fields,
811
+ count: fields.length,
812
+ length: gs1String.length,
813
+ withinLengthLimit: gs1String.length <= maxTotalLength
814
+ };
815
+ }
816
+
817
+ return gs1String;
818
+ };
819
+
437
820
  module.exports = {
438
821
  get GS1_AI_SPECS() {return GS1_AI_SPECS;},
439
822
  get ALIAS() {return ALIAS;},
@@ -449,6 +832,9 @@ module.exports = {
449
832
  encd_one,
450
833
  _encd,
451
834
  encd,
835
+ decd,
452
836
  list_names: ()=>FIXED_NAMES,
453
- look_requirements: (name)=>GS1_AI_SPECS[name_to_ai(name)]
837
+ look_requirements: (name)=>GS1_AI_SPECS[name_to_ai(name)],
838
+ rand_one,rand,
839
+
454
840
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nv-img-barcode-cmmn",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"