@@ -50,11 +50,15 @@ local utils = M.utils
50-- @param indices list of indices, starting with the most deeply nested
50-- @param indices list of indices, starting with the most deeply nested
51-- @return newly created function
51-- @return newly created function
53−function make_indexing_function(template, indices)
53+function make_indexing_function(template, ...)
54+ local indices = {...}
54 local loadstring = loadstring or load
55 local loadstring = loadstring or load
55 local bracketed = {}
56 local bracketed = {}
56 for i = 1, #indices do
57 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)
59 local fnstr = string.format('return ' .. template, table.concat(bracketed))
63 local fnstr = string.format('return ' .. template, table.concat(bracketed))
60 return assert(loadstring(fnstr))()
64 return assert(loadstring(fnstr))()
@@ -69,11 +73,16 @@ local function create_accessor_functions (fn_template, accessors)
69 local res = {}
73 local res = {}
70 function add_accessors(acc, ...)
74 function add_accessors(acc, ...)
71 if type(acc) == 'string' then
75 if type(acc) == 'string' then
72− res[acc] = make_indexing_function(fn_template, {...})
76+ res[acc] = make_indexing_function(fn_template, ...)
73 elseif type(acc) == 'table' and #acc == 0 and next(acc) then
77 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`.
74 local name, substructure = next(acc)
81 local name, substructure = next(acc)
75− res[name] = make_indexing_function(fn_template, {...})
82+ res[name] = make_indexing_function(fn_template, ...)
76− add_accessors(substructure, ...)
83+ for _, subname in ipairs(substructure) do
84+ res[subname] = make_indexing_function(fn_template, subname, ...)
78 for i = 1, #(acc or {}) do
87 for i = 1, #(acc or {}) do
79 add_accessors(acc[i], i, ...)
88 add_accessors(acc[i], i, ...)
@@ -272,6 +281,35 @@ local function ensureDefinitionPairs (pair)
272 return {inlines, blocks}
281 return {inlines, blocks}
284+--- Split a string into it's words, using whitespace as separators.
285+local function words (str)
287+ for w in str:gmatch("([^%s]+)") do ws[#ws + 1] = w end
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
296+ -- assume HTML-like key-value pairs
297+ local ident = attr.id or ''
298+ local classes = words(attr.class or '')
299+ local attributes = attr
301+ attributes.class = nil
302+ return M.Attr(ident, classes, attributes)
303+ elseif attr == nil then
305+ elseif type(attr) == 'string' then
306+ -- treat argument as ID
310+ error('Could not convert to Attr')
275------------------------------------------------------------------------
313------------------------------------------------------------------------
276--- Pandoc Document
314--- Pandoc Document
277-- @section document
315-- @section document
@@ -402,7 +440,7 @@ M.BulletList = M.Block:create_constructor(
402-- @treturn Block code block element
440-- @treturn Block code block element
403M.CodeBlock = M.Block:create_constructor(
441M.CodeBlock = M.Block:create_constructor(
404 "CodeBlock",
442 "CodeBlock",
405− function(text, attr) return {c = {attr or M.Attr(), text}} end,
443+ function(text, attr) return {c = {ensureAttr(attr), text}} end,
406 {{attr = {"identifier", "classes", "attributes"}}, "text"}
444 {{attr = {"identifier", "classes", "attributes"}}, "text"}
@@ -426,7 +464,7 @@ M.DefinitionList = M.Block:create_constructor(
426M.Div = M.Block:create_constructor(
464M.Div = M.Block:create_constructor(
428 function(content, attr)
466 function(content, attr)
429− return {c = {attr or M.Attr(), ensureList(content)}}
467+ return {c = {ensureAttr(attr), ensureList(content)}}
431 {{attr = {"identifier", "classes", "attributes"}}, "content"}
469 {{attr = {"identifier", "classes", "attributes"}}, "content"}
@@ -440,7 +478,7 @@ M.Div = M.Block:create_constructor(
440M.Header = M.Block:create_constructor(
478M.Header = M.Block:create_constructor(
441 "Header",
479 "Header",
442 function(level, content, attr)
480 function(level, content, attr)
443− return {c = {level, attr or M.Attr(), ensureInlineList(content)}}
481+ return {c = {level, ensureAttr(attr), ensureInlineList(content)}}
445 {"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
483 {"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
@@ -569,7 +607,7 @@ M.Cite = M.Inline:create_constructor(
569-- @treturn Inline code element
607-- @treturn Inline code element
570M.Code = M.Inline:create_constructor(
608M.Code = M.Inline:create_constructor(
572− function(text, attr) return {c = {attr or M.Attr(), text}} end,
610+ function(text, attr) return {c = {ensureAttr(attr), text}} end,
573 {{attr = {"identifier", "classes", "attributes"}}, "text"}
611 {{attr = {"identifier", "classes", "attributes"}}, "text"}
@@ -594,8 +632,7 @@ M.Image = M.Inline:create_constructor(
595 function(caption, src, title, attr)
633 function(caption, src, title, attr)
596 title = title or ""
634 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}}}
600 {{attr = {"identifier", "classes", "attributes"}}, "caption", {"src", "title"}}
637 {{attr = {"identifier", "classes", "attributes"}}, "caption", {"src", "title"}}
@@ -619,7 +656,7 @@ M.Link = M.Inline:create_constructor(
620 function(content, target, title, attr)
657 function(content, target, title, attr)
621 title = title or ""
658 title = title or ""
622− attr = attr or M.Attr()
659+ attr = ensureAttr(attr)
623 return {c = {attr, ensureInlineList(content), {target, title}}}
660 return {c = {attr, ensureInlineList(content), {target, title}}}
625 {{attr = {"identifier", "classes", "attributes"}}, "content", {"target", "title"}}
662 {{attr = {"identifier", "classes", "attributes"}}, "content", {"target", "title"}}
@@ -743,7 +780,7 @@ M.Space = M.Inline:create_constructor(
743M.Span = M.Inline:create_constructor(
780M.Span = M.Inline:create_constructor(
745 function(content, attr)
782 function(content, attr)
746− return {c = {attr or M.Attr(), ensureInlineList(content)}}
783+ return {c = {ensureAttr(attr), ensureInlineList(content)}}
748 {{attr = {"identifier", "classes", "attributes"}}, "content"}
785 {{attr = {"identifier", "classes", "attributes"}}, "content"}
@@ -902,17 +939,21 @@ function M.Attr:new (identifier, classes, attributes)
902 identifier = identifier or ''
939 identifier = identifier or ''
903 classes = ensureList(classes or {})
940 classes = ensureList(classes or {})
904 attributes = setmetatable(to_alist(attributes or {}), AttributeList)
941 attributes = setmetatable(to_alist(attributes or {}), AttributeList)
905− return {identifier, classes, attributes}
942+ return setmetatable({identifier, classes, attributes}, self.behavior)
907M.Attr.behavior.clone = M.types.clone.Attr
944M.Attr.behavior.clone = M.types.clone.Attr
945+M.Attr.behavior.tag = 'Attr'
908M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
946M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
909M.Attr.behavior.__eq = utils.equals
947M.Attr.behavior.__eq = utils.equals
910M.Attr.behavior.__index = function(t, k)
948M.Attr.behavior.__index = function(t, k)
949+ return (k == 't' and t.tag) or
911− return rawget(t, getmetatable(t)._field_names[k]) or
950+ rawget(t, getmetatable(t)._field_names[k]) or
912 getmetatable(t)[k]
951 getmetatable(t)[k]
914M.Attr.behavior.__newindex = function(t, k, v)
953M.Attr.behavior.__newindex = function(t, k, v)
954+ if k == 'attributes' then
955+ rawset(t, 3, setmetatable(to_alist(v or {}), AttributeList))
915− if getmetatable(t)._field_names[k] then
956+ elseif getmetatable(t)._field_names[k] then
916 rawset(t, getmetatable(t)._field_names[k], v)
957 rawset(t, getmetatable(t)._field_names[k], v)
918 rawset(t, k, v)
959 rawset(t, k, v)
@@ -927,6 +968,24 @@ M.Attr.behavior.__pairs = function(t)
927 return make_next_function(fields), t, nil
968 return make_next_function(fields), t, nil
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))
981+for _, blk in pairs(M.Block.constructor) do
982+ augment_attr_setter(blk.behavior.setters)
984+for _, inln in pairs(M.Inline.constructor) do
985+ augment_attr_setter(inln.behavior.setters)
930-- Citation
989-- Citation
931M.Citation = AstElement:make_subtype'Citation'
990M.Citation = AstElement:make_subtype'Citation'
932M.Citation.behavior.clone = M.types.clone.Citation
991M.Citation.behavior.clone = M.types.clone.Citation