docs: def _subspan(self, s, span, nextspan):
          """Recursively subdivide spans based on a series of rules."""
          text = s[span[0]:span[1]]
          lowertext = text.lower()
  
          # Skip if only a single character or a split sequence
          if span[1] - span[0] < 2 or text in self.SPLIT or text in self.SPLIT_END_WORD or text in self.SPLIT_START_WORD or lowertext in self.NO_SPLIT:
              return [span]
  
          # Skip if it looks like URL
          if text.startswith('http://') or text.startswith('ftp://') or text.startswith('www.'):
              return [span]
  
          # Split full stop at end of final token (allow certain characters to follow) unless ellipsis
          if self.split_last_stop and nextspan is None and text not in self.NO_SPLIT_STOP and not text[-3:] == '...':
              if text[-1] == '.':
                  return self._split_span(span, -1)
              ind = text.rfind('.')
              if ind > -1 and all(t in '\'‘’"“”)]}' for t in text[ind + 1:]):
                  return self._split_span(span, ind, 1)
  
          # Split off certain sequences at the end of a token
          for spl in self.SPLIT_END:
              if text.endswith(spl) and len(text) > len(spl):
                  return self._split_span(span, -len(spl), 0)
  
          # Split off certain sequences at the end of a word
          for spl in self.SPLIT_END_WORD:
              if text.endswith(spl) and len(text) > len(spl) and text[-len(spl) - 1].isalpha():
                  return self._split_span(span, -len(spl), 0)
  
          # Split off certain sequences at the end of a word
          for spl in self.SPLIT_START_WORD:
              if text.startswith(spl) and len(text) > len(spl) and text[-len(spl) - 1].isalpha():
                  return self._split_span(span, len(spl), 0)
  
          # Split around certain sequences
          for spl in self.SPLIT:
              ind = text.find(spl)
              if ind > -1:
                  return self._split_span(span, ind, len(spl))
  
          # Split around certain sequences unless followed by a digit
          # - We skip this because of difficulty with chemical names.
          # for spl in self.SPLIT_NO_DIGIT:
          #     ind = text.rfind(spl)
          #     if ind > -1 and (len(text) <= ind + len(spl) or not text[ind + len(spl)].isdigit()):
          #         return self._split_span(span, ind, len(spl))
  
          # Split off certain sequences at the end of a token unless preceded by a digit
          for spl in self.SPLIT_END_NO_DIGIT:
              if text.endswith(spl) and len(text) > len(spl) and not text[-len(spl) - 1].isdigit():
                  return self._split_span(span, -len(spl), 0)
  
          # Regular Bracket at both start and end, break off both provided they correspond
          if text.startswith('(') and text.endswith(')') and self._closing_bracket_index(text) == len(text) - 1:
              return self._split_span(span, 1, len(text)-2)
  
          # Split things like IR(KBr)
          if text.startswith('IR(') and text.endswith(')'):
              return self._split_span(span, 2, 1)
  
          # Split things like \d+\.\d+([a-z]+) e.g. UV-vis/IR peaks with bracketed strength/shape
          m = re.match('^(\d+\.\d+|\d{3,})(\([a-z]+\))$', text, re.I)
          if m:
              return self._split_span(span, m.start(2), 1)
  
          # Split brackets off start and end if the corresponding bracket isn't within token
          for bpair in [('(', ')'), ('{', '}'), ('[', ']')]:
              #level = bracket_level(text, open=[bpair[0]], close=[bpair[1]])
              # Bracket at start, bracketlevel > 0, break it off
              if text.startswith(bpair[0]) and self._closing_bracket_index(text, bpair=bpair) is None:
                  return self._split_span(span, 1, 0)
              # Bracket at end, bracketlevel < 0, break it off
              if text.endswith(bpair[1]) and self._opening_bracket_index(text, bpair=bpair) is None:
                  return self._split_span(span, -1, 0)
  
          # TODO: Consider splitting around comma in limited circumstances. Mainly to fix whitespace errors.
  
          # Characters to split around, but with exceptions
          for i, char in enumerate(text):
              before = text[:i]
              after = text[i+1:]
              if char in {':', ';'}:
                  # Split around colon unless it looks like we're in a chemical name
                  if not (before and after and after[0].isdigit() and before.rstrip('′\'')[-1:].isdigit() and '-' in after) and not (self.NO_SPLIT_CHEM.search(before) and self.NO_SPLIT_CHEM.search(after)):
                      return self._split_span(span, i, 1)
              elif char in {'x', '+', '−'}:
                  # Split around x, +, − (\u2212 minus) between two numbers or at start followed by numbers
                  if (i == 0 or self._is_number(before)) and self._is_number(after):
                      return self._split_span(span, i, 1)
                  # Also plit around − (\u2212 minus) between two letters
                  if char == '−' and before and before[-1].isalpha() and after and after[0].isalpha():
                      return self._split_span(span, i, 1)
              elif char == '±':
                  # Split around ± unless surrounded by brackets
                  if not (before and after and before[-1] == '(' and after[0] == ')'):
                      return self._split_span(span, i, 1)
              elif char == '/':
                  # Split around / unless '+/-' or '-/-' etc.
                  if not (before and after and before[-1] in self.NO_SPLIT_SLASH and after[0] in self.NO_SPLIT_SLASH):
                      return self._split_span(span, i, 1)
              elif char == '>':
                  if not (before and before[-1] == '-'):
                      # Split if preceding is not -
                      return self._split_span(span, i, 1)
                  if before and before[-1] == '-':
                      # If preceding is -, split around -> unless in chemical name
                      if not text == '->' and not self._is_saccharide_arrow(before[:-1], after):
                          return self._split_span(span, i-1, 2)
              elif char is '→' and not self._is_saccharide_arrow(before, after):
                  # TODO: 'is' should be '=='... this never splits!?
                  # Split around → unless in chemical name
                  return self._split_span(span, i, 1)
              elif char == '(' and self._is_number(before) and not '(' in after and not ')' in after:
                  # Split around open bracket after a number
                  return self._split_span(span, i, 1)
              elif char == '-':
                  lowerbefore = lowertext[:i]
                  lowerafter = lowertext[i+1:]
                  # Always split on -of-the- -to- -in- -by- -of- -or- -and- -per- -the-
                  if lowerafter[:7] == 'of-the-':
                      return [(span[0], span[0] + i), (span[0] + i, span[0] + i + 1), (span[0] + i + 1, span[0] + i + 3), (span[0] + i + 3, span[0] + i + 4), (span[0] + i + 4, span[0] + i + 7), (span[0] + i + 7, span[0] + i + 8), (span[0] + i + 8, span[1])]
                  if lowerafter[:5] in {'on-a-', 'of-a-'}:
                      return [(span[0], span[0] + i), (span[0] + i, span[0] + i + 1), (span[0] + i + 1, span[0] + i + 3), (span[0] + i + 3, span[0] + i + 4), (span[0] + i + 4, span[0] + i + 5), (span[0] + i + 5, span[0] + i + 6), (span[0] + i + 6, span[1])]
                  if lowerafter[:3] in {'to-', 'in-', 'by-', 'of-', 'or-', 'on-'}:
                      return [(span[0], span[0] + i), (span[0] + i, span[0] + i + 1), (span[0] + i + 1, span[0] + i + 3), (span[0] + i + 3, span[0] + i + 4), (span[0] + i + 4, span[1])]
                  if lowerafter[:4] in {'and-', 'per-', 'the-'}:
                      return [(span[0], span[0] + i), (span[0] + i, span[0] + i + 1), (span[0] + i + 1, span[0] + i + 4), (span[0] + i + 4, span[0] + i + 5), (span[0] + i + 5, span[1])]
  
                  # By default we split on hyphens
                  split = True
                  if lowerafter == 'nmr':
                      split = True  # Always split NMR off end
                  elif bracket_level(text) == 0 and (not bracket_level(after) == 0 or not bracket_level(before) == 0):
                      split = False  # Don't split if within brackets
                  elif after and after[0] == '>':
                      split = False  # Don't split if followed by >
                  elif lowerbefore in self.NO_SPLIT_PREFIX or lowerafter in self.NO_SPLIT_SUFFIX:
                      split = False  # Don't split if prefix or suffix in list
                  elif self.NO_SPLIT_PREFIX_ENDING.search(lowerbefore):
                      split = False  # Don't split if prefix ends with pattern
                  elif lowerafter in self.SPLIT_SUFFIX:
                      split = True  # Do split if suffix in list
                  elif len(before) <= 1 or len(after) <= 2:
                      split = False  # Don't split if not at least 2 char before and 3 after
                  elif self.NO_SPLIT_CHEM.search(lowerbefore) or self.NO_SPLIT_CHEM.search(lowerafter):
                      split = False  # Don't split if prefix or suffix match chem regex
                  if split:
                      return self._split_span(span, i, 1)
  
                  # TODO: Errors:
                  # [³H]-choline
                  # S,S'-...
                  # 1,4-di-substituted
                  # 11-β - hydroxysteroid
                  # Spelt out greek: 11beta - hydroxysteroid
                  # ...-N-substituted like 2,5-dimethyl-N-substituted pyrroles
                  # 4-(2-Butyl-6,7-dichloro-2-cyclopentyl-indan-1-on-5-yl) oxobutyric acid
                  # Adenosine - monophosphate
                  # Consistency for amino acids: Arg-Arg and Arg-Arg-Asp... probably always split
                  # D,L-α-peptide?
                  # N'-formylkynurenine
                  # poly(D,L-lactic acid )?
                  # poly(methyl metha-acrylate )?
                  # Poly(N - alkyl Acrylamide )
                  # poly(N - isopropyl acrylamide )
                  # R,S - lorazepam
                  # S,S - dioxide
  
          # Split units off the end of a numeric value
          quantity = self.QUANTITY_RE.search(text)
          if quantity:
              return self._split_span(span, len(quantity.group(6) or quantity.group(3) or quantity.group(2)), 0)
  
          # Split pH off the start of a numeric value
          if text.startswith('pH') and self._is_number(text[2:]):
              return self._split_span(span, 2, 0)
  
          # Split contraction words
          for contraction in self.CONTRACTIONS:
              if lowertext == contraction[0]:
                  return self._split_span(span, contraction[1])
  
          if nextspan:
              nexttext = s[nextspan[0]:nextspan[1]]
              # Split NMR isotope whitespace errors (joined with previous sentence full stop)
              if nexttext == 'NMR':
                  ind = text.rfind('.')
                  if ind > -1 and text[ind + 1:] in {'1H', '13C', '15N', '31P', '19F', '11B', '29Si', '170', '73Ge', '195Pt', '33S', '13C{1H}'}:
                      return self._split_span(span, ind, 1)
  
  
          return [span]