1--[[
2pandoc.lua
3
4Copyright © 2017–2019 Albert Krewinkel
5
6Permission to use, copy, modify, and/or distribute this software for any purpose
7with or without fee is hereby granted, provided that the above copyright notice
8and this permission notice appear in all copies.
9
10THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
12FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
16THIS SOFTWARE.
17]]
18
19---
20-- Lua functions for pandoc scripts.
21--
22-- @author Albert Krewinkel
23-- @copyright © 2017–2019 Albert Krewinkel
24-- @license MIT
25local M = {}
26
27-- Re-export bundled modules
28M.List = require 'pandoc.List'
29M.mediabag = require 'pandoc.mediabag'
30M.system = require 'pandoc.system'
31M.types = require 'pandoc.types'
32M.utils = require 'pandoc.utils'
33M.text = require 'text'
34
35-- Local names for modules which this module depends on.
36local List = M.List
37local utils = M.utils
38
39
40------------------------------------------------------------------------
41-- Accessor objects
42--
43-- Create metatables which allow to access numerical indices via accessor
44-- methods.
45-- @section
46-- @local
47
48--- Create a new indexing function.
49-- @param template function template
50-- @param indices list of indices, starting with the most deeply nested
51-- @return newly created function
52-- @local
53function make_indexing_function(template, ...)
54  local indices = {...}
55  local loadstring = loadstring or load
56  local bracketed = {}
57  for i = 1, #indices do
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)
62  end
63  local fnstr = string.format('return ' .. template, table.concat(bracketed))
64  return assert(loadstring(fnstr))()
65end
66
67--- Create accessor functions using a function template.
68-- @param fn_template function template in which '%s' is replacd with indices
69-- @param accessors list of accessors
70-- @return mapping from accessor names to accessor functions
71-- @local
72local function create_accessor_functions (fn_template, accessors)
73  local res = {}
74  function add_accessors(acc, ...)
75    if type(acc) == 'string' then
76      res[acc] = make_indexing_function(fn_template, ...)
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`.
81      local name, substructure = next(acc)
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
86    else
87      for i = 1, #(acc or {}) do
88        add_accessors(acc[i], i, ...)
89      end
90    end
91  end
92  add_accessors(accessors)
93  return res
94end
95
96--- Get list of top-level fields from field descriptor table.
97-- E.g.: `top_level_fields{'foo', {bar='baz'}, {'qux', 'quux'}}`
98-- gives {'foo, 'bar', 'qux', 'quux'}
99-- @local
100local function top_level_fields (fields)
101  local result = List:new{}
102  for _, v in ipairs(fields) do
103    if type(v) == 'string' then
104      table.insert(result, v)
105    elseif type(v) == 'table' and #v == 0 and next(v) then
106      table.insert(result, (next(v)))
107    else
108      result:extend(top_level_fields(v))
109    end
110  end
111  return result
112end
113
114--- Creates a function which behaves like next, but respects field names.
115-- @local
116local function make_next_function (fields)
117  local field_indices = {}
118  for i, f in ipairs(fields) do
119    field_indices[f] = i
120  end
121
122  return function (t, field)
123    local raw_idx = field == nil and 0 or field_indices[field]
124    local next_field = fields[raw_idx + 1]
125    return next_field, t[next_field]
126  end
127end
128
129--- Create a new table which allows to access numerical indices via accessor
130-- functions.
131-- @local
132local function create_accessor_behavior (tag, accessors)
133  local behavior = {tag = tag}
134  behavior.getters = create_accessor_functions(
135    'function (x) return x.c%s end',
136    accessors
137  )
138  behavior.setters = create_accessor_functions(
139    'function (x, v) x.c%s = v end',
140    accessors
141  )
142  behavior.__eq = utils.equals
143  behavior.__index = function(t, k)
144    if getmetatable(t).getters[k] then
145      return getmetatable(t).getters[k](t)
146    elseif k == "t" then
147      return getmetatable(t)["tag"]
148    else
149      return getmetatable(t)[k]
150    end
151  end
152  behavior.__newindex = function(t, k, v)
153    if getmetatable(t).setters[k] then
154      getmetatable(t).setters[k](t, v)
155    else
156      rawset(t, k, v)
157    end
158  end
159  behavior.__pairs = function (t)
160    if accessors == nil then
161      return next, t
162    end
163    local iterable_fields = type(accessors) == 'string'
164      and {accessors}
165      or top_level_fields(accessors)
166    return make_next_function(iterable_fields), t
167  end
168  return behavior
169end
170
171
172------------------------------------------------------------------------
173-- The base class for types
174-- @type Type
175-- @local
176local Type = {}
177Type.name = 'Type'
178Type.__index = Type
179Type.behavior = {
180  __type = Type,
181  new = function (obj)
182    obj = obj or {}
183    setmetatable(obj, self)
184    return obj
185  end
186}
187Type.behavior.__index = Type.behavior
188
189--- Set a new behavior for the type, inheriting that of the parent type if none
190--- is specified explicitly
191-- @param behavior the behavior object for this type.
192-- @local
193function Type:set_behavior (behavior)
194  behavior = behavior or {}
195  behavior.__index = rawget(behavior, '__index') or behavior
196  behavior.__type = self
197  if not getmetatable(behavior) and getmetatable(self) then
198    setmetatable(behavior, getmetatable(self).behavior)
199  end
200  self.behavior = behavior
201end
202
203--- Create a new subtype, using the given table as base.
204-- @param name name of the new type
205-- @param[opt] behavior behavioral object for the new type.
206-- @return a new type
207-- @local
208function Type:make_subtype(name, behavior)
209  local newtype = setmetatable({}, self)
210  newtype.name = name
211  newtype.__index = newtype
212  newtype:set_behavior(behavior)
213  return newtype
214end
215
216
217------------------------------------------------------------------------
218-- The base class for pandoc's AST elements.
219-- @type AstElement
220-- @local
221local AstElement = Type:make_subtype 'AstElement'
222AstElement.__call = function(t, ...)
223  local success, ret = pcall(t.new, t, ...)
224  if success then
225    return setmetatable(ret, t.behavior)
226  else
227    error(string.format('Constructor for %s failed: %s\n', t.name, ret))
228  end
229end
230
231--- Make a new subtype which constructs a new value when called.
232-- @local
233function AstElement:make_subtype(...)
234  local newtype = Type.make_subtype(self, ...)
235  newtype.__call = self.__call
236  return newtype
237end
238
239--- Create a new constructor
240-- @local
241-- @param tag Tag used to identify the constructor
242-- @param fn Function to be called when constructing a new element
243-- @param accessors names to use as accessors for numerical fields
244-- @return function that constructs a new element
245function AstElement:create_constructor(tag, fn, accessors)
246  local constr = self:make_subtype(tag, create_accessor_behavior(tag, accessors))
247  function constr:new(...)
248    return setmetatable(fn(...), self.behavior)
249  end
250  self.constructor = self.constructor or {}
251  self.constructor[tag] = constr
252  return constr
253end
254
255--- Convert AstElement input into a list if necessary.
256-- @local
257local function ensureList (x)
258  if x.tag then
259    -- Lists are not tagged, but all elements are
260    return List:new{x}
261  else
262    return List:new(x)
263  end
264end
265
266--- Ensure a given object is an Inline element, or convert it into one.
267-- @local
268local function ensureInlineList (x)
269  if type(x) == 'string' then
270    return List:new{M.Str(x)}
271  else
272    return ensureList(x)
273  end
274end
275
276--- Ensure that the given object is a definition pair, convert if necessary.
277-- @local
278local function ensureDefinitionPairs (pair)
279  local inlines = ensureInlineList(pair[1] or {})
280  local blocks = ensureList(pair[2] or {}):map(ensureList)
281  return {inlines, blocks}
282end
283
284--- Split a string into it's words, using whitespace as separators.
285local function words (str)
286  local ws = {}
287  for w in str:gmatch("([^%s]+)") do ws[#ws + 1] = w end
288  return ws
289end
290
291--- Try hard to turn the arguments into an Attr object.
292local 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')
311end
312
313------------------------------------------------------------------------
314--- Pandoc Document
315-- @section document
316
317--- A complete pandoc document
318-- @function Pandoc
319-- @tparam      {Block,...} blocks      document content
320-- @tparam[opt] Meta        meta        document meta data
321M.Pandoc = AstElement:make_subtype'Pandoc'
322M.Pandoc.behavior.clone = M.types.clone.Pandoc
323function M.Pandoc:new (blocks, meta)
324  return {
325    blocks = ensureList(blocks),
326    meta = meta or {},
327  }
328end
329
330-- DEPRECATED synonym:
331M.Doc = M.Pandoc
332
333------------------------------------------------------------------------
334-- Meta
335-- @section Meta
336
337--- Create a new Meta object. It sets the metatable of the given table to
338--- `Meta`.
339-- @function Meta
340-- @tparam meta table table containing document meta information
341M.Meta = AstElement:make_subtype'Meta'
342M.Meta.behavior.clone = M.types.clone.Meta
343function M.Meta:new (meta) return meta end
344
345
346------------------------------------------------------------------------
347-- MetaValue
348-- @section MetaValue
349M.MetaValue = AstElement:make_subtype('MetaValue')
350M.MetaValue.behavior.clone = M.types.clone.MetaValue
351
352--- Meta blocks
353-- @function MetaBlocks
354-- @tparam {Block,...} blocks blocks
355M.MetaBlocks = M.MetaValue:create_constructor(
356  'MetaBlocks',
357  function (content) return ensureList(content) end
358)
359
360--- Meta inlines
361-- @function MetaInlines
362-- @tparam {Inline,...} inlines inlines
363M.MetaInlines = M.MetaValue:create_constructor(
364  'MetaInlines',
365  function (content) return ensureInlineList(content) end
366)
367
368--- Meta list
369-- @function MetaList
370-- @tparam {MetaValue,...} meta_values list of meta values
371M.MetaList = M.MetaValue:create_constructor(
372  'MetaList',
373  function (content)
374    if content.tag == 'MetaList' then
375      return content
376    end
377    return ensureList(content)
378  end
379)
380for k, v in pairs(List) do
381  M.MetaList.behavior[k] = v
382end
383
384--- Meta map
385-- @function MetaMap
386-- @tparam table key_value_map a string-indexed map of meta values
387M.MetaMap = M.MetaValue:create_constructor(
388  "MetaMap",
389  function (mm) return mm end
390)
391
392--- Creates string to be used in meta data.
393-- Does nothing, lua strings are meta strings.
394-- @function MetaString
395-- @tparam string str string value
396function M.MetaString(str)
397  return str
398end
399
400--- Creates boolean to be used in meta data.
401-- Does nothing, lua booleans are meta booleans.
402-- @function MetaBool
403-- @tparam boolean bool boolean value
404function M.MetaBool(bool)
405  return bool
406end
407
408------------------------------------------------------------------------
409-- Blocks
410-- @section Block
411
412--- Block elements
413M.Block = AstElement:make_subtype'Block'
414M.Block.behavior.clone = M.types.clone.Block
415
416--- Creates a block quote element
417-- @function BlockQuote
418-- @tparam      {Block,...} content     block content
419-- @treturn     Block                   block quote element
420M.BlockQuote = M.Block:create_constructor(
421  "BlockQuote",
422  function(content) return {c = ensureList(content)} end,
423  "content"
424)
425
426--- Creates a bullet (i.e. unordered) list.
427-- @function BulletList
428-- @tparam      {{Block,...},...} content     list of items
429-- @treturn     Block                         bullet list element
430M.BulletList = M.Block:create_constructor(
431  "BulletList",
432  function(content) return {c = ensureList(content):map(ensureList)} end,
433  "content"
434)
435
436--- Creates a code block element
437-- @function CodeBlock
438-- @tparam      string      text        code string
439-- @tparam[opt] Attr        attr element attributes
440-- @treturn     Block                   code block element
441M.CodeBlock = M.Block:create_constructor(
442  "CodeBlock",
443  function(text, attr) return {c = {ensureAttr(attr), text}} end,
444  {{attr = {"identifier", "classes", "attributes"}}, "text"}
445)
446
447--- Creates a definition list, containing terms and their explanation.
448-- @function DefinitionList
449-- @tparam      {{{Inline,...},{{Block,...}}},...} content     list of items
450-- @treturn     Block                  definition list element
451M.DefinitionList = M.Block:create_constructor(
452  "DefinitionList",
453  function(content)
454    return {c = ensureList(content):map(ensureDefinitionPairs)}
455  end,
456  "content"
457)
458
459--- Creates a div element
460-- @function Div
461-- @tparam      {Block,...} content     block content
462-- @tparam[opt] Attr        attr  element attributes
463-- @treturn     Block                   div element
464M.Div = M.Block:create_constructor(
465  "Div",
466  function(content, attr)
467    return {c = {ensureAttr(attr), ensureList(content)}}
468  end,
469  {{attr = {"identifier", "classes", "attributes"}}, "content"}
470)
471
472--- Creates a header element.
473-- @function Header
474-- @tparam      int          level       header level
475-- @tparam      {Inline,...} content     inline content
476-- @tparam[opt] Attr         attr element attributes
477-- @treturn     Block                    header element
478M.Header = M.Block:create_constructor(
479  "Header",
480  function(level, content, attr)
481    return {c = {level, ensureAttr(attr), ensureInlineList(content)}}
482  end,
483  {"level", {attr = {"identifier", "classes", "attributes"}}, "content"}
484)
485
486--- Creates a horizontal rule.
487-- @function HorizontalRule
488-- @treturn     Block                   horizontal rule
489M.HorizontalRule = M.Block:create_constructor(
490  "HorizontalRule",
491  function() return {} end
492)
493
494--- Creates a line block element.
495-- @function LineBlock
496-- @tparam      {{Inline,...},...} content    inline content
497-- @treturn     Block                   line block element
498M.LineBlock = M.Block:create_constructor(
499  "LineBlock",
500  function(content) return {c = ensureList(content):map(ensureInlineList)} end,
501  "content"
502)
503
504--- Creates a null element.
505-- @function Null
506-- @treturn     Block                   null element
507M.Null = M.Block:create_constructor(
508  "Null",
509  function() return {} end
510)
511
512--- Creates an ordered list.
513-- @function OrderedList
514-- @tparam      {{Block,...},...} items list items
515-- @param[opt]  listAttributes list parameters
516-- @treturn     Block  ordered list element
517M.OrderedList = M.Block:create_constructor(
518  "OrderedList",
519  function(items, listAttributes)
520    listAttributes = listAttributes or M.ListAttributes()
521    return {c = {listAttributes, ensureList(items):map(ensureList)}}
522  end,
523  {{listAttributes = {"start", "style", "delimiter"}}, "content"}
524)
525
526--- Creates a para element.
527-- @function Para
528-- @tparam      {Inline,...} content    inline content
529-- @treturn     Block                   paragraph element
530M.Para = M.Block:create_constructor(
531  "Para",
532  function(content) return {c = ensureInlineList(content)} end,
533  "content"
534)
535
536--- Creates a plain element.
537-- @function Plain
538-- @tparam      {Inline,...} content    inline content
539-- @treturn     Block                   plain element
540M.Plain = M.Block:create_constructor(
541  "Plain",
542  function(content) return {c = ensureInlineList(content)} end,
543  "content"
544)
545
546--- Creates a raw content block of the specified format.
547-- @function RawBlock
548-- @tparam      string      format      format of content
549-- @tparam      string      text        string content
550-- @treturn     Block                   raw block element
551M.RawBlock = M.Block:create_constructor(
552  "RawBlock",
553  function(format, text) return {c = {format, text}} end,
554  {"format", "text"}
555)
556
557--- Creates a table element.
558-- @function Table
559-- @tparam      Caption      caption    table caption
560-- @tparam      {ColSpec,...} colspecs  column alignments and widths
561-- @tparam      TableHead    head       table head
562-- @tparam      {TableBody,..} bodies   table bodies
563-- @treturn     TableFoot    foot       table foot
564-- @tparam[opt] Attr         attr       attributes
565M.Table = M.Block:create_constructor(
566  "Table",
567  function(caption, colspecs, head, bodies, foot, attr)
568    return {
569      c = {
570        ensureAttr(attr),
571        caption,
572        List:new(colspecs),
573        head,
574        List:new(bodies),
575        foot
576      }
577    }
578  end,
579  {"attr", "caption", "colspecs", "head", "bodies", "foot"}
580)
581
582
583------------------------------------------------------------------------
584-- Inline
585-- @section Inline
586
587--- Inline element class
588M.Inline = AstElement:make_subtype'Inline'
589M.Inline.behavior.clone = M.types.clone.Inline
590
591--- Creates a Cite inline element
592-- @function Cite
593-- @tparam {Inline,...}   content       List of inlines
594-- @tparam {Citation,...} citations     List of citations
595-- @treturn Inline citations element
596M.Cite = M.Inline:create_constructor(
597  "Cite",
598  function(content, citations)
599    return {c = {ensureList(citations), ensureInlineList(content)}}
600  end,
601  {"citations", "content"}
602)
603
604--- Creates a Code inline element
605-- @function Code
606-- @tparam      string      text  code string
607-- @tparam[opt] Attr        attr  additional attributes
608-- @treturn Inline code element
609M.Code = M.Inline:create_constructor(
610  "Code",
611  function(text, attr) return {c = {ensureAttr(attr), text}} end,
612  {{attr = {"identifier", "classes", "attributes"}}, "text"}
613)
614
615--- Creates an inline element representing emphasised text.
616-- @function Emph
617-- @tparam      {Inline,..} content     inline content
618-- @treturn Inline emphasis element
619M.Emph = M.Inline:create_constructor(
620  "Emph",
621  function(content) return {c = ensureInlineList(content)} end,
622  "content"
623)
624
625--- Creates a Image inline element
626-- @function Image
627-- @tparam      {Inline,..} caption     text used to describe the image
628-- @tparam      string      src         path to the image file
629-- @tparam[opt] string      title       brief image description
630-- @tparam[opt] Attr        attr additional attributes
631-- @treturn Inline image element
632M.Image = M.Inline:create_constructor(
633  "Image",
634  function(caption, src, title, attr)
635    title = title or ""
636    return {c = {ensureAttr(attr), ensureInlineList(caption), {src, title}}}
637  end,
638  {{attr = {"identifier", "classes", "attributes"}}, "caption", {"src", "title"}}
639)
640
641--- Create a LineBreak inline element
642-- @function LineBreak
643-- @treturn Inline linebreak element
644M.LineBreak = M.Inline:create_constructor(
645  "LineBreak",
646  function() return {} end
647)
648
649--- Creates a link inline element, usually a hyperlink.
650-- @function Link
651-- @tparam      {Inline,..} content     text for this link
652-- @tparam      string      target      the link target
653-- @tparam[opt] string      title       brief link description
654-- @tparam[opt] Attr        attr additional attributes
655-- @treturn Inline image element
656M.Link = M.Inline:create_constructor(
657  "Link",
658  function(content, target, title, attr)
659    title = title or ""
660    attr = ensureAttr(attr)
661    return {c = {attr, ensureInlineList(content), {target, title}}}
662  end,
663  {{attr = {"identifier", "classes", "attributes"}}, "content", {"target", "title"}}
664)
665
666--- Creates a Math element, either inline or displayed.
667-- @function Math
668-- @tparam      "InlineMath"|"DisplayMath" mathtype rendering specifier
669-- @tparam      string      text        Math content
670-- @treturn     Inline                  Math element
671M.Math = M.Inline:create_constructor(
672  "Math",
673  function(mathtype, text)
674    return {c = {mathtype, text}}
675  end,
676  {"mathtype", "text"}
677)
678--- Creates a DisplayMath element (DEPRECATED).
679-- @function DisplayMath
680-- @tparam      string      text        Math content
681-- @treturn     Inline                  Math element
682M.DisplayMath = M.Inline:create_constructor(
683  "DisplayMath",
684  function(text) return M.Math("DisplayMath", text) end,
685  {"mathtype", "text"}
686)
687--- Creates an InlineMath inline element (DEPRECATED).
688-- @function InlineMath
689-- @tparam      string      text        Math content
690-- @treturn     Inline                  Math element
691M.InlineMath = M.Inline:create_constructor(
692  "InlineMath",
693  function(text) return M.Math("InlineMath", text) end,
694  {"mathtype", "text"}
695)
696
697--- Creates a Note inline element
698-- @function Note
699-- @tparam      {Block,...} content     footnote block content
700M.Note = M.Inline:create_constructor(
701  "Note",
702  function(content) return {c = ensureList(content)} end,
703  "content"
704)
705
706--- Creates a Quoted inline element given the quote type and quoted content.
707-- @function Quoted
708-- @tparam      "DoubleQuote"|"SingleQuote" quotetype type of quotes to be used
709-- @tparam      {Inline,..} content     inline content
710-- @treturn     Inline                  quoted element
711M.Quoted = M.Inline:create_constructor(
712  "Quoted",
713  function(quotetype, content)
714    return {c = {quotetype, ensureInlineList(content)}}
715  end,
716  {"quotetype", "content"}
717)
718--- Creates a single-quoted inline element (DEPRECATED).
719-- @function SingleQuoted
720-- @tparam      {Inline,..} content     inline content
721-- @treturn     Inline                  quoted element
722-- @see Quoted
723M.SingleQuoted = M.Inline:create_constructor(
724  "SingleQuoted",
725  function(content) return M.Quoted(M.SingleQuote, content) end,
726  {"quotetype", "content"}
727)
728--- Creates a single-quoted inline element (DEPRECATED).
729-- @function DoubleQuoted
730-- @tparam      {Inline,..} content     inline content
731-- @treturn     Inline                  quoted element
732-- @see Quoted
733M.DoubleQuoted = M.Inline:create_constructor(
734  "DoubleQuoted",
735  function(content) return M.Quoted("DoubleQuote", content) end,
736  {"quotetype", "content"}
737)
738
739--- Creates a RawInline inline element
740-- @function RawInline
741-- @tparam      string      format      format of the contents
742-- @tparam      string      text        string content
743-- @treturn     Inline                  raw inline element
744M.RawInline = M.Inline:create_constructor(
745  "RawInline",
746  function(format, text) return {c = {format, text}} end,
747  {"format", "text"}
748)
749
750--- Creates text rendered in small caps
751-- @function SmallCaps
752-- @tparam      {Inline,..} content     inline content
753-- @treturn     Inline                  smallcaps element
754M.SmallCaps = M.Inline:create_constructor(
755  "SmallCaps",
756  function(content) return {c = ensureInlineList(content)} end,
757  "content"
758)
759
760--- Creates a SoftBreak inline element.
761-- @function SoftBreak
762-- @treturn     Inline                  softbreak element
763M.SoftBreak = M.Inline:create_constructor(
764  "SoftBreak",
765  function() return {} end
766)
767
768--- Create a Space inline element
769-- @function Space
770-- @treturn Inline space element
771M.Space = M.Inline:create_constructor(
772  "Space",
773  function() return {} end
774)
775
776--- Creates a Span inline element
777-- @function Span
778-- @tparam      {Inline,..} content     inline content
779-- @tparam[opt] Attr        attr  additional attributes
780-- @treturn Inline span element
781M.Span = M.Inline:create_constructor(
782  "Span",
783  function(content, attr)
784    return {c = {ensureAttr(attr), ensureInlineList(content)}}
785  end,
786  {{attr = {"identifier", "classes", "attributes"}}, "content"}
787)
788
789--- Creates a Str inline element
790-- @function Str
791-- @tparam      string      text        content
792-- @treturn     Inline                  string element
793M.Str = M.Inline:create_constructor(
794  "Str",
795  function(text) return {c = text} end,
796  "text"
797)
798
799--- Creates text which is striked out.
800-- @function Strikeout
801-- @tparam      {Inline,..} content     inline content
802-- @treturn     Inline                  strikeout element
803M.Strikeout = M.Inline:create_constructor(
804  "Strikeout",
805  function(content) return {c = ensureInlineList(content)} end,
806  "content"
807)
808
809--- Creates a Strong element, whose text is usually displayed in a bold font.
810-- @function Strong
811-- @tparam      {Inline,..} content     inline content
812-- @treturn     Inline                  strong element
813M.Strong = M.Inline:create_constructor(
814  "Strong",
815  function(content) return {c = ensureInlineList(content)} end,
816  "content"
817)
818
819--- Creates a Subscript inline element
820-- @function Subscript
821-- @tparam      {Inline,..} content     inline content
822-- @treturn     Inline                  subscript element
823M.Subscript = M.Inline:create_constructor(
824  "Subscript",
825  function(content) return {c = ensureInlineList(content)} end,
826  "content"
827)
828
829--- Creates a Superscript inline element
830-- @function Superscript
831-- @tparam      {Inline,..} content     inline content
832-- @treturn     Inline                  superscript element
833M.Superscript = M.Inline:create_constructor(
834  "Superscript",
835  function(content) return {c = ensureInlineList(content)} end,
836  "content"
837)
838
839--- Creates an Underline inline element
840-- @function Underline
841-- @tparam      {Inline,..} content     inline content
842-- @treturn     Inline                  underline element
843M.Underline = M.Inline:create_constructor(
844  "Underline",
845  function(content) return {c = ensureInlineList(content)} end,
846  "content"
847)
848
849
850------------------------------------------------------------------------
851-- Element components
852-- @section components
853
854--- Check if the first element of a pair matches the given value.
855-- @param x  key value to be checked
856-- @return function returning true iff first element of its argument matches x
857-- @local
858local function assoc_key_equals (x)
859  return function (y) return y[1] == x end
860end
861
862--- Lookup a value in an associative list
863-- @function lookup
864-- @local
865-- @tparam {{key, value},...} alist associative list
866-- @param key key for which the associated value is to be looked up
867local function lookup(alist, key)
868  return (List.find_if(alist, assoc_key_equals(key)) or {})[2]
869end
870
871--- Return an iterator which returns key-value pairs of an associative list.
872-- @function apairs
873-- @local
874-- @tparam {{key, value},...} alist associative list
875local apairs = function (alist)
876  local i = 1
877  local cur
878  function nxt ()
879    cur = rawget(alist, i)
880    if cur then
881      i = i + 1
882      return cur[1], cur[2]
883    end
884    return nil
885  end
886  return nxt, nil, nil
887end
888
889--- AttributeList, a metatable to allow table-like access to attribute lists
890-- represented by associative lists.
891-- @local
892local AttributeList = {
893  __index = function (t, k)
894    if type(k) == "number" then
895      return rawget(t, k)
896    else
897      return lookup(t, k)
898    end
899  end,
900
901  __newindex = function (t, k, v)
902    local cur, idx = List.find_if(t, assoc_key_equals(k))
903    if v == nil and not cur then
904      -- deleted key does not exists in list
905      return
906    elseif v == nil then
907      table.remove(t, idx)
908    elseif cur then
909      cur[2] = v
910    elseif type(k) == "number" then
911      rawset(t, k, v)
912    else
913      rawset(t, #t + 1, {k, v})
914    end
915  end,
916
917  __pairs = apairs
918}
919
920--- Convert a table to an associative list. The order of key-value pairs in the
921-- alist is undefined. The table should either contain no numeric keys or
922-- already be an associative list.
923-- @local
924-- @tparam table tbl associative list or table without numeric keys.
925-- @treturn table associative list
926local to_alist = function (tbl)
927  if #tbl ~= 0 or next(tbl) == nil then
928    -- probably already an alist
929    return tbl
930  end
931  local alist = {}
932  local i = 1
933  for k, v in pairs(tbl) do
934    alist[i] = {k, v}
935    i = i + 1
936  end
937  return alist
938end
939
940-- Attr
941
942--- Create a new set of attributes (Attr).
943-- @function Attr
944-- @tparam[opt] string       identifier element identifier
945-- @tparam[opt] {string,...} classes    element classes
946-- @tparam[opt] table        attributes table containing string keys and values
947-- @return element attributes
948M.Attr = AstElement:make_subtype'Attr'
949function M.Attr:new (identifier, classes, attributes)
950  identifier = identifier or ''
951  classes = ensureList(classes or {})
952  attributes = setmetatable(to_alist(attributes or {}), AttributeList)
953  return setmetatable({identifier, classes, attributes}, self.behavior)
954end
955M.Attr.behavior.clone = M.types.clone.Attr
956M.Attr.behavior.tag = 'Attr'
957M.Attr.behavior._field_names = {identifier = 1, classes = 2, attributes = 3}
958M.Attr.behavior.__eq = utils.equals
959M.Attr.behavior.__index = function(t, k)
960  return (k == 't' and t.tag) or
961    rawget(t, getmetatable(t)._field_names[k]) or
962    getmetatable(t)[k]
963end
964M.Attr.behavior.__newindex = function(t, k, v)
965  if k == 'attributes' then
966    rawset(t, 3, setmetatable(to_alist(v or {}), AttributeList))
967  elseif getmetatable(t)._field_names[k] then
968    rawset(t, getmetatable(t)._field_names[k], v)
969  else
970    rawset(t, k, v)
971  end
972end
973M.Attr.behavior.__pairs = function(t)
974  local field_names = M.Attr.behavior._field_names
975  local fields = {}
976  for name, i in pairs(field_names) do
977    fields[i] = name
978  end
979  return make_next_function(fields), t, nil
980end
981
982-- Monkey-patch setters for `attr` fields to be more forgiving in the input that
983-- results in a valid Attr value.
984function augment_attr_setter (setters)
985  if setters.attr then
986    local orig = setters.attr
987    setters.attr = function(k, v)
988      orig(k, ensureAttr(v))
989    end
990  end
991end
992for _, blk in pairs(M.Block.constructor) do
993  augment_attr_setter(blk.behavior.setters)
994end
995for _, inln in pairs(M.Inline.constructor) do
996  augment_attr_setter(inln.behavior.setters)
997end
998
999
1000-- Citation
1001M.Citation = AstElement:make_subtype'Citation'
1002M.Citation.behavior.clone = M.types.clone.Citation
1003
1004--- Creates a single citation.
1005-- @function Citation
1006-- @tparam      string       id       citation identifier (like a bibtex key)
1007-- @tparam      AuthorInText|SuppressAuthor|NormalCitation mode citation mode
1008-- @tparam[opt] {Inline,...} prefix   citation prefix
1009-- @tparam[opt] {Inline,...} suffix   citation suffix
1010-- @tparam[opt] int          note_num note number
1011-- @tparam[opt] int          hash  hash number
1012function M.Citation:new (id, mode, prefix, suffix, note_num, hash)
1013  return {
1014    id = id,
1015    mode = mode,
1016    prefix = ensureList(prefix or {}),
1017    suffix = ensureList(suffix or {}),
1018    note_num = note_num or 0,
1019    hash = hash or 0,
1020  }
1021end
1022
1023-- ListAttributes
1024M.ListAttributes = AstElement:make_subtype 'ListAttributes'
1025M.ListAttributes.behavior.clone = M.types.clone.ListAttributes
1026
1027--- Creates a set of list attributes.
1028-- @function ListAttributes
1029-- @tparam[opt] integer start number of the first list item
1030-- @tparam[opt] string  style style used for list numbering
1031-- @tparam[opt] DefaultDelim|Period|OneParen|TwoParens delimiter delimiter of list numbers
1032-- @treturn table list attributes table
1033function M.ListAttributes:new (start, style, delimiter)
1034  start = start or 1
1035  style = style or 'DefaultStyle'
1036  delimiter = delimiter or 'DefaultDelim'
1037  return {start, style, delimiter}
1038end
1039M.ListAttributes.behavior._field_names = {start = 1, style = 2, delimiter = 3}
1040M.ListAttributes.behavior.__eq = utils.equals
1041M.ListAttributes.behavior.__index = function (t, k)
1042  return rawget(t, getmetatable(t)._field_names[k]) or
1043    getmetatable(t)[k]
1044end
1045M.ListAttributes.behavior.__newindex = function (t, k, v)
1046  if getmetatable(t)._field_names[k] then
1047    rawset(t, getmetatable(t)._field_names[k], v)
1048  else
1049    rawset(t, k, v)
1050  end
1051end
1052M.ListAttributes.behavior.__pairs = function(t)
1053  local field_names = M.ListAttributes.behavior._field_names
1054  local fields = {}
1055  for name, i in pairs(field_names) do
1056    fields[i] = name
1057  end
1058  return make_next_function(fields), t, nil
1059end
1060
1061--
1062-- Legacy and compatibility types
1063--
1064
1065--- Creates a simple (old style) table element.
1066-- @function SimpleTable
1067-- @tparam      {Inline,...} caption    table caption
1068-- @tparam      {AlignDefault|AlignLeft|AlignRight|AlignCenter,...} aligns alignments
1069-- @tparam      {int,...}    widths     column widths
1070-- @tparam      {Block,...}  headers    header row
1071-- @tparam      {{Block,...}} rows      table rows
1072-- @treturn     Block                   table element
1073M.SimpleTable = function(caption, aligns, widths, headers, rows)
1074  return {
1075    caption = ensureInlineList(caption),
1076    aligns = List:new(aligns),
1077    widths = List:new(widths),
1078    headers = List:new(headers),
1079    rows = List:new(rows),
1080    tag = "SimpleTable",
1081    t = "SimpleTable",
1082  }
1083end
1084
1085
1086------------------------------------------------------------------------
1087-- Constants
1088-- @section constants
1089
1090--- Author name is mentioned in the text.
1091-- @see Citation
1092-- @see Cite
1093M.AuthorInText = "AuthorInText"
1094
1095--- Author name is suppressed.
1096-- @see Citation
1097-- @see Cite
1098M.SuppressAuthor = "SuppressAuthor"
1099
1100--- Default citation style is used.
1101-- @see Citation
1102-- @see Cite
1103M.NormalCitation = "NormalCitation"
1104
1105--- Table cells aligned left.
1106-- @see Table
1107M.AlignLeft = "AlignLeft"
1108
1109--- Table cells right-aligned.
1110-- @see Table
1111M.AlignRight = "AlignRight"
1112
1113--- Table cell content is centered.
1114-- @see Table
1115M.AlignCenter = "AlignCenter"
1116
1117--- Table cells are alignment is unaltered.
1118-- @see Table
1119M.AlignDefault = "AlignDefault"
1120
1121--- Default list number delimiters are used.
1122-- @see OrderedList
1123M.DefaultDelim = "DefaultDelim"
1124
1125--- List numbers are delimited by a period.
1126-- @see OrderedList
1127M.Period = "Period"
1128
1129--- List numbers are delimited by a single parenthesis.
1130-- @see OrderedList
1131M.OneParen = "OneParen"
1132
1133--- List numbers are delimited by a double parentheses.
1134-- @see OrderedList
1135M.TwoParens = "TwoParens"
1136
1137--- List are numbered in the default style
1138-- @see OrderedList
1139M.DefaultStyle = "DefaultStyle"
1140
1141--- List items are numbered as examples.
1142-- @see OrderedList
1143M.Example = "Example"
1144
1145--- List are numbered using decimal integers.
1146-- @see OrderedList
1147M.Decimal = "Decimal"
1148
1149--- List are numbered using lower-case roman numerals.
1150-- @see OrderedList
1151M.LowerRoman = "LowerRoman"
1152
1153--- List are numbered using upper-case roman numerals
1154-- @see OrderedList
1155M.UpperRoman = "UpperRoman"
1156
1157--- List are numbered using lower-case alphabetic characters.
1158-- @see OrderedList
1159M.LowerAlpha = "LowerAlpha"
1160
1161--- List are numbered using upper-case alphabetic characters.
1162-- @see OrderedList
1163M.UpperAlpha = "UpperAlpha"
1164
1165------------------------------------------------------------------------
1166-- Functions which have moved to different modules
1167M.sha1 = utils.sha1
1168
1169return M
1170