public inbox archive for pandoc-discuss@googlegroups.com
 help / color / mirror / Atom feed
* Rewriting table filters
@ 2020-08-17 13:36 BPJ
       [not found] ` <e47081d5-e61c-6b57-e747-65e22085f976-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  0 siblings, 1 reply; 3+ messages in thread
From: BPJ @ 2020-08-17 13:36 UTC (permalink / raw)
  To: pandoc-discuss-/JYPxA39Uh5TLH3MbocFFw

[-- Attachment #1: Type: text/plain, Size: 2729 bytes --]

I would be grateful if someone who knows and understands the new table
model could comment on or assess how hard it might be to rewrite the two
attached filters which modify and create tables, and which I rely quite
heavily on. The more simple of them is written directly in Lua, the
other one is written in MoonScript and compiled into Lua code, for which
see below!

The — considerably — simpler of the two allows fine control over the
widths of table columns according to a “list” of percentages given in an
attribute of an enclosing div.

The other filter — considerably more complex already — which turns a
list of lists into a table or a table into a list of lists (similar in
the method it uses to specify column widths and column alignments) for
which I fear that a version for the new table model would be crazy
complex.

This second filter is written in MoonScript <http://moonscript.org>, a
terser scripting language which compiles source-to-source into Lua. The
Lua code produced by the MoonScript compiler is included for reference,
but it is far from elegant/idiomatic Lua code and not meant for humans
to read/work on. The filter is complex enough as is, so I don’t feel
like rewriting it in idiomatic Lua just for expository purposes! If you
know a bunch of scripting languages — in particular (not incidentally!)
CoffeeScript — MoonScript code isn’t hard to follow. The intro at
<https://learnxinyminutes.com/docs/moonscript/> might be helpful.

However it can be hard to see what this filter does just from looking at
the code, so I have included a Markdown file with description and
examples.

I originally wrote the list—table filter for a document where there is a
long table where each row has a very narrow left column containing a
symbol — but not so narrow in the source since the symbol is wrapped in
a span with both an id (unique among about a hundred ids) and a class —,
and two wider cells, which may contain several paragraphs and lists with
various spans with attributes, describing the use of each symbol in two
different contexts. I did try to write that table as a grid table but
soon had to give up. Writing a list of lists with three items in each
second-level list made for a much more unconstrained writing process.

-- 
You received this message because you are subscribed to the Google Groups "pandoc-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pandoc-discuss+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web visit https://groups.google.com/d/msgid/pandoc-discuss/e47081d5-e61c-6b57-e747-65e22085f976%40gmail.com.

[-- Attachment #2: set-table-col-widths.lua --]
[-- Type: text/x-lua, Size: 3905 bytes --]

-- set-table-col-widths.lua
--
-- Pandoc filter which sets the widths of columns to explicit
-- percentages of the available width according to values given
-- in an attribute `table-column-widths` or `table-col-widths` of
-- an enclosing div.
-- for example
--
-- ````markdown
-- ::: {table-col-widths="20 20 40"}
--
-- |This|That|Description
-- |----|----|-----------
-- |Foo |Bar |Foo and bar
--
-- :::
-- ````
--
-- will make the first two colums 20% wide and the third
-- column 40% wide, taking up a total of 80% of the available
-- width.
--
-- If the attribute contains more widths than there are
-- columns in an enclosed table the list of new widths will be
-- truncated to fit, ignoring excess items. If on the other
-- hand the attribute contains fewer widths than there are
-- columns in an enclosed table the list of new widths will be
-- extended to fit **by appending copies of the last item!**
--
-- Note that any substrings in the attribute value which are not
-- sequences of digits will be ignored except as separators.
-- In particular something like "20.5" will count as two column 
-- widths of 20% and 5% respectively!
-- If you want to make your attribute values more human
-- friendly you can include commas, spaces and per cent signs
-- like `20%, 20%, 40%`. The filter still only "sees" the
-- three substrings `20`, `20` and `40` and something "crazy"
-- like `-20:20 -- !40` would be
-- entirely equivalent!
--
-- Note that a percentage of zero has a special meaning:
-- it makes the width of the corresponding column "automatic"
-- so that it will fit the content of the row with the
-- widest content for that column.
--
-- If no sequences of digits are found in the attribute value
-- all column widths are set to zero — i.e. automatic —, so that
-- a human-friendly way of making all column widths
-- automatic is to set the attribute value to the string
-- `auto`, although `foo` or `Ni` or `""` will have the same
-- effect!
--
-- By default the enclosing div will be "removed", i.e. replaced
-- with its possibly altered content.  To avoid this set a class `.keep-div` on the div!

local function copy_table (t)
  local c = {}
  for k,v in pairs(t) do
    c[k] = v
  end
  return c
end

local function gen_set_widths (wanted)
  local function set_widths (table)
    local old = table.widths
    local new = copy_table(wanted)
    if #new > #old then
      -- If there are more wanted widths than columns
      -- then truncate the list of new widths
      local start = #old+1
      for i=#new, start, -1 do
        new[i] = nil
      end
    elseif #old > #new then
      -- If there are fewer wanted widths than columns then
      -- extend the list of new widths with copies of the last
      -- new width specified
      local last = new[#new]
      local start = #new+1
      for i=start, #old do
        new[i] = last
      end
    end
    table.widths = new
    return table
  end
  return { Table = set_widths }
end

-- "Parse" a string of wanted widths by extracting all
-- sequences of digits with string.gmatch, meaning that
-- any sequences of non-digits are ignored except as separators.
-- These are taken to be percentages of the total width
-- available, so are divided by 100 to get the floats Pandoc wants
local function extract_widths (str)
  wd = {}
  for w in str:gmatch('%d+') do
    wd[#wd+1] = tonumber(w, 10) / 100
  end
  -- If no widths were found default to automatic width for all columns!
  if #wd == 0 then
    wd[1] = 0.0
  end
  return wd
end

function Div (div)
  local wd_string = div.attributes['table-column-widths']
    or div.attributes['table-col-widths']
  if not wd_string then return nil end
  new_widths = extract_widths(wd_string)
  filter = gen_set_widths(new_widths)
  new_div = pandoc.walk_block(div, filter)
  if div.classes:includes('keep-div') then
    return new_div
  else
    return new_div.content
  end
end


[-- Attachment #3: README.md --]
[-- Type: text/markdown, Size: 5568 bytes --]

# list-table

This Pandoc filter allows to convert lists of lists (bullet lists and/or
ordered lists) into tables. This makes it easier to type long tables and
tables with "complicated" content because you don't need to
draw any ASCII art.

The filter can also convert tables to lists of lists,
allowing full roundtripping.

## Usage

Obviously it would be dysfunctional if all lists of lists
were converted to tables.  Hence you must tell the filter
that you want to convert a given list of list to a table by
wrapping the list in a div with the class `lol2table` (short
for "list-of-lists-to-table":

````pandoc
:::lol2table
*   -   foo
    -   bar
    -   baz
*   -   +   tic
        +   pic
    -   +   tac
        +   pac
:::
````

When running this through pandoc with the filter enabled and
with markdown as output format it is replaced with this:

````pandoc
+---------+---------+-----+
| foo     | bar     | baz |
+=========+=========+=====+
| -   tic | -   tac |     |
| -   pic | -   pac |     |
+---------+---------+-----+
````

Note how each item in the top level list becomes a table
row and each item in the second level list becomes a table
cell, while third level list remain lists. Note also that
the filter handles the situation where there are only two
items in the second second-level list: if any rows are shorter
than the longest row they are padded with empty cells
towards the end.

### Headerless tables

To turn a list of lists into a headerless table just include
the class `no-header` (or `noheader`) on the wrapping div:

````pandoc
::: {.lol2table}
Table with header

1.  1.  foo
    2.  bar
    3.  baz

2.  1.  tic
    2.  tac
    3.  toc
:::

::: {.lol2table .no-header}
Table without header

1.  1.  foo
    2.  bar
    3.  baz

2.  1.  tic
    2.  tac
    3.  toc
:::
````

````pandoc
foo   bar   baz
----- ----- -----
tic   tac   toc

: Table with header

----- ----- -----
foo   bar   baz
tic   tac   toc
----- ----- -----

: Table without header
````

### Captions

The previous example also shows how to set a caption on the
table: just include a paragraph with the caption text inside
the div.

### Custom alignments and custom column widths

To specify the alignment of the table columns set an
attribute `align` on the div.  Its value should be a comma
separated "list" (and I mean *comma* separated, not
comma-and-whitespace!) of any of the letters `d l c r`
(for `AlignDefault`, `AlignLeft`, `AlignCenter`, `AlignRight`
respectively):

````pandoc
::: {.lol2table align="l,c,r"}
*   -   foo
    -   bar
    -   baz
*   -   +   tic
        +   pic
    -   +   tac
        +   pac
:::
````

````pandoc
+---------+---------+-----+
| foo     | bar     | baz |
+:========+:=======:+====:+
| -   tic | -   tac |     |
| -   pic | -   pac |     |
+---------+---------+-----+
````

Likewise to specify the relative width of columns include an
attribute `widths` on the div.  Its value should be
a comma-separated (again really *comma* separated!) "list" of
integers between 0 and 100, where each integer is the
percentage of the available total width which should be the
width of the respective column:

````pandoc
::: {.lol2table widths="20,40,10"}
*   -   foo
    -   bar
    -   baz
*   -   +   tic
        +   pic
    -   +   tac
        +   pac
:::
````

````pandoc
+-------------+---------------------------+------+
| foo         | bar                       | baz  |
+=============+===========================+======+
| -   tic     | -   tac                   |      |
| -   pic     | -   pac                   |      |
+-------------+---------------------------+------+
````

Naturally you can combine the two:

````pandoc
::: {.lol2table align="l,c,r" widths="20,40,10"}
*   -   foo
    -   bar
    -   baz
*   -   tic
    -   tac
:::
````

````pandoc
---------------------------------------------------
foo                        bar                  baz
-------------- ---------------------------- -------
tic                        tac              

---------------------------------------------------
````

If you specify more alignments or widths than there are
columns the extra alignments/widths will be ignored.

If you specify fewer alignments than there are columns the
list of alignments is padded to the right length with copies
of the rightmost alignment actually specified.
If you specify fewer widths than there are columns the list
of widths is padded to the right length with zeroes.
This should cause Pandoc to distribute the remaining width
between them.

## Roundtripping

To convert a table into a list of lists you wrap it in a div
with the class `table2lol`:

````pandoc
:::table2lol

|foo|bar|baz
|---|---|---
|tic|tac|toc

:::
````

````pandoc
0.  1.  foo
    2.  bar
    3.  baz

1.  1.  tic
    2.  tac
    3.  toc
````

Note that the resulting lists always are numbered lists and
that if there was a header row the numbering of the
top-level list starts at zero.

### Keeping the div

If you include a class `keep-div` (or `keepdiv`) on the div
the result will also be wrapped in a div, designed to make
roundtripping easier:


````pandoc
::: {#alpha .lol2table .keep-div}
1.  1.  foo
    2.  bar
    3.  baz
2.  1.  tic
    2.  tac
    3.  toc
:::

::: {#beta .table2lol .keep-div}
  foo   bar   baz
  ----- ----- -----
  tic   tac   toc
:::
````

::: {#alpha .maybe-table2lol .keep-div}
  foo   bar   baz
  ----- ----- -----
  tic   tac   toc
:::

::: {#beta .maybe-lol2table .keep-div align="l,l,l" widths="0,0,0"}
0.  1.  foo
    2.  bar
    3.  baz

1.  1.  tic
    2.  tac
    3.  toc
:::

[-- Attachment #4: list-table.lua --]
[-- Type: text/x-lua, Size: 9135 bytes --]

local concat, insert, pack, remove
do
  local _obj_0 = table
  concat, insert, pack, remove = _obj_0.concat, _obj_0.insert, _obj_0.pack, _obj_0.remove
end
local floor
floor = math.floor
local assertion
assertion = function(msg, val)
  return assert(val, msg)
end
local contains_any
contains_any = function(...)
  local wanted
  do
    local _tbl_0 = { }
    local _list_0 = pack(...)
    for _index_0 = 1, #_list_0 do
      local w = _list_0[_index_0]
      _tbl_0[w] = true
    end
    wanted = _tbl_0
  end
  return function(list)
    local _exp_0 = type(list)
    if 'table' == _exp_0 then
      for _index_0 = 1, #list do
        local v = list[_index_0]
        if wanted[v] then
          return true
        end
      end
    else
      return nil
    end
    return false
  end
end
local is_elem
is_elem = function(x, ...)
  local _exp_0 = type(x)
  if 'table' == _exp_0 then
    local tag = x.tag
    local _exp_1 = type(tag)
    if 'string' == _exp_1 then
      local tags = pack(...)
      if #tags > 0 then
        for _index_0 = 1, #tags do
          local t = tags[_index_0]
          if t == tag then
            return tag
          end
        end
        return nil
      end
      return true
    end
    return false
  end
end
local get_div_id
get_div_id = function(cls, div, div_count)
  if div_count == nil then
    div_count = ""
  end
  local div_id = div.identifier or ""
  if "" == div_id then
    div_id = div_count
  end
  return tostring(cls) .. " div #" .. tostring(div_id)
end
local letter2align = {
  d = 'AlignDefault',
  l = 'AlignLeft',
  c = 'AlignCenter',
  r = 'AlignRight'
}
local align2letter
do
  local _tbl_0 = { }
  for k, v in pairs(letter2align) do
    _tbl_0[v] = k
  end
  align2letter = _tbl_0
end
local contains_no_header = contains_any('no-header', 'noheader')
local contains_keep_div = contains_any('keep-div', 'keepdiv')
local lol2table
do
  local div_count = 0
  lol2table = function(div)
    div_count = div_count + 1
    local div_id = get_div_id('lol2table', div, div_count)
    local lol, caption = nil, nil
    local _list_0 = div.content
    for _index_0 = 1, #_list_0 do
      local _continue_0 = false
      repeat
        local item = _list_0[_index_0]
        if not (is_elem(item)) then
          _continue_0 = true
          break
        end
        local _exp_0 = item.tag
        if 'BulletList' == _exp_0 or 'OrderedList' == _exp_0 then
          if lol then
            error("Expected only one list in " .. tostring(div_id), 2)
          end
          lol = item
        elseif 'Para' == _exp_0 or 'Plain' == _exp_0 then
          if caption then
            error("Expected only one caption paragraph in " .. tostring(div_id), 2)
          end
          caption = item.content
        else
          error("Didn't expect " .. tostring(item.tag) .. " in " .. tostring(div_id), 2)
        end
        _continue_0 = true
      until true
      if not _continue_0 then
        break
      end
    end
    if not (lol) then
      return nil
    end
    caption = caption or { }
    if not (is_elem(lol, 'BulletList', 'OrderedList')) then
      return nil
    end
    local header = not (contains_no_header(div.classes))
    local rows = { }
    local col_count = 0
    local _list_1 = lol.content
    for _index_0 = 1, #_list_1 do
      local item = _list_1[_index_0]
      assertion("Expected list in " .. tostring(div_id) .. " to be list of lists", #item == 1 and is_elem(item[1], 'BulletList', 'OrderedList'))
      local row = item[1].content
      if #row > col_count then
        col_count = #row
      end
      rows[#rows + 1] = row
    end
    for _index_0 = 1, #rows do
      local row = rows[_index_0]
      while #row < col_count do
        row[#row + 1] = { }
      end
    end
    local headers
    if header then
      headers = remove(rows, 1)
    else
      headers = { }
    end
    local aligns = { }
    local align = (div.attributes.align or ""):lower()
    if "" == align then
      align = 'd'
    end
    for a in align:gmatch('[^,]+') do
      aligns[#aligns + 1] = assertion("Unknown column alignment in " .. tostring(div_id) .. ": '" .. tostring(a) .. "'", letter2align[a])
      if #aligns == col_count then
        break
      end
    end
    while #aligns < col_count do
      aligns[#aligns + 1] = aligns[#aligns]
    end
    local widths = { }
    local width = div.attributes.widths or ""
    if "" == width then
      width = '0'
    end
    for w in width:gmatch('[^,]+') do
      assertion("Expected column width in " .. tostring(div_id) .. " to be percentage, not '" .. tostring(w) .. "'", w:match('^[01]?%d?%d$'))
      widths[#widths + 1] = tonumber(w, 10) / 100
      if #widths == col_count then
        break
      end
    end
    while #widths < col_count do
      widths[#widths + 1] = 0
    end
    local ok, res = pcall(pandoc.Table, caption, aligns, widths, headers, rows)
    assert(ok, "Error converting list to table in " .. tostring(div_id) .. ": " .. tostring(res))
    if contains_keep_div(div.classes) then
      local attr = div.attr
      local _list_2 = {
        'align',
        'widths'
      }
      for _index_0 = 1, #_list_2 do
        local key = _list_2[_index_0]
        attr.attributes[key] = nil
      end
      do
        local _accum_0 = { }
        local _len_0 = 1
        local _list_3 = div.classes
        for _index_0 = 1, #_list_3 do
          local c = _list_3[_index_0]
          if 'lol2table' ~= c then
            _accum_0[_len_0] = c
            _len_0 = _len_0 + 1
          end
        end
        attr.classes = _accum_0
      end
      insert(attr.classes, 1, 'maybe-table2lol')
      return pandoc.Div({
        res
      }, attr)
    end
    return res
  end
end
local table2lol
do
  local no_class = {
    table2lol = true,
    ['no-header'] = true,
    noheader = true
  }
  local div_count = 0
  table2lol = function(div)
    div_count = div_count + 1
    if #div.content == 0 then
      return nil
    end
    local div_id = get_div_id('table2lol', div, div_count)
    assertion("Expected " .. tostring(div_id) .. " to contain only a table", #div.content == 1 and is_elem(div.content[1], 'Table'))
    local tab = div.content[1]
    local caption, headers, rows = tab.caption, tab.headers, tab.rows
    local header = false
    for _index_0 = 1, #headers do
      local h = headers[_index_0]
      if #h > 0 then
        header = true
      end
    end
    local lol
    do
      local _accum_0 = { }
      local _len_0 = 1
      for _index_0 = 1, #rows do
        local row = rows[_index_0]
        _accum_0[_len_0] = {
          pandoc.OrderedList(row)
        }
        _len_0 = _len_0 + 1
      end
      lol = _accum_0
    end
    local list_attr = pandoc.ListAttributes()
    if header then
      insert(lol, 1, {
        pandoc.OrderedList(headers)
      })
      list_attr.start = 0
    end
    lol = pandoc.OrderedList(lol, list_attr)
    if contains_keep_div(div.classes) then
      local cols = {
        align = (function()
          local _accum_0 = { }
          local _len_0 = 1
          local _list_0 = tab.aligns
          for _index_0 = 1, #_list_0 do
            local a = _list_0[_index_0]
            _accum_0[_len_0] = align2letter[a]
            _len_0 = _len_0 + 1
          end
          return _accum_0
        end)(),
        widths = (function()
          local _accum_0 = { }
          local _len_0 = 1
          local _list_0 = tab.widths
          for _index_0 = 1, #_list_0 do
            local w = _list_0[_index_0]
            _accum_0[_len_0] = floor(w * 100)
            _len_0 = _len_0 + 1
          end
          return _accum_0
        end)()
      }
      local classes
      do
        local _accum_0 = { }
        local _len_0 = 1
        local _list_0 = div.classes
        for _index_0 = 1, #_list_0 do
          local c = _list_0[_index_0]
          if not no_class[c] then
            _accum_0[_len_0] = c
            _len_0 = _len_0 + 1
          end
        end
        classes = _accum_0
      end
      if #caption > 0 then
        caption = pandoc.Para(caption)
      else
        caption = pandoc.Null()
      end
      if not (header) then
        insert(classes, 1, 'no-header')
      end
      insert(classes, 1, 'maybe-lol2table')
      local attr = div.attr
      attr.classes = classes
      for key, list in pairs(cols) do
        attr.attributes[key] = concat(list, ",")
      end
      return pandoc.Div({
        lol,
        caption
      }, attr)
    end
    return lol
  end
end
do
  local div_count = 0
  return {
    {
      Div = function(self)
        div_count = div_count + 1
        local is_lol2table = self.classes:includes('lol2table')
        local is_table2lol = self.classes:includes('table2lol')
        if is_lol2table and is_table2lol then
          local div_id = get_div_id("", self, div_count)
          error("Expected" .. tostring(div_id) .. " to have class .lol2table or class .table2lol, not both")
        elseif is_lol2table then
          return lol2table(self)
        elseif is_table2lol then
          return table2lol(self)
        end
        return nil
      end
    }
  }
end

[-- Attachment #5: list-table.moon --]
[-- Type: text/plain, Size: 9192 bytes --]

import concat, insert, pack, remove from table
import floor from math

assertion = (msg, val) -> assert val, msg

-- contains_any(val1 [, val2, ...])
  -- returns a closure such that closure(x) returns
  --  * nil if x is not a table
  --  * true if x is an array and contains a value
  --    which is equal to one of val1, ...
  --  * false otherwise
contains_any = (...) ->
  wanted = {w, true for w in *pack ...}
  return (list) ->
    switch type list
      when 'table'
        for v in *list
          if wanted[v]
            return true
      else
        return nil
    return false

-- is_elem(val, tag1 [, tag2, ...])
  -- returns
  --  * false if x is not a table
  --  * false if x.tag is not a string
  --  * x.tag if x.tag equals one of tag1, ...
  --  * nil otherwise
-- is_elem(x)
  -- returns
  --  * false if x is not a table
  --  * false if x.tag is not a string
  --  * true otherwise
is_elem = (x, ...) ->
  switch type x
    when 'table'
      tag = x.tag
      switch type tag
        when 'string'
          tags = pack ...
          if #tags > 0
            for t in *tags
              if t == tag
                return tag
            return nil
          return true
      return false

-- get_div_id(cls, div [, div_count])
-- 
-- Takes the following arguments:
-- 
-- 1.  A string. May be a class name, something else which
--    migh serve as a "div type", or an empty string.
-- 
-- 2.  An actual Pandoc Div object.
-- 
-- 3.  An optional number, assumed to be the number of divs of
--     the same "type" already seen, including the current
--     one.
-- 
-- Returns a string of the form `<cls> div #<id>`, where
-- `<id>` is either the id attribute of `div`, or if
-- that is empty the `div_count`.

