@@ -627,6 +627,97 @@ M.Superscript = M.Inline:create_constructor(
627627-- Helpers
628628-- @section helpers
629629
630+-- Find a value pair in a list.
631+-- @function find
632+-- @tparam table list to be searched
633+-- @param needle element to search for
634+-- @param[opt] key when non-nil, compare on this field of each list element
635+local function find (alist, needle, key)
636+ local test
637+ if key then
638+ test = function(x) return x[key] == needle end
639+ else
640+ test = function(x) return x == needle end
641+ end
642+ for i, k in ipairs(alist) do
643+ if test(k) then
644+ return i, k
645+ end
646+ end
647+ return nil
648+end
649+
650+-- Lookup a value in an associative list
651+-- @function lookup
652+-- @tparam {{key, value},...} alist associative list
653+-- @param key key for which the associated value is to be looked up
654+local function lookup(alist, key)
655+ return (select(2, find(alist, key, 1)) or {})[2]
656+end
657+
658+--- Return an iterator which returns key-value pairs of an associative list.
659+-- @function apairs
660+-- @tparam {{key, value},...} alist associative list
661+local apairs = function (alist)
662+ local i = 1
663+ local cur
664+ function nxt ()
665+ cur = rawget(alist, i)
666+ if cur then
667+ i = i + 1
668+ return cur[1], cur[2]
669+ end
670+ return nil
671+ end
672+ return nxt, nil, nil
673+end
674+
675+-- AttributeList, a metatable to allow table-like access to attribute lists
676+-- represented by associative lists.
677+local AttributeList = {
678+ __index = function (t, k)
679+ if type(k) == "number" then
680+ return rawget(t, k)
681+ else
682+ return lookup(t, k)
683+ end
684+ end,
685+
686+ __newindex = function (t, k, v)
687+ local idx, cur = find(t, k, 1)
688+ if v == nil then
689+ table.remove(t, idx)
690+ elseif cur then
691+ cur[2] = v
692+ elseif type(k) == "number" then
693+ rawset(t, k, v)
694+ else
695+ rawset(t, #t + 1, {k, v})
696+ end
697+ end,
698+
699+ __pairs = apairs
700+}
701+
702+-- convert a table to an associative list. The order of key-value pairs in the
703+-- alist is undefined. The table should either contain no numeric keys or
704+-- already be an associative list.
705+-- @tparam table associative list or table without numeric keys.
706+-- @treturn table associative list
707+local to_alist = function (tbl)
708+ if #tbl ~= 0 or next(tbl) == nil then
709+ -- probably already an alist
710+ return tbl
711+ end
712+ local alist = {}
713+ local i = 1
714+ for k, v in pairs(tbl) do
715+ alist[i] = {k, v}
716+ i = i + 1
717+ end
718+ return alist
719+end
720+
630721-- Attr
631722M.Attr = {}
632723M.Attr._field_names = {identifier = 1, classes = 2, attributes = 3}
@@ -639,7 +730,7 @@ M.Attr._field_names = {identifier = 1, classes = 2, attributes = 3}
639730M.Attr.__call = function(t, identifier, classes, attributes)
640731 identifier = identifier or ''
641732 classes = classes or {}
642− attributes = attributes or {}
733+ attributes = setmetatable(to_alist(attributes or {}), AttributeList)
643734 local attr = {identifier, classes, attributes}
644735 setmetatable(attr, t)
645736 return attr