text: from itertools import zip_longest
  from importlib import import_module
  from .helpers import cached_property
  from . import helpers
  from . import errors
  
  
  class Header(list):
      """Header representation
  
      API      | Usage
      -------- | --------
      Public   | `from frictionless import Header`
  
      > Constructor of this object is not Public API
  
      Parameters:
          labels (any[]): header row labels
          fields (Field[]): table fields
          field_positions (int[]): field positions
          row_positions (int[]): row positions
          ignore_case (bool): ignore case
  
      """
  
      def __init__(
          self,
          labels,
          *,
          fields,
          field_positions,
          row_positions,
          ignore_case=False,
      ):
          super().__init__(field.name for field in fields)
          self.__fields = [field.to_copy() for field in fields]
          self.__field_names = self.copy()
          self.__field_positions = field_positions
          self.__row_positions = row_positions
          self.__ignore_case = ignore_case
          self.__labels = labels
          self.__errors = []
          self.__process()
  
      @cached_property
      def labels(self):
          """
          Returns:
              Schema: table labels
          """
          return self.__labels
  
      @cached_property
      def fields(self):
          """
          Returns:
              Schema: table fields
          """
          return self.__fields
  
      @cached_property
      def field_names(self):
          """
          Returns:
              str[]: table field names
          """
          return self.__field_names
  
      @cached_property
      def field_positions(self):
          """
          Returns:
              int[]: table field positions
          """
          return self.__field_positions
  
      @cached_property
      def row_positions(self):
          """
          Returns:
              int[]: table row positions
          """
          return self.__row_positions
  
      @cached_property
      def missing(self):
          """
          Returns:
              bool: if there is not header
          """
          return not self.__labels
  
      @cached_property
      def errors(self):
          """
          Returns:
              Error[]: header errors
          """
          return self.__errors
  
      @cached_property
      def valid(self):
          """
          Returns:
              bool: if header valid
          """
          return not self.__errors
  
      # Import/Export
  
      def to_str(self):
          """
          Returns:
              str: a row as a CSV string
          """
  
          plugin = import_module("frictionless.plugins.csv")
          cells = self.to_list(types=plugin.CsvParser.supported_types)
          return helpers.stringify_csv_string(cells)
  
      def to_list(self):
          """Convert to a list"""
          return self.copy()
  
      # Process
  
      def __process(self):
  
          # Skip missing
          if self.missing:
              return
  
          # Prepare context
          labels = self.__labels
          fields = self.__fields
          field_positions = self.__field_positions
  
          # Extra label
          if len(fields) < len(labels):
              iterator = labels[len(fields) :]
              start = max(field_positions[: len(fields)]) + 1
              for field_position, label in enumerate(iterator, start=start):
                  self.__errors.append(
                      errors.ExtraLabelError(
                          note="",
                          labels=list(map(str, labels)),
                          row_positions=self.__row_positions,
                          label="",
                          field_name="",
                          field_number=len(fields) + field_position - start,
                          field_position=field_position,
                      )
                  )
  
          # Missing label
          if len(fields) > len(labels):
              start = len(labels) + 1
              iterator = zip_longest(field_positions[len(labels) :], fields[len(labels) :])
              for field_number, (field_position, field) in enumerate(iterator, start=start):
                  if field is not None:
                      self.__errors.append(
                          errors.MissingLabelError(
                              note="",
                              labels=list(map(str, labels)),
                              row_positions=self.__row_positions,
                              label="",
                              field_name=field.name,
                              field_number=field_number,
                              field_position=field_position
                              or max(field_positions, default=0) + field_number - start + 1,
                          )
                      )
  
          # Iterate items
          field_number = 0
          for field_position, field, label in zip(field_positions, fields, labels):
              field_number += 1
  
              # Blank label
              if not label:
                  self.__errors.append(
                      errors.BlankLabelError(
                          note="",
                          labels=list(map(str, labels)),
                          row_positions=self.__row_positions,
                          label="",
                          field_name=field.name,
                          field_number=field_number,
                          field_position=field_position,
                      )
                  )
  
              # Duplicated label
              if label:
                  duplicate_field_positions = []
                  seen_cells = labels[0 : field_number - 1]
                  seen_field_positions = field_positions[0 : field_number - 1]
                  for seen_position, seen_cell in zip(seen_field_positions, seen_cells):
                      if label == seen_cell:
                          duplicate_field_positions.append(seen_position)
                  if duplicate_field_positions:
                      label = None
                      note = 'at position "%s"'
                      note = note % ", ".join(map(str, duplicate_field_positions))
                      self.__errors.append(
                          errors.DuplicateLabelError(
                              note=note,
                              labels=list(map(str, labels)),
                              row_positions=self.__row_positions,
                              label=str(labels[field_number - 1]),
                              field_name=field.name,
                              field_number=field_number,
                              field_position=field_position,
                          )
                      )
  
              # Incorrect Label
              if label:
                  name = field.name
                  # NOTE: review where we normalize the label/name
                  lname = label.replace("\n", " ").strip()
                  if name.lower() != lname.lower() if self.__ignore_case else name != lname:
                      self.__errors.append(
                          errors.IncorrectLabelError(
                              note="",
                              labels=list(map(str, labels)),
                              row_positions=self.__row_positions,
                              label=str(label),
                              field_name=field.name,
                              field_number=field_number,
                              field_position=field_position,
                          )
                      )
  
          # Blank header
          if not labels:
              self.__errors = [
                  errors.BlankHeaderError(
                      note="",
                      labels=list(map(str, labels)),
                      row_positions=self.__row_positions,
                  )
              ]
  