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
This commit is contained in:
parent
f580da2033
commit
d0261d7387
3 changed files with 129 additions and 17 deletions
|
@ -50,11 +50,15 @@ local utils = M.utils
|
|||
-- @param indices list of indices, starting with the most deeply nested
|
||||
-- @return newly created function
|
||||
-- @local
|
||||
function make_indexing_function(template, indices)
|
||||
function make_indexing_function(template, ...)
|
||||
local indices = {...}
|
||||
local loadstring = loadstring or load
|
||||
local bracketed = {}
|
||||
for i = 1, #indices do
|
||||
bracketed[i] = string.format('[%d]', indices[#indices - i + 1])
|
||||
local idx = indices[#indices - i + 1]
|
||||
bracketed[i] = type(idx) == 'number'
|
||||
and string.format('[%d]', idx)
|
||||
or string.format('.%s', idx)
|
||||
end
|
||||
local fnstr = string.format('return ' .. template, table.concat(bracketed))
|
||||
return assert(loadstring(fnstr))()
|
||||
|
@ -69,11 +73,16 @@ local function create_accessor_functions (fn_template, accessors)
|
|||
local res = {}
|
||||
function add_accessors(acc, ...)
|
||||
if type(acc) == 'string' then
|
||||
res[acc] = make_indexing_function(fn_template, {...})
|
||||
res[acc] = make_indexing_function(fn_template, ...)
|
||||
elseif type(acc) == 'table' and #acc == 0 and next(acc) then
|
||||
-- Named substructure: the given names are accessed via the substructure,
|
||||
-- but the accessors are also added to the result table, enabling direct
|
||||
-- access from the parent element. Mainly used for `attr`.
|
||||
local name, substructure = next(acc)
|
||||
res[name] = make_indexing_function(fn_template, {...})
|
||||
add_accessors(substructure, ...)
|
||||
res[name] = make_indexing_function(fn_template, ...)
|
||||
for _, subname in ipairs(substructure) do
|
||||
res[subname] = make_indexing_function(fn_template, subname, ...)
|
||||
end
|
||||
else
|
||||
for i = 1, #(acc or {}) do
|
||||
add_accessors(acc[i], i, ...)
|
||||
|
@ -272,6 +281,35 @@ local function ensureDefinitionPairs (pair)
|
|||
return {inlines, blocks}
|
||||
end
|
||||
|
||||
--- Split a string into it's words, using whitespace as separators.
|
||||
local function words (str)
|
||||
local ws = {}
|
||||
for w in str:gmatch("([^%s]+)") do ws[#ws + 1] = w end
|
||||
return ws
|
||||
end
|
||||
|
||||
--- Try hard to turn the arguments into an Attr object.
|
||||
local function ensureAttr(attr)
|
||||
if type(attr) == 'table' then
|
||||
if #attr > 0 then return M.Attr(table.unpack(attr)) end
|
||||
|
||||
-- assume HTML-like key-value pairs
|
||||
local ident = attr.id or ''
|
||||
local classes = words(attr.class or '')
|
||||
local attributes = attr
|
||||
attributes.id = nil
|
||||
attributes.class = nil
|
||||
return M.Attr(ident, classes, attributes)
|
||||
elseif attr == nil then
|
||||
return M.Attr()
|
||||
elseif type(attr) == 'string' then
|
||||
-- treat argument as ID
|
||||
return M.Attr(attr)
|
||||
end
|
||||
-- print(arg, ...)
|
||||
error('Could not convert to Attr')
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
--- Pandoc Document
|
||||
-- @section document
|
||||
|
@ -402,7 +440,7 @@ M.BulletList = M.Block:create_constructor(
|
|||
-- @treturn Block code block element
|
||||
M.CodeBlock = M.Block:create_constructor(
|
||||
"CodeBlock",
|
||||
function(text, attr) return {c = {attr or M.Attr(), text}} end,
|
||||
function(text, attr) return {c = {ensureAttr(attr), text}} end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "text"}
|
||||
)
|
||||
|
||||
|
@ -426,7 +464,7 @@ M.DefinitionList = M.Block:create_constructor(
|
|||
M.Div = M.Block:create_constructor(
|
||||
"Div",
|
||||
function(content, attr)
|
||||
return {c = {attr or M.Attr(), ensureList(content)}}
|
||||
return {c = {ensureAttr(attr), ensureList(content)}}
|
||||
end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "content"}
|
||||
)
|
||||
|
@ -440,7 +478,7 @@ M.Div = M.Block:create_constructor(
|
|||
M.Header = M.Block:create_constructor(
|
||||
"Header",
|
||||
function(level, content, attr)
|
||||
return {c = {level, attr or M.Attr(), ensureInlineList(content)}}
|
||||
return {c = {level, ensureAttr(attr), ensureInlineList(content)}}
|
||||
end,
|
||||
{"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
|
||||
)
|
||||
|
@ -569,7 +607,7 @@ M.Cite = M.Inline:create_constructor(
|
|||
-- @treturn Inline code element
|
||||
M.Code = M.Inline:create_constructor(
|
||||
"Code",
|
||||
function(text, attr) return {c = {attr or M.Attr(), text}} end,
|
||||
function(text, attr) return {c = {ensureAttr(attr), text}} end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "text"}
|
||||
)
|
||||
|
||||
|
@ -594,8 +632,7 @@ M.Image = M.Inline:create_constructor(
|
|||
"Image",
|
||||
function(caption, src, title, attr)
|
||||
title = title or ""
|
||||
attr = attr or M.Attr()
|
||||
return {c = {attr, ensureInlineList(caption), {src, title}}}
|
||||
return {c = {ensureAttr(attr), ensureInlineList(caption), {src, title}}}
|
||||
end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "caption", {"src", "title"}}
|
||||
)
|
||||
|
@ -619,7 +656,7 @@ M.Link = M.Inline:create_constructor(
|
|||
"Link",
|
||||
function(content, target, title, attr)
|
||||
title = title or ""
|
||||
attr = attr or M.Attr()
|
||||
attr = ensureAttr(attr)
|
||||
return {c = {attr, ensureInlineList(content), {target, title}}}
|
||||
end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "content", {"target", "title"}}
|
||||
|
@ -743,7 +780,7 @@ M.Space = M.Inline:create_constructor(
|
|||
M.Span = M.Inline:create_constructor(
|
||||
"Span",
|
||||
function(content, attr)
|
||||
return {c = {attr or M.Attr(), ensureInlineList(content)}}
|
||||
return {c = {ensureAttr(attr), ensureInlineList(content)}}
|
||||
end,
|
||||
{{attr = {"identifier", "classes", "attributes"}}, "content"}
|
||||
)
|
||||
|
@ -902,17 +939,21 @@ function M.Attr:new (identifier, classes, attributes)
|
|||
identifier = identifier or ''
|
||||
classes = ensureList(classes or {})
|
||||
attributes = setmetatable(to_alist(attributes or {}), AttributeList)
|
||||
return {identifier, classes, attributes}
|
||||
return setmetatable({identifier, classes, attributes}, self.behavior)
|
||||
end
|
||||
M.Attr.behavior.clone = M.types.clone.Attr
|
||||
M.Attr.behavior.tag = 'Attr'
|
||||
M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
|
||||
M.Attr.behavior.__eq = utils.equals
|
||||
M.Attr.behavior.__index = function(t, k)
|
||||
return rawget(t, getmetatable(t)._field_names[k]) or
|
||||
return (k == 't' and t.tag) or
|
||||
rawget(t, getmetatable(t)._field_names[k]) or
|
||||
getmetatable(t)[k]
|
||||
end
|
||||
M.Attr.behavior.__newindex = function(t, k, v)
|
||||
if getmetatable(t)._field_names[k] then
|
||||
if k == 'attributes' then
|
||||
rawset(t, 3, setmetatable(to_alist(v or {}), AttributeList))
|
||||
elseif getmetatable(t)._field_names[k] then
|
||||
rawset(t, getmetatable(t)._field_names[k], v)
|
||||
else
|
||||
rawset(t, k, v)
|
||||
|
@ -927,6 +968,24 @@ M.Attr.behavior.__pairs = function(t)
|
|||
return make_next_function(fields), t, nil
|
||||
end
|
||||
|
||||
-- Monkey-patch setters for `attr` fields to be more forgiving in the input that
|
||||
-- results in a valid Attr value.
|
||||
function augment_attr_setter (setters)
|
||||
if setters.attr then
|
||||
local orig = setters.attr
|
||||
setters.attr = function(k, v)
|
||||
orig(k, ensureAttr(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, blk in pairs(M.Block.constructor) do
|
||||
augment_attr_setter(blk.behavior.setters)
|
||||
end
|
||||
for _, inln in pairs(M.Inline.constructor) do
|
||||
augment_attr_setter(inln.behavior.setters)
|
||||
end
|
||||
|
||||
|
||||
-- Citation
|
||||
M.Citation = AstElement:make_subtype'Citation'
|
||||
M.Citation.behavior.clone = M.types.clone.Citation
|
||||
|
|
|
@ -1247,7 +1247,19 @@ Superscripted text
|
|||
|
||||
### Attr {#type-ref-Attr}
|
||||
|
||||
A set of element attributes
|
||||
A set of element attributes. Values of this type can be created
|
||||
with the [`pandoc.Attr`](#Attr) constructor. For convenience, it
|
||||
is usually not necessary to construct the value directly if it is
|
||||
part of an element, and it is sufficient to pass an HTML-like
|
||||
table. E.g., to create a span with identifier "text" and classes
|
||||
"a" and "b", on can write:
|
||||
|
||||
local span = pandoc.Span('text', {id = 'text', class = 'a b'})
|
||||
|
||||
This also works when using the `attr` setter:
|
||||
|
||||
local span = pandoc.Span 'text'
|
||||
span.attr = {id = 'text', class = 'a b', other_attribute = '1'}
|
||||
|
||||
Object equality is determined via
|
||||
[`pandoc.utils.equals`](#utils-equals).
|
||||
|
|
|
@ -86,6 +86,47 @@ return {
|
|||
assert.are_equal(count, 3)
|
||||
end)
|
||||
},
|
||||
group 'HTML-like attribute tables' {
|
||||
test('in element constructor', function ()
|
||||
local html_attributes = {
|
||||
id = 'the-id',
|
||||
class = 'class1 class2',
|
||||
width = '11',
|
||||
height = '12'
|
||||
}
|
||||
local attr = pandoc.Span('test', html_attributes).attr
|
||||
assert.are_equal(attr.identifier, 'the-id')
|
||||
assert.are_equal(attr.classes[1], 'class1')
|
||||
assert.are_equal(attr.classes[2], 'class2')
|
||||
assert.are_equal(attr.attributes.width, '11')
|
||||
assert.are_equal(attr.attributes.height, '12')
|
||||
end),
|
||||
test('element attr setter', function ()
|
||||
local html_attributes = {
|
||||
id = 'the-id',
|
||||
class = 'class1 class2',
|
||||
width = "11",
|
||||
height = "12"
|
||||
}
|
||||
local span = pandoc.Span 'test'
|
||||
span.attr = html_attributes
|
||||
assert.are_equal(span.attr.identifier, 'the-id')
|
||||
assert.are_equal(span.attr.classes[1], 'class1')
|
||||
assert.are_equal(span.attr.classes[2], 'class2')
|
||||
assert.are_equal(span.attr.attributes.width, '11')
|
||||
assert.are_equal(span.attr.attributes.height, '12')
|
||||
end),
|
||||
test('element attrbutes setter', function ()
|
||||
local attributes = {
|
||||
width = "11",
|
||||
height = "12"
|
||||
}
|
||||
local span = pandoc.Span 'test'
|
||||
span.attributes = attributes
|
||||
assert.are_equal(span.attr.attributes.width, '11')
|
||||
assert.are_equal(span.attr.attributes.height, '12')
|
||||
end)
|
||||
}
|
||||
},
|
||||
|
||||
group 'clone' {
|
||||
|
|
Loading…
Reference in a new issue