Search code examples
luapandoc

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

  • 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!

    -- working as of Pandoc 2.18
    
    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