Skip to content

biip.gs1_messages

Support for barcode data with GS1 messages.

The biip.gs1_messages module contains Biip's support for parsing data consisting of GS1 messages and element strings. GS1 messages are text strings consisting of one or more GS1 element strings. Each GS1 Element String is identified by a GS1 Application Identifier (AI) prefix followed by the element's value.

Data of this format is found in the following types of barcodes:

If you only want to parse GS1 Messages, you can import the GS1 Message parser directly instead of using biip.parse().

>>> from biip.gs1_messages import GS1Message

If the parsing succeeds, it returns a GS1Message object.

>>> msg = GS1Message.parse("010703206980498815210526100329")

The GS1Message can be represented as an HRI, short for "human readable interpretation", using msg.as_hri(). The HRI is the text usually printed below or next to the barcode.

>>> msg.value
'010703206980498815210526100329'
>>> msg.as_hri()
'(01)07032069804988(15)210526(10)0329'

HRI can also be parsed using GS1Message.parse_hri().

>>> msg = GS1Message.parse_hri("(01)07032069804988(15)210526(10)0329")

A message can contain multiple element strings.

>>> len(msg.element_strings)
3

In this example, the first element string is a GTIN.

>>> pprint(msg.element_strings[0])
GS1ElementString(
    ai=GS1ApplicationIdentifier(
        ai='01',
        description='Global Trade Item Number (GTIN)',
        data_title='GTIN',
        separator_required=False,
        format='N2+N14'
    ),
    value='07032069804988',
    pattern_groups=[
        '07032069804988'
    ],
    gtin=Gtin(
        value='07032069804988',
        format=GtinFormat.GTIN_13,
        prefix=GS1Prefix(
            value='703',
            usage='GS1 Norway'
        ),
        company_prefix=GS1CompanyPrefix(
            value='703206'
        ),
        payload='703206980498',
        check_digit=8
    )
)

The element_strings attribute has element_strings.get() and element_strings.filter() methods to lookup element strings either by the Application Identifier's "data title" or its AI number.

>>> pprint(msg.element_strings.get(data_title='BEST BY'))
GS1ElementString(
    ai=GS1ApplicationIdentifier(
        ai='15',
        description='Best before date (YYMMDD)',
        data_title='BEST BEFORE or BEST BY',
        separator_required=False,
        format='N2+N6'
    ),
    value='210526',
    pattern_groups=[
        '210526'
    ],
    date=datetime.date(2021, 5, 26)
)
>>> pprint(msg.element_strings.get(ai="10"))
GS1ElementString(
    ai=GS1ApplicationIdentifier(
        ai='10',
        description='Batch or lot number',
        data_title='BATCH/LOT',
        separator_required=True,
        format='N2+X..20'
    ),
    value='0329',
    pattern_groups=[
        '0329'
    ]
)

GS1Message dataclass

A GS1 message is the result of a single barcode scan.

It may contain one or more GS1 element strings.

Examples:

See biip.gs1_messages for a usage example.

