docs: def yaml_filter(element, doc, tag=None, function=None, tags=None,
                  strict_yaml=False):
      '''
      Convenience function for parsing code blocks with YAML options
  
      This function is useful to create a filter that applies to
      code blocks that have specific classes.
  
      It is used as an argument of ``run_filter``, with two additional options:
      ``tag`` and ``function``.
  
      Using this is equivalent to having filter functions that:
  
      1. Check if the element is a code block
      2. Check if the element belongs to a specific class
      3. Split the YAML options (at the beginning of the block, by looking
         for ``...`` or ``---`` strings in a separate line
      4. Parse the YAML
      5. Use the YAML options and (optionally) the data that follows the YAML
         to return a new or modified element
  
      Instead, you just need to:
  
      1. Call ``run_filter`` with ``yaml_filter`` as the action function, and
         with the additional arguments ``tag`` and ``function``
      2. Construct a ``fenced_action`` function that takes four arguments:
         (options, data, element, doc). Note that options is a dict and data
         is a raw string. Notice that this is similar to the ``action``
         functions of standard filters, but with *options* and *data* as the
         new ones.
  
      Note: if you want to apply multiple functions to separate classes,
      you can use the ``tags`` argument, which receives a dict of
      ``tag: function`` pairs.
  
      Note: use the ``strict_yaml=True`` option in order to allow for more verbose
      but flexible YAML metadata: more than one YAML blocks are allowed, but
      they all must start with ``---`` (even at the beginning) and end with
      ``---`` or ``...``. Also, YAML is not the default content
      when no delimiters are set.
  
      Example::
  
          """
          Replace code blocks of class 'foo' with # horizontal rules
          """
  
          import panflute as pf
  
          def fenced_action(options, data, element, doc):
              count = options.get('count', 1)
              div = pf.Div(attributes={'count': str(count)})
              div.content.extend([pf.HorizontalRule] * count)
              return div
  
          if __name__ == '__main__':
              pf.run_filter(pf.yaml_filter, tag='foo', function=fenced_action)
      '''
  
      # Allow for either tag+function or a dict {tag: function}
      assert (tag is None) + (tags is None) == 1  # XOR
      if tags is None:
          tags = {tag: function}
  
      if type(element) == CodeBlock:
          for tag in tags:
              if tag in element.classes:
                  function = tags[tag]
  
                  if not strict_yaml:
                      # Split YAML and data parts (separated by ... or ---)
                      raw = re.split("^([.]{3,}|[-]{3,})$",
                                     element.text, 1, re.MULTILINE)
                      data = raw[2] if len(raw) > 2 else ''
                      data = data.lstrip('\n')
                      raw = raw[0]
                      try:
                          options = yaml.safe_load(raw)
                      except yaml.scanner.ScannerError:
                          debug("panflute: malformed YAML block")
                          return
                      if options is None:
                          options = {}
  
                  else:
                      options = {}
                      data = []
                      raw = re.split("^([.]{3,}|[-]{3,})$",
                                     element.text, 0, re.MULTILINE)
                      rawmode = True
                      for chunk in raw:
  
                          chunk = chunk.strip('\n')
                          if not chunk:
                              continue
  
                          if rawmode:
                              if chunk.startswith('---'):
                                  rawmode = False
                              else:
                                  data.append(chunk)
                          else:
                              if chunk.startswith('---') or chunk.startswith('...'):
                                  rawmode = True
                              else:
                                  try:
                                      options.update(yaml.safe_load(chunk))
                                  except yaml.scanner.ScannerError:
                                      debug("panflute: malformed YAML block")
                                      return
  
                      data = '\n'.join(data)
  
                  return function(options=options, data=data,
                                  element=element, doc=doc)