docs: def readComponents(streamOrString, validate=False, transform=True,
                     ignoreUnreadable=False, allowQP=False):
      """
      Generate one Component at a time from a stream.
      """
      if isinstance(streamOrString, basestring):
          stream = six.StringIO(streamOrString)
      else:
          stream = streamOrString
  
      try:
          stack = Stack()
          versionLine = None
          n = 0
          for line, n in getLogicalLines(stream, allowQP):
              if ignoreUnreadable:
                  try:
                      vline = textLineToContentLine(line, n)
                  except VObjectError as e:
                      if e.lineNumber is not None:
                          msg = "Skipped line {lineNumber}, message: {msg}"
                      else:
                          msg = "Skipped a line, message: {msg}"
                      logger.error(msg.format(**{'lineNumber': e.lineNumber, 'msg': str(e)}))
                      continue
              else:
                  vline = textLineToContentLine(line, n)
              if vline.name == "VERSION":
                  versionLine = vline
                  stack.modifyTop(vline)
              elif vline.name == "BEGIN":
                  stack.push(Component(vline.value, group=vline.group))
              elif vline.name == "PROFILE":
                  if not stack.top():
                      stack.push(Component())
                  stack.top().setProfile(vline.value)
              elif vline.name == "END":
                  if len(stack) == 0:
                      err = "Attempted to end the {0} component but it was never opened"
                      raise ParseError(err.format(vline.value), n)
  
                  if vline.value.upper() == stack.topName():  # START matches END
                      if len(stack) == 1:
                          component = stack.pop()
                          if versionLine is not None:
                              component.setBehaviorFromVersionLine(versionLine)
                          else:
                              behavior = getBehavior(component.name)
                              if behavior:
                                  component.setBehavior(behavior)
                          if validate:
                              component.validate(raiseException=True)
                          if transform:
                              component.transformChildrenToNative()
                          yield component  # EXIT POINT
                      else:
                          stack.modifyTop(stack.pop())
                  else:
                      err = "{0} component wasn't closed"
                      raise ParseError(err.format(stack.topName()), n)
              else:
                  stack.modifyTop(vline)  # not a START or END line
          if stack.top():
              if stack.topName() is None:
                  logger.warning("Top level component was never named")
              elif stack.top().useBegin:
                  raise ParseError("Component {0!s} was never closed".format(
                                   (stack.topName())), n)
              yield stack.pop()
  
      except ParseError as e:
          e.input = streamOrString
          raise