docs: def exercise_callback_factory(match, url_template,
                                mc_client=None, token=None, mml_url=None):
      """Create a callback function to replace an exercise by fetching from
      a server."""
  
      def _replace_exercises(elem):
          item_code = elem.get('href')[len(match):]
          url = url_template.format(itemCode=item_code)
          exercise = {}
          if mc_client:
              mc_key = item_code + (token or '')
              exercise = json.loads(mc_client.get(mc_key) or '{}')
  
          if not exercise:
              if token:
                  headers = {'Authorization': 'Bearer {}'.format(token)}
                  res = requests.get(url, headers=headers)
              else:
                  res = requests.get(url)
              if res:
                  # grab the json exercise, run it through Jinja2 template,
                  # replace element w/ it
                  exercise = res.json()
                  if mc_client:
                      mc_client.set(mc_key, res.text)
  
          if exercise['total_count'] == 0:
              logger.warning('MISSING EXERCISE: {}'.format(url))
  
              XHTML = '{{{}}}'.format(HTML_DOCUMENT_NAMESPACES['xhtml'])
              missing = etree.Element(XHTML + 'div',
                                      {'class': 'missing-exercise'},
                                      nsmap=HTML_DOCUMENT_NAMESPACES)
              missing.text = 'MISSING EXERCISE: tag:{}'.format(item_code)
              nodes = [missing]
          else:
              html = EXERCISE_TEMPLATE.render(data=exercise)
              try:
                  nodes = etree.fromstring('<div>{}</div>'.format(html))
              except etree.XMLSyntaxError:  # Probably HTML
                  nodes = etree.HTML(html)[0]  # body node
  
              if mml_url:
                  for node in nodes.xpath('//*[@data-math]'):
                      mathml = _replace_tex_math(node, mml_url, mc_client)
                      if mathml is not None:
                          mparent = node.getparent()
                          mparent.replace(node, mathml)
                      else:
                          mathtext = node.get('data-math') or node.text or ''
                          logger.warning('BAD TEX CONVERSION: "%s" URL: %s'
                                         % (mathtext.encode('utf-8'), url))
  
          parent = elem.getparent()
          if etree.QName(parent.tag).localname == 'p':
              elem = parent
              parent = elem.getparent()
  
          parent.remove(elem)  # Special case - assumes single wrapper elem
          for child in nodes:
              parent.append(child)
  
      xpath = '//xhtml:a[contains(@href, "{}")]'.format(match)
      return (xpath, _replace_exercises)