Source code in src/biip/gs1_messages.py
@dataclass(frozen=True)
class GS1Message:
    """A GS1 message is the result of a single barcode scan.

    It may contain one or more GS1 element strings.

    Examples:
        See `biip.gs1_messages` for a usage example.
    """

    value: str
    """Raw unprocessed value."""

    element_strings: GS1ElementStrings
    """List of element strings found in the message.

    See [`GS1ElementStrings`][biip.gs1_element_strings.GS1ElementStrings] for
    methods to extract interesting element strings from the list.
    """

    @classmethod
    def parse(
        cls,
        value: str,
        *,
        config: ParseConfig | None = None,
    ) -> GS1Message:
        """Parse a string from a barcode scan as a GS1 message with AIs.

        Args:
            value: The string to parse.
            config: Configuration options for parsing.

        Returns:
            A message object with one or more element strings.

        Raises:
            ValueError: If the `separator_char` isn't exactly 1 character long.
            ParseError: If the parsing fails.
        """
        if config is None:
            config = ParseConfig()

        value = value.strip()
        element_strings = GS1ElementStrings()
        rest = value[:]

        while rest:
            element_string = GS1ElementString.extract(rest, config=config)
            element_strings.append(element_string)

            rest = rest[len(element_string) :]

            # Separator characters are accepted inbetween any element string,
            # even if the AI doesn't require it. See GS1 General Specifications,
            # section 7.8.6 for details.
            while rest.startswith(tuple(config.separator_chars)):
                rest = rest[1:]

        return cls(value=value, element_strings=element_strings)

    @classmethod
    def from_element_strings(cls, element_strings: GS1ElementStrings) -> GS1Message:
        """Create a GS1 message from a list of element strings.

        Args:
            element_strings: A list of GS1 element strings.

        Returns:
            GS1Message: The created GS1 message.
        """
        parts = chain(
            *[
                [
                    es.ai.ai,
                    es.value,
                    ("\x1d" if es.ai.separator_required else ""),
                ]
                for es in element_strings
            ]
        )
        normalized_string = "".join(parts).removesuffix("\x1d")
        return cls(value=normalized_string, element_strings=element_strings)

    @classmethod
    def parse_hri(
        cls,
        value: str,
        *,
        config: ParseConfig | None = None,
    ) -> GS1Message:
        """Parse the GS1 string given in HRI (human readable interpretation) format.

        Args:
            value: The HRI string to parse.
            config: Configuration options for parsing.

        Returns:
            A message object with one or more element strings.

        Raises:
            ParseError: If parsing of the data fails.
        """
        if config is None:
            config = ParseConfig()

        value = value.strip()
        if not value.startswith("("):
            msg = f"Expected HRI string {value!r} to start with a parenthesis."
            raise ParseError(msg)

        pattern = r"\((\d+)\)(\w+)"
        matches: list[tuple[str, str]] = re.findall(pattern, value)
        if not matches:
            msg = (
                f"Could not find any GS1 Application Identifiers in {value!r}. "
                "Expected format: '(AI)DATA(AI)DATA'."
            )
            raise ParseError(msg)

        element_strings = GS1ElementStrings()
        for ai_number, ai_data in matches:
            if ai_number not in _GS1_APPLICATION_IDENTIFIERS:
                msg = f"Unknown GS1 Application Identifier {ai_number!r} in {value!r}."
                raise ParseError(msg)
            element_strings.append(
                GS1ElementString.extract(f"{ai_number}{ai_data}", config=config)
            )

        return GS1Message.from_element_strings(element_strings)

    def as_hri(self) -> str:
        """Render as a human readable interpretation (HRI).

        The HRI is often printed directly below barcodes.

        Returns:
            A human-readable string where the AIs are wrapped in parenthesis.
        """
        return "".join(es.as_hri() for es in self.element_strings)

    def as_gs1_web_uri(self) -> GS1WebURI:
        """Convert to a GS1 Web URI."""
        from biip.gs1_web_uris import GS1WebURI

        return GS1WebURI.from_element_strings(self.element_strings)

element_strings instance-attribute

element_strings: GS1ElementStrings

List of element strings found in the message.

See GS1ElementStrings for methods to extract interesting element strings from the list.

value instance-attribute

value: str

Raw unprocessed value.

as_gs1_web_uri

as_gs1_web_uri() -> GS1WebURI

Convert to a GS1 Web URI.

Source code in src/biip/gs1_messages.py
def as_gs1_web_uri(self) -> GS1WebURI:
    """Convert to a GS1 Web URI."""
    from biip.gs1_web_uris import GS1WebURI

    return GS1WebURI.from_element_strings(self.element_strings)

as_hri

as_hri() -> str

Render as a human readable interpretation (HRI).

The HRI is often printed directly below barcodes.

Returns:

  • str

    A human-readable string where the AIs are wrapped in parenthesis.

Source code in src/biip/gs1_messages.py
def as_hri(self) -> str:
    """Render as a human readable interpretation (HRI).

    The HRI is often printed directly below barcodes.

    Returns:
        A human-readable string where the AIs are wrapped in parenthesis.
    """
    return "".join(es.as_hri() for es in self.element_strings)

from_element_strings classmethod

from_element_strings(
    element_strings: GS1ElementStrings,
) -> GS1Message

