def extract(cls, value: str) -> SymbologyIdentifier: """Extract the Symbology Identifier from the given value. Args: value: The string to extract a Symbology Identifier from. Returns: Metadata about the extracted Symbology Identifier. Raises: ParseError: If the parsing fails.o """ if not value.startswith("]"): raise ParseError( f"Failed to get Symbology Identifier from {value!r}. " "No initial ']' flag character found." ) try: symbology = Symbology(value[1]) except ValueError: raise ParseError( f"Failed to get Symbology Identifier from {value!r}. " f"{value[1]!r} is not a recognized code character." ) if symbology == Symbology.SYSTEM_EXPANSION: modifiers_length = int(value[2]) + 1 else: modifiers_length = 1 modifiers = value[2 : 2 + modifiers_length] value = f"]{symbology.value}{modifiers}" gs1_symbology: Optional[GS1Symbology] try: gs1_symbology = GS1Symbology(f"{symbology.value}{modifiers}") except ValueError: gs1_symbology = None return cls( value=value, symbology=symbology, modifiers=modifiers, gs1_symbology=gs1_symbology, )
def parse( value: str, *, rcn_region: Optional[RcnRegion] = None, separator_chars: Iterable[str] = DEFAULT_SEPARATOR_CHARS, ) -> ParseResult: """Identify data format and parse data. The current strategy is: 1. If Symbology Identifier prefix indicates a GTIN or GS1 Message, attempt to parse and validate as that. 2. Else, if not Symbology Identifier, attempt to parse with all parsers. Args: value: The data to classify and parse. rcn_region: The geographical region whose rules should be used to interpret Restricted Circulation Numbers (RCN). Needed to extract e.g. variable weight/price from GTIN. separator_chars: Characters used in place of the FNC1 symbol. Defaults to `<GS>` (ASCII value 29). If variable-length fields in the middle of the message are not terminated with a separator character, the parser might greedily consume the rest of the message. Returns: A data class depending upon what type of data is parsed. Raises: ParseError: If parsing of the data fails. """ value = value.strip() config = ParseConfig( rcn_region=rcn_region, separator_chars=separator_chars, ) result = ParseResult(value=value) # Extract Symbology Identifier if value.startswith("]"): result.symbology_identifier = SymbologyIdentifier.extract(value) value = value[len(result.symbology_identifier) :] # Select parsers queue: ParseQueue = [] if result.symbology_identifier is not None: if result.symbology_identifier.gs1_symbology in GS1Symbology.with_gtin(): queue.append((_parse_gtin, value)) if ( result.symbology_identifier.gs1_symbology in GS1Symbology.with_ai_element_strings() ): queue.append((_parse_gs1_message, value)) if not queue: # If we're not able to select a subset based on Symbology Identifiers, # run all parsers on the full value. queue = [ (_parse_gs1_message, value), (_parse_gtin, value), (_parse_sscc, value), (_parse_upc, value), ] # Work through queue of parsers and the values to run them on. Any parser may # add additional work to the queue. Only the first result for a field is kept. while queue: (parse_func, val) = queue.pop(0) parse_func(val, config=config, queue=queue, result=result) if result._has_result(): return result else: raise ParseError(f"Failed to parse {value!r}:\n{result._get_errors_list()}")
def test_gs1_symbology_with_ai_element_strings() -> None: assert GS1Symbology.EAN_13 not in GS1Symbology.with_ai_element_strings() assert GS1Symbology.GS1_128 in GS1Symbology.with_ai_element_strings()
def test_gs1_symbology_enum() -> None: assert GS1Symbology("C1") == GS1Symbology.GS1_128
def test_gs1_symbology_with_gtin() -> None: assert GS1Symbology.EAN_13 in GS1Symbology.with_gtin() # Even though GS1-128 can contain a GTIN, # it cannot be parsed with a pure GTIN parser. assert GS1Symbology.GS1_128 not in GS1Symbology.with_gtin()
def parse( value: str, *, rcn_region: Optional[RcnRegion] = None, separator_chars: Iterable[str] = DEFAULT_SEPARATOR_CHARS, ) -> ParseResult: """Identify data format and parse data. The current strategy is: 1. If Symbology Identifier prefix indicates a GTIN or GS1 Message, attempt to parse and validate as that. 2. Else, if not Symbology Identifier, attempt to parse with all parsers. Args: value: The data to classify and parse. rcn_region: The geographical region whose rules should be used to interpret Restricted Circulation Numbers (RCN). Needed to extract e.g. variable weight/price from GTIN. separator_chars: Characters used in place of the FNC1 symbol. Defaults to `<GS>` (ASCII value 29). If variable-length fields in the middle of the message are not terminated with a separator character, the parser might greedily consume the rest of the message. Returns: A data class depending upon what type of data is parsed. Raises: ParseError: If parsing of the data fails. """ value = value.strip() result = ParseResult(value=value) # Extract Symbology Identifier if value.startswith("]"): result.symbology_identifier = SymbologyIdentifier.extract(value) value = value[len(result.symbology_identifier):] # Select parsers parsers: List[ParserType] = [] if result.symbology_identifier is not None: if (result.symbology_identifier.gs1_symbology in GS1Symbology.with_gtin()): parsers.append(Gtin) if (result.symbology_identifier.gs1_symbology in GS1Symbology.with_ai_element_strings()): parsers.append(GS1Message) if not parsers: # If we're not able to select a subset based on Symbology Identifiers, # run all parsers in the default order. parsers = [Gtin, Sscc, GS1Message] # Run all parsers in order for parser in parsers: if parser == Gtin: try: result.gtin = Gtin.parse(value, rcn_region=rcn_region) except ParseError as exc: result.gtin_error = str(exc) if parser == Sscc: try: result.sscc = Sscc.parse(value) except ParseError as exc: result.sscc_error = str(exc) if parser == GS1Message: try: result.gs1_message = GS1Message.parse( value, rcn_region=rcn_region, separator_chars=separator_chars, ) except ParseError as exc: result.gs1_message_error = str(exc) else: ai_00 = result.gs1_message.get(ai="00") if ai_00 is not None: # GS1 Message contains an SSCC result.sscc = ai_00.sscc # Clear error from parsing full value a SSCC. result.sscc_error = None ai_01 = result.gs1_message.get(ai="01") if ai_01 is not None: # GS1 Message contains a GTIN. result.gtin = ai_01.gtin # Clear error from parsing full value as GTIN. result.gtin_error = None if result._has_result(): return result else: raise ParseError( f"Failed to parse {value!r}:\n{result._get_errors_list()}")