docs: def _analyze(self, signature, offset):
          '''
          Analyzes self.data for the specified signature data at the specified offset .
  
          @signature - The signature to apply to the data.
          @offset    - The offset in self.data to apply the signature to.
  
          Returns a dictionary of tags parsed from the data.
          '''
          description = []
          max_line_level = 0
          previous_line_end = 0
          tags = {'id': signature.id, 'offset':
                  offset, 'invalid': False, 'once': False}
  
          # Apply each line of the signature to self.data, starting at the
          # specified offset
          for n in range(0, len(signature.lines)):
              line = signature.lines[n]
  
              # Ignore indentation levels above the current max indent level
              if line.level <= max_line_level:
                  # If the relative offset of this signature line is just an
                  # integer value, use it
                  if isinstance(line.offset, int):
                      line_offset = line.offset
                  # Else, evaluate the complex expression
                  else:
                      # Format the previous_line_end value into a string. Add the '+' sign to explicitly
                      # state that this value is to be added to any subsequent values in the expression
                      # (e.g., '&0' becomes '4+0').
                      ple = '%d+' % previous_line_end
                      # Allow users to use either the '&0' (libmagic) or '&+0' (explcit addition) sytaxes;
                      # replace both with the ple text.
                      line_offset_text = line.offset.replace('&+', ple).replace('&', ple)
                      # Evaluate the expression
                      line_offset = self._do_math(offset, line_offset_text)
  
                  # Sanity check
                  if not isinstance(line_offset, int):
                      raise ParserException("Failed to convert offset '%s' to a number: '%s'" % (line.offset, line.text))
  
                  # The start of the data needed by this line is at offset + line_offset.
                  # The end of the data will be line.size bytes later.
                  start = offset + line_offset
                  end = start + line.size
  
                  # If the line has a packed format string, unpack it
                  if line.pkfmt:
                      try:
                          dvalue = struct.unpack(line.pkfmt, binwalk.core.compat.str2bytes(self.data[start:end]))[0]
                      # Not enough bytes left in self.data for the specified
                      # format size
                      except struct.error as e:
                          dvalue = 0
                  # Else, this is a string
                  else:
                      # Wildcard strings have line.value == None
                      if line.value is None:
                          # Check to see if this is a string whose size is known and has been specified on a previous
                          # signature line.
                          if binwalk.core.compat.has_key(tags, 'strlen') and binwalk.core.compat.has_key(line.tags, 'string'):
                              dvalue = self.data[start:(start + tags['strlen'])]
                          # Else, just terminate the string at the first newline,
                          # carriage return, or NULL byte
                          else:
                              dvalue = self.data[start:end].split('\x00')[0].split('\r')[0].split('\n')[0]
                      # Non-wildcard strings have a known length, specified in
                      # the signature line
                      else:
                          dvalue = self.data[start:end]
  
                  # Some integer values have special operations that need to be performed on them
                  # before comparison (e.g., "belong&0x0000FFFF"). Complex math expressions are
                  # supported here as well.
                  # if isinstance(dvalue, int) and line.operator:
                  if line.operator:
                      try:
                          # If the operator value of this signature line is just
                          # an integer value, use it
                          if isinstance(line.opvalue, int) or isinstance(line.opvalue, long):
                              opval = line.opvalue
                          # Else, evaluate the complex expression
                          else:
                              opval = self._do_math(offset, line.opvalue)
  
                          # Perform the specified operation
                          if line.operator == '&':
                              dvalue &= opval
                          elif line.operator == '|':
                              dvalue |= opval
                          elif line.operator == '*':
                              dvalue *= opval
                          elif line.operator == '+':
                              dvalue += opval
                          elif line.operator == '-':
                              dvalue -= opval
                          elif line.operator == '/':
                              dvalue /= opval
                          elif line.operator == '~':
                              dvalue = ~opval
                          elif line.operator == '^':
                              dvalue ^= opval
                      except KeyboardInterrupt as e:
                          raise e
                      except Exception as e:
                          raise ParserException("Operation '" +
                                                str(dvalue) +
                                                " " +
                                                str(line.operator) +
                                                "= " +
                                                str(line.opvalue) +
                                                "' failed: " + str(e))
  
                  # Does the data (dvalue) match the specified comparison?
                  if ((line.value is None) or
                      (line.regex and line.value.match(dvalue)) or
                      (line.condition == '=' and dvalue == line.value) or
                      (line.condition == '>' and dvalue > line.value) or
                      (line.condition == '<' and dvalue < line.value) or
                      (line.condition == '!' and dvalue != line.value) or
                      (line.condition == '~' and (dvalue == ~line.value)) or
                      (line.condition == '^' and (dvalue ^ line.value)) or
                      (line.condition == '&' and (dvalue & line.value)) or
                          (line.condition == '|' and (dvalue | line.value))):
  
                      # Up until this point, date fields are treated as integer values,
                      # but we want to display them as nicely formatted strings.
                      if line.type == 'date':
                          try:
                              ts = datetime.datetime.utcfromtimestamp(dvalue)
                              dvalue = ts.strftime("%Y-%m-%d %H:%M:%S")
                          except KeyboardInterrupt as e:
                              raise e
                          except Exception:
                              dvalue = "invalid timestamp"
  
                      # Generate the tuple for the format string
                      dvalue_tuple = ()
                      for x in self.fmtstr.finditer(line.format):
                          dvalue_tuple += (dvalue,)
  
                      # Format the description string
                      desc = line.format % dvalue_tuple
  
                      # If there was any description string, append it to the
                      # list of description string parts
                      if desc:
                          description.append(desc)
  
                      # Process tag keywords specified in the signature line. These have already been parsed out of the
                      # original format string so that they can be processed
                      # separately from the printed description string.
                      for (tag_name, tag_value) in binwalk.core.compat.iterator(line.tags):
                          # If the tag value is a string, try to format it
                          if isinstance(tag_value, str):
                              # Generate the tuple for the format string
                              dvalue_tuple = ()
                              for x in self.fmtstr.finditer(tag_value):
                                  dvalue_tuple += (dvalue,)
  
                              # Format the tag string
                              tags[tag_name] = tag_value % dvalue_tuple
                          # Else, just use the raw tag value
                          else:
                              tags[tag_name] = tag_value
  
                          # Some tag values are intended to be integer values, so
                          # try to convert them as such
                          try:
                              tags[tag_name] = int(tags[tag_name], 0)
                          except KeyboardInterrupt as e:
                              raise e
                          except Exception as e:
                              pass
  
                      # Abort processing soon as this signature is marked invalid, unless invalid results
                      # were explicitly requested. This means that the sooner invalid checks are made in a
                      # given signature, the faster the scan can filter out false
                      # positives.
                      if not self.show_invalid and tags['invalid']:
                          break
  
                      # Look ahead to the next line in the signature; if its indent level is greater than
                      # that of the current line, then track the end of data for the current line. This is
                      # so that subsequent lines can use the '>>&0' offset syntax to specify relative offsets
                      # from previous lines.
                      try:
                          next_line = signature.lines[n + 1]
                          if next_line.level > line.level:
                              if line.type == 'string':
                                  previous_line_end = line_offset + len(dvalue)
                              else:
                                  previous_line_end = line_offset + line.size
                      except IndexError as e:
                          pass
  
                      # If this line satisfied its comparison, +1 the max
                      # indentation level
                      max_line_level = line.level + 1
                  else:
                      # No match on the first line, abort
                      if line.level == 0:
                          break
                      else:
                          # If this line did not satisfy its comparison, then higher
                          # indentation levels will not be accepted.
                          max_line_level = line.level
  
          # Join the formatted description strings and remove backspace
          # characters (plus the preceeding character as well)
          tags['description'] = self.bspace.sub('', " ".join(description))
  
          # This should never happen
          if not tags['description']:
              tags['display'] = False
              tags['invalid'] = True
  
          # If the formatted string contains non-printable characters, consider
          # it invalid
          if self.printable.match(tags['description']).group() != tags['description']:
              tags['invalid'] = True
  
          return tags