Path: blob/main/src/resources/extensions/quarto/confluence/overrides.lua
12923 views
local function startsWith(text, prefix)1return text:find(prefix, 1, true) == 12end34local function startsWithHttp(source)5return startsWith(source, "http")6end78local function escape(s, in_attribute)9return s:gsub("[<>&\"']",10function(x)11if x == '<' then12return '<'13elseif x == '>' then14return '>'15elseif x == '&' then16return '&'17elseif in_attribute and x == '"' then18return '"'19elseif in_attribute and x == "'" then20return '''21else22return x23end24end)25end2627local function dumpObject(o)28if type(o) == 'table' then29local s = '{ '30for k,v in pairs(o) do31if type(k) ~= 'number' then k = '"'..k..'"' end32s = s .. '['..k..'] = ' .. dumpObject(v) .. ','33end34return s .. '} '35else36return tostring(o)37end38end3940local function log(label, object)41print(label or '' .. ': ', dumpObject(object))42end4344local function isEmpty(s)45return s == nil or s == ''46end4748local function interpolate(str, vars)49-- Allow replace_vars{str, vars} syntax as well as replace_vars(str, {vars})50if not vars then51vars = str52str = vars[1]53end54return (string.gsub(str, "({([^}]+)})",55function(whole, i)56return vars[i] or whole57end))58end5960function updateSizing(existingSizing, attributes, key)61local value = ''62if (attributes and attributes[key]) then63value = escape(attributes[key], '')64if not isEmpty(value) then65return existingSizing .. '\n ac:' .. key .. '="' .. value ..'"'66end67end68return existingSizing69end7071function CaptionedImageConfluence(source, title, caption, attr, id)72--Note Title isn't usable by confluence at this time it will73-- serve as the default value for attr.alt-text7475local CAPTION_SNIPPET = [[<ac:caption>{caption}</ac:caption>]]7677local IMAGE_SNIPPET = [[{anchor}<ac:image78ac:align="{align}"79ac:layout="{layout}"{sizing}80ac:alt="{alt}">81<ri:attachment ri:filename="{source}" />{caption}82</ac:image>]]8384local sourceValue = source85local titleValue = title86local captionValue = caption87local anchorValue = ""88local sizing = ""8990if (id and #id > 0) then91-- wrap in `p` to prevent an issue with CSF not redering correctly92anchorValue = "<p>" .. HTMLAnchorConfluence(id) .. "</p>"93end9495local alignValue = 'center'96if (attr and attr['fig-align']) then97alignValue = escape(attr['fig-align'], 'center')98end99100local altValue = titleValue;101if (attr and attr['fig-alt']) then102altValue = escape(attr['fig-alt'], '')103end104105local layoutValue = 'center'106if alignValue == 'right' then layoutValue = 'align-end' end107if alignValue == 'left' then layoutValue = 'align-start' end108109sizing = updateSizing(sizing, attr, 'width')110sizing = updateSizing(sizing, attr, 'height')111112if not isEmpty(captionValue) then113captionValue =114interpolate {115CAPTION_SNIPPET,116caption = escape(captionValue)}117end118119if(not startsWithHttp(source)) then120return interpolate {121IMAGE_SNIPPET,122source = sourceValue,123align = alignValue,124layout = layoutValue,125sizing = sizing,126alt = altValue,127caption = captionValue,128anchor = anchorValue129}130end131132return "<img src='" .. escape(source,true) .. "' title='" ..133escape(title,true) .. "'/>"134135end136137function LinkConfluence(source, target, title, attr)138-- For some reason, rendering converts spaces to a double line-break139source = string.gsub(source, "\n\n", " ")140source = string.gsub(source, "\n \n", " ")141source = string.gsub(source, "(\n", "")142source = string.gsub(source, "\n)", "")143144local LINK_ATTACHMENT_SNIPPET = [[<ac:link><ri:attachment ri:filename="{target}"/><ac:plain-text-link-body><![CDATA[{source}{doubleBraket}></ac:plain-text-link-body></ac:link>]]145146if(not startsWithHttp(target) and (not string.find(target, ".qmd"))) then147return interpolate {148LINK_ATTACHMENT_SNIPPET,149source = escape(source),150target = target,151doubleBraket = "]]"152}153end154155return "<a href='" .. escape(target,true) .. "' title='" ..156escape(title,true) .. "'>" .. source .. "</a>"157end158159function CalloutConfluence(type, content)160local SNIPPET = [[<ac:structured-macro ac:name="{type}" ac:schema-version="1" ac:macro-id="{id}"><ac:rich-text-body>{content}</ac:rich-text-body></ac:structured-macro>]]161162local MAP_TYPE = {163note = {name = "info", id = "1c8062cd-87de-4701-a698-fd435e057468"},164warning = {name = "note", id = "1049a0d8-470f-4f83-a0d7-b6ad35ea8eda"},165important = {name = "warning", id = "0185f821-7aa4-404a-8748-ec59a46357e1"},166tip = {name = "tip", id = "97c39328-9651-4c56-8a8c-ab5537001d86"},167caution = {name = "note", id = "1049a0d8-470f-4f83-a0d7-b6ad35ea8eda"}168}169170local mappedType = MAP_TYPE[type] or MAP_TYPE['note']171172return interpolate {173SNIPPET,174type = mappedType.name,175id = mappedType.id,176content = content}177end178179function CodeBlockConfluence(codeValue, languageValue)180local CODE_SNIPPET = [[<ac:structured-macro181ac:name="code"182ac:schema-version="1"183ac:macro-id="1d1a2d13-0179-4d8f-b448-b28dfaceea4a">184<ac:parameter ac:name="language">{languageValue}</ac:parameter>185<ac:plain-text-body>186<![CDATA[{codeValue}{doubleBraket}>187</ac:plain-text-body>188</ac:structured-macro>]]189190return interpolate {191CODE_SNIPPET,192languageValue = languageValue or '',193codeValue = codeValue,194doubleBraket = ']]'195}196end197198function HTMLAnchorConfluence(id)199if (not id or #id == 0) then200return ""201end202203local SNIPPET = [[<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:local-id="a6aa6f25-0bee-4a7f-929b-71fcb7eba592" ac:macro-id="d2cb5be1217ae6e086bc60005e9d27b7"><ac:parameter ac:name="">{id}</ac:parameter></ac:structured-macro>]]204205return interpolate {206SNIPPET,207id = id or ''208}209end210211function RawInlineConfluence(text)212if type(text) ~= "string" then return text end213214-- Confluence expects closed Void Elements as proper XHTML215-- https://github.com/quarto-dev/quarto-cli/issues/4479216217if (string.lower(text) == [[<br>]]) then218return "<br/>"219end220221local match = string.match(text, "<img (.-)>")222local matchClosed = string.match(text, "<img (.-)/>")223if (match and not matchClosed) then224return string.gsub(text, ">", "/>")225end226227return text228end229230return {231CaptionedImageConfluence = CaptionedImageConfluence,232CodeBlockConfluence = CodeBlockConfluence,233LinkConfluence = LinkConfluence,234TableConfluence = TableConfluence,235CalloutConfluence = CalloutConfluence,236HTMLAnchorConfluence = HTMLAnchorConfluence,237RawInlineConfluence = RawInlineConfluence,238escape = escape,239interpolate = interpolate240}241242243