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