Place content between headers in LaTeX environments using a lua filter

Viewed 76

Consider this markdown file:

#

This is text below a h1 header

##

This is text below a h2 header

#

This is text below a h1 header

##

This is text below a h2 header

I wonder if it would be possible to grab content below a h1 header and put it in a latex environment A and content below a h2 header in environment B using a lua filter.

Something like this:

function Header(el)
  local beginEnv, endEnv

  if ( el.level == 1 ) then
    beginEnv = "\\begin{A}"
    endEnv = "\\end{A}"
  elseif ( el.level == 2 ) then
    beginEnv = "\\begin{B}"
    endEnv = "\\end{B}"
  end

  -- grab all of the content below the respective header
  -- in the case of the h1 header it should be: "This is text below a h1 header"
  -- in the case of the h2 header it should be: "This is text below a h2 header"
  local contentBelowHeader = ???

  table.insert( contentBelowHeader, 1, pandoc.RawBlock("latex", beginEnv .. "\n" ))
  table.insert( contentBelowHeader, pandoc.RawBlock("latex", "\n" .. endEnv ))

  return el
end

The output should be something like (tex file):

...

\begin{A}
  This is text below a h1 header
\end{A}

\begin{B}
  This is text below a h2 header
\end{B}

\begin{A}
  This is text below a h1 header
\end{A}

\begin{B}
  This is text below a h2 header
\end{B}

...
3 Answers

One solution is to traverse the whole document and insert the \begin{...} and \end{...}.

function latex(str)
  return pandoc.RawInline('latex', str)
end

function Pandoc(doc)
  local blocks = {}
  local pending_end_env = nil

  for i, el in pairs(doc.blocks) do
    if el.t == "Header" then
      if el.level == 1 then
        -- Insert end of last environment if needed
        if pending_end_env then
          table.insert(blocks, latex(pending_end_env))
        end
        -- Insert begin of new environment
        table.insert(blocks, latex('\\begin{A}'))
        -- Store the end of new environment for later use
        pending_end_env = '\\end{A}'
      elseif el.level == 2 then
        -- Same as the el.level == 1 case above
        if pending_end_env then
          table.insert(blocks, latex(pending_end_env))
        end
        table.insert(blocks, latex('\\begin{B}'))
        pending_end_env = '\\end{B}'
      end
    else
      -- Keep all non-header elements as-is
      table.insert(blocks, el)
    end
  end

  -- Close the last environment
  if pending_end_env then
    table.insert(blocks, latex(pending_end_env))
  end

  return pandoc.Pandoc(blocks, doc.meta)
end

With this filter, pandoc produces the following tex file from the example markdown in the question.

\begin{A}

This is text below a h1 header

\end{A}

\begin{B}

This is text below a h2 header

\end{B}

\begin{A}

This is text below a h1 header

\end{A}

\begin{B}

This is text below a h2 header

\end{B}

With the great help of @kotatsuyaki, I was able to find a solution to my problem:

function Pandoc(doc)
  local blocks = {}
  local pending_end_env = nil
  -- Latex environments where each index corresponds to a header level (level 1 == 'A')
  local envs = { 'A', 'B' }

  for _, el in pairs(doc.blocks) do
    -- Check if element is a header and if there exists an environment in envs at header level
    if (el.t == "Header"  and envs[el.level]) then
      -- Insert end of last environment if needed
      if pending_end_env then
        table.insert(blocks, pandoc.RawBlock('latex', pending_end_env ))
      end
      -- Insert begin of new environment
      table.insert(blocks, pandoc.RawBlock('latex', '\\begin{' .. envs[el.level] .. '}' ))
      -- Store the end of new environment for later use
      pending_end_env = '\\end{' .. envs[el.level] .. '}'
    else
      -- Keep all non-header elements as-is
      table.insert(blocks, el)
    end
  end

  -- Close the last environment
  if pending_end_env then
    table.insert(blocks, pandoc.RawBlock('latex', pending_end_env))
  end

  return pandoc.Pandoc(blocks, doc.meta)
end

Most of the code comes from @kotatsuyaki.

If you want the environments to contain all contents below a section, then checkout pandoc's utility function pandoc.utils.make_sections: it wraps all headings and the corresponding contents in Div with class section. This allows to do

function Pandoc(doc)
  -- Latex environments where each index corresponds to a header level
  -- (level 1 == 'A')
  local envs = { 'A', 'B' }

  doc.blocks = pandoc.utils.make_sections(false, nil, doc.blocks)
  return doc:walk {
    Div = function (div)
      local heading = div.content[1]
      if div.classes:includes 'section' and heading and heading.level then
        local begin_env = '\\begin{' .. envs[heading.level] .. '}'
        local end_env = '\\end{' .. envs[heading.level] .. '}'
        div.content:insert(2, pandoc.RawBlock('latex', begin_env))
        div.content:insert(pandoc.RawBlock('latex', end_env))
      end
      return div.content
    end
  }
end

The output is different from what you want though, as it will be of the pattern

\section{...}

\begin{A}

This is text below a h1 header

\subsection{...}

\begin{B}

This is text below a h2 header

\end{B}

\end{A}
Related