Parse the dpkg output.

def Parse(self, cmd, args, stdout, stderr, return_val, time_taken,
            knowledge_base):
    """Parse the dpkg output."""
    _ = stderr, time_taken, args, knowledge_base  # Unused.
    self.CheckReturn(cmd, return_val)
    column_lengths = []
    i = 0
    for i, line in enumerate(stdout.decode("utf-8").splitlines()):
      if line.startswith("+++-"):
        # This is a special header line that determines column size.
        for col in line.split("-")[1:]:
          if not re.match("=*", col):
            raise parser.ParseError("Invalid header parsing for %s at line "
                                    "%s" % (cmd, i))
          column_lengths.append(len(col))
        break

    if not column_lengths:
      return

    packages = []

    remaining_lines = stdout.splitlines()[i + 1:]
    for i, line in enumerate(remaining_lines):
      cols = line.split(None, len(column_lengths))

      # The status column is ignored in column_lengths.
      if len(column_lengths) == 4:
        # Installed, Name, Version, Architecture, Description
        status, name, version, arch, desc = cols
      elif len(column_lengths) == 3:
        # Older versions of dpkg don't print Architecture
        status, name, version, desc = cols
        arch = None
      else:
        raise ValueError("Bad number of columns in dpkg --list output: %s" %
                         len(column_lengths))

      # Status is potentially 3 columns, but always at least two, desired and
      # actual state. We only care about actual state.
      if status[1] == "i":
        status = rdf_client.SoftwarePackage.InstallState.INSTALLED
      else:
        status = rdf_client.SoftwarePackage.InstallState.UNKNOWN
      packages.append(
          rdf_client.SoftwarePackage(
              name=name,
              description=desc,
              version=version,
              architecture=arch,
              install_state=status))

    if packages:
      yield rdf_client.SoftwarePackages(packages=packages)