git.iko.soy/typeable/pandoc
on master help sign in
Lua filters: allow passing of HTML-like tables instead of Attr (#5750)

Attr values can now be given as normal Lua tables; this can be used as a convenient alternative to define Attr values, instead of constructing values with `pandoc.Attr`. Identifiers are taken from the *id* field, classes must be given as space separated words in the *class* field. All remaining fields are included as misc attributes. With this change, the following lines now create equal elements: pandoc.Span('test', {id = 'test', class = 'a b', check = 1}) pandoc.Span('test', pandoc.Attr('test', {'a','b'}, {check = 1})) This also works when using the *attr* setter: local span = pandoc.Span 'text' span.attr = {id = 'test', class = 'a b', check = 1} Furthermore, the *attributes* field of AST elements can now be a plain key-value table even when using the `attributes` accessor: local span = pandoc.Span 'test' span.attributes = {check = 1} -- works as expected now Closes: #5744

commitd0261d7387cdf1a910a1029a747e239453190f71 authorAlbert Krewinkel <albert@zeitkraut.de> dateSun 15 Sep 2019, 21:11 (7 years ago) parentf580da2
3 files changed+129−17
data/pandoc.lua+75−16 doc/lua-filters.md+13−1 test/lua/module/pandoc.lua+41
data/pandoc.lua+75−16
@@ -50,11 +50,15 @@ local utils = M.utils
5050-- @param indices list of indices, starting with the most deeply nested
5151-- @return newly created function
5252-- @local
53−function make_indexing_function(template, indices)
53+function make_indexing_function(template, ...)
54+ local indices = {...}
5455 local loadstring = loadstring or load
5556 local bracketed = {}
5657 for i = 1, #indices do
57− bracketed[i] = string.format('[%d]', indices[#indices - i + 1])
58+ local idx = indices[#indices - i + 1]
59+ bracketed[i] = type(idx) == 'number'
60+ and string.format('[%d]', idx)
61+ or string.format('.%s', idx)
5862 end
5963 local fnstr = string.format('return ' .. template, table.concat(bracketed))
6064 return assert(loadstring(fnstr))()
@@ -69,11 +73,16 @@ local function create_accessor_functions (fn_template, accessors)
6973 local res = {}
7074 function add_accessors(acc, ...)
7175 if type(acc) == 'string' then
72− res[acc] = make_indexing_function(fn_template, {...})
76+ res[acc] = make_indexing_function(fn_template, ...)
7377 elseif type(acc) == 'table' and #acc == 0 and next(acc) then
78+ -- Named substructure: the given names are accessed via the substructure,
79+ -- but the accessors are also added to the result table, enabling direct
80+ -- access from the parent element. Mainly used for `attr`.
7481 local name, substructure = next(acc)
75− res[name] = make_indexing_function(fn_template, {...})
76− add_accessors(substructure, ...)
82+ res[name] = make_indexing_function(fn_template, ...)
83+ for _, subname in ipairs(substructure) do
84+ res[subname] = make_indexing_function(fn_template, subname, ...)
85+ end
7786 else
7887 for i = 1, #(acc or {}) do
7988 add_accessors(acc[i], i, ...)
@@ -272,6 +281,35 @@ local function ensureDefinitionPairs (pair)
272281 return {inlines, blocks}
273282end
274283
284+--- Split a string into it's words, using whitespace as separators.
285+local function words (str)
286+ local ws = {}
287+ for w in str:gmatch("([^%s]+)") do ws[#ws + 1] = w end
288+ return ws
289+end
290+
291+--- Try hard to turn the arguments into an Attr object.
292+local function ensureAttr(attr)
293+ if type(attr) == 'table' then
294+ if #attr > 0 then return M.Attr(table.unpack(attr)) end
295+
296+ -- assume HTML-like key-value pairs
297+ local ident = attr.id or ''
298+ local classes = words(attr.class or '')
299+ local attributes = attr
300+ attributes.id = nil
301+ attributes.class = nil
302+ return M.Attr(ident, classes, attributes)
303+ elseif attr == nil then
304+ return M.Attr()
305+ elseif type(attr) == 'string' then
306+ -- treat argument as ID
307+ return M.Attr(attr)
308+ end
309+ -- print(arg, ...)
310+ error('Could not convert to Attr')
311+end
312+
275313------------------------------------------------------------------------
276314--- Pandoc Document
277315-- @section document
@@ -402,7 +440,7 @@ M.BulletList = M.Block:create_constructor(
402440-- @treturn Block code block element
403441M.CodeBlock = M.Block:create_constructor(
404442 "CodeBlock",
405− function(text, attr) return {c = {attr or M.Attr(), text}} end,
443+ function(text, attr) return {c = {ensureAttr(attr), text}} end,
406444 {{attr = {"identifier", "classes", "attributes"}}, "text"}
407445)
408446
@@ -426,7 +464,7 @@ M.DefinitionList = M.Block:create_constructor(
426464M.Div = M.Block:create_constructor(
427465 "Div",
428466 function(content, attr)
429− return {c = {attr or M.Attr(), ensureList(content)}}
467+ return {c = {ensureAttr(attr), ensureList(content)}}
430468 end,
431469 {{attr = {"identifier", "classes", "attributes"}}, "content"}
432470)
@@ -440,7 +478,7 @@ M.Div = M.Block:create_constructor(
440478M.Header = M.Block:create_constructor(
441479 "Header",
442480 function(level, content, attr)
443− return {c = {level, attr or M.Attr(), ensureInlineList(content)}}
481+ return {c = {level, ensureAttr(attr), ensureInlineList(content)}}
444482 end,
445483 {"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
446484)
@@ -569,7 +607,7 @@ M.Cite = M.Inline:create_constructor(
569607-- @treturn Inline code element
570608M.Code = M.Inline:create_constructor(
571609 "Code",
572− function(text, attr) return {c = {attr or M.Attr(), text}} end,
610+ function(text, attr) return {c = {ensureAttr(attr), text}} end,
573611 {{attr = {"identifier", "classes", "attributes"}}, "text"}
574612)
575613
@@ -594,8 +632,7 @@ M.Image = M.Inline:create_constructor(
594632 "Image",
595633 function(caption, src, title, attr)
596634 title = title or ""
597− attr = attr or M.Attr()
598− return {c = {attr, ensureInlineList(caption), {src, title}}}
635+ return {c = {ensureAttr(attr), ensureInlineList(caption), {src, title}}}
599636 end,
600637 {{attr = {"identifier", "classes", "attributes"}}, "caption", {"src", "title"}}
601638)
@@ -619,7 +656,7 @@ M.Link = M.Inline:create_constructor(
619656 "Link",
620657 function(content, target, title, attr)
621658 title = title or ""
622− attr = attr or M.Attr()
659+ attr = ensureAttr(attr)
623660 return {c = {attr, ensureInlineList(content), {target, title}}}
624661 end,
625662 {{attr = {"identifier", "classes", "attributes"}}, "content", {"target", "title"}}
@@ -743,7 +780,7 @@ M.Space = M.Inline:create_constructor(
743780M.Span = M.Inline:create_constructor(
744781 "Span",
745782 function(content, attr)
746− return {c = {attr or M.Attr(), ensureInlineList(content)}}
783+ return {c = {ensureAttr(attr), ensureInlineList(content)}}
747784 end,
748785 {{attr = {"identifier", "classes", "attributes"}}, "content"}
749786)
@@ -902,17 +939,21 @@ function M.Attr:new (identifier, classes, attributes)
902939 identifier = identifier or ''
903940 classes = ensureList(classes or {})
904941 attributes = setmetatable(to_alist(attributes or {}), AttributeList)
905− return {identifier, classes, attributes}
942+ return setmetatable({identifier, classes, attributes}, self.behavior)
906943end
907944M.Attr.behavior.clone = M.types.clone.Attr
945+M.Attr.behavior.tag = 'Attr'
908946M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
909947M.Attr.behavior.__eq = utils.equals
910948M.Attr.behavior.__index = function(t, k)
911− return rawget(t, getmetatable(t)._field_names[k]) or
949+ return (k == 't' and t.tag) or
950+ rawget(t, getmetatable(t)._field_names[k]) or
912951 getmetatable(t)[k]
913952end
914953M.Attr.behavior.__newindex = function(t, k, v)
915− if getmetatable(t)._field_names[k] then
954+ if k == 'attributes' then
955+ rawset(t, 3, setmetatable(to_alist(v or {}), AttributeList))
956+ elseif getmetatable(t)._field_names[k] then
916957 rawset(t, getmetatable(t)._field_names[k], v)
917958 else
918959 rawset(t, k, v)
@@ -927,6 +968,24 @@ M.Attr.behavior.__pairs = function(t)
927968 return make_next_function(fields), t, nil
928969end
929970
971+-- Monkey-patch setters for `attr` fields to be more forgiving in the input that
972+-- results in a valid Attr value.
973+function augment_attr_setter (setters)
974+ if setters.attr then
975+ local orig = setters.attr
976+ setters.attr = function(k, v)
977+ orig(k, ensureAttr(v))
978+ end
979+ end
980+end
981+for _, blk in pairs(M.Block.constructor) do
982+ augment_attr_setter(blk.behavior.setters)
983+end
984+for _, inln in pairs(M.Inline.constructor) do
985+ augment_attr_setter(inln.behavior.setters)
986+end
987+
988+
930989-- Citation
931990M.Citation = AstElement:make_subtype'Citation'
932991M.Citation.behavior.clone = M.types.clone.Citation
doc/lua-filters.md+13−1
@@ -1247,7 +1247,19 @@ Superscripted text
12471247
12481248### Attr {#type-ref-Attr}
12491249
1250−A set of element attributes
1250+A set of element attributes. Values of this type can be created
1251+with the [`pandoc.Attr`](#Attr) constructor. For convenience, it
1252+is usually not necessary to construct the value directly if it is
1253+part of an element, and it is sufficient to pass an HTML-like
1254+table. E.g., to create a span with identifier "text" and classes
1255+"a" and "b", on can write:
1256+
1257+ local span = pandoc.Span('text', {id = 'text', class = 'a b'})
1258+
1259+This also works when using the `attr` setter:
1260+
1261+ local span = pandoc.Span 'text'
1262+ span.attr = {id = 'text', class = 'a b', other_attribute = '1'}
12511263
12521264Object equality is determined via
12531265[`pandoc.utils.equals`](#utils-equals).
test/lua/module/pandoc.lua+41
@@ -86,6 +86,47 @@ return {
8686 assert.are_equal(count, 3)
8787 end)
8888 },
89+ group 'HTML-like attribute tables' {
90+ test('in element constructor', function ()
91+ local html_attributes = {
92+ id = 'the-id',
93+ class = 'class1 class2',
94+ width = '11',
95+ height = '12'
96+ }
97+ local attr = pandoc.Span('test', html_attributes).attr
98+ assert.are_equal(attr.identifier, 'the-id')
99+ assert.are_equal(attr.classes[1], 'class1')
100+ assert.are_equal(attr.classes[2], 'class2')
101+ assert.are_equal(attr.attributes.width, '11')
102+ assert.are_equal(attr.attributes.height, '12')
103+ end),
104+ test('element attr setter', function ()
105+ local html_attributes = {
106+ id = 'the-id',
107+ class = 'class1 class2',
108+ width = "11",
109+ height = "12"
110+ }
111+ local span = pandoc.Span 'test'
112+ span.attr = html_attributes
113+ assert.are_equal(span.attr.identifier, 'the-id')
114+ assert.are_equal(span.attr.classes[1], 'class1')
115+ assert.are_equal(span.attr.classes[2], 'class2')
116+ assert.are_equal(span.attr.attributes.width, '11')
117+ assert.are_equal(span.attr.attributes.height, '12')
118+ end),
119+ test('element attrbutes setter', function ()
120+ local attributes = {
121+ width = "11",
122+ height = "12"
123+ }
124+ local span = pandoc.Span 'test'
125+ span.attributes = attributes
126+ assert.are_equal(span.attr.attributes.width, '11')
127+ assert.are_equal(span.attr.attributes.height, '12')
128+ end)
129+ }
89130 },
90131
91132 group 'clone' {