private function parseCapturingGroup($group, $charBefore, $charAfter)
  {
    $split = explode(':', $group); # split on ':'

    # $key is before the ':''
    $key = trim($split[0]);
    if ($key === '' || $key == '.') {
      throw new \InvalidArgumentException('Invalid capturing group');
    }
    $this->keys[] = $key;

    # $specifier is after the ':'
    if (!isset($split[1])) { # no $specifier
      if ($charAfter !== '' || $charBefore !== '') {
        return sprintf('([^%s]+)', preg_quote($charAfter !== '' ? $charAfter : $charBefore, '/'));
      }
      return '(.+)';
    }

    $specifier = trim($split[1]);
    if ($specifier === '0') {
      throw new \InvalidArgumentException('Invalid length specifier: 0');
    }

    # last char of $specifier is the $type
    $type = substr($specifier, -1);
    switch ($type) {
      case 'd': # integer
      case 's': # string
        $len = trim(substr($specifier, 0, -1));
        if ($len === '') {
          $len = '1,';
        } else {
          if (!$this->canCastToInteger($len) || intval($len) === 0) {
            throw new \InvalidArgumentException(sprintf('Invalid length specifier: %s', $len));
          }
        }
        break;
      case 'f': # float
        $len = trim(substr($specifier, 0, -1));
        if ($len === '') { # ie. no length specified
          $lenBeforeDec = $lenAfterDec = '1,';
        } else {
          if ($len[0] == '.') { # first char is '.'
            $lenBeforeDec = '0,';
            $lenAfterDec = substr($len, 1);
          } else if (substr($len, -1) == '.') { # last char is '.'
            $lenBeforeDec = substr($len, 0, -1);
            $lenAfterDec = '0,';
          } else {
            if ($len === '0.0' || $len === '0') {
              throw new \InvalidArgumentException('Invalid length specifier: 0.0');
            }
            $split = explode('.', $len);
            $lenBeforeDec = trim($split[0]);
            $lenAfterDec = trim($split[1]);
          }
        }
        break;
      default: # no type
        $len = $specifier;
        if (!$this->canCastToInteger($len)) {
          throw new \InvalidArgumentException(sprintf('Invalid length specifier: %s', $specifier));
        }
    }

    # finally, return the capturing group
    switch ($type) {
      case 'd':
        return sprintf('(\d{%s})', $len);
      case 'f':
        return sprintf('([-+]?\d{%s}\.\d{%s})', $lenBeforeDec, $lenAfterDec);
      default:
        return sprintf('(.{%s})', $len);
    }
  }