'How to change the HTML rendering of a Pandoc element?

I'm trying to customize the default HTML output of footnotes from an .odt file.

For example a file with a footnote like this:

Some text with a footnote1

Will render the HTML output below:

<ol class="footnotes">
    <li id="fn1" role="doc-endnote">
        <p>Content of footnote number 1. <a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p>
    </li>
</ol>

I want instead to have a flat paragraph to be output, with hardcoded a number like following:

<p>1. Content of footnote number 1. <a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p>

I've used parts of sample.lua from the Pandoc repo but is not working, the process is blocked by this error:

$ pandoc --lua-filter=my-filter.lua file.odt -o file.html

Error running filter my-filter.lua:
my-filter.lua:7: bad argument #1 to 'gsub' (string expected, got table)
stack traceback:
        [C]: in function 'string.gsub'
        my-filter.lua:7: in function 'Note'

Below is my attempted script, I guess I'm naively overlooking something obvious or I've badly understood how filters work.

-- Table to store footnotes, so they can be included at the end.
local notes = {}

function Note(s)
    local num = #notes + 1
    -- insert the back reference right before the final closing tag.
    s = string.gsub(s,
          '(.*)</', '%1 <a href="#fnref' .. num ..  '">&#8617;</a></')
    -- add a list item with the note to the note table.
    table.insert(notes, '<p id="fn' .. num .. '">' .. num .. '. ' .. s .. '</p>')
    -- return the footnote reference, linked to the note.
    return '<a id="fnref' .. num .. '" href="#fn' .. num ..
            '"><sup>' .. num .. '</sup></a>'
end

function Pandoc (doc)
    local buffer = {}
    local function add(s)
    table.insert(buffer, s)
    end
    add(doc)
    if #notes > 0 then
    for _,note in pairs(notes) do
      add(note)
    end
    end
    return table.concat(buffer,'\n') .. '\n'
end

Update

Tweaking part of what @tarleb answered I've managed now to modify the inline note reference link, but apparently the second function is not rendering the list of footnotes at the end of the document. What's missing?

local notes = pandoc.List{}

function Note(note)

    local num = #notes + 1

    -- add a list item with the note to the note table.
    notes:insert(pandoc.utils.blocks_to_inlines(note.content))

    -- return the footnote reference, linked to the note.
    return pandoc.RawInline('html', '<a id="fnref' .. num .. '" href="#fn' .. num ..
            '"><sup>' .. num .. '</sup></a>')
end

function Pandoc (doc)
  doc.meta['include-after'] = notes:map(
    function (content, i)
      -- return a paragraph for each note.
        return pandoc.Para({tostring(i) .. '. '} .. content)
    end
  )
  return doc
end



Solution 1:[1]

The sample.lua is an example of a custom Lua writer, not a Lua filter. They can look similar, but are quite different. E.g., filter functions modify abstract document elements, while functions in custom writers generally expect strings, at least in the first argument.

A good way to go about this in a filter could be to place the custom rendering in the include-after metadata:

local notes = pandoc.List{}

function Pandoc (doc)
  doc.blocks:walk {
    Note = function (note)
      notes:insert(pandoc.utils.blocks_to_inlines(note.content))
      -- Raw HTML goes into an RawInline element
      return pandoc.RawInline('html', 'footnote link HTML goes here')
    end
  }

  doc.meta['include-after'] = notes:map(
    function (content, i)
      -- return a paragraph for each note.
      return pandoc.Para({tostring(i) .. ' '} .. content)
    end
  )
  return doc
end

Solution 2:[2]

I've managed after some trial and error to get a result that is working as intended, but "stylistically" not absolutely perfect.

Please read my commentary below mostly as an excercise, I'm trying to understand better how to use this great tool the way I wanted, not the way any reasonable person should in a productive way (or any way at all). ;)

What I'd like to improve:

  • I have to wrap the p elements in a div because as of Pandoc 2.18 is not possible to provide direct attributes to a Paragraph. This is a minor code bloat but acceptable.

  • I'd like to use a section element instead of a div to put all the notes at end of document (used in the Pandoc function), but I haven't found a way to create a RawBlock element and then add the note blocks to it.

I'm tottaly not proficient in Lua and barely grasped a few concept of how Pandoc works, so I'm pretty confident that what I've done below is non optimal. Suggestions are welcome!

local notes = pandoc.List{}

function Note(note)

    local num = #notes + 1

    -- create a paragraph for the note content
    local footNote = pandoc.Para(
        -- Prefix content with number, ex. '1. '
        {tostring(num) .. '. '} ..
        -- paragraph accept Inline objects as content, Note content are Block objects
        -- and must be converted to inlines
        pandoc.utils.blocks_to_inlines(note.content) ..
        -- append backlink
        { pandoc.RawInline('html', '<a class="footnote-back" href="#fnref' .. num ..  '" role="doc-backlink"> ??</a>')}
        )

    -- it's not possible to render paragraphs with attribute elements as of Pandoc 2.18
    -- so wrap the footnote in a <div> with attributes and append the element to the list
    notes:insert(pandoc.Div(footNote, {id = 'fn' .. num, role = 'doc-endnote'}))

    -- return the inline body footnote reference, linked to the note.
    return pandoc.RawInline('html', '<a id="fnref' .. num .. '" href="#fn' .. num ..
            '"><sup>' .. num .. '</sup></a>')
end

function Pandoc (doc)

  if #notes > 0 then
  -- append collected notes to block list, the end of the document
  doc.blocks:insert(
    pandoc.Div(notes:map(
        function (note)
            return note
        end
      )),
      -- attributes
      {class = 'footnotes', role = 'doc-endnotes'}
  )
  end

  return doc
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gruber
Solution 2 Gruber