Search code examples
dartbarcodegs1-ai-syntax

Extracting the GS1 Company Prefix from a GTIN


I am trying to get the company prefix from barcodes using dart:

String extractCompanyPrefix(String gtin) {
  // Ensure the GTIN is a string to handle leading zeros and perform string operations
  String gtinStr = gtin.padLeft(14, '0');

  String prefix = gtinStr.substring(0, 8);
  // remove leading zeros
  prefix = prefix.replaceFirst(RegExp(r'^0+'), '');
  return prefix;
}

void main(List<String> arguments) {
  if (arguments.isEmpty) {
    print('Please provide a barcode.');
    return;
  }

  String barcode = arguments.first;
  String companyPrefix = extractCompanyPrefix(barcode);
  print('Company prefix: $companyPrefix');
}

❯ dart run dart/company_prefix_checker.dart 8710398527905
Company prefix: 8710398
❯ dart run dart/company_prefix_checker.dart 40111445
Company prefix: 40

Is that the correct way and will it result in an accurate GS1 Company Prefix?


Updated #2 code to add @Terry Burton recommendations:

import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'lib/application_identifiers.dart';
import 'lib/gcp_length.dart';


Future<String> extractCompanyPrefixFromPrefixList(
    String barcode, Map<String, int> gcpLengths) async {
  if (barcode.length > 12) {
    // remove the leading zeros
    barcode = barcode.replaceFirst(RegExp(r'^0+'), '');
  }else {
    // pad the barcode with leading zeros
    barcode = barcode.padLeft(12, '0');
  }
  // loop through the numbers from 3 to 12 and compare the substring to the GCP list
  for (var i = 3; i <= 12; i++) {
    var prefix = barcode.substring(0, i);
    if (gcpLengths.containsKey(prefix)) {
      print(
          'Found GCP prefix format: $prefix with length: ${gcpLengths[prefix]}');
      return barcode.substring(0, gcpLengths[prefix]!);
    }
  }
  return '';
}

Future<String> extractCompanyPrefix(
    String barcode, Map<String, int> gcpLengths) async {
  var result = await extractCompanyPrefixFromPrefixList(barcode, gcpLengths);
  if (result.isNotEmpty) {
    return result;
  }

  var gai = await GS1ApplicationIdentifier.extract(barcode);
  result = getGCPLength(gai.ai, barcode.substring(gai.ai.length));
  if (result !=
      'There is no matching value. Try GEPIR (https://gepir.gs1.org/) or contact local GS1 MO.') {
    return result;
  }

  return "Invalid Barcode or GCP Not Found";
}

Future<Map<String, int>> loadGcpLengths(String filePath) async {
  try {
    var file = File(filePath);
    var contents = await file.readAsString();
    var jsonData = json.decode(contents);

    Map<String, int> gcpLengths = {};
    for (var entry in jsonData['GCPPrefixFormatList']['entry']) {
      gcpLengths[entry['prefix']] = entry['gcpLength'];
    }

    return gcpLengths;
  } catch (e) {
    throw Exception('Failed to load GCP length data from file: $e');
  }
}

void main(List<String> arguments) async {
  if (arguments.isEmpty) {
    print('Please provide a barcode.');
    return;
  }

  // Specify the path to your local 'gcpprefixformatlist.json' file
  var jsonFilePath =
      path.join(Directory.current.path, 'lib/gcpprefixformatlist.json');

  String barcode = arguments.first;
  try {
    Map<String, int> gcpLengths = await loadGcpLengths(jsonFilePath);
    String companyPrefix = await extractCompanyPrefix(barcode, gcpLengths);
    print('Company prefix: $companyPrefix');
  } catch (e) {
    print('Error: $e');
  }
}


lib/application_identifiers.dart:


import 'dart:convert';
import 'dart:io';

class GS1ApplicationIdentifier {
  final String ai;
  final String description;
  final String dataTitle;
  final bool fnc1Required;
  final String format;
  final String pattern;

  const GS1ApplicationIdentifier({
    required this.ai,
    required this.description,
    required this.dataTitle,
    required this.fnc1Required,
    required this.format,
    required this.pattern,
  });

  static Future<GS1ApplicationIdentifier> extract(String value) async {
    final applicationIdentifiers = await _loadApplicationIdentifiers();
    for (var ai in applicationIdentifiers) {
      if (value.startsWith(ai.ai)) {
        return ai;
      }
    }
    throw FormatException('Failed to get GS1 Application Identifier from $value.');
  }

  @override
  String toString() {
    return '($ai)';
  }

  static Future<List<GS1ApplicationIdentifier>> _loadApplicationIdentifiers() async {
    // final jsonString = await rootBundle.loadString('lib/application_identifiers.json');
    final jsonString = await File('lib/_application_identifiers.json').readAsString();
    final List<dynamic> jsonList = json.decode(jsonString);
    return jsonList.map((json) => GS1ApplicationIdentifier.fromJson(json)).toList();
  }

