docs: def _transform_abstract(plpy, module_ident):
      """Transform abstract, bi-directionally.
  
      Transforms an abstract using one of content columns
      ('abstract' or 'html') to determine which direction the transform
      will go (cnxml->html or html->cnxml).
      A transform is done on either one of them to make
      the other value. If no value is supplied, the trigger raises an error.
      If both values are supplied, the trigger will skip.
      """
      plan = plpy.prepare("""\
  SELECT a.abstractid, a.abstract, a.html
  FROM modules AS m NATURAL JOIN abstracts AS a
  WHERE m.module_ident = $1""", ('integer',))
      result = plpy.execute(plan, (module_ident,), 1)[0]
      abstractid, cnxml, html = (
          result['abstractid'], result['abstract'], result['html'])
      if cnxml is not None and html is not None:
          return  # skip
      # TODO Prevent blank abstracts (abstract = null & html = null).
  
      msg = "produce {}->{} for abstractid={}"
      if cnxml is None:
          # Transform html->cnxml
          msg = msg.format('html', 'cnxml', abstractid)
          content = html
          column = 'abstract'
          transform_func = transform_abstract_to_cnxml
      else:
          # Transform cnxml->html
          msg = msg.format('cnxml', 'html', abstractid)
          content = cnxml
          column = 'html'
          transform_func = transform_abstract_to_html
  
      content, messages = transform_func(content, module_ident, plpy)
      plan = plpy.prepare(
          "UPDATE abstracts SET {} = $1 WHERE abstractid = $2".format(column),
          ('text', 'integer'))
      plpy.execute(plan, (content, abstractid,))
      return msg