get_div_id = (cls, div, div_count="") ->
  div_id = div.identifier or ""
  div_id = div_count if "" == div_id
  return "#{cls} div ##{div_id}"

-- Map one-letter abbreviations to full alignment type names.
letter2align = {
  d: 'AlignDefault'
  l: 'AlignLeft'
  c: 'AlignCenter'
  r: 'AlignRight'
}
-- Map full alignment type names to one-letter abbreviations.
align2letter = {v,k for k,v in pairs letter2align}

-- Functions to look for variants of the 'magic' classes.
contains_no_header = contains_any 'no-header', 'noheader'
contains_keep_div = contains_any 'keep-div', 'keepdiv'

-- Function to convert a list of lists to a table
lol2table = do
  -- Keep track of how many lol2table divs we have seen.
  div_count = 0
  -- The function receives the enclosing div as argument
  (div) ->
    -- Increment the count
    div_count += 1
    -- Get a moniker for this div
    div_id = get_div_id 'lol2table', div, div_count
    -- Now look up the LoL and the caption paragraph if any.
    -- Start by declaring the variables
    lol, caption = nil, nil
    -- Now loop through the children of the div:
    for item in *div.content
      continue unless is_elem item -- can't happen!
      -- See what kind of element we got
      switch item.tag
        when 'BulletList', 'OrderedList'
          -- Complain if we already saw a list
          if lol
            error "Expected only one list in #{div_id}", 2
          lol = item
        when 'Para', 'Plain'
          -- Complain if we already saw a paragraph
          if caption
            error "Expected only one caption paragraph in #{div_id}", 2
          caption = item.content
        else
          -- Complain if we see something other than a list or para
          error "Didn't expect #{item.tag} in #{div_id}", 2
    -- Abort if we didn't see any list
    return nil unless lol
    -- The caption defaults to an empty list
    caption or= {}
    -- This can't really happen, so why is this check there?
    unless is_elem lol, 'BulletList', 'OrderedList'
      return nil
    -- Do we want a table with a header?
    header = not( contains_no_header div.classes )
    -- Init the array of rows
    rows = {}
    -- Init the column count
    col_count = 0
    -- Loop through the list items
    for item in *lol.content
      -- Check that the item contains a list and nothing else
      assertion "Expected list in #{div_id} to be list of lists",
        #item == 1 and is_elem item[1], 'BulletList', 'OrderedList'
      -- The items of the inner list are the next table row
      row = item[1].content
      -- If this row is longer than any seen before
      -- we update the column count
      if #row > col_count
        col_count = #row
      rows[#rows+1] = row
    -- Make sure all rows are the same length by adding empty
    -- cells until they are the same length as the longest row
    for row in *rows
      while #row < col_count
        row[#row+1] = {}
    -- If we want a header use the first row,
    -- else set the headers to an empty list
    headers = if header
      remove rows, 1
    else
      {}
    -- Init the list of aligns
    aligns = {}
    -- Get the align attribute if any and coerce it to lowercase
    align = (div.attributes.align or "")\lower!
    -- If the align attr is empty it defaults to a single d
    align = 'd' if "" == align
    -- Now step through the comma-separated "items"
    for a in align\gmatch '[^,]+'
      -- Check that we have a valid "align-letter" and
      -- append its expansion to the list of aligns
      aligns[#aligns+1] = assertion "Unknown column alignment in #{div_id}: '#{a}'",
        letter2align[a]
      -- Don't look any further if we got the right number of aligns
      if #aligns == col_count
        break
    -- If we got too few aligns pad out with copies of the last
    while #aligns < col_count
      aligns[#aligns+1] = aligns[#aligns]
    -- Now do the same with widths
    widths = {}
    width = div.attributes.widths or ""
    -- Widths default to automatic widths
    width = '0' if "" == width
    for w in width\gmatch '[^,]+'
      -- A width is a percentage of the available total width,
      -- tableso an integer with up to three digits
      assertion "Expected column width in #{div_id} to be percentage, not '#{w}'",
        w\match '^[01]?%d?%d$'
      -- Convert it to a float and append it to the
      -- list of widths
      widths[#widths+1] = tonumber(w, 10) / 100
      if #widths == col_count
        break
    while #widths < col_count
      -- Pad with auto widths if we got too few widths
      widths[#widths+1] = 0
    -- See if we can create a table
    ok, res = pcall pandoc.Table, caption, aligns, widths, headers, rows
    -- and give a nice error message if we fail
    assert ok, "Error converting list to table in #{div_id}: #{res}"
    -- Do we want to keep the div?
    if contains_keep_div div.classes
      -- Reuse the attrs of the old div as far as possible!
      attr = div.attr
      -- Remove any old align/widths attrs since they may
      -- become inaccurate if the table is altered.
      for key in *{'align', 'widths'}
        attr.attributes[key] = nil
      -- Remove the lol2table class which certainly is
      -- wrong now
      attr.classes = [c for c in *div.classes when 'lol2table' != c]
      -- but make it easy for the user to revert to a LoL
      -- should they want to!
      insert attr.classes, 1, 'maybe-table2lol'
      -- Return a div with the table and the attributes
      return pandoc.Div {res}, attr
    -- Else don't keep the div, just return the table!
    return res

table2lol = do
  no_class = table2lol: true, 'no-header': true, noheader: true
  div_count = 0
  (div) ->
    div_count += 1
    return nil if #div.content == 0
    div_id = get_div_id 'table2lol', div, div_count
    assertion "Expected #{div_id} to contain only a table",
      #div.content == 1 and is_elem div.content[1], 'Table'
    tab = div.content[1]
    caption, headers, rows = tab.caption, tab.headers, tab.rows
    header = false
    for h in *headers
      header = true if #h > 0
    lol = [ {pandoc.OrderedList(row)} for row in *rows ]
    list_attr = pandoc.ListAttributes!
    if header
      insert lol, 1, {pandoc.OrderedList(headers)}
      list_attr.start = 0
    lol = pandoc.OrderedList lol, list_attr
    if contains_keep_div div.classes
      cols = {
        align:  [align2letter[a] for a in *tab.aligns]
        widths: [floor(w * 100) for w in *tab.widths]
      }
      classes = [ c for c in *div.classes when not no_class[c] ]
      caption = if #caption > 0
        pandoc.Para caption
      else
        pandoc.Null!
      unless header
        insert classes, 1, 'no-header'
      insert classes, 1, 'maybe-lol2table'
      attr = div.attr
      attr.classes = classes
      for key, list in pairs cols
        attr.attributes[key] = concat list, ","
      return pandoc.Div {lol, caption}, attr
    return lol





return do
  div_count = 0
  {
    {
      Div: =>
        div_count += 1
        is_lol2table = @classes\includes 'lol2table'
        is_table2lol = @classes\includes 'table2lol'
        if is_lol2table and is_table2lol
          div_id = get_div_id "", @, div_count
          error "Expected#{div_id} to have class .lol2table or class .table2lol, not both"
        elseif is_lol2table
          return lol2table @
        elseif is_table2lol
          return table2lol @
        return nil
    }
  }


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Rewriting table filters
       [not found] ` <e47081d5-e61c-6b57-e747-65e22085f976-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
@ 2020-08-18  0:09   ` EBkysko
       [not found]     ` <702aad33-76ea-4766-9bc9-4c2127b938c1o-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org>
  0 siblings, 1 reply; 3+ messages in thread
From: EBkysko @ 2020-08-18  0:09 UTC (permalink / raw)
  To: pandoc-discuss


[-- Attachment #1.1: Type: text/plain, Size: 874 bytes --]

See the pull request from AW/tarleb : *Lua filters: allow using SimpleTable 
instead of Table #6575 <https://github.com/jgm/pandoc/pull/6575>* which 
would give a way to adapt to the previous table scheme, when it is 
available.

So if your scripts works with the previous format, they would still work 
with the new.

In the meantime, you might *look here at my own attempt at a shim 
<https://groups.google.com/d/msg/pandoc-discuss/E1k7Wf9VGwQ/GsiUHhG3BQAJ>*.


-- 
You received this message because you are subscribed to the Google Groups "pandoc-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pandoc-discuss+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web visit https://groups.google.com/d/msgid/pandoc-discuss/702aad33-76ea-4766-9bc9-4c2127b938c1o%40googlegroups.com.

[-- Attachment #1.2: Type: text/html, Size: 1301 bytes --]

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Rewriting table filters
       [not found]     ` <702aad33-76ea-4766-9bc9-4c2127b938c1o-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org>
@ 2020-08-18  0:10       ` EBkysko
  0 siblings, 0 replies; 3+ messages in thread
From: EBkysko @ 2020-08-18  0:10 UTC (permalink / raw)
  To: pandoc-discuss


[-- Attachment #1.1: Type: text/plain, Size: 21 bytes --]

edit, typo: AW -> AK

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2020-08-18  0:10 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-17 13:36 Rewriting table filters BPJ
     [not found] ` <e47081d5-e61c-6b57-e747-65e22085f976-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2020-08-18  0:09   ` EBkysko
     [not found]     ` <702aad33-76ea-4766-9bc9-4c2127b938c1o-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org>
2020-08-18  0:10       ` EBkysko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).