@@ -265,59 +265,75 @@ local function get_type(v)
265265 return pandoc_type
266266end
267267
268- local function as_inlines (v )
269- if v == nil then
270- return pandoc .Inlines ({})
271- end
272- local t = pandoc .utils .type (v )
273- if t == " Inlines" then
274- --- @cast v pandoc.Inlines
275- return v
276- elseif t == " Blocks" then
277- return pandoc .utils .blocks_to_inlines (v )
278- elseif t == " Inline" then
279- return pandoc .Inlines ({v })
280- elseif t == " Block" then
281- return pandoc .utils .blocks_to_inlines ({v })
282- end
268+ --- Blocks metatable
269+ local BlocksMT = getmetatable (pandoc .Blocks {})
270+ --- Inlines metatable
271+ local InlinesMT = getmetatable (pandoc .Inlines {})
283272
284- if type (v ) == " table" then
285- local result = pandoc .Inlines ({})
286- for i , v in ipairs (v ) do
287- tappend (result , as_inlines (v ))
273+ --- Turns the given object into a `Inlines` list.
274+ --
275+ -- Works mostly like `pandoc.Inlines`, but doesn't a do a full
276+ -- unmarshal/marshal roundtrip. This buys performance, at the cost of
277+ -- less thorough type checks.
278+ --
279+ -- NOTE: The input object might be modified *destructively*!
280+ local function as_inlines (obj )
281+ local pt = pandoc .utils .type (obj )
282+ if pt == ' Inlines' then
283+ return obj
284+ elseif pt == " Inline" then
285+ -- Faster than calling pandoc.Inlines
286+ return setmetatable ({obj }, InlinesMT )
287+ elseif pt == ' List' or pt == ' table' then
288+ if obj [1 ] and pandoc .utils .type (obj [1 ]) == ' Block' then
289+ return pandoc .utils .blocks_to_inlines (obj )
288290 end
289- return result
291+ -- Faster than calling pandoc.Inlines
292+ return setmetatable (obj , InlinesMT )
293+ elseif pt == " Block" then
294+ return pandoc .utils .blocks_to_inlines ({obj })
295+ elseif pt == " Blocks" then
296+ return pandoc .utils .blocks_to_inlines (obj )
297+ else
298+ return pandoc .Inlines (obj or {})
290299 end
291-
292- -- luacov: disable
293- fatal (" as_inlines: invalid type " .. t )
294- return pandoc .Inlines ({})
295- -- luacov: enable
296300end
297301
298- local function as_blocks (v )
299- if v == nil then
300- return pandoc .Blocks ({})
301- end
302- local t = pandoc .utils .type (v )
303- if t == " Blocks" then
304- return v
305- elseif t == " Inlines" then
306- return pandoc .Blocks ({pandoc .Plain (v )})
307- elseif t == " Block" then
308- return pandoc .Blocks ({v })
309- elseif t == " Inline" then
310- return pandoc .Blocks ({pandoc .Plain (v )})
311- end
312-
313- if type (v ) == " table" then
314- return pandoc .Blocks (v )
302+ --- Turns the given object into a `Blocks` list.
303+ --
304+ -- Works mostly like `pandoc.Blocks`, but doesn't a do a full
305+ -- unmarshal/marshal roundtrip. This buys performance, at the cost of
306+ -- less thorough type checks.
307+ --
308+ -- NOTE: The input object might be modified *destructively*!
309+ --
310+ -- This might need some benchmarking.
311+ local function as_blocks (obj )
312+ local pt = pandoc .utils .type (obj )
313+ if pt == ' Blocks' then
314+ return obj
315+ elseif pt == ' Block' then
316+ -- Assigning a metatable directly is faster than calling
317+ -- `pandoc.Blocks`.
318+ return setmetatable ({obj }, BlocksMT )
319+ elseif pt == ' Inline' then
320+ return setmetatable ({pandoc .Plain {obj }}, BlocksMT )
321+ elseif pt == ' Inlines' then
322+ if next (obj ) then
323+ return setmetatable ({pandoc .Plain (obj )}, BlocksMT )
324+ end
325+ return setmetatable ({}, BlocksMT )
326+ elseif pt == ' List' or (pt == ' table' and obj [1 ]) then
327+ if pandoc .utils .type (obj [1 ]) == ' Inline' then
328+ obj = {pandoc .Plain (obj )}
329+ end
330+ return setmetatable (obj , BlocksMT )
331+ elseif (pt == ' table' and obj .long ) or pt == ' Caption' then
332+ -- Looks like a Caption
333+ return as_blocks (obj .long )
334+ else
335+ return pandoc .Blocks (obj or {})
315336 end
316-
317- -- luacov: disable
318- fatal (" as_blocks: invalid type " .. t )
319- return pandoc .Blocks ({})
320- -- luacov: enable
321337end
322338
323339local function match_fun (reset , ...)
@@ -557,6 +573,32 @@ local function match(...)
557573 return match_fun (reset , table.unpack (result ))
558574end
559575
576+ --- Returns `true` iff the given AST node is empty.
577+ -- A node is considered "empty" if it's an empty list, table, or a node
578+ -- without any text or nested AST nodes.
579+ local function is_empty_node (node )
580+ if not node then
581+ return true
582+ elseif type (node ) == ' table' then
583+ -- tables are considered empty if they don't have any fields.
584+ return not next (node )
585+ elseif node .content then
586+ return not next (node .content )
587+ elseif node .caption then
588+ -- looks like an image, figure, or table
589+ if node .caption .long then
590+ return not next (node .caption .long )
591+ end
592+ return not next (node .caption )
593+ elseif node .text then
594+ -- looks like a code node or text node
595+ return node .text ~= ' '
596+ else
597+ -- Not sure what this is, but it's probably not empty.
598+ return false
599+ end
600+ end
601+
560602return {
561603 dump = dump ,
562604 type = get_type ,
@@ -567,6 +609,7 @@ return {
567609 },
568610 as_inlines = as_inlines ,
569611 as_blocks = as_blocks ,
612+ is_empty_node = is_empty_node ,
570613 match = match ,
571614 add_to_blocks = function (blocks , block )
572615 if pandoc .utils .type (blocks ) ~= " Blocks" then
0 commit comments