docs: def make_xpath_ranges(html, phrase):
      '''Given a HTML string and a `phrase`, build a regex to find offsets
      for the phrase, and then build a list of `XPathRange` objects for
      it.  If this fails, return empty list.
  
      '''
      if not html:
          return []
      if not isinstance(phrase, unicode):
          try:
              phrase = phrase.decode('utf8')
          except:
              logger.info('failed %r.decode("utf8")', exc_info=True)
              return []
  
      phrase_re = re.compile(
          phrase, flags=re.UNICODE | re.IGNORECASE | re.MULTILINE)
      spans = []
      for match in phrase_re.finditer(html, overlapped=False):
          spans.append(match.span())  # a list of tuple(start, end) char indexes
  
      # now run fancy aligner magic to get xpath info and format them as
      # XPathRange per above
      try:
          xpath_ranges = list(char_offsets_to_xpaths(html, spans))
      except:
          logger.info('failed to get xpaths', exc_info=True)
          return []
      ranges = []
      for xpath_range in filter(None, xpath_ranges):
          ranges.append(dict(
              start=dict(node=xpath_range.start_xpath,
                         idx=xpath_range.start_offset),
              end=dict(node=xpath_range.end_xpath,
                       idx=xpath_range.end_offset)))
  
      return ranges