Search code examples
luapandoc

Pandoc Lua : how to walk inside a div?


I need the two BulletList to be treated differently.

The one inside the Div is correctly parsed when I simply return the element in the normal BulletList.

But how to make the walk inside the div work when I change the normal Bulletlist and return RawInline?

# normal chapter
* a normal list
* with items
* or more
* to finish

::: Special

# special chapter
* at the start
* something else
* the end

:::

The expected result would be like this. The items of each list are differently treated.

<h1 class="normalHeader" id="normal-chapter">normal chapter</h1>
<ul>
<li class="normal-item">a normal list</li>
<li class="normal-item">with items</li>
<li class="normal-item">or more</li>
<li class="normal-item">to finish</li>
</ul>
<section id="special-chapter" class="Special" class="specialHeader">
<h1 class="specialHeader">special chapter</h1>
<ul >
<li class="special-item">at the start</li>
<li class="special-item">something else</li>
<li class="special-item">the end</li>
</ul>

</section>

Lua filter:


Div = function (el)
    if el.classes[1] == 'Special' then
      return pandoc.walk_block(el, {
        Header = function(el)
            el.attributes['class']='specialHeader'
            return el
        end,
        BulletList = function(el)
            print("specialltest")
              local mylist ='<ul >\n'
              for i, item in ipairs(el.content) do
                local first = item[1]
                if first  then
                  mylist =  mylist .. '<li class="special-item">' .. pandoc.utils.stringify(first) ..  '</li>\n'
                end
              end
           mylist =  mylist .. '</ul>\n'
           return pandoc.RawInline('html', mylist)
        end,
      })
    end
    return el
end

Header = function (el)
    el.attributes['class']='normalHeader'
    return el
end


BulletList = function (el)
            print("normalltest")
              local mylist ='<ul >\n'
              for i, item in ipairs(el.content) do
                local first = item[1]
                if first  then
                  mylist =  mylist .. '<li class="normal-item">' .. pandoc.utils.stringify(first) ..  '</li>\n'
                end
              end
           mylist =  mylist .. '</ul>\n'
           return pandoc.RawInline('html', mylist)
           -- works if I return el without treatment
end

Solution

  • Below is a possible way to solve this. It defines two filters, one for the normal environments, one for the special environments inside some divs. The kind of filter to be used is selected inside the div. The filters use a non-standard traversal order (traverse = 'topdown'). We apply the normal filter to the whole document to make sure that it will be applied to elements not wrapped in a div.

    Note that some filter functions now return false as a second value: this signals to pandoc that the element's subtree should not be filtered any further.

    local normal_filter, special_filter
    special_filter = {
      traverse = 'topdown',
      Header = function(el)
        el.classes = {'specialHeader'}
        return el
      end,
      BulletList = function (el)
        local mylist ='<ul >\n'
        for i, item in ipairs(el.content) do
          local first = item[1]
          if first  then
            mylist =  mylist .. '<li class="special-item">' .. pandoc.utils.stringify(first) ..  '</li>\n'
          end
        end
        mylist =  mylist .. '</ul>\n'
        return pandoc.RawInline('html', mylist)
      end
    }
    
    normal_filter = {
      traverse = 'topdown',
      Header = function (el)
        el.classes = {'normalHeader'}
        return el
      end,
      BulletList = function (el)
        local mylist ='<ul >\n'
        for i, item in ipairs(el.content) do
          local first = item[1]
          if first  then
            mylist =  mylist .. '<li class="normal-item">' .. pandoc.utils.stringify(first) ..  '</li>\n'
          end
        end
        mylist =  mylist .. '</ul>\n'
        return pandoc.RawInline('html', mylist)
      end,
    
      Div = function (div)
        local filter
    
        if div.classes[1] == 'Special' then
          filter = special_filter
        else
          filter = normal_filter
        end
        return div:walk(filter), false
      end
    }
    
    Pandoc = function (doc)
      return doc:walk(normal_filter)
    end
    

    The reason for the above filter not working is that pandoc traverses the document tree bottom-up, i.e., it processes the deeply nested elements first. So it processes the nested bullet list before it processes the div. After that, the div no longer contains a bullet list but raw content; the latter is not processed by the filter.

    That's also why the code did work for the heading: the Header is modified instead of replaced, so the Div filter can modify it further.