  factory GS1ApplicationIdentifier.fromJson(Map<String, dynamic> json) {
    return GS1ApplicationIdentifier(
      ai: json['ai'] as String,
      description: json['description'] as String,
      dataTitle: json['data_title'] as String,
      fnc1Required: json['fnc1_required'] as bool,
      format: json['format'] as String,
      pattern: json['pattern'] as String,
    );
  }
}

lib/gcp_length.dart:

import 'dart:convert';
import 'package:http/http.dart' as http;

final Map<String, String> gs1KeyRegEx = {
  '00': r'^(\d{18})$',
  '01': r'^(\d{14})$',
  '253': r'^(\d{13})([\x21-\x22\x25-\x2F\x30-\x39\x3A-\x3F\x41-\x5A\x5F\x61-\x7A]{0,17})$',
  '255': r'^(\d{13})(\d{0,12})$',
  '401': r'^([\x21-\x22\x25-\x2F\x30-\x39\x3A-\x3F\x41-\x5A\x5F\x61-\x7A]{0,30})$',
  '402': r'^(\d{17})$',
  '414': r'^(\d{13})$',
  '417': r'^(\d{13})$',
  '8003': r'^(\d{14})([\x21-\x22\x25-\x2F\x30-\x39\x3A-\x3F\x41-\x5A\x5F\x61-\x7A]{0,16})$',
  '8004': r'^([\x21-\x22\x25-\x2F\x30-\x39\x3A-\x3F\x41-\x5A\x5F\x61-\x7A]{0,30})$',
  '8006': r'^(\d{14})(\d{2})(\d{2})$',
  '8010': r'^([\x23\x2D\x2F\x30-\x39\x41-\x5A]{0,30})$',
  '8017': r'^(\d{18})$',
  '8018': r'^(\d{18})$',
};

final Map<String, bool> keyStartsWithGCP = {
  '00': false,
  '01': false,
  '253': true,
  '255': true,
  '401': true,
  '402': true,
  '414': true,
  '417': true,
  '8003': false,
  '8004': true,
  '8006': false,
  '8010': true,
  '8017': true,
  '8018': true,
};


List<Map<String, dynamic>> gcpDict = [];

Future<void> fetchGcpData() async {
  final response = await http.get(Uri.parse('https://www.gs1.org/sites/default/files/docs/gcp_length/gcpprefixformatlist.json'));
  if (response.statusCode == 200) {
    final allGCPs = jsonDecode(response.body) as Map<String, dynamic>;
    gcpDict = List<Map<String, dynamic>>.from(allGCPs["GCPPrefixFormatList"]["entry"]);
  } else {
    throw Exception('Failed to fetch GCP Length Table data.');
  }
}

String getGCPLength(String aI, String gs1Key) {
  // Check if GS1 Key complies with its corresponding RegEx
  final regex = RegExp(gs1KeyRegEx[aI] ?? '');
  if (!regex.hasMatch(gs1Key)) {
    return 'The GS1 Key has an incorrect length or impermissible characters.';
  }

  String gcpLength = '';
  int j = 12;

  // Normalize input string
  if (keyStartsWithGCP[aI] ?? false) {
    gs1Key = '0' + gs1Key;
  }
  // Determine GCP length
  while (j > 2 && gcpLength.isEmpty) {
    for (var entry in gcpDict) {
      if (entry["prefix"].length == j && gs1Key.startsWith(entry["prefix"])) {
        gcpLength = entry["gcpLength"].toString();
        return gcpLength;
      }
    }
    j -= 1;
  }

  return 'There is no matching value. Try GEPIR (https://gepir.gs1.org/) or contact local GS1 MO.';
}

Future<void> main() async {
  try {
    await fetchGcpData();
    // Example usage
    String gcpLength = getGCPLength('01', '01234567891234');
    print(gcpLength);
  } catch (e) {
    print('Error: $e');
  }
}


❯ dart run company_prefix_checker.dart 8710398527905
Found GCP prefix format: 8710 with length: 7
Company prefix: 8710398
❯ dart run company_prefix_checker.dart 40111445
Found GCP prefix format: 00004 with length: 7
Company prefix: 0000401

The code will search https://www.gs1.org/docs/gcp_length/gcpprefixformatlist.json for a match other wise will fallback to regex and AI.

Is that the correct result?


Solution

  • Is that the correct way and results?

    No.

    The GS1 Company Prefix (GCP) is variable length, between 6 and 12 digits.

    The length of the GCP is not specified within the data itself. The length of an instance of a GCP is entirely at the discretion of the licensing GS1 Member Organisation "MO", i.e. the regional issuer of GCPs. Nearly all MOs report their individual allocation scheme to GS1 Global who occasionally publishes a combined mapping of GCP prefixes to overall GCP length.

    You cannot separate out the GCP from the remaining data without reference to the prefix length tables that are provided in XML and JSON format, here: https://www.gs1.org/standards/bc-epc-interop

    The data is not timely and varies in accuracy with the quality largely depending on the diligence of the GS1 Member Organisation that issued the GCP.