docs: def detect_opensearch(err, package, listed=False):
      'Detect, parse, and validate an OpenSearch provider'
  
      # Parse the file.
      try:
          # Check if it is a file object.
          if hasattr(package, 'read'):
              srch_prov = parse(package)
          else:
              # It's not a file object; open it (the XML parser is bad at this).
              with open(package, 'rb') as package_file:
                  srch_prov = parse(package_file)
      except DefusedXmlException:
          url = 'https://pypi.python.org/pypi/defusedxml/0.3#attack-vectors'
          err.error(
              err_id=('opensearch', 'security_error'),
              error='OpenSearch: XML Security Error',
              description='The OpenSearch extension could not be parsed due to '
                          'a security error in the XML. See {url} for more '
                          'info.'.format(url=url))
          return err
      except ExpatError:
          err.error(
              err_id=('opensearch', 'parse_error'),
              error='OpenSearch: XML Parse Error',
              description='The OpenSearch extension could not be parsed due to '
                          'a syntax error in the XML.')
          return err
  
      # Make sure that the root element is OpenSearchDescription.
      if srch_prov.documentElement.tagName != 'OpenSearchDescription':
          err.error(
              err_id=('opensearch', 'invalid_document_root'),
              error='OpenSearch: Invalid Document Root',
              description='The root element of the OpenSearch provider is not '
                          "'OpenSearchDescription'.")
  
      # Per bug 617822
      if not srch_prov.documentElement.hasAttribute('xmlns'):
          err.error(
              err_id=('opensearch', 'no_xmlns'),
              error='OpenSearch: Missing XMLNS attribute',
              description='The XML namespace attribute is missing from the '
                          'OpenSearch document.')
  
      if ('xmlns' not in srch_prov.documentElement.attributes.keys() or
          srch_prov.documentElement.attributes['xmlns'].value not in (
                      'http://a9.com/-/spec/opensearch/1.0/',
                      'http://a9.com/-/spec/opensearch/1.1/',
                      'http://a9.com/-/spec/opensearchdescription/1.1/',
                      'http://a9.com/-/spec/opensearchdescription/1.0/')):
          err.error(
              err_id=('opensearch', 'invalid_xmlns'),
              error='OpenSearch: Bad XMLNS attribute',
              description='The XML namespace attribute contains an '
                          'value.')
  
      # Make sure that there is exactly one ShortName.
      sn = srch_prov.documentElement.getElementsByTagName('ShortName')
      if not sn:
          err.error(
              err_id=('opensearch', 'missing_shortname'),
              error='OpenSearch: Missing <ShortName> elements',
              description='ShortName elements are mandatory OpenSearch provider '
                          'elements.')
      elif len(sn) > 1:
          err.error(
              err_id=('opensearch', 'extra_shortnames'),
              error='OpenSearch: Too many <ShortName> elements',
              description='Too many ShortName elements exist in the OpenSearch '
                          'provider.')
      else:
          sn_children = sn[0].childNodes
          short_name = 0
          for node in sn_children:
              if node.nodeType == node.TEXT_NODE:
                  short_name += len(node.data)
          if short_name > 16:
              err.error(
                  err_id=('opensearch', 'big_shortname'),
                  error='OpenSearch: <ShortName> element too long',
                  description='The ShortName element must contains less than '
                              'seventeen characters.')
  
      # Make sure that there is exactly one Description.
      if len(srch_prov.documentElement.getElementsByTagName('Description')) != 1:
          err.error(
              err_id=('opensearch', 'missing_description'),
              error='OpenSearch: Invalid number of <Description> elements',
              description='There are too many or too few Description elements '
                          'in the OpenSearch provider.')
  
      # Grab the URLs and make sure that there is at least one.
      urls = srch_prov.documentElement.getElementsByTagName('Url')
      if not urls:
          err.error(
              err_id=('opensearch', 'missing_url'),
              error='OpenSearch: Missing <Url> elements',
              description='The OpenSearch provider is missing a Url element.')
  
      if listed and any(url.hasAttribute('rel') and
                        url.attributes['rel'].value == 'self' for
                        url in urls):
          err.error(
              err_id=('opensearch', 'rel_self'),
              error='OpenSearch: <Url> elements may not be rel=self',
              description='Per AMO guidelines, OpenSearch providers cannot '
                          "contain <Url /> elements with a 'rel' attribute "
                          "pointing to the URL's current location. It must be "
                          'removed before posting this provider to AMO.')
  
      acceptable_mimes = ('text/html', 'application/xhtml+xml')
      acceptable_urls = [url for url in urls if url.hasAttribute('type') and
                            url.attributes['type'].value in acceptable_mimes]
  
      # At least one Url must be text/html
      if not acceptable_urls:
          err.error(
              err_id=('opensearch', 'missing_url_texthtml'),
              error="OpenSearch: Missing <Url> element with 'text/html' type",
              description='OpenSearch providers must have at least one Url '
                          "element with a type attribute set to 'text/html'.")
  
      # Make sure that each Url has the require attributes.
      for url in acceptable_urls:
  
          if url.hasAttribute('rel') and url.attributes['rel'].value == 'self':
              continue
  
          if url.hasAttribute('method') and \
             url.attributes['method'].value.upper() not in ('GET', 'POST'):
              err.error(
                  err_id=('opensearch', 'missing_method'),
                  error="OpenSearch: <Url> element with invalid 'method'",
                  description='A Url element in the OpenSearch provider lists a '
                              'method attribute, but the value is not GET or '
                              'POST.')
  
          # Test for attribute presence.
          if not url.hasAttribute('template'):
              err.error(
                  err_id=('opensearch', 'missing_template'),
                  error='OpenSearch: <Url> element missing template attribute',
                  description='<Url> elements of OpenSearch providers must '
                              'include a template attribute.')
          else:
              url_template = url.attributes['template'].value
              if url_template[:4] != 'http':
                  err.error(
                      err_id=('opensearch', 'invalid_template'),
                      error='OpenSearch: `<Url>` element with invalid '
                            '`template`',
                      description='A `<Url>` element in the OpenSearch '
                                  'provider lists a template attribute, but '
                                  'the value is not a valid HTTP URL.')
  
              # Make sure that there is a {searchTerms} placeholder in the
              # URL template.
              found_template = url_template.count('{searchTerms}') > 0
  
              # If we didn't find it in a simple parse of the template=""
              # attribute, look deeper at the <Param /> elements.
              if not found_template:
                  for param in url.getElementsByTagName('Param'):
                      # As long as we're in here and dependent on the
                      # attributes, we'd might as well validate them.
                      attribute_keys = param.attributes.keys()
                      if 'name' not in attribute_keys or \
                         'value' not in attribute_keys:
                          err.error(
                              err_id=('opensearch', 'param_missing_attrs'),
                              error='OpenSearch: `<Param>` element missing '
                                    'name/value',
                              description='Param elements in the OpenSearch '
                                          'provider must include a name and a '
                                          'value attribute.')
  
                      param_value = (param.attributes['value'].value if
                                     'value' in param.attributes.keys() else
                                     '')
                      if param_value.count('{searchTerms}'):
                          found_template = True
  
                          # Since we're in a validating spirit, continue
                          # looking for more errors and don't break
  
              # If the template still hasn't been found...
              if not found_template:
                  tpl = url.attributes['template'].value
                  err.error(
                      err_id=('opensearch', 'template_not_found'),
                      error='OpenSearch: <Url> element missing template '
                            'placeholder',
                      description=('`<Url>` elements of OpenSearch providers '
                                   'must include a template attribute or '
                                   'specify a placeholder with '
                                   '`{searchTerms}`.',
                                   'Missing template: %s' % tpl))
  
      # Make sure there are no updateURL elements
      if srch_prov.getElementsByTagName('updateURL'):
          err.error(
              err_id=('opensearch', 'banned_updateurl'),
              error='OpenSearch: <updateURL> elements are banned in OpenSearch '
                    'providers.',
              description='OpenSearch providers may not contain <updateURL> '
                          'elements.')
  
      # The OpenSearch provider is valid!
      return err