Create a GS1 message from a list of element strings.

Parameters:

Returns:

  • GS1Message ( GS1Message ) –

    The created GS1 message.

Source code in src/biip/gs1_messages.py
@classmethod
def from_element_strings(cls, element_strings: GS1ElementStrings) -> GS1Message:
    """Create a GS1 message from a list of element strings.

    Args:
        element_strings: A list of GS1 element strings.

    Returns:
        GS1Message: The created GS1 message.
    """
    parts = chain(
        *[
            [
                es.ai.ai,
                es.value,
                ("\x1d" if es.ai.separator_required else ""),
            ]
            for es in element_strings
        ]
    )
    normalized_string = "".join(parts).removesuffix("\x1d")
    return cls(value=normalized_string, element_strings=element_strings)

parse classmethod

parse(
    value: str, *, config: ParseConfig | None = None
) -> GS1Message

Parse a string from a barcode scan as a GS1 message with AIs.

Parameters:

  • value (str) –

    The string to parse.

  • config (ParseConfig | None, default: None ) –

    Configuration options for parsing.

Returns:

  • GS1Message

    A message object with one or more element strings.

Raises:

  • ValueError

    If the separator_char isn't exactly 1 character long.

  • ParseError

    If the parsing fails.

Source code in src/biip/gs1_messages.py
@classmethod
def parse(
    cls,
    value: str,
    *,
    config: ParseConfig | None = None,
) -> GS1Message:
    """Parse a string from a barcode scan as a GS1 message with AIs.

    Args:
        value: The string to parse.
        config: Configuration options for parsing.

    Returns:
        A message object with one or more element strings.

    Raises:
        ValueError: If the `separator_char` isn't exactly 1 character long.
        ParseError: If the parsing fails.
    """
    if config is None:
        config = ParseConfig()

    value = value.strip()
    element_strings = GS1ElementStrings()
    rest = value[:]

    while rest:
        element_string = GS1ElementString.extract(rest, config=config)
        element_strings.append(element_string)

        rest = rest[len(element_string) :]

        # Separator characters are accepted inbetween any element string,
        # even if the AI doesn't require it. See GS1 General Specifications,
        # section 7.8.6 for details.
        while rest.startswith(tuple(config.separator_chars)):
            rest = rest[1:]

    return cls(value=value, element_strings=element_strings)

parse_hri classmethod

parse_hri(
    value: str, *, config: ParseConfig | None = None
) -> GS1Message

Parse the GS1 string given in HRI (human readable interpretation) format.

Parameters:

  • value (str) –

    The HRI string to parse.

  • config (ParseConfig | None, default: None ) –

    Configuration options for parsing.

Returns:

  • GS1Message

    A message object with one or more element strings.

Raises:

Source code in src/biip/gs1_messages.py
@classmethod
def parse_hri(
    cls,
    value: str,
    *,
    config: ParseConfig | None = None,
) -> GS1Message:
    """Parse the GS1 string given in HRI (human readable interpretation) format.

    Args:
        value: The HRI string to parse.
        config: Configuration options for parsing.

    Returns:
        A message object with one or more element strings.

    Raises:
        ParseError: If parsing of the data fails.
    """
    if config is None:
        config = ParseConfig()

    value = value.strip()
    if not value.startswith("("):
        msg = f"Expected HRI string {value!r} to start with a parenthesis."
        raise ParseError(msg)

    pattern = r"\((\d+)\)(\w+)"
    matches: list[tuple[str, str]] = re.findall(pattern, value)
    if not matches:
        msg = (
            f"Could not find any GS1 Application Identifiers in {value!r}. "
            "Expected format: '(AI)DATA(AI)DATA'."
        )
        raise ParseError(msg)

    element_strings = GS1ElementStrings()
    for ai_number, ai_data in matches:
        if ai_number not in _GS1_APPLICATION_IDENTIFIERS:
            msg = f"Unknown GS1 Application Identifier {ai_number!r} in {value!r}."
            raise ParseError(msg)
        element_strings.append(
            GS1ElementString.extract(f"{ai_number}{ai_data}", config=config)
        )

    return GS1Message.from_element_strings(element_strings)