docs: def declare_api_routes(config):
      """Declare routes, with a custom pregenerator."""
      # The pregenerator makes sure we can generate a path using
      # request.route_path even if we don't have all the variables.
      #
      # For example, instead of having to do this:
      #     request.route_path('resource', hash=hash, ignore='')
      # it's possible to do this:
      #     request.route_path('resource', hash=hash)
      def pregenerator(path):
          # find all the variables in the path
          variables = [(s.split(':')[0], '') for s in path.split('{')[1:]]
  
          def wrapper(request, elements, kwargs):
              modified_kwargs = dict(variables)
              modified_kwargs.update(kwargs)
              return elements, modified_kwargs
          return wrapper
  
      def add_route(name, path, *args, **kwargs):
          return config.add_route(name, path, *args,
                                  pregenerator=pregenerator(path), **kwargs)
  
      add_route('content', '/contents/{ident_hash:([^:/@.]+(@[0-9.]*[0-9]+)?)}{separator:(:?)}{page_ident_hash:([^:/@.]+(@[0-9.]*[0-9]+)?)?}{ignore:(/[^/.]*?/?)?}{ext:([.](html|json))?}')  # noqa cnxarchive.views:get_content
      add_route('resource', '/resources/{hash}{ignore:(/.*)?}')  # noqa cnxarchive.views:get_resource
      add_route('export', '/exports/{ident_hash}.{type}{ignore:(/.*)?}')  # noqa cnxarchive.views:get_export
      add_route('extras', '/extras{key:(/(featured|messages|licenses|subjects|languages))?}')  # noqa cnxarchive.views:extras
      add_route('content-extras', '/extras/{ident_hash:([^:/@.]+(@[0-9.]*[0-9]+)?)}{separator:(:?)}{page_ident_hash:([^:/@.]+(@[0-9.]*[0-9]+)?)?}')  # noqa cnxarchive.views:get_extra
      add_route('search', '/search')  # cnxarchive.views:search
      add_route('in-book-search', '/search/{ident_hash:([^:/]+)}')  # noqa cnxarchive.views:in-book-search
      add_route('in-book-search-page', '/search/{ident_hash:([^:/]+)}:{page_ident_hash}')  # noqa cnxarchive.views:in_book_search_highlighted_results
      add_route('sitemap-index', '/sitemap_index.xml')  # noqa cnxarchive.views:sitemap
      add_route('sitemap', '/sitemap-{from_id}.xml')  # noqa cnxarchive.views:sitemap
      add_route('robots', '/robots.txt')  # noqa cnxarchive.views:robots
      add_route('legacy-redirect', '/content/{objid}{ignore:(/)?}')  # noqa cnxarchive.views:redirect_legacy_content
      add_route('legacy-redirect-latest', '/content/{objid}/latest{ignore:(/)?}{filename:(.+)?}')  # noqa cnxarchive.views:redirect_legacy_content
      add_route('legacy-redirect-w-version', '/content/{objid}/{objver}{ignore:(/)?}{filename:(.+)?}')  # noqa cnxarchive.views:redirect_legacy_content
      add_route('recent', '/feeds/recent.rss')  # cnxarchive.views:recent
      add_route('oai', '/feeds/oai')  # cnxarchive.views:oai
      add_route('xpath', '/xpath.html')  # cnxarchive.views.xpath
      add_route('xpath-json', '/xpath.json')