docs: def get_composite_keywords(ckw_db, fulltext, skw_spans):
      """Return a list of composite keywords bound with number of occurrences.
  
      :param ckw_db: list of KewordToken objects
                     (they are supposed to be composite ones)
      :param fulltext: string to search in
      :param skw_spans: dictionary of already identified single keywords
  
      :return : dictionary of matches in a format {
              <keyword object>, [[position, position...], [info_about_matches] ],
              ..
              }
      """
      timer_start = time.clock()
  
      # Build the list of composite candidates
      ckw_out = {}
      skw_as_components = []
  
      for composite_keyword in ckw_db.values():
          # Counters for the composite keyword. First count is for the
          # number of occurrences in the whole document and second count
          # is for the human defined keywords.
          ckw_count = 0
          matched_spans = []
  
          # First search in the fulltext using the regex pattern of the whole
          # composite keyword (including the alternative labels)
          for regex in composite_keyword.regex:
              for match in regex.finditer(fulltext):
                  span = list(match.span())
                  span[1] -= 1
                  span = tuple(span)
                  if span not in matched_spans:
                      ckw_count += 1
                      matched_spans.append(span)
  
          # Get the single keywords locations.
          try:
              components = composite_keyword.compositeof
          except AttributeError:
              current_app.logger.error(
                  "Cached ontology is corrupted. Please "
                  "remove the cached ontology in your temporary file."
              )
              raise OntologyError('Cached ontology is corrupted.')
  
          spans = []
          try:
              spans = [skw_spans[component][0] for component in components]
          except KeyError:
              # Some of the keyword components are not to be found in the text.
              # Therefore we cannot continue because the match is incomplete.
              pass
  
          ckw_spans = []
          for index in range(len(spans) - 1):
              len_ckw = len(ckw_spans)
              if ckw_spans:  # cause ckw_spans include the previous
                  previous_spans = ckw_spans
              else:
                  previous_spans = spans[index]
  
              for new_span in [(span0, colmd1) for span0 in previous_spans
                               for colmd1 in spans[index + 1]]:
                  span = _get_ckw_span(fulltext, new_span)
                  if span is not None:
                      ckw_spans.append(span)
  
              # the spans must be overlapping to be included
              if index > 0 and ckw_spans:
                  _ckw_spans = []
                  for _span in ckw_spans[len_ckw:]:  # new spans
                      for _colmd2 in ckw_spans[:len_ckw]:
                          s = _span_overlapping(_span, _colmd2)
                          if s:
                              _ckw_spans.append(s)
                  ckw_spans = _ckw_spans
  
          for matched_span in [mspan for mspan in ckw_spans
                               if mspan not in matched_spans]:
              ckw_count += 1
              matched_spans.append(matched_span)
  
          if ckw_count:
              # Gather the component counts.
              component_counts = []
              for component in components:
                  skw_as_components.append(component)
                  # Get the single keyword count.
                  try:
                      component_counts.append(len(skw_spans[component][0]))
                  except KeyError:
                      component_counts.append(0)
  
              # Store the composite keyword
              ckw_out[composite_keyword] = [matched_spans, component_counts]
  
      # Remove the single keywords that appear as components from the list
      # of single keywords.
      for skw in skw_as_components:
          try:
              del skw_spans[skw]
          except KeyError:
              pass
  
      # Remove the composite keywords that are fully present in
      # longer composite keywords
      _ckw_base = filter(lambda x: len(x.compositeof) == 2, ckw_out.keys())
      _ckw_extended = sorted(
          filter(lambda x: len(x.compositeof) > 2, ckw_out.keys()),
          key=lambda x: len(x.compositeof))
      if _ckw_extended:
          candidates = []
          for kw1 in _ckw_base:
              s1 = set(kw1.compositeof)
              for kw2 in _ckw_extended:
                  s2 = set(kw2.compositeof)
                  if s1.issubset(s2):
                      candidates.append((kw1, kw2))
                      # break  # don't stop because this keyword may be
                      # partly contained by kw_x and kw_y
          for i in range(len(_ckw_extended)):
              kw1 = _ckw_extended[i]
              s1 = set(kw1.compositeof)
              for ii in range(i + 1, len(_ckw_extended)):
                  kw2 = _ckw_extended[ii]
                  s2 = set(kw2.compositeof)
                  if s1.issubset(s2):
                      candidates.append((kw1, kw2))
                      break
          if candidates:
              for kw1, kw2 in candidates:
                  try:
                      match1 = ckw_out[kw1]  # subset of the kw2
                      match2 = ckw_out[kw2]
                  except KeyError:
                      continue
                  positions1 = match1[0]
                  for pos1 in positions1:
                      for pos2 in match2[0]:
                          if _span_overlapping(pos1, pos2):
                              del positions1[positions1.index(pos1)]
                              # if we removed all the matches also
                              # delete the keyword
                              if len(positions1) == 0:
                                  del ckw_out[kw1]
                              break
  
      current_app.logger.info(
          "Matching composite keywords... %d keywords found "
          "in %.1f sec." % (len(ckw_out), time.clock() - timer_start),
      )
      return ckw_out